《编写高质量代码改善C++程序的150个建议》读书笔记
- 第一部分 语法篇
- 第1章 从C继承而来的
- 建议0:不要让main函数返回void
- 建议1:区分0的四种面孔
- 建议2:避免那些有运算符引发的混乱
- 建议3:对表达式计算顺序不要想当然
- 建议4:小心宏#define使用中的陷阱
- 建议5:不要忘记指针变量地初始化
- 建议6:明细逗号分隔表达式的奇怪之处
- 建议7:时刻提防内存溢出
- 建议8:拒绝晦涩难懂的函数指针
- 建议9:防止重复包含头文件
- 建议10:优化结构体中元素的布局
- 建议11:将强制转型减到最少
- 建议12:优先使用前缀操作符
- 建议13:掌握变量定义的位置与时机
- 建议14:小心typedef使用中的陷阱
- 建议15:尽量不要使用可变参数
- 建议16:慎用goto
- 建议17:提防隐式转换带来的麻烦
- 建议18:正确区分void与void*
- 第2章 从C到C++,需要做出一些改变
第一部分 语法篇
第1章 从C继承而来的
建议0:不要让main函数返回void
main函数只有以下两种定义方法是正确的:
int main(void)
int main(int argc, char *argv[])
main返回值的作用:
int main()
{
return 0;
}
上面的代码在Linux环境下,采用命令:
g++ main.cpp
生成可执行文件a.out,然后,执行命令:
./a.out && ehco "success"
结果输出success
如果上述程序为:
int main()
{
return -1;
}
则无输出。
建议1:区分0的四种面孔
- 整形0
00000000 00000000 00000000 00000000 - 空指针NULL
32位,代表地址 - 字符串结束标志 ‘\0’
占8位,00000000 - 逻辑FALSE/false
FALSE/TRUE 是int类型,占32位,false/true 是bool类型,占1位
建议2:避免那些有运算符引发的混乱
注意 == 和 = 的区别,为了防止出现问题,可以通过良好的代码习惯避免。
if(0 == n)
{
// do something
}
此外还有&&和&,||和|的区别。
建议3:对表达式计算顺序不要想当然
熟悉操作符的优先级,多写几个括号。
如:
if(n & m == k)
if((n & m) == k)
注意函数参数和操作数的评估求值顺序问题。
如:
int i = 2010;
printf("the result are: %d %d", i, i+=1);
printf("the result are: %d %d", i, i+1);
a = p() + q() * r();
int para1 = p();
int para2 = q();
a = para1 + para2 * r();
建议4:小心宏#define使用中的陷阱
(1)使用宏定义表达式时,要使用完备的括号
如:
#define ADD(a, b) a + b
#define ADD(a, b) (a + b)
#define ADD(a, b) (a) + (b)
如果计算ADD(a, b) * ADD(c, d),本意是对(a+b)*(c+d)求值,但代码展开后变成了如下形式:
a + b * c + d
(a + b) * (c + d)
(a) + (b) * (c) + (d)
又如:
#define MULTIPLE(a, b) (a * b)
在计算(a+b)*c时,得到
(a + b * c)
要避免这些问题,要做的就是:用完备的括号完备地保护各个宏参数。正确的定义应为:
#define ADD(a, b) ((a) + (b))
#define MULTIPLE(a, b) ((a) * (b))
(2)使用宏时,不允许参数发生变化,警惕++,–等运算符。
(3)用大括号将宏定义地多条表达式括起来。
建议5:不要忘记指针变量地初始化
在适应局部指针变量时,一定要将其初始化。对于全局变量来说,在声明地同时,编译器会完成对变量地初始化。
建议6:明细逗号分隔表达式的奇怪之处
例如:
if(++x, --y, x<20 && y>0)
if的判断以最后一个表达式为准。
建议7:时刻提防内存溢出
在调用C语言字符串经典函数(如strcpy,strcat,gets等)时,尽量追踪传入数据的流向。在访问数据时,注意对边界数据要特殊情况特殊处理。还要杜绝使用未初始化指针和失效后未置NULL的“野指针”。
建议8:拒绝晦涩难懂的函数指针
如:
void (*p[10])(void(*)());
改为
typedef void(*pfv)();
typedef void (*pFun_taking_pfv) (pfv);
pFun_taking_pfv p[10];
使用typedef可以让函数指针更直观和易维护。
建议9:防止重复包含头文件
// 解决方法1
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
// statements
#endif
//解决方法2,仅VC++支持
#pragma once
建议10:优化结构体中元素的布局
如下面的代码:
struct A
{
int a;
char b;
short c;
}; //sizeof(struct A) = 8
struct B
{
char b;
int a;
short c;
}; //sizeof(struct B) = 12
了解结构体中元素的对齐规则,合理地为结构体元素进行布局。这样不仅可以有效地节约空间,还可以提高元素的存取效率。
建议11:将强制转型减到最少
// 将表达式的类型转换为T
(T) expression
T(expression)
这两种形式之间没有本质上的区别。
新风格的强制转型:
- const_cast<T*>(a)
它用于从一个类中去除以下这些属性:const、volatile和__unaligned.
class A { //...};
void Function()
{
const A *pConstObj = new A;
A *pObj = pConstObj; //ERROR: 不能讲const对象指针赋值给非const 对象
pObj = const_cast<A*>(pConstObj); //OK
}
- dynamic_cast<T*>(a)
它将a值转换成类型为T的对象指针,主要用老实现类层次结构的提升。
class B { //...};
class D: public B { //...};
void Function(D *pObjD)
{
D *pObj = dynamic_cast<D*>(pObjD)
}
建议12:优先使用前缀操作符
浅醉操作符省去了临时对象的构造,因此它在效率上由于后缀操作。
建议13:掌握变量定义的位置与时机
建议14:小心typedef使用中的陷阱
用途:
- 声明struct对象
- 定义一些与平台无关的类型
- 为复杂的声明定义一个简单的别名
建议15:尽量不要使用可变参数
可变参数的类型和个数在函数中由程序代码控制,导致编译器对可变参数的函数原型检查不够严格,难于查错,不利于写出高质量的代码。
建议16:慎用goto
建议17:提防隐式转换带来的麻烦
- 基本类型之间隐式转换(int,float)
- T* 指针到void* 的隐式转换
- 使用具名转换函数(cout<<1/2<<endl)
- 使用explicit限制的构造函数
建议18:正确区分void与void*
void 无类型
void 无类型指针
第2章 从C到C++,需要做出一些改变
建议19:明白再C++中如何使用C
extern "C"
建议20:是应用memcpy()系列函数时要足够小心
对于POD对象,可以通过对象的及地址和数据成员的偏移量获得数据成员的地址,但是在C++中,非POD对象的内存布局不确定。
建议21:尽量用new/delete代替malloc/free
new/delete在管理内存的同时调用了构造和析构函数,而malloc/free仅仅实现了内存分配与释放。
malloc/free时C/C++语言的标准库函数,而new/delete是C++的运算符。
建议22:灵活地使用不同风格的注释
// 以及/* */
建议23:尽量使用C++标准的iostream
建议24:尽量采用C++风格的强制转型
参考建议11
建议25:尽量用const、enum、inline替换#define(尽量把工作交给编译器而非预处理器)
在预处理阶段,预处理器会完成宏替换,因为此过程并不在百衲衣过程中进行,所以难以发现潜在的错误以及其他代码维护问题。
未完待续