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

高质量C++编程 Note

定义1:能长期稳定地编写出高质量程序的程序员称为编程老手。

定义2:能长期稳定地编写出高难度、高质量程序的程序员称为编程高手。

版权和版本的声明位于头文件和定义文件的开头 ,主要内容有:
(1)版权信息。
(2)文件名称,标识符,摘要。
(3)当前版本号,作者/修改者,完成日期。
(4)版本历史信息。
 
头文件由三部分内容组成:
(1)头文件开头处的版权和版本声明。
(2)预处理块。
(3)函数和类结构声明等。

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

 如果一个软件的头文件数目比较多(如超过十个) ,通常应将头文件和定义文件分别保存于不同的目录,以便于维护。

在每个类声明之后、每个函数定义结束之后都要加空行。

在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。

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

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

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

函数名之后不要留空格,紧跟左括号‘ (’ ,以与关键字区别。

‘ (’向后紧跟, ‘) ’ 、 ‘, ’ 、 ‘;’向前紧跟,紧跟处不留空格。

 ‘, ’之后要留空格,如 Function(x, y, z)。如果‘;’不是一行的结束
符号,其后要留空格,如 for (initialization; condition; update)。

赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=” 、 “+=”  “>=” 、 “<=” 、 “+” 、 “*” 、 “%” 、 “&&” 、 “||” 、 “<<”,“^”等二元操作符的前后应当加空格。

一元操作符如“!” 、 “~” 、 “++” 、 “--” 、 “&” (地址运算符)等前后不加空格。

象“ [] ” 、 “.” 、 “->”这类操作符前后不加空格。

对于表达式比较长的 for 语句和 if 语句,为了紧凑起见可以适当地去
掉一些空格,如 for (i=0; i<10; i++)和 if ((a<=b) && (c<=d)) 

建议采用“以行为为中心”的书写方式,即首先考虑类应该提供什么样的函
数。这是很多人的经验——“这样做不仅让自己在设计类时思路清晰,而且方便别人阅读。因为用户最关心的是接口,谁愿意先看到一堆私有数据成员! ”

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

 const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应) 。   有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。
 
有时我们希望某些常量只在类中有效。由于#define 定义的宏常量是全局的,不能达到目的,于是想当然地觉得应该用 const 修饰数据成员来实现。const 数据成员的确是存在的,但其含义却不是我们所期望的。const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其 const 数据成员的值可以不同。   不能在类声明中初始化 const 数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道 SIZE 的值是什么。 const 数据成员的初始化只能在类构造函数的初始化表中进行。
 
怎样才能建立在整个类中都恒定的常量呢?别指望 const 数据成员了,应该用类中的枚举常量来实现。枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数。

参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字如果函数没有参数,则用 void 填充。 一般地,应将目的参数放在前面,源参数放在后面。

如果函数参数是指针,且仅作输入用,则应在类型前加 const,以防止该
指针在函数体内被意外修改。如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。

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

很多程序错误是由非法参数引起的,我们应该充分理解并正确使用“断言” (assert)来防止此类错误。

 
return 语句不可返回指向“栈内存”的“指针”或者“引用” ,因为该内存在函数体结束时被自动销毁。要搞清楚返回的究竟是“值” 、 “指针”还是“引用”。如果函数返回值是一个对象,要考虑 return 语句的效率。

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

函数的功能要单一,不要设计多用途的函数。 函数体的规模要小,尽量控制在 50 行代码之内。尽量避免函数带有“记忆”功能。相同的输入应当产生相同的输出。  用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。

断言 assert 是仅在 Debug 版本起作用的宏,它用于检查“不应该”发生的情况。

在函数的入口处,使用断言检查参数的有效性(合法性) 。 在编写函数时, 要进行反复的考查, 并且自问: “我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。

)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化) 。)不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是 NULL) 。一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象) 。引用的主要功能是传
递函数的参数和返回值

640K ought to be enough for everybody!     — Bill Gates 1981

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

常见的内存错误如下:  内存分配未成功,却使用了它;内存分配虽然成功,但是尚未初始化就引用它;内存分配成功并且已经初始化,但操作越过了内存的边界;忘记了释放内存,造成内存泄露;释放了内存却继续使用它。
用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为 NULL 的内存。 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。 避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1”操作。动态内存的申请与释放必须配对,防止内存泄漏。用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产生“野指针”.

常量字符串的内容是不可以被修改的。不能对数组名进行直接复制与比较。

(未完待续)

你可能感兴趣的:(编程,存储,编译器,initialization,null,delete)