《Effective C++》学习笔记(1)

2017-03-29  本文已影响0人  暗夜望月

1 让自己习惯 C++

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

将C++视为一个由相关语言组成的联邦而非单一语言。在某个次语言(sublanguage)中,各种守则与通例都倾向简单、直观易懂、并且容易记住。然而当你从一个次语言移往另一个次语言,守则可能改变。

** note: **
C++高效编程守则视状况而改变,取决于你使用C++的哪一部分。


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

C++ 编译过程:预处理 --> 编译 --> 链接
预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换。预处理过程还会删除程序中的注释和多余的空白字符。

“宁可以编译器替换预处理器”。就是尽量少用预处理。

class GamePlays{
private:
  static const int NumTurns = 5;      // static整数型class常量
  enum { NumTurns = 5 };             // 枚举
  int scores[NumTurns];
... ...
}

有了consts 、enums 和inlines,我们对预处理器(特别是#define) 的需求降低了,但并非完全消除。#include 仍然是必需品,而 #ifdef / #ifndef 也继续扮演控制编译的重要角色。目前还不到预处理器全面引退的时候,但我们要尽量限制预处理器的使用。

** note: **

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

条款03:尽可能使用 const

const允许你告诉编译器和其他程序员某值应保持不变,只要“某值”确实是不该被改变的,那就该确实说出来。如果const修饰变量,则表示这个变量不可变;如果const修饰指针,表示指针指向的位置不可改变。

  1. 如果关键字const出现在星号左边,表示被指物事常量。const char *pchar const *p两种写法意义一样,都说明所致对象为常量。
  2. 如果关键字const出现在星号右边,表示指针自身是常量。
const char * p = "hello"; // *p的hello不可变, 与char const * p = "hello"等价
char * const p = "hello"; // 表示p的值不可变,即p不能指向其它位置
  1. 声明迭代器为const就像声明指针为const一样(即声明一个T* const指针),表示这个迭代器不得指向不同的东西,但它所指的东西的值可以改变。
  2. 如果想要迭代器所指的东西不可改变(即模拟一个const T*指针),使用const_iterator。
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin(); //类似T* const
*iter = 10;  //没问题,改变iter所指物
++iter;      //错误!iter是const
std::vector<int>const_iterator cIter = vec.begin();  //类似const T*
*iter = 10;  //错误,*iter是const
iter++;      //没问题,可以改变iter
class Rational { ... };
const Rational operator* (const Rational& lhs, const Rational& rhs);
...
Rational a, b, c;
if(a * b = c)...  //把==错写成=,比较变成了赋值
  1. 可以知道哪些函数可以改变对象内容,哪些函数不可以。
  2. 改善C++效率,通过pass by reference_to_const(const对象的引用)方式传递对象可改善C++效率。
    下面是常量函数与非常量函数的形式:
class TextBlock{
    public:
        ...
        const char& operator[] (std:size_t position) const
        {    return text[position];    }
        char& operator[] (std:size_t position) 
        {    return text[position];    }
    private:
        std::string text;
};
/* 使用operator[] */
TextBlock tb("hello");              //non-const 对象
cout<<tb[0]<<endl;    //调用的是non-const TextBlock::operator[]
tb[0] = 'x';          //没问题,写一个non-const对象
const TextBlock cTb("hello");    //const 对象
cout<<cTb[0]<<endl;   //调用的是const TextBlock:operator[]
cTb[0] = 'x';          //错误,写一个const对象

在C++中,只有被声明为const的成员函数才能被一个const类对象调用。

  1. bitwise const主张const成员函数不可以改变对象内任何non-static成员变量。但一个更改了“指针所指物”的成员函数虽然不能算const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为bitwise const不会引发编译器异议。
  2. logical const主张成员函数可以修改它所处理的对象内的某些bits,但要在客户端侦测不出的情况下才得如此。
    编译器默认执行bitwise。如果想要在const函数中修改non-static变量,需将变量声明为mutable(可变的)。
class TextBlock{
    private:
        char* pText;
        mutable std::size_t textLength;    // 即使在const成员函数内,
        mutable bool lengthIsValid;        // 这些成员变量也可能会被更改。
    public:
        ...
        std::size_t length() const; 
};
std::size_t TextBlock::length() const{
    if (!lengthIsValid){
        textLength = std::strlen(pText);  //加上mutable修饰后,便可以修改其值
        lengthIsValid = true;
    }
}
class TextBlock{
    public:
        const char& operator[](std:size_t position) const
            ...
            return text[position];
        }

        char& operator[] (std:size_t position){
            return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
        } 
};

上面代码进行了两次转型:

  1. 第一次用static_cast来为*this添加const,这使接下来调用operator[ ]时得以调用const版本;
  2. 第二次则是用const_cast从const operator[]的返回值转除const,以符合non-const返回值类型。

** note: **

  1. 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  2. 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”;
  3. 当cosnt和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

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

// FileSystem源文件 class FileSystem{ public: ... std::size_t numDisks() const; };
extern FileSystem tfs;
// Directory源文件,与FileSystem处于不同的编译单元
class Directory{
    public:
        Directory(params);
        ...
};
Directory::Directory(params){
    ...
    //调用未初始化的tfs会出现错误
    std::size_t disks = tfs.numDisks();
}

C++对“定义于不同编译单元内的non-local static对象”的初始化相对次序并无明确定义,因此,为防止A的初始化需要B,但B尚未初始化的错误,将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static),然后用户调用这些函数,而不直接涉及这些对象。

class FileSystem { ... };
FileSystem& tfs(){
    static FileSystem fs;
    return fs;
}
class Directory { ... };
Directory::Directory(params){
    std::size_t disks = tfs().numberDisks();
}
Directory& tempDir(){
    static Directory td;
    return td;
}

经过上面的处理,将non-local转换了local对象,这样做的原理是:函数内的local static 对象会在"该函数被调用期间","首次遇上该对象之定义式"时被初始化,这样就保证了对象被初始化。使用函数返回的“指向static对象”的reference,而不再使用static对象本身。这样做的好处是不调用函数时,不会产生对象的构造和析构。但对多线程这样的方法会有问题。

** note: **

  1. 为内置对象进行手工初始化,因为C++不保证初始化它们;
  2. 构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在类中的声明次序相同;
  3. 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。
上一篇下一篇

猜你喜欢

热点阅读