文章目录
01:C++是一个语言联邦
可以将C++视为四种主要的此语言:
- C语言
- Objedct-Oriented C++:继承,封装,堕胎,虚函数(动态绑定)
- Template C++:泛型编程
- STL:容器、迭代器、算法、函数对象进行规约与协调
守则依据使用C++的哪一部分来进行变化。
02:尽量使用const、enum、inline替换#define
- 单纯常量,使用const对象或enum对象替换#defines
- 形似函数的宏,使用inline来替换#define
使用#define的不足:
常数问题
#define PI 3.14
记号 PI 可能被预处理器给删除,导致编译器没能将其送入符号表;同时,报错时可能会提到3.14而不是PI。
专属常量
class Test {
private:
#define T 21213;
}
这时,我们仍可以在其他地方使用T,而不是我们希望的那样,称为一个类专属的数值。
函数
#define CALL_MAX(a,b) f((a)>(b)?(a):(b))
首先,变量必须加括号;其次如果使用CALL_MAX(a++,b)
,那么a增加的次数和b有关,因为它会将表达式中的a全部替换成a++。
可以使用template inline,并通过这种方式确保类型安全。
template<typename T>
inline void callMax(const T&a, const T& b) { f(a>b?a:b); }
通过引用,防止传入a++的情况,也不用担心赋值多次;同时可以用作一个private函数。
特殊情况
- 常量指针
因为常量定义常被放在头文件中,所有有必要讲指针(而不是其所指向的东西)声明成const - class
为了保证常量只有一份实体,需要声明为static成员
in-class初值设定只允许对整数常量进行,如果编译器不支持或初始化其他变量,那么就将定义放在实现文件内。 - 编译期间需要常量值(enum hack)
通过enum,让NumTurns成为5的一个记号名称
enum hack更像#define,取enum地址不合法;无法让指针指向某个整数常量;不会导致没有必要的内存分配。class GamePlay { private: enum {NumTurns = 5}; int scores[NumTurns]; ... }
03:尽可能使用const
- 将参数、返回类型、成员函数加上cosnt可以帮助编译器识别错误的用法
- 编译器使用bitwise constness,但编写程序时要使用conceptual constness
- 使用non-const版本表用const版本可以避免代码重复
const用法
- 放在星号左边,表示指向的对象是常量;放在星号右边,表示指针式常量。(可以讲将int*理解成一个类型)。
- const可以放在类型之前也可以放在类型之后,下面两个语句等价。
int const * p; const int *p;
- 当与迭代器一同使用的时候,const iterator表示迭代器指向的东西不可改变,例如不能使用自增操作;而如果想表示指向的对象的内容不可改变,则需要使用const_iterator
- const可以与函数返回值,各个参数,函数自身(如果是成员函数)产生关联
比如对乘法操作进行重载,如果返回的不是一个const类型,那么可能会出现:a * b = c
的情况,这是是a* b返回的那个临时对象赋值成c。
const成员函数
const作用于成员函数是为了确认成员函数可以作用于const对象。这样做有两个好处:
- 使class接口已于理解,可以得知哪个函数可以改动对象内容而哪个函数不行。
- 使“操作const”对象成为可能。
两个函数常量性(constness)不同,那么可以重载。比如对于[]操作,可以返回一个常量引用,也可以返回一个普通引用。
这是为了解决类似array[i] = obj;
的操作,如果不返回引用,则实际上是对临时变量修改;如果返回常量引用,则无法对其修改。
成员函数是const有两种理解:
-
bitwise constness:不改变对象任何成员变量,不改变其中任何一个bit。这也是c++对constness的定义。所以const成员函数不能改变任何non-const成员变量。(当然也不是完全不能修改,比如利用指针)
-
logical constness:成员函数可以处理对象内某些bits,但只有用户侦测不出的情况下才可以。比如对privte的变量进行修改。这时候要将需要修改的变量前加上mutable,表示这些变量总是可以被更改,即使在const成员函数内部。
class A { private: mutable bool isEmpty; int length; public: bool is_empty() const { if (!length) { isEmpty = true; } else { isEmpty = false; } return isEmpty; } };
在const 和 non-const 成员函数中避免重复
比如对[]重载,可能返回一个正常引用,也可能返回一个常量引用。
但其中要进行一些边界检查,进行数据访问日志的记录,检验数据完整性等。如果两个函数都要进行同样的操作,就造成了代码重复,所以考虑一个调用另一个。
一般是使用non-const成员函数调用const成员函数。这里先将this转换成常量,于是会调用常量版本的[],随后去掉const。
反过来是不行的,const保证不改变成员的逻辑状态,但是如果const调用non-const,non-const函数可能会改变成员的逻辑状态。
const T& operator[] (size_t pos) const {
...
...
return array[pos];
}
T& operator[] (size_t pos) {
return const_cast<T&> {
static_cast<const T&>(*this)[pos];
)
}
04:确定对象被使用前先被初始化
- 对内置对象类型(int,char等)手工初始化,C++不保证初始化他们
- 构造函数最好使用成员初始值列(member initialization list),而不要在构造函数中使用赋值操作。构造函数过多,可以抽出来共同的、影响较小的变量进行赋值。初始值成员变量排列次序应该与在class中的声明次序相同。
- 为了避免“跨编译单元初始化”,使用local static对象替换non-local static对象
static对象指的对象的寿命从被构造出来开始到程序结束为止,包括global对象,定义域namespace的对象,classes内、函数内、及file作用域内被声明为static的对象。
函数内的static对象成为local static对象,其他static对象称为non-local static对象。
编译单元指的产出单一的目标文件的源码,时单一源码文件加上其所含的头文件。
两个源码中文件中,每一个都喊一个non-local static对象,假设为A和B。如果B的初始化中使用了A,那么可能会出现问题,因为C++并没有对non-local static对象的初始化顺序做出规定,并且也无法做出规定。
可以将non-static对象搬到函数内,因为函数中的static对象会在函数被调用期间,首次遇到该对象定义式的时候被初始化。所以如果用“函数调用”替换“直接访问non-local static”对象,那么可以保证reference指向一个经过初始化的变量。
class A {...};
A& getA() {
static A a;
return a;
}
class B {
static A a;
B() {
a = getA();
}
}