Chinaunix首页 | 论坛 | 博客
  • 博客访问: 407168
  • 博文数量: 128
  • 博客积分: 2247
  • 博客等级: 大尉
  • 技术积分: 767
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-17 09:30
文章分类

全部博文(128)

文章存档

2011年(4)

2010年(124)

我的朋友

分类: C/C++

2010-06-28 11:06:04

高质量C++/C编程指南:hinaunix.net/develop/c&c++/c/c.htm

写函数strcpy的代码。一个函数从三个方面来考查:
(1)编程风格;
(2)出错处理;
(3)算法复杂度分析(用于提 高性能) 。

试问有多少软件开发人员对正确性、健壮性、可靠性、效率、易用性、可读性(可理解性) 、可扩展性、可复用性、兼容性、可移植性等质量属性了如指掌?并且能在实践中运用自如?

希望和我一样在国内土生土长的程序员朋友们能够做到:
(1)知错就改;
(2)经常温故而知新;
(3)坚持学习,天天向上。

阅读并按照CMMI规范做事

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

1.1 版权和版本的声明
版权和版本的声明位于头文件和定义文件的开头(参见示例 1-1),主要内容有:
(1)版权信息。
(2)文件名称,标识符,摘要。
(3)当前版本号,作者/修改者,完成日期。
(4)版本历史信息。

1.2 头文件的结构
头文件由三部分内容组成:
(1)头文件开头处的版权和版本声明 。
(2)预处理块。
(3)函数和类结 构声明等。

【建议 1-2-1】头文件中只存放“声明”而不存放“定义”
【建议 1-2-2】不提倡使用全局变量,尽量不要在头文件中出现象extern int value这类声明。

1.3 定义文件的结构
定义文件有三部分内容:
(1)定义文件开头处的版权和版本声明 。
(2)对一些头文件的引用。
(3)程序的实现体(包括数据和代码)。

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

【规 则 2-2-1】一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。

【规则 2-2-2】if、for、while、do等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{}。这样可以防止书写失误。

【建 议 2-2-1】尽可能在定义变量的同时初始化该变量(就近原则)

【规则 2-3-1】关键字之后要留空格。象const、virtual、inline、case  等关键字之后至少要留一个空格,否则无法辨析关键字。象 if、for、while 等关键字之后应留一个空格再跟左括号‘(’ ,以突出关键字。

【规则 2-3-2】函数名之后不要留空格,紧跟左括号‘(’ ,以与关键字区别。

【规则 2-3-7】象“[] ” 、 “.” 、 “->”这类操作符前后不加空格。

【规 则 2-6-1】应当将修饰符 * 和 & 紧靠变量名

注释通常用于:
(1)版本、版权声明;
(2)函数接 口说明;
(3)重要的代码行或段落提示

类的版式主要有两种方式:
(1)将 private 类型的数据写在前面,而将 public 类型的函数写在后面,如示例 8-3(a) 。采用这种版式的程序员主张类的设计“以数据为中心” ,重点关注类的内部结构。
(2)将 public 类型的函数写在前面,而将 private 类型的数据写在后面,如示例 8.3(b)采用这种版式的程序员主张类的设计“以行为为中心” ,重点关注的是类应该提供什么样的接口(或服务) 。
建议采用“以行为为中 心”的书写方式

【规则 3-1-2】标识符的长度应当符合“min-length && max-information”原则。

【规则 3-1-3】命名规则尽量与所采用的操作系统或开发工具的风格保持一致。

【规 则 3-1-6】变量的名字应当使用“名词”或者“形容词+名词”

【规则 3-1-7】全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)

【规则 3-2-4】静态变量加前缀 s_(表示 static)

【规则 3-2-6】类的数据成员加前缀 m_(表示 member),这样可以避免数据成员与成员函数的参数同名。



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

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

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

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

【建议 4-4-1】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。

【建议 4-4-2】如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。

【规则 4-5-1】不可在 for 循环体内修改循环变量,防止 for 循环失去控制。

【建议 4-5-1】建议 for语句的循环控制变量的取值采用“半开半闭区间”写法。

【规 则 5-1-1】 尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。

有时我们希望某些常量只在类中有效。由于#define 定义的宏常量是全局的,不能达到目的,于是想当然地觉得应该用 const 修饰数据成员来实现。const 数据成员的确是存在的,但其含义却不是我们所期望的。const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其 const数据成员的值可以不同。
不能在类声明中初始化 const 数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道 SIZE的值是什么。
怎样才能建立在整个类中都恒定的常量呢?别指望 const 数据成员了,应该用类中的枚举常量来实现。
枚举常量的缺点是:它的隐含数据类型是整 数,其最大值有限,且不能表示浮点数(如 PI=3.14159)

函数接口的两个要素是参数和返回值。C 语言中,函数的参数和返回值的传递方式有两种:值传递(pass by value)和指针传递(pass by pointer) 。C++ 语言中多了引用传递(pass by reference)。由于引用传递的性质象指针传递,而使用方式却象值传递,初学者常常迷惑不解,容易引起混乱,请先阅读 6.6 节“引用与指针的比较” 。

一般地,应将目的参数放在前面,源参数放在后面。

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

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

正常值用输出参数获得,而错误标志用 return 语句返回。

【建议 6-2-2】如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。

【规则 6-3-1】在函数体的“入口处”,对参数的有效性进行检查。

【规则 6-3-1】在函数体的“入口处”,对参数的有效性进行检查。
(1)return 语句不可返回指向“栈内存”的“指针”或者“引用” ,因为该内存在函数体结束时被自动销毁。
(2)要搞清楚返回的究竟是“值”、“指针”还是“引用” 。
(3)如果函数返回值是一个对象,要考虑 return 语句的效率。

— 【建议 6-4-1】函数的功能要单一,不要设计多用途的函数。
— 【建议 6-4-2】函数体的规模要小,尽量控制在 50 行代码之内。
— 【建议 6-4-3】尽量避免函数带有“记忆”功能。
— 【建议 6-4-4】不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。
— 【建议 6-4-5】用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。

【规则 6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
【规则 6-5-2】在函数的入口处,使用断言检查参数的有效性(合法性) 。
【建议 6-5-1】 在编写函数时, 要进行反复的考查, 并且自问: “我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。
【建议 6-5-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要 使用断言进行报警。

引用的一些规则如下:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化) 。
(2)不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是 NULL) 。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可 以随时改变所指的对象) 。

