博文

C++/C代码审查表(四)(2007-04-11 22:08:00)

摘要: 重要 是否用内联函数取代了宏代码?     ……   类的构造函数、析构函数和赋值函数 重要性 审查项 结论 重要 是否违背编程规范而让C++ 编译器自动为类产生四个缺省的函数:(1)缺省的无参数构造函数;(2)缺省的拷贝构造函数;(3)缺省的析构函数;(4)缺省的赋值函数。   重要 构造函数中是否遗漏了某些初始化工作?   重要 是否正确地使用构造函数的初始化表?   重要 析构函数中是否遗漏了某些清除工作?     是否错写、错用了拷贝构造函数和赋值函数?   重要 赋值函数一般分四个步骤:(1)检查自赋值;(2)释放原有内存资源;(3)分配新的内存资源,并复制内容;(4)返回 *this。是否遗漏了重要步骤?   重要 是否正确地编写了派生类的构造函数、析构函数、赋值函数?注意事项: (1)派生类不可能继承基类的构造函数、析构函数、赋值函数。 (2)派生类的构造函数应在其初始化表里调用基类的构造函数。 (3)基类与派生类的析构函数应该为虚(即加virtual关键字)。 (4)在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。     ……   类的高级特性 重要性 审查项 结论 重要 是否违背了继承和组合的规则? (1)若在逻辑上B是A的“一种”,并且A的所有功能和属性对B而言都有意义,则允许B继承A的功能和属性。 (2)若在逻辑上A是B的“一部分”(a part of),则不允许B从A派生,而是要用A和其它东西组合出B。     ……   其它常见问题 重要性 审查项 结论 重要 数据类型问题: (1)变量的数据类型有错误吗? (2)存在不同数据类型的赋值吗? (3)存在不同数据类型的比较吗?......

阅读全文(2218) | 评论:0

C++/C代码审查表(三)(2007-04-11 22:08:00)

摘要: 重要 是否将正常值和错误标志混在一起返回?正常值应当用输出参数获得,而错误标志用return语句返回。   重要 在函数体的“入口处”,是否用assert对参数的有效性进行检查?   重要 使用滥用了assert? 例如混淆非法情况与错误情况,后者是必然存在的并且是一定要作出处理的。   重要 return语句是否返回指向“栈内存”的“指针”或者“引用”?     是否使用const提高函数的健壮性?const可以强制保护函数的参数、返回值,甚至函数的定义体。“Use const whenever you need”     ……   内存管理 重要性 审查项 结论 重要 用malloc或new申请内存之后,是否立即检查指针值是否为NULL?(防止使用指针值为NULL的内存)   重要 是否忘记为数组和动态内存赋初值?(防止将未被初始化的内存作为右值使用)   重要 数组或指针的下标是否越界?   重要 动态内存的申请与释放是否配对?(防止内存泄漏)   重要 是否有效地处理了“内存耗尽”问题?   重要 是否修改“指向常量的指针”的内容?   重要 是否出现野指针?例如 (1)指针变量没有被初始化。 (2)用free或delete释放了内存之后,忘记将指针设置为NULL。   重要 是否将malloc/free 和 new/delete 混淆使用?   重要 malloc语句是否正确无误?例如字节数是否正确?类型转换是否正确?   重要 在创建与释放动态对象数组时,new/delete的语句是否正确无误?     ……   C++ 函数的高级特性 ......

阅读全文(2629) | 评论:0

C++/C代码审查表(二)(2007-04-11 22:07:00)

摘要:   ……   表达式与基本语句 重要性 审查项 结论 重要 如果代码行中的运算符比较多,是否已经用括号清楚地确定表达式的操作顺序?     是否编写太复杂或者多用途的复合表达式?   重要 是否将复合表达式与“真正的数学表达式”混淆?   重要 是否用隐含错误的方式写if语句? 例如 (1)将布尔变量直接与TRUE、FALSE或者1、0进行比较。 (2)将浮点变量用“==”或“!=”与任何数字比较。 (3)将指针变量用“==”或“!=”与NULL比较。     如果循环体内存在逻辑判断,并且循环次数很大,是否已经将逻辑判断移到循环体的外面?   重要 Case语句的结尾是否忘了加break?   重要 是否忘记写switch的default分支?   重要 使用goto 语句时是否留下隐患? 例如跳过了某些对象的构造、变量的初始化、重要的计算等。     ……   常量 重要性 审查项 结论   是否使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串?     在C++ 程序中,是否用const常量取代宏常量?   重要 如果某一常量与其它常量密切相关,是否在定义中包含了这种关系?     是否误解了类中的const数据成员?因为const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。     ……   函数设计 重要性 审查项 结论   参数的书写是否完整?不要贪图省事只写参数的类型而省略参数名字。     参数命......

