《Effective C++》读书笔记第一章:让自己习惯c++

Accustoming Youself to C++

条款1.视c++为一个语言联邦

C++是个多重范型编程语言,同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式。视c++为一个语言联邦,包括以下四个部分:

  1. C. C++说到底还是以C为基础。这是面向过程的部分,C与C++相比,少了模板、异常以及重载。
  2. Object-Oriented. 面向对象部分,包括封装、继承、多态以及虚函数绑定等。
  3. Template C++. 泛型编程部分,template metaprogramming(TMP,模板元编程),其中C++标准模板库STL在这各部分的基础上。
  4. STL. C++标准模板库,包括容器、迭代器、算法以及函数对象等。

    请记住: 
    C++高效编程守则视状况而变化,取决于使用的是这四个部分中的哪一部分。

条款2.尽量以const,enum,inline代替#define

等价描述为宁可以编译器代替预处理器。 
#define即宏定义在预处理阶段进行处理,通常情况下记号名称会进入记号表(symbol table),也有可能在编译器处理前就被预处理器移走了,没有进入记号表,这时就会编译出错。解决的办法使用常量来代替宏。 
例子:const double AspecRatio = 1.653;来代替#define ASPEC_RATIO 1.653; 
特别注意两点: 
- 指针常量与常量指针,特别地,使用const string代替const char* const; 
- class专属常量:static const来修饰,静态常量: 
例如:class GamePlayer{ static const int NumTurns = 5; //常量声明式}; 
或者使用常量类内声明+类外定义: 
class GamePlayer{static const int NumTurns;};// 常量声明式 
const int GamePlayer::NumTurns = 5;//常量定义式
 
另外无法使用宏定义来创建一个class 专属常量,因为宏定义并不重视作用域。宏定义只是简单嵌入,并不做类型检查,所以在定义宏时,请为宏的所有实参加上小括号。 
例如:#define CALL_MAX(a, b) ((a) > (b) ? (a) : (b)) 
调用:

int a = 5, b = 0;
CALL_MAX(++a, b);   //a被累加1次
CALL_MAX(++a, b + 10);  //a被累加2次
  • 1
  • 2
  • 3

这是不可预料的行为,因此使用内联函数来代替宏定义函数。 
请记住: 
对于单纯常量,最好使用const对象或enum替换#defines。 
对于形似函数的宏,最好改用inline函数替换#defines。

条款3.尽可能使用const

const关键字告诉编译器某值应该保持不变,即为只读的。 
常量指针与指针常量 
例如:STL中的迭代器。

const vector<int>::iterator ite1;//类似于常量指针,ite1是只读的
vector<int>::const_iterator ite2;//类似于指针常量,*ite2是只读的
  • 1
  • 2
  • const可以帮助编译器甄别错误,例如判断“==”和赋值。
const Rational operator*(const Rational& lhs, const Rational& rhs);//有理数类乘法重载
Rational a, b, c;
if(a * b = c)...//这一步本来是想做比较操作,误写成了赋值操作,因为返回const对象,编译器就能发现这个错误。
  • 1
  • 2
  • 3
  • const成员函数 
    const成员函数代表this指针的类型为:const className const*。 
    改善C++程序效率的一个根本方法是以pass by reference-to-const方式传递对象,这一前提是必须使用const成员函数来处理const对象。 
    const成员函数的两个流行概念: 
    bitwise constness(physical constness):任何成员变量都是只读时才可以说是const。(在Object Model 2.2 Copy Constructor的构造操作中,bitwise copies) 
    logical constness:一个const成员函数可以修改它处理的对象内的某些bits,但只有在客户端侦测不出来的情况下才可以,例如修改指针所指的对象。 
    如何释放non-static成员函数的bitwise constness约束:使用mutable关键字,即使是在const成员函数中,mutable修饰的成员也是可变的。 
    • 在const和non-cons成员函数中避免重复 
      利用non-const版本调用const版本,再加上转型const_cast、static_cast,例如:
const char& operator[](int pos) const;//const版本
char& operator[](int pos) { //non-const版本
    return const_cast<char&> //返回值转型,移除const
     (static_cast<const className&>(*this)[pos]);//*this转型,增加const
}
  • 1
  • 2
  • 3
  • 4
  • 5

反过来使用const版本调用non-const版本则是一种错误行为。 
请记住: 
将某些东西声明为const可以帮助编译器侦测除错误;const可以被施加在任何作用域内的对象、函数参数、函数返回类型、成员函数本身。 
编译器强制实施bitwise constness,但是你编写程序时应该使用“概念上的常量性”(conceptual constness)。 
当const和non-const成员函数有着实质等价的实现时,使用non-const版本来调用const版本可以避免代码重复。

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

永远在对象使用之前将其初始化,对于内置类型,请手动初始化;对于非内置类型,初始化的任务则落在了构造函数身上。 
别混淆赋值和初始化的概念,例如:

class ABEntry {
public:
    ABEntry(const string& name, const string& address, int num) {
        //这里都是赋值,而非初始化,实际上是先调用default ctor,再赋值
        _name = name;
        _address = address;
        _num = num; 
        }
private:
    string _name;
    string _address;
    int _num;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

更好的办法是使用所谓的成员初值列(member initialization list):

ABEntry::ABEntry(const string& name, const string& address, int num)
    : _name(name), _address(address), _num(num) { }
//直接进行copy ctor,就是初始化   
  • 1
  • 2
  • 3

特别注意的是:在初值列中列出所有的成员变量,避免遗漏;另外,如果成员变量是const 或者reference,一定需要初值,而不能赋值,最简单的做法就是使用成员初值列。 
C++有着固定的初始化顺序:按照声明顺序进行初始化,且基类先于派生类初始化

class init {
public:
        init(int i) : _j(i), _i(_j);
        //这里会出现错误,因为先对_i初始化,再对_j初始化。
private:
    int _i;
    int _j;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

对于local static object(局部静态对象),程序结束之后,该对象会被自动销毁。 
基于局部静态对象的singleton设计模式:

class Singleton
{
public:
    static Singleton& getInstance() {
        static Singleton s;
        return s;
    }
private:
    Singleton() {}
    Singleton(const Singleton&);
    void operator=(const Singleton&);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

请记住: 
为内置类型进行手工初始化,C++不保证初始化它们。 
构造函数最好使用成员初值列,不要在构造函数中使用赋值操作,初值列中的成员变量的顺序应该和声明顺序一致。 
为免除“跨编译单元之初始化次序”问题,使用返回local static对象引用的函数来代替non-local static对象。

参考自侯捷老师翻译的《Effective C++》中文版第三版

转载自:https://blog.csdn.net/qq_25467397/article/details/80344261

猜你喜欢

转载自blog.csdn.net/ShiHongYu_/article/details/81106673
今日推荐