断言assert是尽在debug版本起作用的宏, 它用于检查”不应该”发生的情况.
void* memcpy(void *pTo, const void* pFrom, size_t size)
{
assert((pTo != NULL) && (pFrom != NULL); // 使用断言
byte* ptTo = (byte*)pTo; //防止改变pTo的地址
byte* ptFrom = (byte*)pFrom; //防止改变pFrom的地址
while (size-- > 0) {
*ptTo++ = *btFrom++;
}
return pvTo;
}
在运行过程中, 如果assert的参数为假, 那么程序就会终止(一般还会出现提示对话, 说明在什么地方引发了assert).
使用断言assert的规则:
- 使用断言捕捉不应该发生的非法情况, 不要混淆非法情况和错误情况之间的区别, 后者是必然存在的, 并且是一定要做出处理的.
- 在函数的入口处, 使用断言检查参数的有效性.
- 在编写函数时, 要进行反复的考查, 并且自问: ” 我打算作那些设定?”, 一旦确定了假设, 就要使用断言对嘉定进行检查.
- 一般教科书都鼓励程序员们进行防错设计, 但要记住这种编码风格可能会隐瞒错误. 当进行防错设计时, 如果”不可能发生”的事情的确发生了, 则要使用断言进行报警.
布尔变量和零值的比较
if (flag) OK
if (flag == TRUE) if (flag == 1) // 不良风格
整形变量和零值比较
if (value == 0) OK
if (value) // 不良风格
浮点变量和零值的比较
浮点变量都有精度限制, 所以避免将浮点变量用 ==或!=与数字比较, 应该设法转换成 >= 或者<=.
if (x == 0.0) // 隐含错误的比较
if ((x>=-EPSINON) && (x<=EPSINON)) OK
指针变量和零值的比较
if (NULL == p) OK
if (p == 0) // 让人误解p是整形变量
const更大的魅力是可以修饰函数的参数, 返回值, 甚至是函数的定义体.
1 用const修饰函数的参数
输出的参数, 不论是什么数据类型, 也不论是采用”指针传递”还是”引用传递”都不能加const修饰, 否则会该参数会失去输出功能.
const只能修饰输入参数
输入参数采用”指针传递”, 那么加const修饰可以防止意外地改动该指针, 起到保护作用.
如果输入参数是”值传递”, 由于函数将自动产生临时变量用于复制该参数, 该参数本来就无需保护, 所以不要加const.为了提高效率, 可以将函数声明改动为void Fun(A &a), 因为”引用传递”仅借用一下参数的别名而已, 不需要产生临时变量.
但是这样会存在一个问题, “引用传递”有可能改变参数, 这不是我们期望的, 加上const修饰符就可以解决这个问题,
因此最后函数的声明会变成void Fun(const A &a);
2 用const修饰函数的返回值
如果给以”指针传递”方式的函数返回值加const修饰符, 那么返回的返回值(即指针)内容不能被修改,
该返回值只能被赋给加const修饰的同类型的指针.
const char* getString(void);
以下语句:char* str = getString(); // 编译错误
正确的写法是:const char* str = getString();
函数返回值采用”值传递”方式, 由于函数会把返回值复制到外部临时的存储单元中, 加const修饰符没有任何价值.
如:
不要把 int getInt(void)写成 const int getInt(void)
如果返回值不是内部数据类型, 将函数A getA(void)写成const A getA(void)会提高效率, 其中A是用户自定义类型. 小心, 一定要搞清楚函数究竟是想返回一个对象的”拷贝”还是仅返回”别名”就可以了, 否则程序会出错.
函数return语句不要返回指向”栈内存”的指针或者引用. 因为该内存在函数体结束后被自动销毁.
如果函数的参数是一个指针, 那么不要指望用该指针去申请动态内存.
如:
void getMemory(char* p, int num)
{
p = (char*)malloc(sizeof(char)*num);
// p是实参的一个副本, 该行代码p申请了新的内存, 只是把p所指的内存地址改变了, 但是原来的实参丝毫未变
}
void test(void)
{
char* str = NULL;
getMemory(str, 100);
// str仍为NULL, 每执行一次getMemory就会造成一次内存泄漏, 因为没有free释放.
strcpy(str, "hello");
}
编译器总是要为函数的每个参数制作临时副本, 指针p的副本是_p, 编译器使_p = p. 如果函数体的程序修改了_p的内容, 就导致了参数p的内容作相应的修改, 这就是指针可以用作输出的原因.
如果非要使用指针参数去申请内存, 那么就应该改用”二级指针”:
void getMemory(char** p, int num)
{
*p = (char*)malloc(sizeof(char)*num);
}
void test(void)
{
char* str = NULL;
getMemory(&str, 100); // 此处是&str
strcpy(str, "hello");
free(str); // free释放
}
当然也可以使用函数的返回值来传递动态内存:
char* getMemory(int num)
{
char* p = (char*)malloc(sizeof(char)*num);
return p;
}
void test(void)
{
char* str = getMemory(100);
strcpy(str, "hello");
free(str); // free释放
}
不要使用return返回”栈内存”的指针, 因为该内存在函数结束后自动消亡了.
如:
char* getMemory(void)
{
char p[] = "hello world"; //
return p; // 编译器将提出警告
}
void test(void)
{
char* str = getMemory(); //str的内容是垃圾
strcpy(str, "hello");
}
如果:
char* getString(void)
{
char *p = "hello world"; //
return p;
}
void test(void)
{
char* str = getString();
}
虽然不会出错, getString设计概念是错误的, 因为getString的”hello world”是字符串常量, 位于静态存储区, 在程序生命周期内恒定不变. 无论什么时候调用, 返回的始终是一个”只读”的内存块.
在C/C++中, for循环使用最高, while次之, do语句很少用.
循环语句的几个建议:
- 在多重循环中, 如果可能应当把最长的循环放在最内层, 最短的循环放在最外层, 以减少CPU跨切循环层的次数.
- 如果循环体内存存在逻辑判断, 并且循环次数很大, 宜将逻辑判断移到循环体的外面.
如: (以下如果N较小, 效率差别不大)
for (i=0; iif (condition)
// 多执行了N-1次判断, 总是进行逻辑判断, 打断了循环"流水线"作业, 使得编译器不能对循环进行优化处理, 降低了效率.
doSomething();
else {
doOtherSomething();
}
}
if (condition) { // 效率高, 但程序不简洁
for (i=0; idoSomething():
}
}
else {
for (i=0; idoSomething():
}
}
for语句循环控制变量:
不可再for循环体中修改循环变量, 防止for循环失去控制.
建议for语句的循环控制变量的取值采用”半开半闭区间”写法.