美文网首页
极客班C++面向对象高级编程(上)第二周笔记

极客班C++面向对象高级编程(上)第二周笔记

作者: Wancho | 来源:发表于2016-08-01 21:01 被阅读0次

    Classes的两个经典分类

    Class without pointer member(s)

    complex

    Class with pointer member(s)

    string

    带指针的class

    String class

    它的Header的结构

    它将实现的功能:

    三种构造方式:

    无初值的、有初值的 和 拷贝构造。

    两种操作符重载:

    “<<”(输出到“cout”) & “=”(拷贝赋值)

    Big Three 三个特殊函数

    四个主要函数:

    1.默认用指针构造;

    2.拷贝构造,接受的是自身的类型的对象;

    3.拷贝赋值,只有类带有指针,一定要包含有这个函数;

    4.析构函数,当以这个类创建的对象,即将死亡的时候,它就会被调用。

    其中2,3,4 被称为 Big Three .

    ctor 和 dtor (构造函数 和 析构函数)

    构造函数 ctor :

    字符串会以头指针的形式传入,并以‘\0’为结尾;因此构造函数要适应不同长度的,所以用‘new’分配一块大小适合内存。

    析构函数 dtor:

    用于清理,清理动态分配的内存,不然就是内存泄漏了,用 delete[] 关键字释放动态分配的内存;

    在调用的示例中:

    指针p指向动态分配的内存,所以用 delete 释放内存;

    当离开作用域的时候,会调用3次析构函数,包括以new动态分配的String对象的析构函数。

    class with pointer members (带有指针的类)必须有 copy ctor (拷贝构造函数) 和 copy operator= (拷贝赋值)

    如果不写拷贝赋值函数,而是直接使用原来‘=’把a的地址复制到b里去,b只会指向a所指的内存,原来所指的内存也将会丢失(造成内存泄漏),这叫浅拷贝,不是我们想要的结果,我们要的是把内容拷贝过来的深拷贝。

    在设计中应该避免有别名出现,即多个指针指向同一块内存。

    copy ctor (拷贝构造函数)

    copy assignment operator(拷贝赋值函数)

    经典的写法

    ① 释放原来的内存

    ② 重新申请一个足够大的内存

    ③ 将内容拷贝进来

    然后,在最前面检测自我赋值的情况;如果出现自我赋值,而又没有检测自我赋值,这样会因为内存被释放而丢失原来的内容。

    (这里的this是指向调用者的,而且这个函数它还是成员函数,所以可以直接更改)

    output函数

    operator<< 函数一定是全局函数,如果它是成员函数的话cout就要在右边,因为成员函数的左边一定是自身类的指针 *this

    所谓stack(栈) ,所谓heap(堆)

    Stack,是存在于作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack用来旋转它所接收的参数,以及返回地址。

    在函数本体(function body)内声明的任何变量,其所使用的内存块都取自上述stack。

    Heap,或调system heap,是指由操作系统提供的一块global(全局的)内存空间,程序可动态分配,从中获得若干块。

    c1所占用的空间来自Stack;当离开作用域时,它的生命自然结束;

    Complex(3)是个临时创建的对象,占用的空间是以new动态分配非得,并由heap提供;它必须需要手动delete掉。

    关于生命期

    stack object:

    c1便是,其生命在作用域结束之际结束。又称为auto object,因为它会被[自动]清理。

    static local object(静态对象):

    c2便是所谓static object,其生命在作用域结束之后仍然存在,直到整个程序结束。

    global object(全局对象):

    c3便是所谓global object,它的生命在整个程序结束之后才结束。也可以把它被视为一种static object,其作用域是[整个程序]。

    heap objects:

    p所指的便是heap object,其生命在它被deleted之际结束。

    在创建时,我们得到的是一个指针p,所以我们应该delete指针p,同时会调用被删除对象内的析构函数。

    如果没把heap对象 delete掉,就会出现内存泄漏(memory leak),即是程序失去对内存块的控制(内存块再也找不回来了)

    以上便会出现内存泄漏,因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)

    动态分配所得的内存块(关于new和delete)

    new其实也是一个函数

    new调用时,先分配memory,再调用ctor

    以上便是编译器将new转化的三个动作,实际上调了malloc()函数分配内存。

    delete:先调用dtor,再释放memory

    编译器转化的两个动作,它里面也调用了free()函数。

    动态分配数组对象

    以上的用法叫做array new和array delete;

    array new一定要搭配array delete,因为delete只是会删除指针所指的内存,数组内的其它内存就会因此而丢失(内存泄漏)。

    (注:动态分配的内存区块中,头尾也会有一个称为cookie标识的内存块,标识中用16进位表示状态,结尾为1表示“分配”,并且动态分配的内存区块一定是16的倍数)

    扩展补充

    关于static

    一般的成员函数都会有this point

    以上是从编译器角度看到的成员函数的调用。

    如果一个数据在每个同类的对象都是相同的,这时候它就是应该变成static数据,在它的声明前加上static便是;

    static数据要在class body外定义,上面黄色部分便是。

    static函数:它只能处理static的数据,也没this point;它可以通过object调用(通过已经创建的对象调用),也可以通过class name调用(直接修改类的static数据,作用于所有通过这个类创建的对象)

    关于把ctors放在private区

    之前说过这种把构造函数放在private中的类,叫做Singleton(单体)

    上面的便所谓的Singleton,因为它不能被外境构造,所以它须要在自身内被构造,并且一定是static的;

    因为static函数可以通过class name调用(上面的小框便是),通过getInstance()调用‘a’,这样就解决了不能调用的问题。

    但就是因为static的数据一被定义了就会直到整个程序结束,它的生命才会结束;

    用上面的写法可以解决static object不被使用也一直存在的问题。

    关于cout

    cout属于右上角的class,而这个类型继承自ostream,所以它也是属于ostream。

    从ostream里可以发现,它做了好多种operator<<的重载,这正是cout可以接受多种类型数据的原因。

    class template(类模板)

    如果在设计没有确定类里面的参数在使用时的类型,可以通过类模板在使用时再确定它们的数据类型。

    写法如上:在类的前面声明T是一个关键字;用T替换参数类型的位置。

    图中左下角便是它的调用方法。

    function template(函数模板)

    用法:正如上面的比大小函数,为了适用于所有不同类型的对象,所以它采用了函数模板,代替未知的对象类型;它的声明和类模板相似,要在函数之前用加上黄色的语句。

    (使用这种方式时,不要忘了还要对相应的操作符进行重载)

    namespace

    它的作用是将它里面的东西包装起来,防止和其它人重名。(std是指标准库,标准库的所有东西都被包在std里)

    用法:

    最简单的用法就是using directive(全开)(写法如上),这样在下面的所以标准库的函数都不用写命名了(cin和cout的全名是std::cin和std::cout)。

    using declaration(逐个打开‘声明’)

    正如上面的,它只声明了cout,所以就是只有cout不用写全名。

    更多拓展,此课程不再详述

    Composition(复合),表示has-a

    表示queue(队列,先进先出)包含deque(两端都可以进出的队列);

    queue拥有deque的所有功能,这样便是所谓Composition,而queue自身没有实现功能只是改变功能的名字,这类情况叫做Adapter(改装);

    Adapter:适用于已经存在一个类能实现所需的功能时,只是有一些情况不同(可能是接口不一样-函数名不同),它是复合中一种特殊的情况

    Composition类的大小要加上它所包含的所有类的大小

    Composition关系下的构造和析构

    当Container包含Component时:

    编译器会在Container的构造函数名之后,自动加上Component的默认构造(‘ : Component() ’),这便使它先要执行所含类的构造函数(如果需要调用所含类的其它构造函数,需要自己写上)然后才执行自己;

    编译器也会在Container的析构函数中加上所含有类的析构函数,且是先执行自己。

    Delegation (委托) . Composition by reference

    这里是通过指针,指向功能实现的类,而自身却只是一个对外的接口,所有功能的实现都是通过指针委托‘功能实现类’完成的,这样它便有了高度的弹性(可以改变它功能,而不影响整体,也可以方便地增加它的功能)

    这样的写法称为pimpl(private implementation),左边的是Handle,右边的是Body,其主要作用是解开类的使用接口和实现的耦合

    (这里用到是指针,但为什么也叫by reference而不是by point?因为业界中没有by point这个说法,by point也叫by reference)

    Inheritance(继承),表示is-a

    (在C++中,struct其实是一种class)

    语法:就是加黄色的一行;上面的是父类,下面的是子类(子类继承父类)。

    C++的继承拥有三种方式,分别是public / private / protected,其中最重要的public。(而Java中只有public)

    子class拥有自己的part(成份)同时,还涵盖了父class的prat。

    图示,用空心三角形表示继承。

    Inheritance(继承)关系下的构造和析构

    与Composition的关系相似,但前提是父类中的构造函数一定要是虚函数(后面会解释虚函数),否则不会出图中下面是两个动作

    Inheritance(继承)with virtual functions(虚函数)

    父类的成员函数分三种类型:非虚函数、虚函数、纯虚函数。

    虚函数的语法:在非虚函数之前加virtual。

    非虚函数,子类不能重新定义,在父类中定义好,供子类使用。

    虚函数,在父类中定义,子类也可以重新定义它。

    纯虚函数,在父类中声明,在子类中定义。

    Template Method(模板方法):二十三种设计模式之一。

    示例中,通过子类的对象调用父类的函数,当调用虚函数时,编译器会检查子类是否有重新定义。

    Inheritance + Composition关系下的构造和析构

    在第一种情况中,Base part和Component part它两的构造的先后是顺序,而析构则是逆序的。

    在第二种情况中,与之前的相类似,不多说。

    Delegation(委托) + Inheritance(继承)

    左边委托右边,右边作为父类,可以被继承。

    这样的写法可以使左边被创建,内容可以被多个继承右边的子类观察。(就像一个文件在同一个软件里被打开,但有不同的查看窗口)

    相关文章

      网友评论

          本文标题:极客班C++面向对象高级编程(上)第二周笔记

          本文链接:https://www.haomeiwen.com/subject/inqfsttx.html