读书笔记,书名:《编写高质量代码:改善C++程序的150个建议》
持续更新中...
目录
第一部分 语法篇
- 第一章 从C继承而来
- 第二章 C到C++
- 第三章 内存管理
- 第四章 类
第1章 从C继承而来
建议0:不要让main返回void
C++03中给出的2种main函数定义方式:
int main()
int main( int argc, char *argv[] )
在linux中执行./out && echo "success"
,如果main函数return -1
,则不会输出"success"
C++中还有个规定,如果main函数中不写return,编译器会隐式地加上return 0
建议1:区分0的4种面孔
- 整型
0
- 空指针NULL,
#define NULL 0
。 C++中空指针被定义为nullptr
- 字符串结束符
'\0'
- 逻辑
false
指针与int类型所占空间一样,都是32位。
建议2:== 的正确使用方式
if (0 == value){
}
建议3:表达式的计算顺序
- 不确定优先级时,使用括号解决
- 函数的参数求值顺序不确定:
printf("%d %d", fun_1(), fun_2() )
- 操作数的求职顺序不确定:
a = p() + q() * r();
- 短路运算有顺序:
a < b && c < d
- 三目运算有顺序:
a < b? c: d
建议4:宏#define的陷阱
- 使用完备的括号
#define ADD(a, b) a + b // 不完备,a+b*c+d
#define ADD(a, b) (a + b) // 不完备,(a+b)*(c+d),碰巧没问题
#define MULTIPLY(a, b) (a * b) // 不完备,MULTIPLY(a+b, c) -> (a+b*c)
#define ADD(a, b) (a) + (b) // 不完备
#define ADD(a, b) ((a) + (b)) // 完备
#define MULTIPLY(a, b) ((a) * (b)) // 完备
- 调用宏定义函数时,不要使参数发生变化
#define SQUARE( a ) ((a) * (a))
int Square(int a) { return a*a; }
int nValue1 = 10, nValue2 = 10;
int nSquare1 = SQUARE(nValue1++); // nSquare1=110, nValue1=12
int nSquare2 = Square(nValue2++); // nSquare2=100, nValue2=11
- 用大括号将多条表达式括起来
建议5:指针必须赋初始化值
如果不赋初值,编译器会初始化一个随机值,这将是很危险的。
int* p = NULL;
int* q = new int(1);
建议6:逗号分隔符
表达式1, 表达式2, 表达式3, ..., 表达式n
整个逗号分隔表达式的值为表达式n
的值。
建议7:时刻提防内存溢出
在处理字符串、数组时,时刻提防内存溢出。
建议8:拒绝晦涩难懂的函数指针
建议9:防止重复包含头文件
为了避免同一个文件被包含多次,C/C++中有两种处理方式,一种是#ifndef方式,另一种是#pragma once方式。
注意:不同的头文件不能使用相同的宏名,否则编译器会报"找不到声明"的错误
- 方式1:
#ifndef _____H__
#define _____H__
// 声明、定义语句
#endif
- 方式2:(GCC已经取消支持,Visual Studio还支持)
#pragma once
// 声明、定义语句
建议10:优化结构体中的元素布局
字节对齐:为了提高变量的访问效率,某些特定类型的数据只能从某些特定地址存取,以空间换时间。
struct A{
int a;
char b;
char reserved; // 保留字节,空间换时间
short c;
};
建议11:将强制转换减到最少
C++的四种强制转换形式:
- const_cast
(a),从类中去除一下属性:const、volatile、__unaligned。不推荐使用。 - dynamic_cast
(a),"类的安全的向下转换",用于将基类指针转换为派生类指针,基类指针可以指向派生类对象,能实现多态,转换是安全的。 - reinterpret_cast
(a),"重新解释",不相关类型之间的转换,是不安全的。 - static_cast
(a),相当于传统的C语言里的强制转换,在运行时转换过程中,不进行类型检查,是不安全的。
class Base {
};
class Derived : public Base {
};
int main() {
Base *pB = new Base();
if (Derived *pD = static_cast(pB)) {
// 编译期和运行时不做类型检查
// 向上转换是不安全的(坚决抵制这种方法)
}
Derived *pD = new Derived();
if (Base *p1 = static_cast (pD)) {
// 向下转换是安全的
}
if (Derived *p2 = dynamic_cast(pB)) {
// 编译期报错,error: 'Base' is not polymorphic
}
if (Base *p3 = dynamic_cast(pD)) {
// 向下转换是安全的
}
return 0;
}
建议12:优先使用前缀操作符
后缀操作过程中会构造一个临时对象,因此效率不如前缀操作。
但是,对于整型和长整型而言,性能区别通常可以忽略。
另外,编译器一般会对后缀操作符进行优化。
建议13:掌握变量定义的时机
- 尽量不在循环内定义变量或实例化对象
- 定义变量尽可能晚、尽可能local
建议14:小心typedef的陷阱
- typedef与#define的区别
define只是简单的字符串替换,typedef具有一定的封装性,更易于定义变量。
typedef int* PTR_INT1
#define PTRINT2
PTR_INT1 pNum1, pNum2; // pNum2是int*类型
PTR_INT2 pNum3, pNum4; // nNum4是int类型
- typedef可以定义平台无关的类型,如
uint64_t
#if __WORDSIZE == 64
typedef unsigned long int uint64_t;
#else
__extension__
typedef unsigned long long int uint64_t;
#endif
- typedef是一个存储类的关键字,类似于static、mutable、register等,因此不能与这些关键字一起使用。
建议15:尽量不要使用可变参数
1.可变参数的函数没有类型检查,运行时会报错:
printf("%d %d", "Hello", 2019);
2.禁用了语言类型检查,容易出错,不安全。
3.不支持自定义数据类型。
可以尝试Boost中的format替换printf。
建议16:禁用goto
建议17:小心隐式转换
- 基本类型之间的隐式转换,要注意精度损失问题。
- C++中规定
T*
到void*
单向的转换,是安全的。而C中允许双向转换,不安全。 - 不明确的构造函数,不检查参数类型;转换过程中会调用类的构造函数、析构函数。
//编译器会隐式地将 float 类型的 fVal 转成 int ,然后调用类A的构造函数。
//解决方法:为单参数的构造函数加上explicite关键字,防止隐式转换。
class A
{
public:
A(int x): m_data(x){}
private:
int m_data;
}
void DoSomething(A a);
float fVal = 1.23f;
DoSomething(fVal);
- 安全的隐式转换:子类到基类的转换、const到non-const的转换
建议18:正确区分void与void*
void
是『无类型』,void*
是『无类型指针』。void*
可以指向任何类型。
如果函数的参数可以是任意类型指针,应声明为void*
类型:
void* memcpy(void* dest, const void* src, size_t len);
void* memset(void* buffer, int c, size_t num);