《C/C++高质量编程》笔记

来源:互联网 发布:windows 8.1怎么激活 编辑:程序博客网 时间:2024/05/29 07:36

引言:《C/C++高质量编程》这本书,很好的给出了一些编程规范,是每一个C/C++程序员都应该认真读取的一本书。几次阅读,几次忘记,今天把一些关键点记录下来,便于以后继续学习和参考。建议此书和谷歌C++编码规范一起使用,相得益彰。


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

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

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

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

【规则2-5-1代码行最大长度宜控制在7080个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印。

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

例如:

char  *name;

    int   *x, y;  // 此处y不会被误解为指针

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

例如:

float  value;

float  oldValue;

float  newValue;

【规则3-1-7】全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。

例如:

DrawBox();              // 全局函数

              box->Draw();        // 类的成员函数


【建议3-1-1尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号。

【规则3-2-1】类名和函数名用大写字母开头的单词组合而成。

例如:

  class Node;              // 类名

  class LeafNode;           // 类名

  void  Draw(void);     // 函数名

  void  SetValue(int value);  // 函数名

【规则3-2-2】变量和参数用小写字母开头的单词组合而成。

例如:

    BOOL flag;

    int  drawMode;

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

【规则3-2-5如果不得已需要全局变量,则使全局变量加前缀g_(表示global)。

const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。

    不能在类声明中初始化const数据成员。

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

    class A

    {…

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

        const int SIZE ;

    };

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

 【建议6-2-2如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。

【规则6-3-2】在函数体的“出口处”,对return语句的正确性和效率进行检查。

     如果函数有返回值,那么函数的“出口处”是return语句。我们不要轻视return语句。如果return语句写得不好,函数要么出错,要么效率低下。

注意事项如下:

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

    char * Func(void)

    {

        char str[] = “hello world”;    // str的内存位于栈上

        …

        return str;     // 将导致错误

    }

(2)要搞清楚返回的究竟是“值”、“指针”还是“引用”。

(3)如果函数返回值是一个对象,要考虑return语句的效率。例如   

              return String(s1 + s2);

这是临时对象的语法,表示“创建一个临时对象并返回它”。不要以为它与“先创建一个局部对象temp并返回它的结果”是等价的,如

String temp(s1 + s2);

return temp;

实质不然,上述代码将发生三件事。首先,temp对象被创建,同时完成初始化;然后拷贝构造函数把temp拷贝到保存返回值的外部存储单元中;最后,temp在函数结束时被销毁(调用析构函数)。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了效率。

类似地,我们不要将 

return int(x + y); // 创建一个临时变量并返回它

写成

int temp = x + y;

return temp;

由于内部数据类型如int,float,double的变量不存在构造函数与析构函数,虽然该“临时变量的语法”不会提高多少效率,但是程序更加简洁易读。


断言assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。如果程序在assert处终止了,并不是说含有该assert的函数有错误,而是调用者出了差错,assert可以帮助我们找到发生错误的原因。

内存分配方式

内存分配方式有三种:

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

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

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


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

 【规则7-2-5freedelete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

内存耗尽怎么办?

  如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。

(1)判断指针是否为NULL,如果是则马上用return语句终止本函数。

2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。

3)为newmalloc设置异常处理函数。

越是怕指针,就越要使用指针。不会正确使用指针,肯定算不上是合格的程序员。

关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。

使用const提高函数的健壮性

const常量,const更大的魅力是它可以修饰函数的参数、返回值,甚至函数的定义体。

用const修饰函数的参数,const只能修饰输入参数。

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

如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const修饰。

例如不要将函数void Func1(int x) 写成void Func1(const int x)。同理不要将函数void Func2(A a) 写成void Func2(const A a)。其中A为用户自定义的数据类型。

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

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

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

例如函数

        const char * GetString(void);

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

        char *str = GetString();

正确的用法是

        const char *str = GetString();

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

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

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

    如果返回值不是内部数据类型,将函数A GetA(void) 改写为const A & GetA(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。

函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
0 0