序言
Effective C++ 的系列博文是本人学习了《Effective C++》(第三版)之后的学习整理,大部分内容来源于原书。为了更方便地回顾和复习,所以将一些重要的知识点提取出来。分享给大家一起进步学习 ?
第一章:让自己习惯C++
条款1:视C++为一个语言联邦
将C++视为一个由相关语言组成的联邦而非单一的语言。可以理解为四个:
- C:区块(block)、语句(statements)、预处理器(preprocessor)、内置数据类型、数组(array)、指针(pointers)等
- Objective-C:类(class)、封装、继承、多态、虚函数等
- Template C++:泛型编程
- STL:template程序库。包含容器、迭代器、算法以及函数对象等。
条款2:尽量以const,enum,inline替换#define
让编译器替换预处理器会更好。因为#define 不被视为语言的一部分。因为所使用的名称可能并没有进入记号表(symbol table)
#define ASPECT_RATIO 1.653
//替换为
const double AspectRatio = 1.653;
define无法实现创建class专属常量,只能使用const
class Game
{
private:
static const int NumTurns = 5;
};
有些旧式编译器不支持static成员在其声明式上获得初值。可以使用enum代替:
class GamePlayer
{
private:
enum { NumTurns = 5; }
int scores[NumTurns];
};
宏函数
#define CALL_WITH_MAX(a,b) f((a)>(b) ? (a): (b))
会遭遇传参等一些不可预料的行为,建议使用inline函数代替。
- 我们并不是不再使用预处理器,而是降低需求。我们仍然需要使用#include #ifdef/#ifndef
条款3:尽可能使用const
- const 指针问题
char greeting[] = "Hello";
const char* p = greeting; //non-const pointer, const data
char* const p = greeting; //const pointer, non-const data
const char* const p = greeting; //const pointer, const data
- 常量迭代器:迭代器所指的东西不可被改动
std::vector<int> vec;
const std::vecotr<int>::iterator iter = vec.begin();
- const成员函数:可以知道哪个函数可以改动对象内容而哪个函数不行.
在const函数中不可以修改对象内任何non-static成员
class Text
{
public:
const char& operator[](int position) const
{ return text[position];}
}
- 可修改常量mutable
class CText
{
public:
mutable size_t textLength; //这些成员变量即使在mutable中也可以被修改
size_t length() const
{
textLength = 1;
}
}
- 常量的变换
//const_cast 常量移除
const_cast<char&>(var);
//static_cast 转为const
static_cast<char&>(var);
条款4:确定对象被使用前已先被初始化
数组(来自C++的C部分)不保证其内容被初始化,而vector(来自C++的STL)部分却有这个保证。
- 构造函数
class ABEntry
{
public:
ABEntry(const string& name, const string address);
private:
string thename;
string theaddress;
};
ABEntry::ABEntry(const string& name, const string address)
{
thename = name;
theaddress = address;
}
解析:会先调用默认构造函数为thename, theaddress设初值,然后立刻再对它们赋予新值。【先调用默认(default)构造函数,然后是复制操作(copy assignment)】
效率更高的写法:成员初值列
ABEntry::ABEntry(const string& name, const string address):thename(name),theaddress(address)
{ }
解析:这个版本的构造函数和上一个的最终结果相同,但通常效率较高,因为初值列中针对各个成员变量而设的实参,被拿去作为各成员变量之构造函数的实参。【只调用一次复制(copy)构造函数】
对于内置变量来说(如int)其实初始化和赋值的成本相同,但为了一致性,最好使用成员初始值来初始化。
- 不同编译单元内定义之non-local static 对象 的初始化顺序
-
static对象:
函数内的static对象称为local static对象,其他static对象称为non-local static对象。 -
编译单元:
单一目标文件(single object file)的那些源码。基本上是单一源码加上所含入的头文件。 -
如果某编译单元的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化。因为C++对于定义在不同编译单元内的non-local static对象的初始化次序并无明确定义。
class FileSystem{
public:
std::size_t numDisks() const;
};
extern FileSystem tfs;
//
class Directory{
public:
Directory(parms);
};
Directory::Directory(parms)
{
std::size_t disk = tfs.numDisks(); //无法确定tfs在tempDir之前先被初始化
}
Directory tempDir(parms);
解决办法:将每个non-local static对象搬到自己的专属函数内。这些函数返回一个reference指向它所含的对象。
class FileSystem{...};
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
class Directory{...};
Directory::Directory(parms)
{
std::size_t disks = tfs().numDisks();
}
Directory& tempDir()
{
static Directory td;
return td;
}