一个人的常量可能是另一个人的变量。——Alan Prelis
第 14 条:宁要编译、链接时错误,也不要运行时错误
以下是一些静态检查的例子:
编译时布尔条件
如果有些布尔值可以在编译期获得并需要检查,可以用 static_assert 代替运行时检查的 assert 。
编译时多态
用 “模板”(编译时多态)代替 “虚函数”(运行时多态)可以减少出错机会。
枚举
用枚举或定义完整的类型来表示符号常量。
向下强制(downcast)
如果程序经常使用 dynamic_cast 或 static_cast,说明基类的设计不够合理。
第 15 条:积极使用 const
能用 const 则尽量使用 const,它可以提供更严格的静态检查。
重载中的 const
需要注意,以下两种声明是等价的:
void f(int x);
void f(const int x);
第 16 条:避免使用宏
因为宏不受任何静态检查的约束,所以它很容易出错。
如:宏无法识别模板参数:
MACRO(F<int, double>)
宏会认为传入的参数是 f
第 17 条:避免使用 “魔数”
避免在程序中,尤其是条件表达式中直接出现字面值,这会让人难以理解,并且使程序难以维护。
if (height < 42) ... // 鬼知道 42 是什么
const min_height = 42;
if (height < min_height) ... // 这下就好理解多了
第 18 条:尽可能声明局部变量
全局变量具有以下缺点:
- 相关数据的距离被大大拉长,使程序难以理解、维护。
- 污染上下文名称空间。
- 静态变量的初始化顺序未定义,容易出错。
例外
- 有时将变量提出循环是有好处的。
- 常量不会影响状态,因此本条不适用于常量。
第 19 条:总是初始化变量
下面是一些建议:
使用默认初始值或用 ?:简化变量的定义。
// 初始版本
int a;
if(condition) a = x;
else a =y;
// 修改后的版本
int a = condition? x : y;
用函数代替复杂的计算流,即设计一个初始化函数
// before
int a;
if (condition2)
{
... // 一大堆运算
a = value1;
}
else if (condition2)
{
... // 一大堆计算
a = value2;
}
else
{
... // 一大堆计算
a = value3
}
// after
int init_a()
{
if (condition2)
{
... // 一大堆运算
a = value1;
}
else if (condition2)
{
... // 一大堆计算
a = value2;
}
else
{
... // 一大堆计算
a = value3
}
}
...
int a = init_a();
数组的初始化方式
常见的两种方式:
// 1
char path[MAX_PATH];
path[0] = '\0'; // 仅初始化第一个元素
// 2
char path[MAX_PATH] = {'\0'}; // 初始化全部元素
第 20 条:避免函数过长,避免嵌套过深
以下是对函数设计的建议:
- 尽量紧凑:一个函数只有一个职责。
- 不要自我重复:相似的代码用函数代替。
- 用 && 代替嵌套的 if
- 不要过分使用 try
- 优先使用标准算法
- 用多态函数代替类型标签分支(type tag switch)
第 21 条:便面跨编译单元的初始化依赖
正如多次提到的:静态成员的初始化顺序未知,因此尽量避免相互依赖,循环依赖的情况。
第 22 条:尽量减少定义性依赖,避免循环依赖
简单的循环可以用前向声明解决:
// A,B 互相引用,因此需要前向声明
class A;
class B
{
A* p;
};
class A
{
B* p;
};
如果循环链更长,前向声明也会变得复杂。
真正解决问题的手段是 “依赖倒置原则”(Dependency Inversion Principle):不要让高层模块依赖底层模块,而应让两者都依赖于抽象。
依赖倒置原则一般在一些介绍面向对象范式的书中会进行详细介绍。
第 23 条:头文件应该自给自足
就是说一个头文件应包含自己需要的全部东西,若头文件 A 需要头文件 B 的内容,就应该直接在 A 中加入 #include<B> 这段代码,而不是在文档中写:如果要使用头文件 A 的话,用户应自行先引入头文件 B 。
第 24 条:总是编写内部 #include 保护符,绝不要写外部 #include 保护符
常见的头文件保护符格式为:
#ifudef FILENAME_H_INCLUDED_
#define FILENAME_H_INCLUDED_
// 文件的内容
#endif
有些编译器也可以这样写:
#pragma once
当然,这种方式可移植性要差一些。
不过无论用何种方式,一定要保证头文件最多被包含一次。
在一些比较老的书中,可能会介绍网布包含保护符:
#ifndef XXX_H_INCLUDED_
#include "XXX.h"
#define XXX_H_INCLUDED_
#endif
其中 XXX.h 是头文件的实际内容。
这种方式把保护符放在头文件外部,因此叫外部保护符,与之相对的第一种方式叫内部保护符。
这种方式不仅麻烦,且没有任何优势,是一种已经过时的做法,不建议使用。
到这里就结束啦,下一篇记录函数与操作符的部分。