《effective c++》学习笔记

从今天开始看《effective c++》这本书,把学到的东西当做笔记记下来,算是督促自己学习吧,也算是和大家一起分享一点东西,理解不当的地方,请谅解。(每天更新三个条款)。

一:让自己习惯C++

条款1:视C++为一个语言联邦

条款2:尽量以const,enum,inline替换#define

条款3:尽可能使用const

条款4:确定对象被使用前已先被初始化

二:构造/析构/赋值运算

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

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

条款7:为多态基类声明virtual析构函数

条款8:别让异常逃离析构函数

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

[条款10:令operator=返回一个reference to *this](#条款10:令operator=返回一个reference to *this)

条款11:在operator=中处理"自我赋值"

条款12:复制对象时勿忘其每一个成分

条款1:视C++为一个语言联邦

主要内容:

1、这个条款主要说把C++看做多个次语言的联邦,包含C,object-Oriented C++,Template C++,STL。视情况而定用哪一部分。

ps:可能就像我写C++就是套着类的框,写面向过程编程代码~

条款2:尽量以const,enum,inline替换#define

主要内容:

1、宏定义的常量通常在预处理时期就被替换了,所以如果报错的话,可能很难追踪到错误点,用const常量定义会比较好.

2、const替换宏定义有两点需要注意:

(1)定义常量指针的时候,需要指针和指针所指内容都是const,所以用const string比较好。

(2)想要为某个类定义一个专属的常量,这时候用宏定义就不太行了,因为宏定义了之后,整个文件后面都可以用,除非#define #undefine限制?这时候用const常量比较好。

3、假如定义一个const常量作为类的专属常量,这时候类里面有个数组,需要const常量作为其下标,有的编译器可能没办法直接声明const常量的时候定义给于初值,必须在类外去定义初始化。这时候就可以用enum代替const常量。

4、宏也可用来定义函数,它没有函数调用的额外开销,直接是在预处理期间被替换了,但是宏函数也有副作用,这时候用inline去替换宏函数比较好。

条款3:尽可能使用const

主要内容:

1、const可以作用于对象,以及函数各个部分,能用const的地方尽量用const,防止不小心导致的错误更改。

2、const修饰成员函数,成员函数可以进行重载为const和非const成员函数。

调用关系:

(1)const对象可以调用const成员函数,不可以调用非const成员函数,而非const对象可以调用const和非const成员函数。

(2)const成员函数只能调用const成员函数,而非const成员函数可以调用非const成员函数和const成员函数。

3、bitwise constness和logical constness。

(1)bitwise constness认为只要成员函数是const修饰的,那么里面的任何bit都不能修改。不过,假如一个类里面有一个char*类型的指针,我们不能修改指针,但是我们可以修改指针所指物。

(2)logical constness认为成员函数是const修饰的,那么里面的某些bit是可以修改的。比如上述3-1例子,还比如我们有一个类,里面某些成员我们是可以通过mutable进行修饰,达到修改其的意图。

4、const和non-const成员函数避免重复。

成员函数有const和非const,如果里面做的事情一样,会造成代码冗余,解决方法就是通过非const成员函数调用const成员函数,类似于这样:const_cast<char&>(static_cast<const T&>(*this)[pos]);也就是说先将对象转为const类型调用const成员函数,因为非const成员函数要返回是非const的结果,所以用const_cast去除const成员函数返回的结果的const属性。

条款4:确定对象被使用前已先被初始化

主要内容:

1、不管是内置类型还是自定义类型的对象,都应该在使用之前进行初始化,防止出现意想不到的错误(除非你明确其在用之前一定会被赋值)。

2、尽量使用成员初始化列表去初始化类的成员,一方面是效率会高(省了无畏的赋值),另一个方面是类的某些成员只能通过初始化去完成,比如const类型,引用类型。

3、成员初始化列表初始化成员变量的顺序取决于声明成员变量的次序。

4、非局部的static对象(全局的,namespace作用域的或者class内或file作用域内的static对象)在多个文件里面使用时,它在使用之前是否已初始化是不确定的,解决方法是将其搬到自己的专属函数内,然后返回其引用即可,转化为局部的static对象。

5、多线程情况下可能会有竞争,4这个方法不能适用,除非是多线程启动之前,单线程去一一调用函数完成初始化。

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

主要内容:

1、一个空的class,编译器默认会生成默认的构造函数,析构函数,拷贝构造,赋值构造函数。生成默认构造和析构函数,作用是调用父类或者非static成员变量的构造和析构函数。如果自己声明了自定义的构造和析构,那么编译器将不会默认生成。拷贝构造,赋值构造函数是浅拷贝,如果类内管理资源,析构的时候可能会有问题,需要重载拷贝构造,赋值构造函数。编译器有时也会拒绝生成默认的拷贝构造,赋值构造函数,比如类内有const成员或者引用类型成员,如果想要为引用类型成员赋值,就得自己重载拷贝构造,赋值构造函数。

2、父类的拷贝构造函数如果是private的,那么子类也不会生成一个默认的拷贝构造函数,因为没有权限。

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

主要内容:

1、如果不想让类支持拷贝或者赋值,那么可以声明拷贝构造,赋值构造函数两个函数为private,这样做就不会被拷贝或者赋值了,也不用定义出来其实现,防止类内的其他成员函数或者友元函数调用。

条款7:为多态基类声明virtual析构函数

主要内容:

1、类定义出来如果是作为其他类的基类,那么就要给它的析构函数定义为virtual析构函数,防止在释放基类指针指向派生类对象的时候发生内存泄漏。

2、如果类不打算作为其他类的基类,那么就不要把析构函数定义为virtual析构函数,否则对象的体积将会增加,且因含有虚表指针,就不具有移植性了。这是因为实现virtual函数的原理是对象内部包含了一个虚表指针,基类派生类的虚函数的指针都存放在数组里面,对象调用哪个虚函数,是由虚表指针去函数指针数组里面找到然后调用。每个对象都含有一个虚表指针,一个指针在32位机器上占4个字节,在64位机器上占8个字节。

3、如果类没有将析构函数定义为virtual析构函数,那么最好不要继承它。

条款8:别让异常逃离析构函数

主要内容:

1、析构函数中有异常的话,如果此时是vector类型,那么可能第一个对象就销毁时就抛异常,继续调用其他对象析构,第二个对象抛出异常,此时系统将会出现不明确的行为或者过早结束,所以不要在析构的时候抛异常。

2、较好的方法是将可能抛出异常的函数开放给用户,让用户去调用,此时用户就会决定异常抛出后的一个反应,程序员在析构时可以加一个双层保险,如果没有调用函数标记,那么析构这里可以调用一次,捕获异常并且记录或者结束程序。

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

主要内容:

1、不要在析构或者构造过程中调用virtual函数。假设现在有一个基类A,里面有一个A构造函数,logTransaction虚函数,A构造函数内部调用logTransaction虚函数,此时B和C继承自A类,B和C类重写了自己的logTransaction函数。如果此时定义一个B类,B的构造函数会被调用,但是首先应该先构造B的基类A,这时候A去构造函数过程中调用A类的虚函数logTransaction(基类构造期间,虚函数还不是虚函数),这个现象的原因是此时B类还没有构造好,它里面的成员对象都是未初始化的,编译器会当做B类还不存在。这种代码一般编译器会有警告。

2、还有一种情况,当基类构造函数调用普通的init函数,然后init函数里面调用了虚函数,那么此时编译器就有可能就不会有任何告警,但是程序执行现象就不会如我们期望的一般。

3、解决方法是基类logTransaction函数不要定义为虚函数,将其定义为普通函数,然后通过构造函数去调用,接受参数,这时候,派生类在构造的时候将自己这边的参数传到基类的构造函数那边,完成调用。也就是说我们无法使用虚函数从基类向下调用,在构造期间,我们可以让派生类将必要的构造信息向上传递给基类构造函数。

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

主要内容:

1、按照内置类型可以连续赋值的形式,我们在写operator=函数的时候,就必须返回返回*this引用,+=也是如此。否则返回值效率也会降低。

条款11:在operator=中处理"自我赋值"

主要内容:

1、在写operator=的时候,需要处理自我赋值的情况,提高效率,还有一点是需要注意在抛出异常的时候,原本对象已经被破坏的情况,正常来说,应该是先申请临时对象,再进行复制,将原对象释放掉,让指针重新指向新的临时对象,返回*this。

2、如果追求效率, 可以先把要复制的对象拷贝一份,然后用swap函数交换当前对象和拷贝的临时对象。

条款12:复制对象时勿忘其每一个成分

主要内容:

1、拷贝构造函数和赋值函数中必须处理每一个成员变量,即使是后面新加了一个变量,也要在拷贝构造函数和赋值函数中进行处理。否则会有问题。

2、派生类进行拷贝构造函数和赋值函数的时候,也需要通过派生类的拷贝构造去调用基类的拷贝构造,不能忘记了。

3、不要妄想用拷贝构造函数调用赋值函数,或者赋值函数调用拷贝构造函数。如果真的要消除两者之间重复的代码,可以将重复的代码提取出来为一个init函数,放在private里,让拷贝构造函数和赋值函数调用init函数即可。

相关推荐
黑叶白树3 分钟前
简单的签到程序 python笔记
笔记·python
@小博的博客6 分钟前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
幸运超级加倍~40 分钟前
软件设计师-上午题-15 计算机网络(5分)
笔记·计算机网络
南宫生1 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
爱吃喵的鲤鱼1 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
懒惰才能让科技进步1 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
7年老菜鸡2 小时前
策略模式(C++)三分钟读懂
c++·qt·策略模式
Ni-Guvara2 小时前
函数对象笔记
c++·算法
love_and_hope2 小时前
Pytorch学习--神经网络--搭建小实战(手撕CIFAR 10 model structure)和 Sequential 的使用
人工智能·pytorch·python·深度学习·学习
似霰2 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder