《Effective C++》笔记 条款01-条款10

条款01,视C++为一个语言城邦

C++语言有很多特性,适合哪个用哪个。分为四部分,分别是:CC with Class面向对象Template C++模板编程STL
pass-by-referencepass-by-value相比,后者多调用了一次拷贝构造函数与析构函数,且当形参为非基本类型时,也将在栈区开辟更多的空间。因此,参数传递尽可能使用pass-by-reference方式,如果不希望函数内部对实参进行更改,可以在形参前加canst修饰。
但是,并不是pass-by-reference方式一定优于pass-by-value,由于引用的实质是指针实现,引用真正传递的是指针,进行间接寻址,在传递内置类型时(如 int),指针所占空间甚至高于变量所占内存空间(char类型占用一个字节,而32bit系统指针占用4个字节),且间接寻址的效率要低些,因此,此时应选用pass-by-value更有效。同理,大多数STL容器内含的东西仅仅比指针类型多一点。因此,pass-by-reference节省空间有限,且间接寻址降低了程序效率,选用pass-by-value效率更高。

条款02,用const enum和inline代替#define

宏定义是简单的字符串替换,在调试时不会有变量名的提示。并且#define的作用域太大。宏函数的编写容易出错,需要特别注意括号的使用。
对于常量,应该用const和enum代替。 enum值不能取地址和引用,非常接近#define。
对于函数,可以用inline代替。
引入inline内联函数是为了解决频繁调用小函数大量消耗栈空间的问题。inline函数只适用于简单的函数语句。inline函数仅仅是对编译器的一个建议,最终是否内联,取决于函数是否复杂和编译器。内联函数是需要展开的,所有编译器必须能随时找到内联函数的定义,最好把内联函数的定义放在头文件。关键字inline必须要和函数定义放在一起,才能使函数内联。定义在类中的成员函数默认都是内联的,如果在类内没给出定义,则需要在类外定义时加上inline,否则就认为不是内联的。类的构造函数和析构函数最好不要使用inline,因为可能会调用父类的构造函数和析构函数,代码比看起来复杂得多。 inline会造成代码膨胀导致额外的换页行为,降低指令高速缓存装置的命中率。inline代码替换是在编译阶段进行的

条款03,尽可能使用const

如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量。
STL迭代器使用指针写的,所以迭代器的作用就像个T*指针。声明迭代器为const就像声明指针为const一样,表示这个迭代器不得指向不同的东西,但它所指的东西的值是可以改动的。若希望迭代器所指的东西不可被改动,需要用const_iterator。
将const作用于成员函数,是为了确认成员函数可以作用于const对象。有了const标识,我们很容易得知哪个函数可以改动对象内容而哪个函数不可以。
如果两个成员函数只是常量性不同,也可以被重载。
bitwise constness约束:成员函数只有在不修改对象的任何成员变量时才可以说是const,编译器只需寻找成员变量的赋值操作即可。
关键字mutable可以释放掉non-static成员变量的bitwise constness约束。用mutable修饰的成员变量可能总是会被修改,即使在const成员函数内。
为了节省代码,对于仅常量性不同的重载函数,可以临时使用转型casting,让non-const函数调用const函数。反过来不行,const成员函数内不能失去其const特性。
将某些东西声明为const可让编译器帮助侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
编译器强制实施bitwise constness,但写程序时应使用概念上的常量性。
当const和non-const成员函数有着是指等价的实现时,令non-const版本调用const版本可避免代码重复。

条款04,确定对象被使用前已先被初始化

对于无任何成员的内置类型,手工完成初始化。对于内置类型以外的,初始化任务落在构造函数上。确保每一个构造函数都将对象的每一个成员初始化。
对象的成员变量的初始化动作发生在进入构造函数本体之前,在函数体内部的是赋值不是初始化。应该用成员初始化列表代替赋值操作。
在构造函数体内赋值,实际上先调用默认构造函数设初值,再立刻对它们赋予新值。
对于大多数类型,初始化列表初始化是最高效的。对于内置对象,其初始化和赋值的成本相同,但是为了一致性,也使用成员初始化列表来初始化。甚至当想要默认构造一个成员变量,也可以使用成员初始化列表,只需指定无物nothing作为初始化实参即可。

Student::student()
	:theName(),//会调用theName的默认构造函数
	theAddress(),
	theAge(0)

使用初始化列表时要列出所有的成员变量,这样可以记得哪些成员变量无需初始值。该给初值的没给初值,会导致不确定的行为。
如果成员变量是const或references,就一定要初值。
全局变量内置类型,不给初值,默认给初值,int为0;
局部变量内置类型,不进行初始化,使用会报错。
类内成员变量int类型不初始化,直接使用,不报错,但是会导致不确定。
成员初始化次序:基类总是早于子类被初始化。类内的成员变量总是以其声明次序被初始化,即使它们在成员初始化列表中以不同的次序出现。如果对象成员之间有依赖,那么次序非常重要,否则可能使用未初始化的对象。
对于non-local static对象之间的依赖问题,可以使用:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。这是单例模式的常用实现方法。如果从未调用non-local staic对象的“仿真函数”,就绝不会引发构造和析构成本。
总的来说,要手工初始化内置型non-member对象,使用成员初始化列表来处理对象的所有成分。
总结:为内置型对象进行手工初始化,因为C++不保证初始化它们。构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。初始化列表列出所有的成员变量,其排序顺序应该与它们在calss中的声明次序相同。为免除“跨编译单元之初始化次序”问题,应以local static对象替换non-local static对象。

条款05,了解C++默默编写并调用哪些函数

如果定义一个空类,会默认有 默认构造函数、拷贝构造函数、析构函数、拷贝赋值操作符=。

class Empty{};

条款06,若不想使用编译器自动生成的函数,就该明确拒绝

所有编译器产出的函数都是public。为阻止这些函数被创建出来,可以将它们声明为private,并且不要去实现。甚至可以写一个Uncopyable这样的基类。

条款07,为多态基类声明virtual析构函数

当一个子类经由一个父类指针被删除,而基类带着一个non-virtual析构函数,会导致实际执行时对象的子类部分没被销毁。
如果让一个non-virtual类变得有virtual函数,会增加virtual table,增加对象的体积。
所以,最好的方法是:只有当class内含至少一个virtual函数,才为它声明virtual析构函数。
总结:带多态性质的base classer应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
Classer的设计目的如果不是作为base classes使用或者不是为了具备多态性,就不该声明virtual析构函数。

条款08,别让异常逃离析构函数

析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下它们(不传播)或结束程序。
如果客户端需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

条款09,绝不在构造和析构过程中调用virtual函数

对象在子类构造函数开始执行前不会成为一个子类对象,而是一个父类对象。所以,在构造和析构期间调用的virtual函数,可能不是自己想使用的函数。

条款10,令operator= 返回一个reference to *this

赋值采用右结合律,赋值可以连锁。

a=b=c=10;

c=10这个表达式的值等于c等于10。 为了维护这一常用规范,我们在定义class时应该应operator= 返回一个reference to *this。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值