- 全部参数都有默认实参的构造函数本身就相当于一个默认构造函数,就不能再另外定义一个默认构造函数了;
- 构造函数要在初值列处初始化,而不要在函数体内赋值,这两个地方做的动作是不同的;
- class的成员函数的const:
1. class的成员函数的参数列表和函数体之间的那个const: 针对的是调用这个成员函数的那个对象的数据成员是否会被函数修改;
class的成员函数若不会修改数据成员,就一定要声明为const,不加就默认会修改数据成员(若加了const,则无论是不是const类对象都可以调用,但这个成员函数并不会去修改调用者的数据成员;但若不加,就只有非const类对象可以调(且该成员函数被编译器认为会修改调用者的数据成员),const类对象不能调);
若同时写一个成员函数的const和non-const版本,那么就可以保证const类对象只会调用const版本,non-const类对象只会调用non-const版本;
2. class的成员函数的某个参数的const:针对的是传入此函数的参数是否会被该成员函数修改;
传参数时:by reference(本质还是by pointer)而不要by value是为了传递速度块,且无需调用构造函数+析构函数;而by reference to const,代表在函数中不会修改引用指向的对象;
- 相同class的各个objects互为友元;
- 重载<<操作符,由于要支持 cout << complex(1, 2);但<<默认绑定的是左边的参数cout,即若想写成成员函数,只能去修改ostream类;否则只能写成非成员函数,将接受参数写为ostream& operator << (ostream& os, const complex& x) { … }
- 传出去的人不必知道接收方如何接受
例:返回类型是String&, 在函数中return *this;
- 对于data member含有指针的class,写copy assignment operator和构造函数:
1. 写copy assignment operator时,要检测自我赋值(正常流程时:① 释放*this中指针指向的内存 ② 为其分配一块新内存 ③ 把新内容拷贝进新内存);
2. 写构造函数,就无需自我检测(因为这个新构造的class对象还没有构造完,因此其指针数据成员还没有指向某块内快,因此不用先delete再赋新的)
- new搭配delete,new[ ]搭配delete[ ],这里的[ ]实际是提醒编译器我要释放的是一个数组中的所有元素,至于[ ]具体是几个,在这个数组分配的内存中有一块4bite记录了元素个数,因此这里不用填;
- class中的几种成员的辨析:
non-static数据成员:每个class对象都有一份;
non-static成员函数:也只有一份,只能调用non-static数据成员,调用的本质是根据不同的class对象去调用它,即把不同的class对象作为this pointer, 根据this的不同的地址去找到属于该class对象的那份non-static数据成员并对其处理;
static 数据成员:只有一份,和class对象没关系,它是属于class本身的,在class内声明,需要在class外部进行定义并获得内存后才出现这唯一的一份; 而non-static数据成员每一个class对象都对应一份;需要在class外部进行定义去获得内存;
static 成员函数:只有一份; 由于没有this pointer,因此只能处理static数据成员; 2种方式调用: ① class调用 ② 也可以通过某个class的实例化对象调用; 但编译器不会为我们为函数的参数增添this pointer
- 懒汉单例模式:
1. 把class的构造函数都放进private防止创建新的class对象;
2. public种提供一个static成员函数接口,在函数中创建一个static的class对象,则只有当第一次调用该接口时,才会创建唯一的一个class对象;
- class template和function template
class template在使用时,需要指定参数类型,来将指定的类型与模板中的typename绑定;
function template会进行实参推导,根据传入函数的参数类型,自动推导出模板中的typename;
- using namespace std;
若一下全打开,虽方便但无法再使用std中的所有名字;
- adapter模式:
类似于queue的实现:
class queue中has-a deque的实例化对象,queue的所有接口函数的具体实现都是通过调用这个deque对象的成员函数来完成的
- 面向对象的三种特性:复合;委托;继承;
- composition关系:
实心菱形标志
has-a的关系:创建一个queue对象时,内部拥有的那个deque对象也会被创建出来,即二者的生命周期是相同的
//复合和继承关系下的构造及析构顺序是一样的,所以下面图为析构的图
构造由内而外:Container::Container(...) : Component(...) { ... } //写外部Container的构造函数时,不用管内部part的构造,编译器会在初值列处自动调用内部的default构造函数,如果想指定内部的某一种构造函数,就需要自己手动显式完成内部part的构造;
析构由外而内:Container::~Container(...) {... ~Component(); } //写外部Container的析构函数时,在函数体中先执行外部part,也不用管内部part的析构,编译器会在完成外部part的析构后自动添加内部part的析构;
2. Delegation(Composition by reference)关系
空心菱形标志
PIMPL模式(即point to implement)(或Handle/Body):
Handle class作为接口class,内部包含的是一个指针,具体的实现都在另一个Body class中, 当需要进行某种操作时,都调用Body去完成;
创建一个Handle对象时,只是创建了一个指针,但指针指向的那个Body对象并没有被创建出来,即二者的生命周期不同;
这样拥有更好的弹性,即可以写多个不同的实现Body class,而Handle class不用改,这样客户代码也就不用改;相当于编译防火墙,若有更改,Handle class不用再编译,只需将实现的Body class重新编译即可;
3. Inheritance关系:
空心三角标志
is-a的关系:人是一种哺乳类;
non-virtual:不希望derived class去override它;
virtual:基类已有默认定义,且希望derived class去override它;
pure virtual:基类对它没有默认定义,且deprived class必须去override它;
继承体系下的构造和析构顺序和复合是一样的;
Template Method:
将一些通用要做的事情写成base class CDocument, 将更具体的任务Serialize作为virtual函数,在成员函数OnFileOpen中完成通用的任务,
当执行到更具体的任务Serialize时,根据调用者不同(即传入的this pointer),通过虚函数表去调用不同的Serialize实现;
- 三种关系混合使用:
- Inheritance + Composition 关系下的构造和析构:
注:不同编译器的顺序可能不同;
构造顺序:
上图:父类->复合类->子类; 下图:复合类->父类->子类;
析构顺序:
上图:子类->复合类->父类;
2. Delegation + Inheritance
基类Subject中的数组m_views存了整个Observer继承体系的对象指针;
通过传入update的this pointer不同,去更新Observer体系中的不同对象
3. Delegation + Inheritance
组合模式:
Composite想要可以既add Composite类型和Primitive类型的,那就让他俩同时继承自一个父类Component;
并且数组c中应该存储父类的指针(因为指针大小相同,而Primitive和Composite对象不一定大小相同);
注:
在此例中,父类Component的add是一个虚函数而不能是一个纯虚函数(因为在此例中子类Primitive不需要有自己的add版本,
若父类的add声明为函数体为空的虚函数,则Primitive不写的话,默认是继承父类的空函数体的add版本,Composite类则override自己版本的add;
若父类声明为纯虚版本,则两个子类都必须override add版本,则Primitive只能写一个空函数体的add版本)
原型模式:
想要去创建未来才出现的对象,现在不知道未来的class名称;
子类需要设法让自己能被父类看到;
父类用一个数组存储Image*,并提供一个addPrototype函数接口给子类使用;
子类拥有一个static数据成员,它会调用自己的构造函数去创建一个自己的实例化对象,并且把这个子类对象的指针添加到父类的数组中;
...只能理解到这里了,一脸懵逼...