【Effection C++】读书笔记 条款01~条款04

【Effection C++】读书笔记 Part1 让自己习惯C++

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

将C++视为一个由相关语言组成的联邦。在其某个次语言中,各种守则简单易懂,容易记住。但当从一个次语言迁往另一个次语言,守则可能改变。
C++的次语言总共有四个:

  1. C: C++以C为基础。区块(blocks),语句(statements),预处理器(proprocessor),内置数据类型(built-in data types),数组(arrays),指针(pointers)等统统来自C,许多C++对问题的解法其实不过是较高级的C解法。

  2. Obiect-Oriented C++: 这部分是”C with Classes”所诉求的:classes(包括构造函数和析构函数),封装(encapsulation),继承(inheritance),多态(polymorphism),虚函数(动态绑定)…等等。这一部分是面向对象在C++上的直接实施。

  3. Template C++: 这是C++的泛型编程(generic programming)部分,由于templates威力强大,它们带来崭新的变成范型(programming paradigm),也就是所谓的template meta-programmingTMP,模板元编程)。

  4. STL(标准模板库):它包括容器(containers),迭代器(iterators),算法(algorithms)以及函数对象(function objects)。

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

从源代码到获取到可执行程序大致流程如下所示:

源代码经过预处理器进行预处理,然后经过编译器近进行编译得到目标代码,最后经过链接器进行链接就得到了可执行文件。

  1. Step1:源代码(source code)
  2. Step2:预处理器(preprocessor)
  3. Step3:编译器(compiler)
  4. Step4:目标代码(object code)
  5. Step5:链接器(Linker)
  6. Step6:可执行文件(executables

先亮观点:

  1. 对于单纯常量,最好以const对象或者enums替换#defines。
  2. 对于形似函数的宏,最好以inline函数替换#define。

对于单纯常量的定义

众所周知,#define就是在预处理阶段来处理的。
预处理阶段所做的任务就是删除注释,包含include文件,以及进行宏定义的替换。

#define N 100

所以对于采用#define定义的常量而言,编译器处理的时候已经没有了相应的宏定义的名称,只是知道具体的值。对于上面的程序,编译器只会意识到100的存在,但是对于符号N的存在是完全意识不到的。

这就导致有着如下结果:

  1. 盲目的将宏名称替换为对应的值,就可能导致目标代码(object code)中出现多份100的备份,若改为常量就不会出现这样的情况。
  2. 使用宏定义定义常量时,如果获得了一个编译错误信息,可能会造成困惑。因为编译器最多只会定位到100这里,而不会提到N。因为编译器并不知道N的存在。
const int i = 10;

对于使用const常量代替宏定义时,有着如下好处:

  1. 对应于上1,const常量的目标代码更小
  2. 编译器对于const常量有者符号和对应的值的记录。
  3. const有着类型,编译器可以进行相应的类型检查。
  4. const可以执行常量折叠(常量折叠是在编译时间简单化常量表达式的一个过程,简单而言就是将常量表达式计算求值,并用求得的值来替换表达式,放入常量表)。
  5. 可以定义一个class专属常量。将此常量的作用域限制域class内。可以定义一个const 成员变量,并且让其称为一个static变量,保证所有类中共享这一个成员。并且如果此类型是int,可以在内部给予初始值进行声明,在不需要对其取地址的时候就可以无需定义。当然,如果需要取地址的时候就要必须定义了。
class 
{
private:
    static const int Num = 5;   //static 整数型class常量的声明式,无需外部定义就可以使用,但如果需要对其取地址的时候就要定义
    int scores[Num];
}

如果编译器不支持“static整数型class常量”完成“in class初值设定”,可以改用enum的反法。

class 
{
private:
    enum {Num = 5};//Num成为5的一个记号名称,要注意对Num取地址是非法的。

    int scores[Num];
}

使用inline来代替带有参数的宏定义

带有参数的宏定义有着其效率上的高效性,但是也有着其局限性。

如下:

#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
int a = 5; int b = 0;
CALL_WITH_MAX(++a,b);     //a被累加一次
CALL_WITH_MAX(++a,b+10);  //a被累加两次

根据a比较的对象不同,a有着不同的累加次数,显然这是一个极大的缺陷。

但是对于inline函数则不会有这种问题,它本身就是一个函数,在适当的时候,编译器会将其展开而不是选择函数调用,来提升效率。

如下:

template<typename T>
void callWithMax(const T &a,const T&b)
{
    f(a>b?a:b);
}

条款03:尽可能使用const

const可用之处是在太多。

const可以修饰指针和迭代器,可以修饰指针,迭代器或者引用所指的对象上。const可以作用于函数的返回参数和函数参数上,可以作用于类的成员函数上。

  1. 将某些东西声明为const,可以帮助编译器检测出错误用法。const可以作用在任何作用域内的对象,函数参数,函数返回类型,成员函数本体。
  2. 编译器强制执行,bitwise constness,但是在使用中可以通过mutable关键字来实现 logical constness。
  3. 当const 和non-const成员函数有着实质等价的表现时候,可以令non-const版本调用const版本来避免代码重复。

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

  1. 对于内置类型对象要进行手工初始化,因为C++并不保证初始化它们。
  2. 构造函数应该最好使用成员初值化列表(member initialization list),而不要在构造函数函数体内进行赋值操作。初始化列表中列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
  3. C++对于定义于不同编译单元内的non-local static对象的初始化次序并无明确定
    1. 所谓non-local static对象就是指的是不是在函数作用域内的static对象,包括global static对象,定义于namespace作用域的static变量,在class内,以及在file作用域内的static对象。
    2. 对于编译单元,一般就是指产出单一目标文件的那些源码。
    3. 由于这些对象初始化次序的未定义,如果这些对象之间初始化的值又是相互依赖的,那么就会导致无法预料的结果。
  4. 将非non-local static的变量,封装成local static对象,即包装在函数内。可以按照要求来实现初始化顺序。主要原因在于C++保证,函数内部的local static对象会在该函数调用期间,首次遇上该对象之定义式时初始化。。并且如果从未调用过相应函数,那么就绝对不会引发相应的构造和析构成本。

你可能感兴趣的:(读书笔记,effective-c++,c++)