Accustoming Youself to C++
条款1.视c++为一个语言联邦
C++是个多重范型编程语言,同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式。视c++为一个语言联邦,包括以下四个部分:
- C. C++说到底还是以C为基础。这是面向过程的部分,C与C++相比,少了模板、异常以及重载。
- Object-Oriented. 面向对象部分,包括封装、继承、多态以及虚函数绑定等。
- Template C++. 泛型编程部分,template metaprogramming(TMP,模板元编程),其中C++标准模板库STL在这各部分的基础上。
-
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和non-cons成员函数中避免重复
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