OOP特性:
- 抽象
- 封装和数据隐藏
- 多态
- 继承
- 代码的可重用性
接口分离: 1.提供类的声明 2.提供类成员函数
析构函数只有在构造函数用了new分配内存,才需要出来delete释放内存。否则无需工作。
const放在函数括号后:即为const成员函数,作用指不修改调用对象。
this指针的引入: 用来指向调用当前成员函数的对象(this作为隐藏参数传递给方法)
类静态成员变量: static const int Len = 30; 只能是整型或枚举的静态常量
类结合操作符重载
成为多态的重要一部分,隐藏了内部操作,强调了抽象的实质意义。
C++操作符重载要点:
- 重载后的操作符至少有一个操作数是用户定义的类型
- 使用操作符不能违反该操作符原有的句法规则
- 无法定义新的操作符
- 不能重载sizeof . .* :: ?: typeid *_cast
友元包含函数,类和成员函数
为何需要? 在为类重载二元操作符时,需要用到友元关系,方便使用。
存在的主要目的是作为类扩展接口的组成部分。
e.g Time乘以double可以用成员函数重载,但double乘以Time时不能,除非要求用户不能如此调用。否则应该引入友元函数重载。
一个常用的友元重载则是 cout << Time,而非用成员函数的 Time<< cout
1 | // 前一种成员函数重载 Time*double |
为了cout <<连续可输出,友元声明如下:
1 | ostream & operator<< (ostream & os, const Time & t) |
接受单一参数的构造函数为类的类型转换提供了蓝图(blueprint)
蓝图是一个有意思的词语,后续多态也会继续接触到,是一个隐性类型表征;在类的类型转换上,需要尤其注意编译器二义性转换的问题。
警告:谨慎地使用隐式转换函数。explicit定义类的构造函数,则相关对象的类型转换需要显式调用,不能隐式转换。
类声明描述了如何分配内存,但并不执行分配内存
static int num;的初始化是在类声明之外,int className::num=0;
1 | StringBad sailor = sports; |
隐式成员函数
C++自动提供下列成员函数
- 默认构造函数
- 复制构造函数
- 赋值操作符
- 默认析构函数
- 地址操作符
析构用了delete的类,所有对象生成的构造函数都应该使用new,否则会引起浅复制析构的错误。绝对避免试图删除已经删除的数据的行为!
书中的解决方案:使用deep copy,每个对象有自己的数据,而不是引用。
增加复制构造函数和赋值操作符,使类正确管理对象使用的内存。
1 | // 拷贝函数深复制 |
适配数组指针的释放语法
1 | char words[15]="bad idea"; |
new 对应 delete, delete[] 对应new []
初始化列表
执行在构造函数之前,因此可用于对const常量进行赋值,对声明为引用的类成员也类似。
- 只能用于构造函数
- 必须以此初始化非静态const数据成员
- 必须以此初始化引用数据成员
继承is-a
用virtual虚函数以及动态指针来实现多态(dynamic binding动态编译,需额外开销),派生可自动向基类类型转换,称为向上强制转换。反之则不可,需显式转换。
引出C++指导原则之一:不要为不使用的特性付出代价
虚函数工作原理
编译器处理虚函数会增加一个隐藏成员指向该函数的地址,若派生重定义了虚函数,则该指针指向新的函数地址。
多态在内存和执行带来一定的成本:1.每个对象因存储地址而增大 2.编译器要为每个类创建虚函数地址表(数组) 3.每个函数调用需要额外查找表中的地址
重载的虚基函数在派生实现时改动需要全部一起改动,称为类型协变
应当把所有派生重新定义的函数再基类设置为虚函数,如果强制需重新定义则=0成纯虚函数
public/protected/private继承
派生公共继承关系是is-a,继承了基类的接口;其他两种是has-a关系,继承了成员成为私有,只可在声明内部使用;
构造函数,析构函数,=号,友元不能自动继承,需重新声明并实现。
protect继承则派生声明不能直接访问基类私有成员,通过public方法调用
1 | //单例模式 |
传对象函数尽量用引用,避免构造和析构的开销;可以将派生对象用等号赋给基类对象,但相反则需提前明确定义。传引用可明确派生对象的类型,否则值引用可能会被编译器自动类型转换,发生意想不到的事情。也可使用dynamic_cast<const baseDMA &>(hs)的方式强制类型转换。