笔记:《编写高质量代码改善C++程序的150个建议》

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. 在指定内存地址上重建构建类对象,不需要分配内存。

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值