条款02:尽量以 const,enum,inline 替换 #define
#define PI 3.14
1)对于这样的宏定义,PI 在编译之前被预处理器全部换成了 3.14,所以 PI 也许并不会进入符号表(symbol table),当运用此常量发生编译错误时,错误信息也许会提到 3.14 而不是 PI ,此时不知道此宏定义的程序员就可能会产生困惑。
解决方法是用 const :
const dobule Pi = 3.14;
2)其次,定义不好的宏会让人痛苦不堪。例如:
#define max(a, b) a > b ? a : b
int a = 1, b = 3, c; c = ++max(a, b);
本意是要a 和 b 中最大值加 1 后的结果赋给 c ,实际经过预处理之后,代码变成了:
c = ++ a > b ? a : b;c 的最后结果是 3 ,而非我们想要的 4。
所以记得要给宏中的所有实参加上小括号!!!如:
#define max(a, b) (((a) > (b)) ? (a) : (b))
即是这样,还是会发生一些意想不到的问题。
比如:
int a = 5, b = 0; max(++a, b); //a被累加二次 max(++a, b + 10); //a被累加一次a 的累加次数竟然取决于“它被拿来和谁比较”。
此时,你需要 inline 来解决此问题:
template <typename T> inline T max(const &T a, const &T b) //由于我们不知道 T 是什么,所以用 pass by reference-to-const { return (a > b ? a : b); }
3)使用 #define 无法定义一个专属于一个 class 的常量或函数,而使用 const 和 inline 完全可以做到。
请记住:
1)对于单纯变量,最好以 const 对象或 enum 替换 #define;
2)对于形似函数的宏,最好改用 inline 函数替换 #define。
条款03:尽可能使用 const
关键字 const 可以指定一个“不该被改动的值” ,而编译器会强制实施这项约束。
如果关键字 const 出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
此外还要注意迭代器的 const 使用:
const std::vector<int>::iterator iter; //类似 T *const, 迭代器本身不能改变 std::vector<int>::const_iterator iter; //类似 const T *,迭代器所指内容不变
许多人漠视的一件事实:两个成员函数如果只是常量性(constness)不同,可以被重载。这也是C++一个重要的特性。
#include <iostream> using namespace std; class TextBlock { public: TextBlock(string _str) { str = _str; } const char& operator[](size_t pos) const //operator[] for const 对象 { return str[pos]; } char& operator[](size_t pos) //operator[] for non-const 对象 { return str[pos]; } private: string str; }; int main() { const class TextBlock ctb("hello"); class TextBlock tb("world"); cout << ctb[0] << endl; cout << tb[0] << endl; cin >> tb[0]; //由于是 non-const,还可以cin cout << tb[0] << endl; return 0; }
不过,non-const 对象是可以调用 const operator[] 的,反过来 const 对象不能调用 non-const operator[](可以注释掉上面的 non-const operator[] 试试 ^_^)。因为 const operator[]保证函数不修改对象的成员变量,而 non-const operator[] 不能保证。考虑到 const operator[] 完全做掉了 non-const operator[] 该做的一切,唯一不同的是其返回类型多了一个 const 修饰,这种情况下将 const 移除是安全的,所以我们可以用 non-const operator[] 调用const operator[] ,来避免代码的重复。
做法如下:
class TextBlock { public: TextBlock(string _str) { str = _str; } const char& operator[](size_t pos) const //operator[] for const 对象 { return str[pos]; } char& operator[](size_t pos) //operator[] for non-const 对象 { return const_cast<char&>( static_cast<const TextBlock&>(*this) //使用 static_cast 将当前对象(*this)强制转换为 [pos] //const类型然后调用 const operator[],最后使用 ); //const_cast将返回值的 const 属性移除 } private: string str; };
请记住:
1)将某些东西声明为 const 可帮助编译器侦测出错误用法。const 可被施加与任何作用域内的对象、函数参数、函数返回类型、成员函数本体;
2)编译器强制实施 bitwise constness,但你编写程序是应该使用“概念上的常量性”(conceptual constness);
3)当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。
条款04: 确定对象被使用前已先被初始化
始终记住这句话:读取未初始化的值会导致不明确的行为。永远在使用对象之前将其初始化。
1)对于内置类型,必须手工初始化;
2)对于内置类型以外的其他东西,初始化责任在构造函数身上,要确保每一个构造函数都将对象的每一个成员初始化。
使用构造函数初始化时,切记区别初始化(Initialization)与赋值(assignment)的区别。
构造函数初始化的地方在成员初始化列表那里,函数体里面其实并非初始化,而是赋值,所以对于未使用列表初始化的成员,编译器会自动调用其对应的 default 构造函数,然后紧接着在进入函数体后对它们赋予新值。
考虑到像 const 或 reference 这样的成员变量,一定要初始化,不能被赋值,所以我们最好总是使用成员初始化列表。
例如:
#include <iostream> using namespace std; class TextBlock { public: TextBlock(const int &_a, int &_b):a(_a), b(_b){} //注意此处引用的参数使用的是 int &,如果使用 const int& private: //那么我们的成员变量也必须是常引用才行,否则报错。 const int a; int &b; }; int main() { int i = 1, j = 2; class TextBlock tb(i, j); return 0; }
当我想把“初始化”的过程放在函数体内时,gcc 报错如下:
error: uninitialized member ‘TextBlock::a’ with ‘const’ type ‘const int’ [-fpermissive] error: uninitialized reference member ‘TextBlock::b’ [-fpermissive]
再一次说明只有成员初始化列表才是成员正真初始化的地方,函数体里只是赋值。
请记住:
1)为内置型对象进行手工初始化,因为C++不保证初始化它们。;
2)构造函数最好使用成员初始化列表(member initialization list),而不要在构造函数体内使用赋值操作(assignment)。初始化列表的排序次序最好与它们在 class 中的声明顺序一致;
3)为免除“跨编译单元之初始化次序”问题,请以 local static 对象替换 non-local static 对象。