引用的主要功能是传递函数的参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递、指 针传递和引用传递。

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

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


常见的内存错误及其对策如下:
1、内存分配未成功,却使用了它。
编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为 NULL。如果指针 p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存, 应该用if(p==NULL)或 if(p!=NULL)进行防错处理。
2、内存分配虽然成功, 但是尚未初始化就引用它。
犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组) 。
3、内存分配成功并且已经初始化,但操作越过了内存的边界。
例如在使用数组时经常发生下标“多 1”或者“少 1”的操作。特别是在 for 循环语句中,循环次数很容易搞错,导致数组操作越界。
4、忘记了释放内存,造成内存泄露。
含有这种错误的函数每被调用一次 就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。
动态内存的申请与释放必须配对,程序 中 malloc 与 free 的使用次数一定要相同,否则肯定有错误(new/delete 同理) 。
5、释放了内存却继续使用它。有三种情况:
(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语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单。
用函数返回值 来传递动态内存这种方法虽然好用,但是常常有人把 return 语句用错了。这里强调不要用 return 语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡。

别看 free 和 delete 的名字恶狠狠的(尤其是 delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。

我们发现指针有一些“似是而非”的特征:
(1)指针消亡了,并不表示它所指的内存会被自动释放。
(2)内存被释放了,并不表示指针会消亡或者成了 NULL 指针。

“野指 针”不是 NULL 指针,是指向“垃圾”内存的指针。人们一般不会错用 NULL指针,因为用 if 语句很容易判断。但是“野指针”是很危险的,if 语句对它不起作用。 “野指针”的成因主要有两种:
(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为 NULL 指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存。
(2)指针 p 被 free 或者 delete 之后,没有置为 NULL,让人误以为 p 是个合法的指针。
(3)指针操作超越了变量的作用范 围。

如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果不用 exit(1)把坏程序杀死,它可能会害死操作系统


函数 malloc 的原型如下:
    void * malloc(size_t size);
用 malloc 申请一块长度为 length 的整数类型的内存,程序如下:
    int  *p = (int *) malloc(sizeof(int) * length);
我们应当把注意力集中在两个要素上: “类型转换”和“sizeof” 。
malloc 返回值的类型是 void *,所以在调用 malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。
malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住 int, float等数据类型的变量的确切字节数。在 malloc 的“()”中使用 sizeof 运算符是良好的风格,但要当心有时我们会昏了头,写出 p = malloc(sizeof(p))这样的程序来。

函数 free 的原型如下:
void free( void * memblock );
如果 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个对象。
阅读(624) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~