【Effective C++】学习笔记
《Effective C++》,作者Scott Meyers
目录:
- 第一课:让自己习惯C++ (简单介绍)
- 第二课:构造/析构/赋值运算
- 第三课:资源管理
- 第四课:设计与申明
- 第五课:实现
- 第六课:继承与面向对象设计
- 第七课:模板与泛型编程
- 第八课:定制new和delete
- 第九课:杂项讨论
第一课:让自己习惯C++
条款01、视C++为一个语言联邦
条款02、尽量以const、enum、inline替换#define
#define是使用预处理器进行处理,无法被编译器知道,因此在发生错误时无法进行追溯。
条款03、尽可能使用const
const修饰指针,指针本身不能改变:char* const p
const修饰内容,指针所指物不能改变(明确来讲应该是不能通过指针来改变所指物的值):const char* p=char const *p
简单总结:const放在星号之前表示修饰内容,const放在星号之后表示修饰指针。
const的使用,
const修饰迭代器,
1、因为迭代器的功能和指针类似,所以const修饰迭代器就和修饰指针有同样的功能,即该迭代器不能指向其他的内容:
const std::vector::iterator iter=vec.begin();
++iter 该指令是错误的,因为该迭代器不能指向别的内容(失去了迭代器的原有功效,所以很少用)
2、若希望所指代的内容不能被改变,则需要使用const_iterator:
std::vector::const_iterator cIter=vec.begin();
*cIter=10 该指令是错误的,因为不能改变所指向的内容
const修饰函数,函数返回值、各参数和函数自身(类的成员函数)
1、函数的返回值:
若函数返回的是一个具体的值,对其进行const修饰没有任何意义,因为返回的值都是进行复制进行赋值的;
或者可以配合值的引用一起使用&,代表返回的是值的引用。
const A &GetA(void)
如果返回的是一个指针,则有一定的意义,该返回值只能被赋给加const修饰的同类型指针。例如函数:
const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();
正确的用法是
const char *str =GetString();
2、函数的参数:
函数输入参数的修饰,与值的修饰和指针的修饰是一样的道理。常与函数的引用传递&一起使用,如:
void Func(const int &x)
3、类的成员函数:
int get() const{ ... }
代表该函数不能修改对象的数据成员而且不能调用非const函数。任何不会修改数据的成员函数都应该使用const进行修饰。
const修饰this是本质,至于说“表示该成员函数不会修改类的数据。否则会编译报错”之类的说法只是一个现象,根源就是因为this是const类型的
但是有一种例外的情况就是const成员函数可以改变成员变量,即使用mutable进行修饰,例变量year:
mutable int year;
则year变量在const函数里面可以被改变。
总结
1、const一般和值的引用&一起进行使用,目的是提高程序的效率的同时又不改变值;
2、const修饰成员函数的主要作用是避免成员函数改变成员变量的值,其根源是修饰*this为const。再者,const对象只能调用const成员函数;
3、相对应的知识还有const_cast、static_cast等强制类型的转换。
条款04、确定对象被使用前已经被初始化
初始化是一个比较复杂的问题,需要后面的知识作为背书。
初始化的问题,一般是落在构造函数身上。
1、构造函数总是使用成员初值列对成员进行初始化;
2、如果成员变量是const或reference,他们就一定需要初值,不能被赋值;
条款05、了解C++默认编写并调用了哪些函数
C++类中会默认生成:
- 默认构造函数;
- copy构造函数;
- copy assignment操作符;
- 析构函数。
条款06、若不想使用编译器自动生成的函数,就该明确拒绝
对于默认生成的函数,如果不想使用该功能,就应该明确进行拒绝!!
如何拒绝呢?
比如说现在创建的类需要禁止copy操作,比如银行账户,账户与账户之间是禁止进行copy的。如果不申明copy构造函数,会有默认的copy构造函数。一种做法就是**在private下申明copy构造函数,并且不去定义它。**例如:
class base
{
public:
base() {};
private:
base(const base &);
};
int main()
{
base a;
base b(a);
return 0;
}
编译会进行报错:
更好的做法是私有继承一个不能拷贝的类,这样不仅外部不能访问copy构造函数,内部成员函数也不能访问基类私有伤员函数copy构造函数,这样在编译的过程中就会报错。
条款07、为多态基类申明virtual析构函数
现在有两个类,一个是基类base class,另一个是派生类derived class。基类指针或引用既能够指向base又可以指向derived,虚析构函数的作用是当基类指针指向派生类时调用的是派生类的析构函数,这就达到了内存释放的多态性。
当base class里有一个函数是虚函数是,析构函数就应该设置为虚析构函数;如果没有多态性的考量,就不应该设置为虚析构函数。
条款08、别让异常逃离析构函数
尽量不要让析构函数法发生异常(或者吐出异常)。
为什么?析构函数发生异常会导致程序过早接续或者出现不明确行为。
怎么解决?
1、当发生异常时使用abort()函数结束程序或者吞下异常;
2、不要让析构函数有机会抛出异常,可以另外设计成员函数解决此功能。
条款09、绝不在构造和析构过程中调用cirtual函数
以构造函数为例。
对于基类和派生类,当派生类调用构造函数时,首先会调用基类的构造函数,然后再调用派生类的构造函数。如果构造函数中有虚函数,就相当于派生类使用的是基类定义的虚函数。会引发不知名的错误。
可以理解为base class在构造期间,virtual函数不是virtual函数。
怎么解决?
不使用虚函数而要实现虚函数的功能,可以令派生类将必要的信息传递到base class构造函数,加以弥补。
*条款10、令operator=返回一个reference to this
主要是为了解决连锁赋值的问题。
条款11、再operator=中处理“自我赋值”
自我赋值就是自己给自己赋值。
在赋值的过程中,可能会有delete的操作。如果赋值的左右两边是同一个对象,那么delete操作就会引发错误。
怎么做?
1、一种做法就是在赋值之前进行证同检测(证明是否一样,这是传统的做法);
2、先生成一个副本,然后再赋值。
条款12、复制对象时切勿忘其每一个成分
对于拷贝构造函数和拷贝赋值操作符,都是需要对对象进行复制。
1、在派生类中的构造函数,不仅仅要复制本地的变量,还要复制基类的数据;
2、拷贝构造函数和拷贝赋值操作符不要进行相互调用的情况。
条款13、
条款14、
条款15、
条款16、
条款17、
条款18、
条款19、
条款20、