effective C++ 读书精华笔记提取
Posted 我爱你,中国!中国加油,武汉加油!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了effective C++ 读书精华笔记提取相关的知识,希望对你有一定的参考价值。
目录
让自己习惯c++
尽可能的使用const
- 当const修饰类成员函数时,如果两个函数只有常量性不同, 可以被重载。(本质上,const其实修饰的是this指针, 所以呢, 本质上还是参数的类型不同导致的重载)
- 如果要修改一个const的类对象内的成员变量, 需要把变量使用mutable修饰成员变量。也就是说使用mutable修饰的变量不受外层类对象状态的影响。
struct Data { int a; mutable int b; }; const Data data{1, 2}; data.b = 100;
- bitwise constness 是C++对常量性的定义。 也就是说,对于const修改的对象,对象本身的内存的占的空间是不可以修改的。例如:假设对象里有指针,指针本身不可以改变,但是指针指向的对象是可以修改的。
确定对象使用前被初始化
-
对于初始化列表:
1 . C++规定,对象的成员变量的初始化动作发生在进入构造函数体之前, 在初始化列表中完成初始化动作。
2. 对于内置类型,初始化列表与赋值的成本是相同的。,但是对于其它类型,它们的效率差异很大。
3. 对于类内的const成员变量或者引用,必须在成员初始化列表中进行初始化。 -
C++的成员初始化顺序:
- 基类对象更早于其子类对象进行初始化。
- 同一个类对象内的成员变量,总是以其声明的次序进行初始化,与初始化列表中的顺序无关。
-
C++对定义于不同的编译单元内的non-local static 对象的初始化相对次序没有明确的定义。
构造、析构和赋值运算
C++默认编写和调用的函数:
- 默认的构造函数 :
- 当用户不定义任何构造函数时(包含拷贝构造函数),编译器才会默认生成一个构造函数。
- 如果类成员变量有引用或者const变量, 编译器无法生成合法默认构造函数, 所以编译失败。
- 默认的拷贝构造函数:
- 当用户不定义拷贝构造函数时,编译器可以尝试生成默认的拷贝构造函数。
- 只有当用户使用到拷贝构造函数时,编译器才会尝试去生成。
- 默认拷贝赋值运算符:
- 当用户不定义拷贝构造函数时,编译器会尝试生成默认赋值运算符。
- 同样的,一些情况下也无法默认生成,例如:类成员变量有引用或者const变量。
- 默认的析构函数
最后,总结一下,编译器默认生成的四种函数,有以下共同点:
- 只有当用户使用到拷贝构造函数时,编译器才会尝试去生成;
- 如果基类中对应的构造/拷贝构造/赋值运算符是私有的,编译器也无法生成。
- 默认生成的函数都是inline的, 并且是public的。
若不想使用编译器自动生成的函数,明确拒绝。
- 只声明想要禁止的函数,不定义,并且声明为private的。
- c++11之后, 引用了delete关键字,使用delete关键字修改。delete关键字可以修饰任何成员函数的。
- 定义一个基类,并且把基类的相应函数声明为delete或者private, 然后子类继承它,这样的的话,子类的相应函数也是无法使用的。
为多态的基类声明virtual 析构函数。
- 如果一个类想作为基类,并且想使用引用或指针用多态,该应该把析构函数声明为virtual的。
- 如果一个类不想作为基类,或者不想用于多态用途,则没有必要声明为virtual的构造函数。
- 一个析构函数可以声明为纯虚函数,但是必须要定义它才行。否则,出现链接错误,子类的析构函数无法调用到基类的析构函数。
- 析构函数的运作方式:最深层的子类的构造函数先执行,然后一层层地向外调用对应基类的析构函数。如果通过基类的指针调用析构函数,并且析构函数不是虚函数,则只会调用基类的虚函数,不会调用子类的虚函数。
绝不在构造和析构函数中调用virtual函数。
- 在构造和构造函数中调用虚函数时,不会执行多态操作。
- 在子类对象的base class 构造期间, 对象的类型不是子类,而是基类。
- 同样的,在子类对象的base 析构期间,子类已经先被析构掉了,此时的对象类型是基类。
- 额外补充一点:虚函数的默认参数值是静态绑定的,不是动态绑定的。所以在多态期间,子类里面的虚函数的默认参数其实是基类的默认参数。
operator=操作符
- 它必须是类成员函数。
- 重载时,它的返回值应该是this指针的引用
- 处理自我赋值的情况:使用技巧 copy and swap 技术。
实现
尽力少做转型的动作
- c++中的四种类型转换
- const_cast, 移除对象的const属性。
- dynamic_cast, 主要用于来执行“安全向下”的类型转换, 它可能会耗费重大的运行成本。 它的许多实现版本执行速度相当慢,例如一个很普遍的实现版本是基于"class 名称之字符串比较". 如果在四层深的单继承体系中的对象是执行dynamic_cast, 可能最多可达四次的strcmp调用,深度继承和多继承的成本更高。
- reinterpret_cast, 意图执行低级转换,实际动作可能取决于编译器,具有不可移植性。
- static_cast, 用于强迫隐式转换。
- 许多程序员认为转型其实什么也不做,这个是错误的。
- 一个类型转换也往往真的令编译器编译出运行期间的执行代码,例如:把一个int 转换为double,几乎肯定会产生一些代码,int 与double的底层实现不同。
- 在c++的多继承中,基类指针与子类指针往往是不相同的,这种情况下会有一个偏移量在运行期被施行于子类指针身上。
透彻了解inline的里里外外
- 使用inline函数可以避免函数调用带来的额外开销。
- 编译器的优化机制通常被设计用于浓缩那些“不含函数调用“的代码,使用inline函数之后,编译器可以进行一部分的优化功能。
- 当inline函数体过大时,如果内联函数调用点过多的话,可能会引起目标文件变大,进而影响到 i cache的命中率。
- 当inline的函数体较小时,可能会导致目标码较小,以及较高的 i cache命中率。
- virtual 函数不会变成内联函数,原因很简单:virtual意味着等到运行期才确定调用哪个函数,而内联是在编译期决定的。
- 使用函数指针对内联函数的调用,编译器通常不会实施inlinling.
- 大部分的调试器对于inline函数往往束手无策,所以开发过程中调试很麻烦的。
继承与面向对象
避免遮掩继承而来的名称
- 先说一下子类与基数的作用域。当子类查找一个函数时,会先在子类空间中查找,如果找不到,再去基类空间去继续,如果还查不到,再去全局作用域空间中去查找。
- 再说一下重载,我理解的重载只有发现在同一层作用域中,子类的同名函数不可以与基类的同名函数之间发生重载,如果形参不匹配,编译直接报错的。
- 对于函数名字的遮掩与重载,与是否为虚函数无关的。函数有的特性,虚函数都有, 虚函数只有当通过指针或引用调用时,才会体现它的多态性,就运行时绑定。通过类对象调用虚函数时也是静态绑定的,编译时决定了。
- 如果子类遮掩了继承而来的基类的函数名字,那么无法隐式调用它们了,只能通过显示调用了,Base::Func();
- 可以在子类中,使用using 把基类的函数名引入到子类作用域中, 例如:using base::func;
绝不重新定义继承而来的虚函数的默认值
- 通过引用或指针调用虚函数执行时,是进行动态绑定的。而虚函数的默认值是静态绑定的.
- 为什么 c++中进行静态绑定函数的默认参数呢? 答案就是因为效率。如果缺省参数值是动态绑定的,编译器就必须有某种方法在运行期为virtual函数决定乱写的参数值,但是这样很复杂并且慢。
子类与基类中的虚函数的public与private属性不一致会怎么样?
- 首先,编译不会报错,允许基类的virtual函数是public, 子类的virtual函数为private,也允许基类的virtual函数为private, 子类的virtual函数为public.
- 虚函数为public还是private, 只会影响到静态绑定。
- 当基类中的函数为private而子类中为public时,你无法通过基类指针或引用调用该虚函数,但是可以通过子类对象调用。 当基类的虚函数为public而子类中为private时,可以通过基类的指针或引用该用该虚函数,但是不可以通过子类调用虚函数。
当子类与基类之间是protected或者private继承时,会怎么样?
- 无法完成由子类到基类的转换,因为基类是无法访问的。
多重继承
- 同一层级的基类之间的函数,不可以进行重载的。
- 当发生棱形继承时,如果共同的基类不是虚继承,在子类中会生产共同基类的复制形为。虚继承是需要代价的。
定制new 与delete
我之前的笔记,已经总结的非常好了。https://www.cnblogs.com/yinheyi/p/12900305.html
以上是关于effective C++ 读书精华笔记提取的主要内容,如果未能解决你的问题,请参考以下文章