高质量C/C++编程指南笔记

来源:互联网 发布:nginx 安装禅道 编辑:程序博客网 时间:2024/05/02 03:07


(1)知错就改;
(2)温故而知新;
(3)坚持学习,天天向上


应当将修饰符 * 和& 紧靠变量名
注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。
当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。
程序中不要出现标识符完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但会使人误解。


假设布尔变量名字为flag,它与零值比较的标准if 语句如下:
if (flag) // 表示flag 为真
if (!flag) // 表示flag 为假


整型变量与零值比较:
应当将整型变量用“==”或“!=”直接与0 比较。
if (value == 0)
if (value != 0)
不可模仿布尔变量的风格而写成
if (value) // 会让人误解 value 是布尔变量
if (!value)


浮点变量与零值比较:


不可将浮点变量用“==”或“!=”与任何数字比较。
千万要留意,无论是float 还是double 类型的变量,都有精度限制。所以一定要
避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。
假设浮点变量的名字为x,应当将
if (x == 0.0) // 隐含错误的比较
转化为
if ((x>=-EPSINON) && (x<=EPSINON))
其中EPSINON 是允许的误差(即精度)。


指针变量与零值比较:
应当将指针变量用“==”或“!=”与NULL 比较。
指针变量的零值是“空”(记为NULL)。尽管NULL 的值与0 相同,但是两者意义不
同。假设指针变量的名字为p,它与零值比较的标准if 语句如下:
if (p == NULL) // p 与NULL 显式比较,强调p 是指针变量
if (p != NULL)
不要写成
if (p == 0) // 容易让人误解p 是整型变量
if (p != 0)
或者
if (p) // 容易让人误解p 是布尔变量
if (!p)



在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的
循环放在最外层,以减少CPU 跨切循环层的次数。例如示例4-4(b)的效率比示例
4-4(a)的高。


C 语言用 #define 来定义常量(称
为宏常量)。C++ 语言除了 #define 外还可以用const 来定义常量(称为const 常量)。


有时我们希望某些常量只在类中有效。由于#define 定义的宏常量是全局的,不能
达到目的,于是想当然地觉得应该用const 修饰数据成员来实现。const 数据成员的确
是存在的,但其含义却不是我们所期望的。const 数据成员只在某个对象生存期内是常
量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const 数
据成员的值可以不同。
不能在类声明中初始化const 数据成员。以下用法是错误的,因为类的对象未被创
建时,编译器不知道SIZE 的值是什么。
class A
{
const int SIZE = 100; // 错误,企图在类声明中初始化const 数据成员
int array[SIZE]; // 错误,未知的SIZE
};


怎样才能建立在整个类中都恒定的常量呢?别指望const 数据成员了,应该用类中
的枚举常量来实现。例如
class A
{
enum { SIZE1 = 100, SIZE2 = 200}; // 枚举常量
int array1[SIZE1];
int array2[SIZE2];
};

枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:
它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如PI=3.14159)。



【规则6-1-1】参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字。
如果函数没有参数,则用void 填充。
例如:
void SetValue(int width, int height); // 良好的风格
void SetValue(int, int); // 不良的风格
float GetValue(void); // 良好的风格
float GetValue(); // 不良的风格


如果参数是指针,且仅作输入用,则应在类型前加const,以防止该
指针在函数体内被意外修改。

void StringCopy(char *strDestination,const char *strSource);

如果输入参数以值传递的方式传递对象,则宜改用“const &”方式
来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。

不要省略返回值的类型。
C 语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,
却容易被误解为void 类型。
C++语言有很严格的类型安全检查,不允许上述情况发生。由于C++程序可以调用
C 函数,为了避免混乱,规定任何C++/ C 函数都必须有类型。如果函数没有返回值,
那么应声明为void 类型。



函数名字与返回值类型在语义上不可冲突。
违反这条规则的典型代表是C 标准库函数getchar。
例如:
char c;
c = getchar();
if (c == EOF)
?
按照 getchar 名字的意思,将变量c 声明为char 类型是很自然的事情。但不幸的是
getchar 的确不是char 类型,而是int 类型,其原型如下:
int getchar(void);
由于c 是char 类型,取值范围是[-128,127],如果宏EOF 的值在char 的取值范围
之外,那么if 语句将总是失败,这种“危险”人们一般哪里料得到!导致本例错误的责
任并不在用户,是函数getchar 误导了使用者。


char *strcpy(char *strDest,const char *strSrc);
strcpy 函数将strSrc 拷贝至输出参数strDest 中,同时函数的返回值又是strDest。
这样做并非多此一举,可以获得如下灵活性:
char str[20];
int length = strlen( strcpy(str, “Hello World”) );


return 语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数
体结束时被自动销毁


以下是“值传递”的示例程序。由于Func1 函数体内的x 是外部变量n 的一份拷贝,
改变x 的值不会影响n, 所以n 的值仍然是0。
void Func1(int x)
{
x = x + 10;
}


int n = 0;
Func1(n);
cout << “n = ” << n << endl; // n = 0


以下是“指针传递”的示例程序。由于Func2 函数体内的x 是指向外部变量n 的指
针,改变该指针的内容将导致n 的值改变,所以n 的值成为10。
void Func2(int *x)
{
(* x) = (* x) + 10;
}
?
int n = 0;
Func2(&n);
cout << “n = ” << n << endl; // n = 10
以下是“引用传递”的示例程序。由于Func3 函数体内的x 是外部变量n 的引用,
x 和n 是同一个东西,改变x 等于改变n,所以n 的值成为10。
void Func3(int &x)
{
x = x + 10;
}
?
int n = 0;
Func3(n);
cout << “n = ” << n << endl; // n = 10
对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式
象“值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引
用”这东西?

内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的
整个运行期间都存在。例如全局变量,static 变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函
数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集
中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意
多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存
期由我们决定,使用非常灵活,但问题也最多。


常见的内存错误及其对策
发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序
运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。
有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。
常见的内存错误及其对策如下:
?? 内存分配未成功,却使用了它。
编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,
在使用内存之前检查指针是否为NULL。如果指针p 是函数的参数,那么在函数的入口
处用assert(p!=NULL)进行检查。如果是用malloc 或new 来申请内存,应该用if(p==NULL)
或if(p!=NULL)进行防错处理。
?? 内存分配虽然成功,但是尚未初始化就引用它。
犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值
全为零,导致引用初值错误(例如数组)。
内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信
其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不
可省略,不要嫌麻烦。
?? 内存分配成功并且已经初始化,但操作越过了内存的边界。
例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for 循环语
句中,循环次数很容易搞错,导致数组操作越界。
?? 忘记了释放内存,造成内存泄露。
含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你
看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。
动态内存的申请与释放必须配对,程序中malloc 与free 的使用次数一定要相同,
否则肯定有错误(new/delete 同理)。
?? 释放了内存却继续使用它。
有三种情况:
(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了
内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
(2)函数的return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,
因为该内存在函数体结束时被自动销毁。
(3)使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。
?? 【规则7-2-1】用malloc 或new 申请内存之后,应该立即检查指针值是否为NULL。
防止使用指针值为NULL 的内存。
?? 【规则7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右
值使用。
?? 【规则7-2-3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”
操作。
?? 【规则7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。
?? 【规则7-2-5】用free 或delete 释放了内存之后,立即将指针设置为NULL,防止
产生“野指针”。


指针与数组的对比
C++/C 程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以
为两者是等价的。
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着
(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改
变。
指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。
下面以字符串为例比较指针与数组的特性。

0 0