转载自: http://blog.sina.com.cn/s/articlelist_1154237180_1_1.html
代码风格与版式
代码风格的重要性怎么强调都不过分。一段稍长一点的无格式代码基本上就是不可读的。
先来看一下这方面的整体原则:
空行的使用
空行起着分隔程序段落的作用。空行得体(不过多也不过少)将使程序的布局更加清晰。空行不会浪费内存,虽然打印含有空行的程序是会多消耗一些纸张,但是值得。所以不要舍不得用空行。
在每个类声明之后、每个函数定义结束之后都要加2行空行。 在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。 语句与代码行
一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。 "if"、"for"、"while"、"do"、"try"、"catch" 等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加 "{ }" 。这样可以防止书写 和修改代码时出现失误。 缩进和对齐
程序的分界符 "{" 和 "}" 应独占一行并且位于同一列,同时与引用它们的语句左对齐。 "{ }" 之内的代码块在 "{" 右边一个制表符(4个半角空格符)处左对齐。如果出现嵌套的 "{ }",则使用缩进对齐。 如果一条语句会对其后的多条语句产生影响的话,应该只对该语句做半缩进(2个半角空格符),以突出该语句。 例如:
void Function(int x) { CSessionLock iLock(mxLock); for (初始化; 终止条件; 更新) { // ... } try { // ... } catch (const exception& err) { // ... } catch (...) { // ... } // ... }
最大长度
代码行最大长度宜控制在70至80个字符以内。代码行不要过长,否则眼睛看不过来,也不便于打印(2009年更新:随着GUI开发环境和高分宽屏的普及,此规则可以视情况适当放宽)。
长行拆分
长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
例如:
if ((very_longer_variable1 >= very_longer_variable2) && (very_longer_variable3 <= very_longer_variable4) && (very_longer_variable5 <= very_longer_variable6) ) { DoSomething(); }
空格的使用
关键字之后要留空格。象 "const"、"virtual"、"inline"、"case" 等关键字之后至少要留一个空格,否则无法辨析关键字。象 "if"、"for"、"while"、"catch" 等关键字之后应留一个空格再跟左括号 "(",以突出关键字。 函数名之后不要留空格,紧跟左括号 "(" ,以与关键字区别。 "(" 向后紧跟。而 ")"、","、";" 向前紧跟,紧跟处不留空格。 "," 之后要留空格,如 Function(x, y, z)。如果 ";" 不是一行的结束符号,其后要留空格,如 for (initialization; condition; update)。 赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如"="、"+=" ">="、"<="、"+"、"*"、"%"、"&&"、"||"、"<<", "^" 等二元操作符的前后应当加空格。 一元操作符如 "!"、"~"、"++"、"--"、"&"(地址运算符)等前后不加空格。 象"[]"、"."、"->"这类操作符前后不加空格。 对于表达式比较长的for、do、while、switch语句和if语句,为了紧凑起见可以适当地去掉一些空格,如for (i=0; i<10; i++)和if ((a<=b) && (c<=d)) 例如:
void Func1(int x, int y, int z); // 良好的风格 void Func1 (int x,int y,int z); // 不良的风格 // =========================================================== if (year >= 2000) // 良好的风格 if(year>=2000) // 不良的风格 if ((a>=b) && (c<=d)) // 良好的风格 if(a>=b&&c<=d) // 不良的风格 // =========================================================== for (i=0; i<10; i++) // 良好的风格 for(i=0;i<10;i++) // 不良的风格 for (i = 0; I < 10; i ++) // 过多的空格 // =========================================================== x = a < b ? a : b; // 良好的风格 x=a<b?a:b; // 不好的风格 // =========================================================== int* x = &y; // 良好的风格 int * x = & y; // 不良的风格 // =========================================================== array[5] = 0; // 不要写成 array [ 5 ] = 0; a.Function(); // 不要写成 a . Function(); b->Function(); // 不要写成 b -> Function();
修饰符的位置
为便于理解,应当将修饰符 "*" 和 "&" 紧靠数据类型。
例如:
char* name; int* x; int y; // 为避免y被误解为指针,这里必须分行写。 int* Function(void* p);
参见:变量、常量的风格与版式 -> 指针或引用类型的定义和声明
注释
注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。 边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。 注释应当准确、易懂,防止注释有二义性。错误的注释不但无益反而有害。 当代码比较长,特别是有多重嵌套时,应当在一些段落的结束处加注释,便于阅读。 与常量的比较
在与宏、常量进行 "==", "!=", ">=", "<=" 等比较运算时,应当将常量写在运算符左边,而变量写在运算符右边。这样可以避免因为偶然写错把比较运算变成了赋值运算的问题。
例如:
if (NULL == p) // 如果把 "==" 错打成 "=",编译器就会报错 { // ... }
为增强代码的可读性而定义的宏
以下预定义宏对程序的编译没有任何影响,只为了增加代码的可读性:
宏
说明
NOTE
需要注意的代码
TODO
尚未实现的接口、类、算法等
UNDONE
已取消的接口、类、算法等
FOR_DBG
标记为调试方便而临时增加的代码
OK
仅用于调试的标记
例如:
TODO class CMyClass; TODO void Function(void); FOR_DBG cout << "...";
类/结构
类是C++中最重要也是使用频率最高的新特性之一。类的版式好坏将极大地影响代码品质。
注释头与类声明
与文件一样,每个类应当有一个注释头用来说明该类的各个方面。
类声明换行紧跟在注释头后面,"class" 关键字由行首开始书写,后跟类名称。界定符 "{" 和 "};" 应独占一行,并与 "class" 关键字左 对齐。
对于功能明显的简单类(接口小于10个),也可以使用简单的单行注释头:
//! <简要说明该类所完成的功能> class CXXX { // ... };
继承
基类直接跟在类名称之后,不换行,访问说明符(public, private, 或protected)不可省略。如:
class CXXX : public CAAA, private CBBB { // ... };
以行为为中心
没人喜欢上来就看到一大堆私有数据,大多数用户关心的是类的接口与其提供的服务,而不是其实现。
所以应当将公有的定义和成员放在类声明的最前面,保护的放在中间,而私有的摆在最后。
访问说明符
访问说明符(public, private, 或protected)应该独占一行,并与类声明中的‘class’关键字左对齐。
类成员的声明版式
对于比较复杂(成员多于20个)的类,其成员必须分类声明。
每类成员的声明由访问说明符(public, private, 或protected)+ 全行注释开始。注释不满全行(80个半角字符)的,由 "/" 字符补齐,最后一个 "/" 字符与注释间要留一个半角空格符。
如果一类声明中有很多组功能不同的成员,还应该用分组注释 将其分组。分组注释也要与 "class" 关键字对齐。
每个成员的声明都应该由 "class" 关键字开始向右缩进一个制表符(4个半角空格符),成员之间左对齐。
例如:
class CXXX { public: /// 类型定义 typedef vector<string> VSTR; public: / 构造、析构、初始化 CXXX(); ~CXXX(); public: /// 公用方法 // [[ 功能组1 void Function1(void) const; long Function2(IN int n); // ]] 功能组1 // [[ 功能组2 void Function3(void) const; bool Function4(OUT int& n); // ]] 功能组2 private: /// 属性 // ... private: / 禁用的方法 // 禁止复制 CXXX(IN const CXXX& rhs); CXXX& operator=(IN const CXXX& rhs); };
正确地使用const和mutable
把不改变对象逻辑状态的成员都标记为const成员不仅有利于用户对成员的理解,更可以最大化对象使用方式的灵活性及合理性(比如通过const指针或const引用的形式传递一个对象)。
如果某个属性的改变并不影响该对象逻辑上的状态,而且这个属性需要在const方法中被改变,则该属性应该标记为 "mutable"。
例如:
class CString { public: //! 查找一个子串,find()不会改变字符串的值所以为const函数 int find(IN const CString& str) const; // ... private: // 最后一次错误值,改动这个值不会影响对象的逻辑状态, // 像find()这样的const函数也可能修改这个值 mutable int m_nLastError; // ... };
也就是说,应当尽量使所有逻辑上只读的操作成为const方法,然后使用mutable解决那些存在逻辑冲突的属性。
嵌套的类声明
在相应的逻辑关系确实存在时,类声明可以嵌套。嵌套类可以使用简单的单行注释头:
// ... class CXXX { //! 嵌套类说明 calss CYYY { // ... }; };
初始化列表
应当尽可能通过构造函数的初始化列表来初始化成员和基类。初始化列表至少独占一行,并且与构造函数的定义保持一个制表符(4个半角空格)的缩进。
例如:
CXXX::CXXXX(IN int nA, IN bool bB) : m_nA(nA), m_bB(bB) { // ... };
初始化列表的书写顺序应当与对象的构造顺序一致,即:先按照声明顺序写基类初始化,再按照声明顺序写成员初始化。
如果一个成员 "a" 需要使用另一个成员 "b" 来初始化,则 "b" 必须在 "a" 之前声明,否则将会产生运行时错误(有些编译器会给出警告)。
例如:
// ... class CXXXX : public CAA, public CBB { // ... CYY m_iA; CZZ m_iB; // m_iA必须在m_iB之前声明 }; CXXX::CXXXX(IN int nA, IN int nB, IN bool bC) : CAA(nA), CBB(nB), m_iA(bC), m_iB(m_iA) // 先基类,后成员, // 分别按照声明顺序书写 { // ... };
内联函数的实现体
定义在类声明之中的函数将自动成为内联函数。但为了使类的声明更为清晰明了,应尽量避免直接在声明中直接定义成员函数的编程风格。鼓励使用 "inline" 关键字将内联函数放在类声明的外部定义。
关于类声明的例子,请参见:类/结构的风格与版式例子
关于类声明的模板,请参见:类声明模板