最近在整理c++代码,顺便参考谷歌的c++编译规范,总结了几点是我们日常碰到比较多的。
一 宏使用
C语言写多了容易喜欢使用宏来定义常量或者代码,而c++中就不建议这样做了。
比如使用define定义Inline代码的话,c++中建议直接使用inline函数的形式
使用define 定义常量,c++则建议使用const来修饰常变量。
宏还有的其他用法比如条件编译什么的,在C++中使用宏之前都想一下是否可以使用其他方法进行替代。
如果要使用宏的话,遵循下面几个方法:
不要在.h文件中使用宏
在使用的代码前使用#define,用完后使用#undef
不要使用#undef替换存在的宏,使用其他的名字
不要使用##去生成function class variable名字
Try not to use macros that expand to unbalanced C++ constructs, or at least document that behavior well.
比如在art代码中使用了const来修饰常变量,而不是定义宏
constexpr u1 kVisibilityBuild = 0x00;
constexpr u1 kVisibilityRuntime = 0x01;
constexpr u1 kVisibilitySystem = 0x02;
二 类型转换
C语言转换比较简单,比如int y = (int)x;这样直接转换,而c++针对不同类型的转换使用不同的方法:
const_cast:const类型转换,一般用于将const/volatile属性去掉或者添加
reinterpret_cast: 任意类型的指针转换
int *r = reinterpret_cast<int*>(&c); // forced conversion
static_catst: 编译过程中进行转换校验,主要转换基本类型或者继承关系类型之间的转换,不太用于指针类型指针类型之间的转换。
int *q = static_cast<int*>(&c); // compile-time error
dynamic_cast:用于进程对象指针之间或引用之间的转换
// Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(base);
三 整型
如果对于变量的size有要求的话建议使用<stdint.h>里面的int16_t, uint32_t,int64_t。当我们使用一般不是很大的数据造成溢出时,使用int比较方便。但是unsigned int不建议使用,当你要使用超过32为的数据是建议使用int64_t或者uint64_t。因为unsigned int往往会造成bug,比如如下:
for (unsigned int i = foo.Length()-1; i >= 0; --i) ...
确保一个数为非负的话不要使用unsigned类型,使用assert进行判断。
四 命名
从一个项目的命名就可以对一个项目的质量做判断,好的软件项目一定会有一个好的命名规则。好的名字包括 函数名 变量名 文件名 类名,能够做到清晰的表达,没有歧义。类型和变量使用名词,函数使用“command” verbs。
通过阅读android的代码可以看出,cpp的代码大部分使用驼峰命名法。
int num_errors; // Good.
int num_completed_connections; // Good
推荐使用上面的例子命名方法,变量没有歧义,表达很清楚,做到一目了然。下面的例子则不推荐,表达歧义,不容易被其他人理解。
int n; // Bad - meaningless.
int nerr; // Bad - ambiguous abbreviation.
int n_comp_conns; // Bad - ambiguous abbreviation.
// Good
// These show proper names with no abbreviations.
int num_dns_connections; // Most people know what "DNS" stands for.
int price_count_reader; // OK, price count. Makes sense.
// Bad!
// Abbreviations can be confusing or ambiguous outside a small group.
int wgc_connections; // Only your group knows what this stands for.
int pc_reader; // Lots of things can be abbreviated "pc".
int error_count; // Good.
int error_cnt; // Bad.
4.1 文件名
文件名使用小写 _ 和-,如果没有特殊的要求的话,优先使用_。
my_useful_class.cc
my-useful-class.cc
myusefulclass.cc
myusefulclass_test.cc // _unittest and _regtest are deprecated.
如果头文件包含很多的inline函数的话,可以单独拿出放到-inl.h文件中
url_table.h // The class declaration.
url_table.cc // The class definition.
url_table-inl.h // Inline functions that include lots of code.
4.2 类型名
类型名义大写为开头,每个新的词第一个字母也要大写,不要使用_,比如MyExcitingClass, MyExcitingEnum。
// classes and structs
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// enums
enum UrlTableErrors { ...
4.3 变量名
变量名统一使用小写,使用””连接,类中的变量在末尾添加””。
string table_name; // OK - uses underscore.
string tablename; // OK - all lowercase.
string tableName; // Bad - mixed case.
对于class里面的变量
string table_name_; // OK - underscore at end.
string tablename_; // OK.
对于结构体中的变量,跟普通变量命名一样。
struct UrlTableProperties {
string name;
int num_entries;
}
全局变量谷歌没有做特殊要求,如果非要做区分的话可以在前面添加”g_”来区分本地变量
4.4 常量命名
常量名前加”k”,比如kDaysInAWeek
const int kDaysInAWeek = 7;
4.5 函数名
函数首字母大写,每个word的首字母也要大写,如果函数因为error而crash的话,在函数的末尾添加OrDie。
AddTableEntry()
DeleteUrl()
OpenFileOrDie()
如果操作变量的话,最好在函数中添加操作的变量名。比如
class MyClass {
public:
...
int num_entries() const { return num_entries_; }
void set_num_entries(int num_entries) { num_entries_ = num_entries; }
private:
int num_entries_;
};
通过观察可得到,谷歌使用大写驼峰命名函数较为普遍,使用小写加下划线的方式较少。
4.6 枚举命名
枚举的命名跟常量或者宏的规则一样。比如kEnumName 或者
ENUM_NAME
enum UrlTableErrors {
kOK = 0,
kErrorOutOfMemory,
kErrorMalformedInput,
};
enum AlternateUrlTableErrors {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};
这里面谷歌提到同宏命名法容易出现跟宏命名重复,推荐使用常量的命名方法
4.7 宏命名
谷歌不推荐使用宏命名,如果非要使用的话,同C一样大写加”_”的方式
#define ROUND(x) ...
#define PI_ROUNDED 3.0
4.8 例外
当然也有例外,下面几种情况可以遵循现有代码的规则。
bigopen()
function name, follows form of open()
uint
typedef
bigpos
struct or class, follows form of pos
sparse_hash_map
STL-like entity; follows STL naming conventions
LONGLONG_MAX
a constant, as in INT_MAX
五 注释
好的代码就是最好的注释,注释的规范如下:
5.1 注释风格
注释使用//或者//,两种都可以,通过阅读谷歌的代码,//一般用在文件开头license,//用在代码注释中。
5.2 文件注释
文件注释,一般在文件的开头,包括版权信息 作者 和文件内容描述。这个每个公司不大一样,就不详细介绍了
5.2 类注释
类注释描述类的用处和使用方法
// An alloc space is a space where objects may be allocated and garbage collected. Not final as may
// be overridden by a MemoryToolMallocSpace.
class DlMallocSpace : public MallocSpace {
5.3 函数注释
函数注释不要写函数如何实现,而是介绍函数的作用和使用方法,具体到注释中可以添加的内容如下:
1) 输入输出
2) Class里面的函数,如果执行中有使用引用,是否会释放他们
3) 如果函数分配内存,则调用者必须释放
4) 参数是否可以是NULL
5) 函数调用方法是否影响性能
6) 如果函数可重入,如何同步
// Returns an iterator for this table. It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
// Iterator* iter = table->NewIterator();
// iter->Seek("");
// return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;
函数定义出应该有注释,比如如下:
// Create a DlMallocSpace from an existing mem_map.
static DlMallocSpace* CreateFromMemMap(MemMap* mem_map, const std::string& name,
size_t starting_size, size_t initial_size,
size_t growth_limit, size_t capacity,
bool can_move_objects);
5.4 变量注释
通过变量名可以得到变量的大部分信息,当然有些情况需要注释来描述变量。
类变量,每个类变量应该有注释描述。如果变量可以是特殊值比如NULL或者-1,在注释中要添加
private:
// Keeps track of the total number of entries in the table.
// Used to ensure we do not go over the limit. -1 means
// that we don't yet know how many entries the table has.
int num_total_entries_;
全局变量:全局变量也要添加注释
// The total number of tests cases that we run through in this regression test.
const int kNumTestCases = 6;
5.5 implementaion 注释
我的理解是操作注释,在函数中,便于别人理解,一些关键性的操作需要添加注释,比如
if (space->IsImageSpace()) {
// Image space end is the end of the mirror objects, it is not necessarily page or card
// aligned. Align up so that the check in ClearCardRange does not fail.
end = AlignUp(end, accounting::CardTable::kCardSize);
}
card_table_->ClearCardRange(space->Begin(), end);
5.6 行注释
如果注释和代码是一行的话,注释应该和代码留有2个空格
// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, mmap_budget - index_->length());
if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
return; // Error already logged.
如果有多行注释时,最好要对齐
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.
}
5.7 NULL, true/false,1,2,3.. 注释
如果传递 NULL, Boolean 立即数参数到函数是,应该添加注释说明
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);
5.8 注释注意事项
注释不要有拼写语法错误,一定要表达清楚。比如应该使用“;”的地方使用了“,”
5.9 TODO住址
添加TODO注释 表明当前的code是暂时的,或者临时解决方案,也或者是代码需要后期维护,不完美的代码。
TODO添加你的联系方式,TODO的作用是便于联系作者去fix掉这个TODO项
//TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
5.10 弃用注释
常见的注释使用如下:
// @deprecated
static inline void Set2BE(uint8_t* buf, uint16_t val) {
*buf++ = (uint8_t)(val >> 8);
*buf = (uint8_t)(val);
}
六 格式
相对注释 命名更主观的规则,代码的格式则是相对客观,只要按照规定来coding即可。
6.1 行长度
一行最多有80个字符的长度。当然也有例外
例外一:如果注释行包含命令示例或者URL的长度超过80行的话
例外二: #include的头文件超过80行
6.2 非ASCII字符
统一使用UTF-8格式
6.3 空格和TAB符
只能使用空格,一次缩进2个字符。
6.4 函数声明和定义
函数的格式如下:
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
...
}
如果函数是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 {
...
}
如果参数不被使用的话,在函数定义的时候注释掉变量名
// Always have named parameters in interfaces.
class Shape {
public:
virtual void Rotate(double radians) = 0;
}
// Always have named parameters in the declaration.
class Circle : public Shape {
public:
virtual void Rotate(double radians);
}
// Comment out unused named parameters in definitions.
void Circle::Rotate(double /*radians*/) {}
// Bad - if someone wants to implement later, it's not clear what the
// variable means.
void Circle::Rotate(double) {}
6.5 函数调用
函数的调用同函数的定义格式差不多
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);
}
6.6 条件执行
推荐使用如下这种方式
if (condition) { // no spaces inside parentheses
... // 2 space indent.
} else if (...) { // The else goes on the same line as the closing brace.
...
} else {
...
}
如果执行的代码较短没有else的话推荐如下使用
if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();
除非与if同一行,if后添加{}
if (condition) {
DoSomething(); // 2 space indent.
}
// Curly braces around both IF and ELSE required because
// one of the clauses used braces.
if (condition) {
foo;
} else {
bar;
}
6.7 循环和switch语句
针对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.
while (condition); // Bad - looks like part of do/while loop.
6.8 指针和引用表达
x = *p;
p = &x;
x = r.y;
x = r->y;
在.或者->周围没有空格
在*或者&后面没有空格
// These are fine, space preceding.
char *c;
const string &str;
// These are fine, space following.
char* c; // but remember to do "char* c, *d, *e, ...;"!
const string& str;
char * c; // Bad - spaces on both sides of *
const string & str; // Bad - spaces on both sides of &
6.9 boolean 表达式
&&放在一行的末尾,这种形式比较多见。
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one) {
...
}
6.10 return 返回值
Return返回值不要带有括号,除非是表达式
return result; // No parentheses in the simple case.
return (some_long_condition && // Parentheses ok to make a complex
another_condition); // expression more readable.
6.11 变量和数组初始化
初始化使用= 或者()
int x = 3;
int x(3);
string name("Some Name");
string name = "Some Name";
6.12预处理指令
预处理放在行开始处
// Good - directives at beginning of line
if (lopsided_score) {
#if DISASTER_PENDING // Correct -- Starts at beginning of line
DropEverything();
# if NOTIFY // OK but not required -- Spaces after #
NotifyClient();
# endif
#endif
BackToNormal();
}
6.13 类格式
有一点稍微注意一下,public函数在上,往下依次是protected 和Private
class MyClass : public OtherClass {
public: // Note the 1 space indent!
MyClass(); // Regular 2 space indent.
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
void SomeFunctionThatDoesNothing() {
}
void set_some_var(int var) { some_var_ = var; }
int some_var() const { return some_var_; }
private:
bool SomeInternalFunction();
int some_var_;
int some_other_var_;
DISALLOW_COPY_AND_ASSIGN(MyClass);
};
6.14 构造函数初始化参数列表
构造函数初始化常用下面两种形式
// When it all fits on one line:
MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) {}
或者
// When it requires multiple lines, indent 4 spaces, putting the colon on
// the first initializer line:
MyClass::MyClass(int var)
: some_var_(var), // 4 space indent
some_other_var_(var + 1) { // lined up
...
DoSomething();
...
}
6.15 namepsace 格式
在namespace里面不要使用缩进
namespace {
void foo() { // Correct. No extra indentation within namespace.
...
}
} // namespace
6.16 水平空格
不要在文件结尾添加空格
void f(bool b) { // Open braces should always have a space before them.
...
int i = 0; // Semicolons usually have no space before them.
int x[] = { 0 }; // Spaces inside braces for array initialization are
int x[] = {0}; // optional. If you use them, put them on both sides!
// Spaces around the colon in inheritance and initializer lists.
class Foo : public Bar {
public:
// For inline function implementations, put spaces between the braces
// and the implementation itself.
Foo(int b) : Bar(), baz_(b) {} // No spaces inside empty braces.
void Reset() { baz_ = 0; } // Spaces separating braces from implementation.
...
循环和条件执行
if (b) { // Space after the keyword in conditions and loops.
} else { // Spaces around else.
}
while (test) {} // There is usually no space inside parentheses.
switch (i) {
for (int i = 0; i < 5; ++i) {
switch ( i ) { // Loops and conditions may have spaces inside
if ( test ) { // parentheses, but this is rare. Be consistent.
for ( int i = 0; i < 5; ++i ) {
for ( ; i < 5 ; ++i) { // For loops always have a space after the
... // semicolon, and may have a space before the
// semicolon.
switch (i) {
case 1: // No space before colon in a switch case.
...
case 2: break; // Use a space after a colon if there's code after it.
运算符
x = 0; // Assignment operators always have spaces around
// them.
x = -5; // No spaces separating unary operators and their
++x; // arguments.
if (x && !y)
...
v = w * x + y / z; // Binary operators usually have spaces around them,
v = w*x + y/z; // but it's okay to remove spaces around factors.
v = w * (x + z); // Parentheses should have no spaces inside them.
模板和转化
vector<string> x; // No spaces inside the angle
y = static_cast<char*>(x); // brackets (< and >), before
// <, or between >( in a cast.
vector<char *> x; // Spaces between type and pointer are
// okay, but be consistent.
set<list<string> > x; // C++ requires a space in > >.
set< list<string> > x; // You may optionally use
// symmetric spacing in < <.
6.17 空行
代码尽量减少空行,在空行没有详细的定义。从可读性出发,可以适当的添加空行。
网友评论