0、 不要让main函数返回void
void main()是不正确的写法,在vc环境可以正常运行,但这是经过微软特殊处理的,在其他环境中可能编译出错。
写 int main()更通用。
1、 区分0的四种面孔
1. 整数0
2. 空指针0
a. 这里的0和整数0有区别。
Int *pValue = 0;//合法,0可以表示地址。相当于 int *pValue = NULL;
Int *pValue = 1;//不合法,1不可以表示地址。
3. 字符串终止符号'\0'
4. Flase
2、 避免那些由运算符引发的混乱
不良习惯:if(value == 1)
良好习惯:if(1 == value)
因为可能笔误写作:if(value = 1)
用细心和良好的代码习惯避免由于运算符混乱带来的麻烦。
3、对表达式计算顺序不要想当然
1. if表达式注意优先级问题
不良习惯:if(a & b == c)
良好习惯:if(a & (b==c)
因为&的优先级大于==
2. 表达式评估求值问题
a. 参数评估求值顺序
不良习惯:
int a = 2022;
printf("%d %d",a, a+1);
良好习惯:
int a = 2022;
int b = a+1;
printf("%d %d",a, b);
因为不良习惯可能输出2022 2023,也可能输出2023 2023。
b. 操作数的评估求值顺序
4、 小心宏#define使用中的陷阱
1. 不良习惯:
#define ADD(a,b) a+b
#define ADD(a,b) (a+b)
#define ADD(a,b) (a)+(b)
良好习惯:
#define ADD(a,b) ((a)+(b))
2. 不要允许参数发生变化
比如#define SQUARE(a) ((a)*(a))
SQUARE(a++)可能会有问题,a会自加两次。
3. 宏定义包含多条表达式,用大括号括起来。
注:宏定义代替函数可以减少系统开销,提高运行效率
5、 指针变量初始化
成员、全局指针变量都要初始化。
6、 明晰逗号分隔表达式的奇怪之处
if(++x, --y, x<20 && y>0)
这种用多个逗号分隔的表达式都会被运行,但是if语句只关心最后一个的结果。
7、 时刻提防内存溢出
1. strcpy函数由于没有检查长度,可能会导致内存溢出。
使用strcpy_s,这是strcpy的安全版本,参数中有长度参数。
strcat、gets都有这个问题。
2. 数组越界:
红色矩形处代码可能导致访问越界,应该把这个判断放到for循环里面。
8、 拒绝晦涩难懂的函数指针
void (*p[10]) (void (*)());
这是一个数组,里面又是个函数指针,这些函数指针的参数和返回值都为空。
使用typedef简化这些声明:
1. 声明一个无参数、返回空的函数指针:
typedef void (*pfv)();
2.构造另一个函数指针简化pfv的引用
typedef void (*pFun) (pfv);
3.声明一个包含十个函数指针的数组
pFun p[10];
函数指针在运行时的动态调用(例如函数回调)中应用广泛。
9、 防止重复包含头文件
会编译失败。
解决方法一:
#ifndef __XX_H__
#define __XX_H__
……
#endif
解决方法二:
#pargma once
……
比如vs创建的文件会加上方法二,但是这种不是c++语言标准,收到了编译器的限制。许多程序员为了代码的兼容性,选择方法一。
10、 优化结构体中元素的布局
两个结构体都是一个int,一个char,一个short
但是A大小为8,B大小为12
原因是字节对齐。
11、将强制转型减到最少
1. 减少c风格强制转换的使用
2. 如果真的有必要,使用C++强制转换
a. const_cast<T*>(a)
从类中去除const、volatile、__unaligned属性
b. dynamic_cast<T*>(a)
安全的向下转型,基类转派派生类。代价更大更慢,性能要求较高时放弃使用。
c. static_cast<T*>(a)
不检查类型,不确保安全性
d. 如果A、B类型存在相关性,使用static_cast。
e. reinterpret_cast<T*>(a)
不安全,类似C风格强转,可能出现内存膨胀或阶段。
如果A、B类型没有任何相关性,使用reinterpret_cast。
12、 优先使用前缀操作符
++a和a++,优先使用++a
前加和后加是重载函数,后加有一个int类型的参数,用于和前加区分。
后加会产生一个临时对象,前加则不会产生。所以前加更快。
13、 掌握变量定义的位置与时机
1. 用时定义,提高可读性
2. 尽量在循环外定义变量,减少开销
14、 小心typedef使用中的陷阱
typedef和define区别
1. #define只是简单的字符串替换
2. #typedef具有封装性,易于定义变量,可以同事声明指针类型的多个队形,而宏定义不行。
typedef用途:
1. 声明结构体对象是使用typedef定义类型。
typedef struct tagRect{
int width;
int height;
}RECT;
2. 定义平台无关类型。
3. 为复杂的声明定义一个简单别名,增加可读性。
15、 尽量不要使用可变参数
缺少类型检查,不能自定义数据类型。
16、 慎用goto
破坏程序结构性,影响可读性,完全可用for和while代替。
17、 提防隐式转换带来的麻烦
上述代码中会把20自动转为A类对象
18、 正确区分void和void*
void是指无类型,用于函数的返回值。
void*是指针,代表任何类型的指针。
1. 任何类型的指针都可以隐式转换为void*类型。
int* pInt;
void* pVoid;
pVoid = pInt;
但是void*类型转换成其他类型指针需要强制转型。
2. ANSI不可以对void*指针进行算法操作,但是GUN标准可以。
3. void*作为参数的典型例子是memcpy和memset,代表任何类型指针都可以出传入,传出的是一块没有数据类型规定的内存。
19 明白在C++中如何使用C
C++和C的只是部分兼容,其中是有差异的,比如:
1. C代码:
double *pDouble = malloc(nCount*sizeof(double));
C++代码:
double *pDouble = (double*)malloc(nCount*sizeof(double));
原因是C只支持从void*隐式转换为其他指针类型,而C++不支持,需要强转。这会带来移植性问题。
2. new和class在C++中是关键字,而在C中可以作为变量名成。
使用extern “C"{/*Code*/}包含C语言代码,这是因为C和C++编译器生成的函数符号不同,告诉C++链接器寻找调用函数的符号时,使用C的方式。
20、 使用memcpy()系列函数是要足够小心
在C语言中所有对象都是POD(plain old data)。
对于POD对象,我们可以通过对象基地址和数据成员的偏移量获取数据成员的地址,但是C++不行,因为引入了虚函数等概念,对象的内存布局并不是连续的,所以有很多不是POD对象。
memset、memcmp等函数只能对POD对象使用,否则就是不安全的。
在C++中使用这一系列的函数时要小心区分是否是POD对象。
21、尽量使用new/delete代替malloc/free
malloc/free:
1. 属于C,是C标准库函数
2. 只进行内存分配和释放,而不调用构造和析构。
3. malloc返回Void*,需要强制转型。
4. malloc失败时直接返回NULL
new/delete:
1. 属于C++,是保留字,是操作符。
2. 不仅进行内存分配和释放,并且调用构造和析构。
3. new返回具体的类型,不需要强制转型。
4. new失败时调用new_handler处理函数
realooc函数是C标准库函数,可以重新设置内存块代销,在C++中没有类似运算符。
new/delete malloc/free配对使用,不要混用,而且尽力将使用new/delete
22、灵活地使用不同风格的注释
1. C风格注释:/*Code*/
2. C++风格注释://Code
尽量使用C++风格注释
23、尽量使用C++标准的iostream
1. C风格:printf()
2. C++风格:operator<<
尽管C标准有优点如下:
1. C stream函数生成的可执行文件更小,效率更高
2. 不会涉及对象构造、析构问题。
3. 更前的可移植能力
但尽量使用C++标准的#include<iostream>
24、尽量采用C++风格的强制转型
更容易识别搜索,更有针对性,让使用者更清晰地了解强制转型的目的。
25、尽量用const、enum、inline替换#define
用模板替代define函数
尽量把工作交给编译器而非预处理器。
26、用引用代替指针
指针和引用并无太多不同。
27、区分内存分配的方式
栈、堆区别
1. 管理方式不同。栈是编译器管理。堆是程序员管理。
2. 空间大小不同。栈小堆大。
3. 碎片问题。堆由于new/delete的频繁,可能会造成内存不连续,产生大量碎片空间。栈是栈结构,先进后出,不会有问题。
4. 生长方向。栈向下,想着内存地址减小方向增长。堆向上,向着内存地址增大方向增长。
5. 分配方式。堆都是动态分配。栈有静态分配也有动态分配。静态是指编译时分配的空间,动态是指运行时分配的空间。
6. 分配效率。栈是系统提供的,底层支持,有寄存器支持,效率高。堆是c++函数库提供的,机制复杂,效率低。
28、 new/delete与new[]/delete[]必须配对使用
new[]会记录数组个数,delete[]会找到这个个数,逐一删除,如果使用delete会删除不完全出现不可知错误。
所有要配对使用。
注意new[]在typedef中的使用:
29、区分new的三种形态
1. new operator
a. 是c++内置函数,不能重载,会调用operator new.
2. operator new
a. 普通运算符,类似加减乘除,可以重载。只分配内存,不进行初始化,默认应该调用malloc。
3. placement new
a. 使用形式和上面两种不同
b. 在指定内存地址上重建构建类对象,不需要分配内存。