目录
1 变量初始化
(1)严禁使用未经初始化的变量作为右值:使用未经初始化的变量作为右值可能导致不可预测的行为和程序错误。因为未初始化的变量可能包含任意的内存残留值,这些值是之前存储在内存位置的数据,它们与当前程序的逻辑无关。
(2)在首次使用前初始化变量,初始化的地方离使用的地方越近越好。
// 不可取的初始化,无意义的初始化
int speedup_factor = 0;
if (condition)
{
speedup_factor = 2;
}
else
{
speedup_factor = -1;
}
// 不可取的初始化,初始化和声明分离
int speedup_factor;
if (condition)
{
speedup_factor = 2;
}
else
{
speedup_factor = -1;
}
// 较好的初始化,使用默认有意义的初始化
int speedup_factor = -1;
if (condition)
{
speedup_factor = 2;
}
// 较好的初始化,使用?:减少数据流和控制流的混合
int speedup_factor = condition ? 2 : -1;
(3)明确全局变量的初始化顺序,避免跨模块的初始化依赖。
说明:系统启动阶段,使用全局变量前,要考虑到该全局变量在什么时候初始化,使用全局变量和初 始化全局变量,两者之间的时序关系,谁先谁后,一定要分析清楚,不然后果往往是低级而又灾难性 的。
2 单一职责原则
2.1 变量功能单一
单一职责原则(Single Responsibility Principle, SRP)对于变量而言,这意味着每个变量应该只负责存储一种类型的数据,并且只用于一个特定的目的。具有两种功能的反例如下:
WORD DelRelTimeQue(void)
{
WORD Locate = 3;
Locate = DeleteFromQue(Locate);
return Locate;
}
正确做法使用两个变量:
WORD DelRelTimeQue(void)
{
WORD Ret;
WORD Locate = 3;
Ret = DeleteFromQue(Locate);
return wValue;
}
2.2 结构体功能单一
结构功能单一原则即一个结构体应该基于一组相关联的信息来构建,以清晰地定义和描述一个特定的对象。结构体中的各个成员变量应当代表同一实体的不同属性或方面,而不是将描述不同实体或它们之间关系较弱的数据混杂在一起。例如,下面是一个结构体的设计示例,它违反了结构功能单一原则:
typedef struct
{
int employeeID; // 员工ID
char name[50]; // 员工姓名
double salary; // 员工薪资
int productID; // 产品ID
char productName[50]; // 产品名称
double price; // 产品价格
} EmployeeProduct;
将上述结构体拆分为两个独立的结构体,每个结构体代表一个单一的概念:
typedef struct
{
int employeeID;
char name[50];
double salary;
} Employee;
typedef struct
{
int productID;
char productName[50];
double price;
} Product;
3 注意字节序
字节序是指多字节数据在计算机内存中的存储顺序。由于不同的计算机体系结构可能有不同的字节序,因此在进行数据交换或网络通信时,字节序的一致性变得非常重要。主要有两种字节序:
大端序:在大端序中,高有效字节存储在低的内存地址处,而低有效字节存储在最高的内存地址处。例如,对于16位的整数0x1234,在大端序存储中,0x12存储在低地址处,0x34存储在高地址处。
小端序:在小端序中,低有效字节存储在低的内存地址处,而高有效字节存储在高的内存地址处。
4 使用宏不允许参数发生变化
4.1 重复求值问题
如果参数是一个带有副作用的表达式(例如自增、自减操作或者函数调用等),可能会出现意外的重复求值情况,导致结果不符合预期且参数的值发生了非预期的改变。比如下面这个例子:
#include <stdio.h>
#define MAX(a, b) ((a) > (b)? (a) : (b))
int main() {
int x = 5;
int y = 10;
int result = MAX(x++, y++);
printf("result: %d, x: %d, y: %d\n", result, x, y);
return 0;
}
在这个例子中,宏 MAX 本意是取两个值中的较大值。但由于宏是简单的文本替换,MAX(x++, y++) 会被替换成 ((x++) > (y++)? (x++) : (y++))。这里 x++ 和 y++ 就会根据条件表达式的求值情况被多次求值,导致 x 和 y 的值改变了不止一次,而且最终结果也可能不是我们预期的 10。
4.2 运算优先级问题
如果参数本身是比较复杂的表达式,且没有合理地使用括号来明确运算顺序,可能会因为宏替换后的运算优先级问题,使得参数看似 “改变” 了原有语义,造成错误。例如:
#define DOUBLE(x) (x * 2)
int main() {
int num = 3 + 4;
int result = DOUBLE(num);
printf("result: %d\n", result);
return 0;
}
这里期望 DOUBLE(num) 能将 num 的值翻倍,但由于宏替换后变成了 (3 + 4 * 2),按照 C 语言运算优先级,先计算乘法再计算加法,得到的结果是 11,而不是预期的 14。
5 使用函数代替宏
宏对比函数,有一些明显的缺点:
- 宏缺乏类型检查,不如函数调用检查严格。
- 宏展开可能会产生意想不到的副作用,如#define SQUARE(a) (a) * (a)这样的定义,如果是 SQUARE(i++),就会导致i被加两次;如果是函数调用double square(double a) {return a * a;}则不 会有此副作用。
- 以宏形式写的代码难以调试难以打断点,不利于定位问题。
- 宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高。
6 使用const常量定义代替宏
“尽量用编译器而不用预处理”,因为#define经常被认为不是语言本身的一部分。看下面的语句:
#define ASPECT_RATIO 1.653
编译器会永远也看不到ASPECT_RATIO这个符号名,因为在源码进入编译器之前,它会被预处理程 序去掉,于是ASPECT_RATIO不会加入到符号列表中。如果涉及到这个常量的代码在编译时报错, 就会很令人费解,因为报错信息指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO不是 在你自己写的头文件中定义的,你就会奇怪1.653是从哪里来的,甚至会花时间跟踪下去。这个问题也 会出现在符号调试器中,因为同样地,你所写的符号名不会出现在符号列表中。
解决这个问题的方案很简单:不用预处理宏,定义一个常量:
const double ASPECT_RATIO = 1.653;
这种方法很有效,但有两个特殊情况要注意。首先,定义指针常量时会有点不同。因为常量定义一般是放在头文件中(许多源文件会包含它)。除了指针所指的类型要定义成const外,重要的是指针也经 常要定义成const。例如,要在头文件中定义一个基于char*的字符串常量,你要写两次const:
const char * const authorName = "Scott Meyers";
7 宏定义不使用改变程序流程的语句
宏定义中应尽量避免使用 return、goto、continue、break 等改变程序流程的语句。因为宏是在预处理阶段进行文本替换,若使用这些语句,会导致程序逻辑混乱且难以调试与维护。以下是一个示例程序说明宏中使用这些语句可能产生的问题:
#include <stdio.h>
// 错误示例宏,包含 break 语句
#define CHECK_VALUE(x) \
if (x == 5) { \
break; \
}
int main() {
for (int i = 0; i < 10; i++) {
CHECK_VALUE(i);
printf("%d ", i);
}
return 0;
}
在上述程序中,由于宏 CHECK_VALUE 中使用了 break 语句,当 i 等于 5 时,本应在循环内正常输出数字的程序会因宏中的 break 提前跳出循环,导致输出结果不符合预期。