头文件
- 头文件避免多重包含。
#ifndef PROJ_PATH_FILE_H_
- 能用前置声明就不要使用
#include
。 - 函数的参数顺序:输入参数,输出参数。
作用域
- 不要使用
using
。 - 命名空间不需缩进。
-
.c
中可以使用using
,.h
中必须在函数、方法、类内部使用。 - 非成员函数、静态成员函数、全局函数尽量放到命名空间中。
- 局部变量声明的时候赋值。在离第一次使用尽可能近的地方声明。
- 可在
while
循环中声明变量限制其作用域。 - 注意有时需在循环外声明变量,比如类的对象,在循环内声明需要循环调用构造函数和析构函数。
类
- 构造函数只进行一些非重要内容的初始化,因为如果操作失败会造成对象初始化失败,进入不确定状态。
可能的话,使用Init()
方法集中初始化有意义的数据。 - 如果类有很多个变量,最好设置默认构造函数,否则编译器可能会生成一个很糟糕的构造函数。
- 对单个参数的构造函数使用
explicit
关键字,防止默认强制类型转换。 - 一般如果没有拷贝构造函数,编译器会自动声明拷贝构造函数,而且是
public
的。
如果是单例模式需要禁止拷贝,则可以在private
中声明但不定义拷贝构造函数,这样当试图使用它们时编译器将报错。用宏来描述:
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \\
TypeName(const TypeName&); \\
void operator=(const TypeName&)
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
- 当只有数据时使用
struct
,其他一概用class
。struct
只可以有构造函数,析构函数,Initialize()
,Reset()
,Validate()
。 - 组合 > 实现继承 > 接口继承 > 私有继承,子类重载的虚函数也要声明
virtual
关键字。 - 使用组合(
composition
)通常比继承更合理。如果需要类继承,使用public
继承。 - 尽量做到
is-a
继承,在has-a
继承时尽量使用composition
。 - 数据成员在任何情况下应该都只是私有的。
- 当定义一个虚函数时,明确声明其为
virtual
,使得代码阅读者在检查基类的时候易于判断。
如果类有虚函数,则析构函数最好也声明为virtual
。 - 真正需要用到多重实现继承的情况少之又少。
只在以下情况我们才允许多重继承:最多只有一个基类是非抽象类,其它基类都是以Interface
为后缀的纯接口类。接口类类名以Interface
为后缀。 - 为降低复杂性,尽量不重载操作符。
- 在类中使用特定的声明顺序:
public
:在private
:之前,成员函数在数据成员(变量)前。顺序: typedefs和枚举
-
常量
->构造函数
析构函数
成员函数, 含静态成员函数
数据成员, 含静态数据成员
- 将所有数据成员声明为 private, 并根据需要提供相应的存取函数。存取函数一般内联在头文件中。
- 倾向编写简短, 凝练的函数。如果函数超过 40 行,可以思索一下能不能在不影响程序结构的前提下对其进行分割。
其他 C++ 特性
- 所有按引用传递参数必须加上
const
。函数中非输出参数最好都加上const
。 - 在任何可能的地方加上
const
。 - 如果你想重载一个函数,考虑让函数名包含参数信息,例如,使用
AppendString()
,AppendInt()
而不是Append()
。 - 尽可能少的将函数的参数设置默认值。
- 减少使用变长数组。
- 允许合理的友元类和友元函数。
- 类型转换时需要明确指明
static_cast
,const_cast
,reinterpret_cast
,dynamic_cast
。 - 只在打印日志的时候使用流。流最大的优势是在输出时不需要关心打印对象的类型。
这是一个亮点,同时也是一个不足:你很容易用错类型,而编译器不会报警。
使用流时容易造成的这类错误:
cout << this; // Prints the address
cout << *this; // Prints the contents
- 对于迭代器和其他模板对象使用前置的自增自减。
- 使用宏时要非常谨慎,尽量以内联函数、枚举和常量代替之。
不要在.h
文件中定义宏。
在马上要使用时才进行#define
。使用后要立即#undef
。 - 整数用 0,实数用0.0,指针用
NULL
,字符(串)用'\\0'
。 - 尽可能用
sizeof(varname)
代替sizeof(type)
,代码中变量类型改变后可以进行自动更新。
命名约定
- 尽可能给出描述性的名称。不要节约行空间,让别人很快理解你的代码更重要。
int num_errors; // Good.
int num_completed_connections; // Good
- 类型和变量名一般为名词:如
FileOpener
,num_errors
。
函数名通常是指令性的,如OpenFile()
,set_num_errors()
。
取值函数是个特例,函数名和它要取值的变量同名。 - 除非该缩写在其它地方都非常普遍,否则不要使用。永远不要用省略字母的缩写。
int error_count; // Good
int error_cnt; // Bad
- 文件名要全部小写,可以包含下划线(_)或连字符 (-)。可接受的文件命名:
my_useful_class.cc
、my-useful-class.cc
、yusefulclass.cc
,通常应尽量让文件名更加明确。 - 类型名称的每个单词首字母均大写,不包含下划线,大驼峰法。
所有类型命名:类、结构体、类型定义(typedef)、枚举
->均使用相同约定。 - 变量名一律小写,单词之间用下划线连接。类的成员变量以下划线结尾。
结构体的数据成员可以和普通变量一样,不用像类那样接下划线。 - 常量名称前加
k
,如:const int kDaysInAWeek = 7
; - 常规函数的函数名的每个单词首字母大写,没有下划线。
- 取值和设值函数要与存取的变量名匹配。
int num_entries_;
-
int num_entries() const { return num_entries_; }
。 - 枚举内数据的命名应当和常量或宏一致:
kEnumName
或是ENUM_NAME
。 - 宏命名:全大小,下划线。如
#define PI_ROUNDED 3.0
。
注释
- 注释要言简意赅, 不要拖沓冗余, 复杂的东西简单化和简单的东西复杂化都是要被鄙视的。
- 注释固然很重要, 但最好的代码本身应该是自文档化。有意义的类型名和变量名,要远胜过要用注释解释的含糊不清的名字。
- 在每一个文件开头加入版权公告,然后是文件内容描述。
每个文件都应该包含以下项, 依次是: - 版权声明 (比如,
Copyright 2008 Google Inc.
) - 许可证. 为项目选择合适的许可证版本 (比如,
Apache 2.0
,BSD
,LGPL
,GPL
) - 作者: 标识文件的原始作者.
- 通常,
.h
文件要对所声明的类的功能和用法作简单说明,.cc
文件通常包含了更多的实现细节或算法技巧讨论。如果你感觉这些实现细节或算法技巧讨论对于理解.h
文件有帮助,可以该注释挪到.h
,并在.cc
中指出文档在.h
。
不要简单的在.h
和.cc
间复制注释,这种偏离了注释的实际意义。 - 每个类的定义都要附带一份注释,描述类的功能和用法。
- 对于代码中巧妙的,晦涩的,有趣的,重要的地方加以注释。
- 比较隐晦的地方要在行尾空两格进行注释。
- 向函数传入
NULL
, 布尔值或整数时, 要注释说明含义, 或使用常量让代码望文知意:
bool success = CalculateSomething(interesting_value,
10, // Default base value.
false, // Not the first time we're calling this.
NULL); // No callback.
或使用常量或描述性变量:
const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool success = CalculateSomething(interesting_value,
kDefaultBaseValue,
kFirstTimeCalling,
null_callback);
- 连续多行输入需要对齐:
DoSomething(); // Comment here so the comments line up.
DoSomethingElseThatIsLonger(); // Comment here so there are two spaces between
// the code and the comment.
{ // One space before comment when opening a new scope is allowed,
// thus the comment lines up with the following comments and code.
DoSomethingElse(); // Two spaces before line comments normally.
}
- 注意永远不要用自然语言翻译代码作为注释。
- TODO注释
// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
如果加 TODO 是为了在 “将来某一天做某事”, 可以附上一个非常明确的时间 “Fix by November 2005”), 或者一个明确的事项 (“Remove this code when all clients can handle XML responses.”)。
格式
- 尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码。
- 返回类型和函数名在同一行, 参数也尽量放在同一行。
函数看上去像这样:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
DoSomething();
...
}
如果同一行文本太多, 放不下所有参数:
ReturnType ClassName::ReallyLongFunctionName(Type par_name1,
Type par_name2,
Type par_name3) {
DoSomething();
...
}
甚至连第一个参数都放不下:
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4 space indent
Type par_name2,
Type par_name3) {
DoSomething(); // 2 space indent
...
}
- 注意以下几点:
- 返回值总是和函数名在同一行;
- 左圆括号总是和函数名在同一行;
- 函数名和左圆括号间没有空格;
- 圆括号与参数间没有空格;
- 左大括号总在最后一个参数同一行的末尾处;
- 右大括号总是单独位于函数最后一行;
- 右圆括号和左大括号间总是有一个空格;
- 函数声明和实现处的所有形参名称必须保持一致;
- 所有形参应尽可能对齐;
- 缺省缩进为 2 个空格;
- 换行后的参数保持 4 个空格的缩进;
- 如果函数声明成
const
, 关键字const
应与最后一个参数位于同一行:
// Everything in this function signature fits on a single line
ReturnType FunctionName(Type par) const {
...
}
// This function signature requires multiple lines, but
// the const keyword is on the line with the last parameter.
ReturnType ReallyLongFunctionName(Type par1,
Type par2) const {
...
}
- 如果有些参数没有用到, 在函数定义处将参数名注释起来。
// Comment out unused named parameters in definitions.
void Circle::Rotate(double /*radians*/) {}
- 函数调用遵循如下形式:
bool retval = DoSomething(argument1, argument2, argument3);
如果同一行放不下, 可断为多行, 后面每一行都和第一个实参对齐, 左圆括号后和右圆括号前不要留空格:
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
如果函数参数很多, 出于可读性的考虑可以在每行只放一个参数:
bool retval = DoSomething(argument1,
argument2,
argument3,
argument4);
如果函数名非常长, 以至于超过 行最大长度, 可以将所有参数独立成行:
if (...) {
...
...
if (...) {
DoSomethingThatRequiresALongFunctionName(
very_long_argument1, // 4 space indent
argument2,
argument3,
argument4);
}
- 条件语句,如if和左圆括号间都有个空格。右圆括号和左大括号之间也要有个空格。
- switch语句:
switch (var) {
case 0: { // 2 space indent
... // 4 space indent
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}
- 空循环体应使用
{}
或continue
, 而不是一个简单的分号。
while (condition) {
// Repeat test until it returns false.
}
for (int i = 0; i < kSomeNumber; ++i) {} // Good - empty body.
while (condition) continue; // Good - continue indicates no logic.
Warning
while (condition); // Bad - looks like part of do/while loop.
- 指针和引用:句点或箭头前后不要有空格. 指针/地址操作符 (*, &) 之后不能有空格。
- 逻辑与 (&&) 操作符总位于行尾:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another & last_one) {
...
}
-
return
表达式中不要用圆括号包围。 - 预处理指令不要缩进,从行首开始。
即使预处理指令位于缩进代码块中, 指令也应从行首开始。
// Good - directives at beginning of line
if (lopsided_score) {
#if DISASTER_PENDING // Correct -- Starts at beginning of line
DropEverything();
#endif
BackToNormal();
}
- 访问控制块的声明依次序是
public:
,protected:
,private:
, 每次缩进 1 个空格。 - 除第一个关键词(一般是
public
)外, 其他关键词前要空一行。这些关键词后不要保留空行。 - 名字空间内容不缩进。
- 垂直留白越少越好。这不仅仅是规则而是原则问题了: 不在万不得已, 不要使用空行,尤其是:
- 两个函数定义之间的空行不要超过 2 行, 函数体首尾不要留空行, 函数体中也不要随意添加空行。
- 基本原则是: 同一屏可以显示的代码越多,越容易理解程序的控制流。
- 当然,过于密集的代码块和过于疏松的代码块同样难看, 取决于你的判断. 但通常是垂直留白越少越好。
结束语
- 运用常识和判断力,并保持一致。
- 风格指南的重点在于提供一个通用的编程规范, 这样大家可以把精力集中在实现内容而不是表现形式上。我们展示了全局的风格规范, 但局部风格也很重要。
- 如果你在一个文件中新加的代码和原有代码风格相去甚远, 这就破坏了文件本身的整体美观, 也影响阅读, 所以要尽量避免。