Effective C++中文版_读书笔记

预处理:#开头的语句
1.宏定义:#define
2.头文件包含:#include
3.条件编译:#ifdef ~ #else #endif
            #ifndef  #define   //防止重复编译
宏定义的预处理最好用const 替换

static 类成员不能在类的构造函数初始化,只能在类外面定义(初始化)。
静态常量可在内部赋值也可在类外,如在内部赋值,外部也要有定义(/声明)。
如编译器不接受类内部定义,类的内部又需要用到时,int可在enum内定义:
如:enum{UNM_MAXINDEX = 255};

采用内敛函数替换宏函数,用inline 声明即可,可以和模版结合
如:
template
inline const T& max(const T& a, const T& b) { return a > b ? a : b; }

scanf 和printf 不是类型安全的,而且没有扩展性,尽量使用cin、cout

对于对象空间的申请释放,使用new/delete 不使用malloc/free;
因为后者只申请了空间而不会创建对象,释放时也不会调用对象的析构函数

operator new:
set_new_handler()用于内存分配不足时调用,如没有此函数,内存分配不足会抛出std::bad_alloc异常
如:
void noMoreMemory()
{
cerr << "Unable to satisfy request for memory\n";
abort();
}
int main()
{
set_new_handler(noMoreMemory);
int *pBigDataArray = new int[100000000];
...

在使用多个指针指向同一内存空间时要注意内存泄漏以及非法访问
内存泄漏:两个指针初始都指向一片内存,相互赋值时可能使其中一个永远不能被释放(指针指向丢失)
非法访问:两个指针指向同一片内存,释放其中一个后用另外的一个指针去操作内存控件

(Notice)
对象的创建分两步:
1. 数据成员初始化。2. 执行被调用构造函数体内的动作。
尽量使用初始化而不要在构造函数里赋值,
1.特别是:(const 和引用)数据成员只能用初始化,不能被赋值
2.效率:构造时,为内部成员赋初始值。
    构造函数内赋值:先调用构造,构造函数再调用赋值运算符
    初始化:先对成员初始化,再调用一次构造函数

(Demo)
初始化列表中成员列出的顺序和它们在类中声明的顺序相同
类成员是按照它们在类里被声明的顺序进行初始化的

(Demo)
当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。(类作为基类析构函数才声明为虚)
类中没有虚函数而将析构声明为虚函数,类中会生成虚函数表,
会改变该类对象的大小,导致代码无法移植(与其他语言生成的对象大小不一致)


引用只是一个别名,不要使用
1.临时(局部)对象的引用(对象不存在)
2.堆栈上的对象引用(容易引起内存泄漏)

(Notice)
当定义自己的赋值运算符时,
赋值运算符要定义为类的成员函数,返回*this。

(Demo)
赋值运算要考虑到自己对自己赋值,如果忽略,
调用赋值运算符时会先释放旧的资源,再为其赋值,
为自己赋值时会导致找不到旧的资源
if (this == &rObject) return *this;
对于不同类有不同的判定是否需要返回*this的方法,
比如字符串只需要比较字符是否相等

基类和派生类都重载了 = 时,派生类需要对基类成员显示赋值
(调用基类的operator=),传入派生类对象。
Base::operator=(SubObject); / static_cast(*this) = SubObject;
拷贝构造函数也要拷贝基类部分 SubClass(const SubClass& rhs): Base(SubObject), y(SubObject.y) {}

构造函数声明为explicit,对象不能被隐式转换

operator>>和operator<<决不能是成员函数。
如果f 是operator>>或operator<<,让f 成为非成员函数。
因为:如果是成员函数,调用时只能让对象位于左边;因为调用成员函数是: 对象.成员方法.
object>>cin;     
object< 如果f 还需要访问C 的非公有成员,让f 成为C 的友元函数。(类中声明)

const 函数中需要修改成员函数时,成员函数用mutable声明

(Notice)
当函数非虚函数,接收基类对象的函数接收实参为派生类对象时,
派生类将会被切割成基类对象,调用的函数也是基类的。
传递对象引用不会引起切割产生

不使用隐式生成的函数需要显示的禁止它,如隐式生成的赋值运算符
隐式生成:编译器自动生成的
解决方法:自定义类中声明要禁止的函数,声明为private,且不要定义它。
如:自定义的数组类禁止两个对象进行赋值
(赋值和拷贝构造函数,想禁止它们其中的一个时,也要禁止另外一个)
private:
    Array& operator=(const Array& rhs);

const String B("Hello World");
String& alsoB = const_cast(B); //引用alsoB不具有const 属性

char *str = B;             // 调用B.operator char*()
strcpy(str, "Hi Mom");  // 修改str 指向的值

const_cast
用法:const_cast(expression)
用来去除复合类型中const和volatile属性(没有真正去除)。
变量本身的const属性是不能去除的,一般是去除指针(或引用)的const属性,再进行间接修改。
也就是说源类型和目标类型除了const属性不同,其他地方完全相同。

static_cast
用法:static_cast (expression)
用途:
a)(把派生类的指针或引用转换成基类的指针或者引用表示)是安全的;
  (把基类指针或引用转换成子类的指针或者引用)是不安全的。