阅读全文(2035) | 评论:0

C++/C代码审查表(一)(2007-04-11 22:07:00)

摘要:  文件结构 重要性 审查项 结论   头文件和定义文件的名称是否合理?     头文件和定义文件的目录结构是否合理?     版权和版本声明是否完整?   重要 头文件是否使用了 ifndef/define/endif 预处理块?     头文件中是否只存放“声明”而不存放“定义”     ……   程序的版式 重要性 审查项 结论   空行是否得体?     代码行内的空格是否得体?     长行拆分是否得体?     “{” 和 “}” 是否各占一行并且对齐于同一列?   重要 一行代码是否只做一件事?如只定义一个变量,只写一条语句。   重要 If、for、while、do等语句自占一行,不论执行语句多少都要加“{}”。   重要 在定义变量(或参数)时,是否将修饰符 * 和 & 紧靠变量名?     注释是否清晰并且必要?   重要 注释是否有错误或者可能导致误解?   重要 类结构的public, protected, private顺序是否在所有的程序中保持一致?     ……   命名规则 重要性 审查项 结论 重要 命名规则是否与所采用的操作系统或开发工具的风格保持一致?     标识符是否直观且可以拼读?     标识符的长度应当符合“min-length && max-info......

阅读全文(2333) | 评论:0

 C++/C编程规范(10-11)----林  锐(2007-04-10 21:23:00)

