高质量C/C++编程

来源:本站
导读:目前正在解读《高质量C/C++编程》的相关信息,《高质量C/C++编程》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《高质量C/C++编程》的详细说明。
简介:高质量C/C++编程(片段)

【规则1-2-1】为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。

l 【规则1-2-2】用 #include <filename.h> 格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。

l 【规则1-2-3】用 #include “filename.h” 格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。

2 【建议1-2-1】头文件中只存放“声明”而不存放“定义

--------------------------------

4.3.1 布尔变量与零值比较

l 【规则4-3-1】不可将布尔变量直接与TRUE、FALSE或者1、0进行比较。

根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值究竟是什么并没有统一的标准。例如Visual C++ 将TRUE定义为1,而Visual Basic则将TRUE定义为-1。

假设布尔变量名字为flag,它与零值比较的标准if语句如下:

if (flag) // 表示flag为真

if (!flag) // 表示flag为假

其它的用法都属于不良风格,例如:

4.3.2 整型变量与零值比较

l 【规则4-3-2】应当将整型变量用“==”或“!=”直接与0比较。

假设整型变量的名字为value,它与零值比较的标准if语句如下:

if (value == 0)

if (value != 0)

4.3.3 浮点变量与零值比较

l 【规则4-3-3】不可将浮点变量用“==”或“!=”与任何数字比较。

千万要留意,无论是float还是double类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。

假设浮点变量的名字为x,应当将

if (x == 0.0) // 隐含错误的比较

转化为

if ((x>=-EPSINON) && (x<=EPSINON))

其中EPSINON是允许的误差(即精度)。

4.3.4 指针变量与零值比较

l 【规则4-3-4】应当将指针变量用“==”或“!=”与NULL比较。

指针变量的零值是“空”(记为NULL)。尽管NULL的值与0相同,但是两者意义不同。假设指针变量的名字为p,它与零值比较的标准if语句如下:

if (p == NULL) // p与NULL显式比较,强调p是指针变量

if (p != NULL)

-------------------------

循环语句的效率

--------------------------

const数据成员的初始化只能在类构造函数的初始化表中进行,例如

class A

{…

A(int size); // 构造函数

const int SIZE ;

};

A::A(int size) : SIZE(size) // 构造函数的初始化表

{

}

A a(100); // 对象 a 的SIZE值为100

A b(200); // 对象 b 的SIZE值为200

---------------------------------

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

例如:

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

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

-------------------------------------

7.1内存分配方式

内存分配方式有三种:

(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

--------------------------------------

u 内存分配未成功,却使用了它。

编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。

------------------------------

C++/C程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险

示例7-3-1中,字符数组a的容量是6个字符,其内容为hello。a的内容可以改变,如a[0]= ‘X’。指针p指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

7.3.2 内容复制与比较

不能对数组名进行直接复制与比较。示例7-3-2中,若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。

语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数malloc为p申请一块容量为strlen(a)+1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。

// 数组…

char a[] = "hello";

char b[10];

strcpy(b, a); // 不能用 b = a;

if(strcmp(b, a) == 0) // 不能用 if (b == a)

// 指针…

int len = strlen(a);

char *p = (char *)malloc(sizeof(char)*(len+1));

strcpy(p,a); // 不要用 p = a;

if(strcmp(p, a) == 0) // 不要用 if (p == a)

------------------------------------

针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量

void Func(char a[100])

{

cout<< sizeof(a) << endl; // 4字节而不是100字节

}

-----------------------------------

毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存

-----------------------------------

用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见示例7-4-4。

char *GetString(void)

{

char p[] = "hello world";

return p; // 编译器将提出警告

}

-------------------------------------

用调试器跟踪示例7-5,发现指针p被free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。

-------------------------------

alloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

函数malloc的原型如下:

void * malloc(size_t size);

用malloc申请一块长度为length的整数类型的内存,程序如下:

int *p = (int *) malloc(sizeof(int) * length);

我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。

umalloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。

umalloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float等数据类型的变量的确切字节数。例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。

free(p)能正确地释放内存。如果p是NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。

-----------------------------------

运算符new使用起来要比函数malloc简单得多,例如:

int *p1 = (int *)malloc(sizeof(int) * length);

int *p2 = new int[length];

这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式。例如

在用delete释放对象数组时,留意不要丢了符号‘[]’。例如

delete []objects;// 正确的用法

delete objects;// 错误的用法

后者相当于delete objects[0],漏掉了另外99个对象。

------------------------------------------

如果C++程序要调用已经被编译后的C函数,该怎么办?

假设某个C函数的声明如下:

void foo(int x, int y);

该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能直接调用C函数。C++提供了一个C连接交换指定符号extern“C”来解决这个问题。例如:

extern “C”

{

void foo(int x, int y);

… // 其它函数

}

或者写成

extern “C”

{

#include “myheader.h”

… // 其它C头文件

}

这就告诉C++编译译器,函数foo是个C连接,应该到库中找名字_foo而不是找_foo_int_int。C++编译器开发商已经对C标准库的头文件作了extern“C”处理,所以我们可以用#include 直接引用这些头文件。

----------------------------------------------

对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A产生四个缺省的函数,如

A(void);// 缺省的无参数构造函数

A(const A &a);// 缺省的拷贝构造函数

~A(void);// 缺省的析构函数

A & operate =(const A &a);// 缺省的赋值函数

-----------------------------------------------

构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程。

一个有趣的现象是,成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序

------------------------------------------------

u如果输入参数采用“指针传递”,那么加const修饰可以防止意外地改动该指针,起到保护作用。

例如StringCopy函数:

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

对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。

对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。

--------------------------------------------------

u如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。

例如函数

const char * GetString(void);

如下语句将出现编译错误:

char *str = GetString();

正确的用法是

const char *str = GetString();

u如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。

例如不要把函数int GetInt(void) 写成const int GetInt(void)。

同理不要把函数A GetA(void) 写成const A GetA(void),其中A为用户自定义的数据类型

----------------------------------------------------

任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。

以下程序中,类stack的成员函数GetCount仅用于计数,从逻辑上讲GetCount应当为const函数。编译器将指出GetCount函数中的错误。

class Stack

{

public:

void Push(int elem);

int Pop(void);

intGetCount(void) const;// const成员函数

private:

intm_num;

intm_data[100];

};

int Stack::GetCount(void) const

{

++ m_num;// 编译错误,企图修改数据成员m_num

Pop();// 编译错误,企图调用非const函数

return m_num;

}

const成员函数的声明看起来怪怪的:const关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了。

----------------------------------------------

#ifndefGRAPHICS_H// 防止graphics.h被重复定义

#defineGRAPHICS_H

#include <math.h>// 引用标准库的头文件…

#include “myheader.h” // 引用非标准库的头文件…

void Function1(…);// 全局函数声明…

class Box // 类结构声明{…};

#endif

提醒:《高质量C/C++编程》最后刷新时间 2024-03-14 01:03:42,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《高质量C/C++编程》该内容的真实性请自行鉴别。