b) 用于基本数据类型之间的转换,如把int转换成char,这种转换的安全性也要由开发人员来保证。
c) 可以把空指针转换成目标类型的空指针(null pointer)。
d) 把任何类型的表达式转换成void类型。
注意: static_cast不能转换掉expression的const、volitale或者__unaligned属性。

dynamic_cast
用法:dynamic_cast (expression)
说明:该运算符把expression转换成typeid类型的对象。
typeid必须是类的指针、类的引用或者void*。(typeid和expression的种类要一致(指针/引用))
一般情况下,dynamic_cast用于具有多态性的类(即有虚函数的类)的类型转换。
用途:主要用于类层次之间的up-casting和down-casting,比static_cast更安全。
检测在运行时进行,如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL。
注意:dynamic_cast不能转换掉expression的const、volitale或者__unaligned属性。

reinterpret_cast
用法:reinterpret_cast (expression)
说明:转换一个指针为其他类型的指针,也允许将一个指针转换为整数类型,反之亦然。
这个操作符能够在非相关的类型之间进行转换。
操作结果只是简单的从一个指针到别的指针的值的二进制拷贝,在类型之间指向的内容不做任何类型的检查和转换。
这是一个强制转换,慎用之。 
注意:reinterpret _cast不能转换掉expression的const、volitale或者__unaligned属性。

一般public成员函数不直接返回pretected/private成员(或引用),
如果确实需要,返回值加上const修饰。

函数中局部变量的定义 尽可能 越晚越好。
确保函数不会中断或异常后再定义所需变量,
即需要时再定义,以免不必要的开销(构造、析构)

内联函数:解决频繁调用进栈出栈的开销,将函数调用处替换为具体代码
较宏替换而言,有类型检查,更加安全

句柄类,协议类。M34
句柄类:
1.类外先声明实现类,
2.类里面只有一个实现类的指针以及一个new实现类对象的函数
3.实现类的构造其实是传值给句柄类,使其为数据成员初始化
句柄类将具体实现与声明分开,避免编译时的依赖

(Demo)
不要重新定义继承而来的非虚函数,这样会使基类非虚函数隐藏。
导致使用基类指针调用的是基类函数,即使她指向的是派生类对象(不满足里氏替换原则)

(Demo)M38
不要在基类虚函数中使用缺省值,更不要在派生类中修改缺省值(修改不成功)
虚函数是动态绑定的,但缺省参数是静态绑定的

不要 向下转型(基类指针转派生类指针),要使用纯虚函数。
如果一定要使用,请用dynamic_cast而不是static_cast。
因为dynamic_cast失败会返回NULL;

当对象的类型 不影响 类中函数的行为时,使用 模板 生成这样一组类。
当对象的类型   影响 类中函数的行为时,使用 继承 生成这样一组类。

能使用组合不使用继承;
需要使用基类的接口,并在派生类重新定义,采用虚函数;
当只需要使用已经有的基类定义时,采用private继承实现;

非局部静态对象的初始化顺序是不定的。
不要让程序成功运行依赖于非局部静态对象的初始化顺序。

你可能感兴趣的:(读书笔记,读书笔记)