摘要:第10章 类的继承与组合   对象(Object)是类(Class)的一个实例(Instance)。如果将对象比作房子,那么类就是房子的设计图纸。所以面向对象设计的重点是类的设计,而不是对象的设计。 对于C++程序而言,设计孤立的类是比较容易的,难的是正确设计基类及其派生类。本章仅仅论述“继承”(Inheritance)和“组合”(Composition)的概念。 注意,当前面向对象技术的应用热点是COM和CORBA,这些内容超出了C++教材的范畴,请阅读COM和CORBA相关论著。 10.1 继承 如果A是基类,B是A的派生类,那么B将继承A的数据和函数。例如:        class A {   public:               void  Func1(void);               void  Func2(void); };   class B : public A {   public:               void  Func3(void);               void  Func4(void); };          main() {               B  b;   &nb......

阅读全文(2337) | 评论:0

 C++/C编程规范(9.5-9.9)----林  锐(2007-04-10 21:22:00)

摘要:9.5 不要轻视拷贝构造函数与赋值函数        由于并非所有的对象都会使用拷贝构造函数和赋值函数,程序员可能对这两个函数有些轻视。请先记住以下的警告,在阅读正文时就会多心: u       本章开头讲过,如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。以类String的两个对象a,b为例,假设a.m_data的内容为“hello”,b.m_data的内容为“world”。 现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成三个错误:一是b.m_data原有的内存没被释放,造成内存泄露;二是b.m_data和a.m_data指向同一块内存,a或b任何一方变动都会影响另一方;三是在对象被析构时,m_data被释放了两次。   u       拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。以下程序中,第三个语句和第四个语句很相似,你分得清楚哪个调用了拷贝构造函数,哪个调用了赋值函数吗? String  a(“hello”); String  b(“world”); String  c = a;  // 调用了拷贝构造函数,最好写成 c(a); c = b; // 调用了赋值函数 本例中第三个语句的风格较差,宜改写成String c(a) 以区别于第四个语句。 9.6 示例:类String的拷贝构造函数与赋值函数     // 拷贝构造函数     String::String(const String &other)     {   // 允许操作other的私有成员m_data     int length = strlen(other.m_d......

阅读全文(2031) | 评论:0

 C++/C编程规范(9.1-9.4)----林  锐(2007-04-10 21:21:00)

摘要:第9章 类的构造函数、析构函数与赋值函数 构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险。        每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A产生四个缺省的函数,如     A(void);                    // 缺省的无参数构造函数     A(const A &a);              // 缺省的拷贝构造函数     ~A(void);                   // 缺省的析构函数     A & operate =(const A &a);  // 缺省的赋值函数   这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写? 原因如下: (1)如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会,C++发明人Stroustrup的好心好意白费了。 (2)“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。        对于那些没有吃够苦头的C++程序员,如果他说编写构造函数、析构函数......

阅读全文(2289) | 评论:0

 C++/C编程规范(8.4-8.6)----林  锐(2007-04-10 21:19:00)

摘要:8.4 运算符重载 8.4.1 概念     在C++语言中,可以用关键字operator加上运算符来表示函数,叫做运算符重载。例如两个复数相加函数:     Complex Add(const Complex &a, const Complex &b); 可以用运算符重载来表示:     Complex operator +(const Complex &a, const Complex &b);     运算符与普通函数在调用时的不同之处是:对于普通函数,参数出现在圆括号内;而对于运算符,参数出现在其左、右侧。例如     Complex a, b, c;     …     c = Add(a, b);  // 用普通函数     c = a + b;      // 用运算符 +     如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符。     如果运算符被重载为类的成员函数,那么一元运算符没有参数,二元运算符只有一个右侧参数,因为对象自己成了左侧参数。     从语法上讲,运算符既可以定义为全局函数,也可以定义为成员函数。文献[Murray , p44-p47]对此问题作了较多的阐述,并总结了表8-4-1的规则。   运算符 规则 所有的一元运算符 建议重载为成员函数 = () [] -> 只能重载为成员函数 += -= /= *= &= |= ~= %= >>= <<= 建议重载为成员函数 所有其它运算符 建议重载为全局函数 表8-4-1 运算符的重载规则   由于C++语言支持函数重载,才能将运算符当成函数来......

阅读全文(2438) | 评论:0

 C++/C编程规范(8.1-8.3)----林  锐(2007-04-10 21:17:00)

摘要:第8章 C++函数的高级特性 对比于C语言的函数,C++增加了重载(overloaded)、内联(inline)、const和virtual四种新机制。其中重载和内联机制既可用于全局函数也可用于类的成员函数,const与virtual机制仅用于类的成员函数。        重载和内联肯定有其好处才会被C++语言采纳,但是不可以当成免费的午餐而滥用。本章将探究重载和内联的优点与局限性,说明什么情况下应该采用、不该采用以及要警惕错用。 8.1 函数重载的概念 8.1.1 重载的起源     自然语言中,一个词可以有许多不同的含义,即该词被重载了。人们可以通过上下文来判断该词到底是哪种含义。“词的重载”可以使语言更加简练。例如“吃饭”的含义十分广泛,人们没有必要每次非得说清楚具体吃什么不可。别迂腐得象孔已己,说茴香豆的茴字有四种写法。     在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,即函数重载。这样便于记忆,提高了函数的易用性,这是C++语言采用重载机制的一个理由。例如示例8-1-1中的函数EatBeef,EatFish,EatChicken可以用同一个函数名Eat表示,用不同类型的参数加以区别。     void EatBeef(…);       // 可以改为     void Eat(Beef …); void EatFish(…);       // 可以改为     void Eat(Fish …); void EatChicken(…);    // 可以改为     void Eat(Chicken …);   示例8-1-1 重载函数Eat       C++语言采用重载机制的另一个理由是:类的构造函数需要重载机制。因为C++规定构造......

阅读全文(2609) | 评论:0

C++/C编程规范(7.6-7.12)----林  锐(2007-04-10 21:13:00)

摘要:7.6 动态内存会被自动释放吗?        函数体内的局部变量在函数结束时自动消亡。很多人误以为示例7-6是正确的。理由是p是局部的指针变量,它消亡的时候会让它所指的动态内存一起完蛋。这是错觉!       void Func(void) {     char *p = (char *) malloc(100); // 动态内存会自动释放吗? } 示例7-6 试图让动态内存自动释放       我们发现指针有一些“似是而非”的特征: (1)指针消亡了,并不表示它所指的内存会被自动释放。 (2)内存被释放了,并不表示指针会消亡或者成了NULL指针。 这表明释放内存并不是一件可以草率对待的事。也许有人不服气,一定要找出可以草率行事的理由:     如果程序终止了运行,一切指针都会消亡,动态内存会被操作系统回收。既然如此,在程序临终前,就可以不必释放内存、不必将指针设置为NULL了。终于可以偷懒而不会发生错误了吧?     想得美。如果别人把那段程序取出来用到其它地方怎么办? 7.7 杜绝“野指针” “野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种: (1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如     char *p = NULL;     char *str = (char *) malloc(100);   (2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。参见7.5节。   (3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:  ......

阅读全文(2210) | 评论:0