赞
踩
.cc
外,其他每一个.cc
都应该有一个对应的.h
举个例子:
----- MyClass.h -----
class MyClass
{
MyClass(std::string s);
};
---- MyClass.cpp -----
#include “MyClass.h”
#include
MyClass::MyClass(std::string s)
{…}
我们在.h头文件中,用到了string这个源文件,但是在cpp文件中,我们先声明头文件,最后会出现string没有包含这个错误,因为头文件不是自给自足的。只有#include 放在#include "MyClass.h"才行,所以为了避免这种错误,我们需要使头文件满足自给自足这个条件。所以我们的头文件写完必须独立运行编译一下看看是否成功,为此,我们需要在头文件中包含其他所有的文件,在这个例子中,myclass中应该包含string这个文件。
<PROJECT>_<PATH>_<FILE>_H_ .
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
#include "base/logging.h"
#include "foo/public/fooserver.h" // 优先位置
#include <sys/types.h>
#include <unistd.h>
#include <hash_map>
#include <vector>
#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"
#include "foo/public/fooserver.h"
#include "base/port.h" // For LANG_CXX11.
#ifdef LANG_CXX11
#include <initializer_list>
#endif // LANG_CXX11
前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;
作用域的使用, 除了考虑名称污染, 可读性之外, 主要是为降低耦合, 提高编译/执行效率.
namespace {
...
} // namespace
/ 禁止 —— 污染命名空间
using namespace foo;
namespace X {
inline namespace Y {
void foo();
} // namespace Y
} // namespace X
// .h 文件
namespace mynamespace {
// 所有声明都置于命名空间中
// 注意不要使用缩进
class MyClass {
public:
...
void Foo();
};
} // namespace mynamespace
// .cc 文件
namespace mynamespace {
// 函数定义都置于命名空间中
void MyClass::Foo() {
...
}
} // namespace mynamespace
// 在 .cc 中使用别名缩短常用的命名空间
namespace baz = ::foo::bar::baz;
// 在 .h 中使用别名缩短常用的命名空间
namespace librarian {
namespace impl { // 仅限内部使用
namespace sidetable = ::pipeline_diagnostics::sidetable;
} // namespace impl
inline void my_inline_function() {
// 限制在一个函数中的命名空间别名
namespace baz = ::foo::bar::baz;
...
}
} // namespace librarian
使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数. 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.
# 推荐 namespace myproject { namespace foo_bar { void Function1(); void Function2(); } // namespace foo_bar } // namespace myproject # 不推荐 namespace myproject { class FooBar { public: static void Function1(); static void Function2(); }; } // namespace myproject
// bad
int i;
i = f(); // 坏——初始化和声明分离
vector<int> v;
v.push_back(1); // 用花括号初始化更好
v.push_back(2);
// ok
int j = g(); // 好——初始化时声明
vector<int> v = {1, 2}; // 好——v 一开始就初始化
while (const char* p = strchr(str, '/')) str = p + 1;
// ------------------低效的实现------
for (int i = 0; i < 1000000; ++i) {
Foo f; // 构造函数和析构函数分别调用 1000000 次!
f.DoSomething(i);
}
// ------------------高效的实现------
Foo f; // 构造函数和析构函数只调用 1 次
for (int i = 0; i < 1000000; ++i) {
f.DoSomething(i);
}
静态生存周期的对象,即包括了全局变量,静态变量,静态类成员变量和函数静态变量,都必须是POD类型以及 POD 类型的指针、数组和结构体,完全禁用 vector (使用 C 数组替代) 和 string (使用 const char [])。。
如果你确实需要一个 class 类型的静态或全局变量,可以考虑在 main() 函数或 pthread_once() 内初始化一个指针且永不回收。注意只能用 raw 指针,别用智能指针,否则析构会出问题
(1)禁止使用类的static变量
(2)静态变量的构造函数、析构函数和初始化的顺序在 C++ 中是只有部分明确的,甚至随着构建变化而变化,导致难以发现的 bug.
(3)用 quick_exit() 来代替 exit() 并中断程序
(4)尽量不用全局变量
(5) 多线程中的全局变量 (含静态成员变量) 不要使用 class 类型 (含 STL 容器), 避免不明确行为导致的 bug.
嵌套类符合局部使用原则, 只是不能在其他头文件中前置声明, 尽量不要 public;
纯接口类是指满足特定条件的类, 这些类以 Interface 为后缀 (不强制)。
为降低复杂性, 尽量不重载操作符, 模板
返回值:
函数参数:
void Foo(const string &in, string *out);
函数应该简单简短,尽量不要超过40行
禁止使用缺省函数参数,少数极端情况除外。尽可能改用函数重载。
建议:只为可以缺省的参数提供默认值。比如vector的增长倍数,这种不会影响功能正确性的可以考虑使用。但不要滥用默认参数,不要只是因为——哈哈,这是目前的应用场景,为了少打几个字,我把它们设置为默认值好了。
C++ 现在允许两种不同的函数声明方式. 以往的写法是将返回类型置于函数名之前. 例如:
int foo(int x);
C++11 引入了这一新的形式. 现在可以在函数名前使用 auto 关键字, 在参数列表之后后置返回类型. 例如:
auto foo(int x) -> int;
后置返回类型为函数作用域. 对于像 int 这样简单的类型, 两种写法没有区别. 但对于复杂的情况, 例如类域中的类型声明或者以函数参数的形式书写的类型, 写法的不同会造成区别.
后置返回类型是显式地指定 Lambda 表达式 的返回值的唯一方式. 某些情况下, 编译器可以自动推导出 Lambda 表达式的返回类型, 但并不是在所有的情况下都能实现. 即使编译器能够自动推导, 显式地指定返回类型也能让读者更明了.
有时在已经出现了的函数参数列表之后指定返回类型, 能够让书写更简单, 也更易读, 尤其是在返回类型依赖于模板参数时.:
template <class T, class U> auto add(T t, U u) -> decltype(t + u);
动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权.
.
cpplint.py 是一个用来分析源文件, 能检查出多种风格错误的工具. 它不并完美, 甚至还会漏报和误报, 但它仍然是一个非常有用的工具. 在行尾加 // NOLINT, 或在上一行加 // NOLINTNEXTLINE, 可以忽略报错.
这一点不是很赞同。对使用 C++ 异常处理应具有怎样的态度?
RTTI 允许程序员在运行时识别 C++ 类对象的类型. 它通过使用 typeid 或者 dynamic_cast 完成.
. 在单元测试中可以使用 RTTI, 但是在其他代码中请尽量避免
如果你的代码需要根据不同的对象类型执行不同的行为的话, 请考虑用以下的两种替代方案之一查询类型:
为什么要这样规定?
if (typeid(*data) == typeid(D1)) {
...
} else if (typeid(*data) == typeid(D2)) {
...
} else if (typeid(*data) == typeid(D3)) {
...
不要去手工实现一个类似 RTTI 的方案. 反对 RTTI 的理由同样适用于这些方案, 比如带类型标签的类继承体系. 而且, 这些方案会掩盖你的真实意图.
不要使用流, 除非是日志接口需要. 使用 printf 之类的代替.
使用流还有很多利弊, 但代码一致性胜过一切. 不要在代码中使用流.
对于变量在自增 (++i 或 i++) 或自减 (–i 或 i–) 后表达式的值又没有没用到的情况下, 需要确定到底是使用前置还是后置的自增 (自减).
我们强烈建议你在任何可能的情况下都要使用 const. 此外有时改用 C++11 推出的 constexpr 更好。
const 变量, 数据成员, 函数和参数为编译时类型检测增加了一层保障; 便于尽早发现错误. 因此, 我们强烈建议在任何可能的情况下使用 const:
关键字 mutable 可以使用, 但是在多线程中是不安全的, 使用时首先要考虑线程安全.
const的位置:
变量可以被声明成 constexpr 以表示它是真正意义上的常量,即在编译时和运行时都不变。函数或构造函数也可以被声明成 constexpr, 以用来定义 constexpr 变量。
在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化。
C++ 没有指定整型的大小. 通常人们假定 short 是 16 位, int 是 32 位, long 是 32 位, long long 是 64 位.
C++ 内建整型中, 仅使用 int. 如果程序中需要不同大小的变量, 可以使用 < stdint.h> 中长度精确的整型。<stdint.h> 定义了 int16_t, uint32_t, int64_t 等整型, 在需要确保整型大小时可以使用它们代替 short, unsigned long long 等
代码应该对 64 位和 32 位系统友好. 处理打印, 比较, 结构体对齐时应切记:
使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之.
如果你要宏, 尽可能遵守:
使用 sizeof(varname) 是因为当代码中变量类型改变时会自动更新
// 推荐
Struct data;
Struct data; memset(&data, 0, sizeof(data));
// 警告
memset(&data, 0, sizeof(Struct));
auto 和 C++11 列表初始化的合体令人摸不着头脑:
auto x(3); // 圆括号。
auto y{3}; // 大括号。
它们不是同一回事:
-----------不推荐---------
[=](int x) {return x + n;}
--------推荐----------
[n](int x) {return x + n;}
_
) 或连字符 ( -
), 依照项目的约定. 如果没有约定, 那么 “_” 更好所有类型命名 —— 类, 结构体, 类型定义 (typedef), 枚举, 类型模板参数 —— 均使用相同约定, 即以大写字母开始, 每个单词首字母均大写, 不包含下划线. 例如:
// 类和结构体
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
// 类型定义
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// using 别名
using PropertiesMap = hash_map<UrlTableProperties *, string>;
// 枚举
enum UrlTableErrors { ...
变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用
string table_name; // 好 - 用下划线.
string tablename; // 好 - 全小写.
string tableName; // 差 - 混合大小写
class TableInfo {
...
private:
string table_name_; // 好 - 后加下划线.
string tablename_; // 好.
static Pool<TableInfo>* pool_; // 好.
};
struct UrlTableProperties {
string name;
int num_entries;
static Pool<UrlTableProperties>* pool;
};
声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合. 例如:
const int kDaysInAWeek = 7;
一般来说, 函数名的每个单词首字母大写 (即 “驼峰变量名” 或 “帕斯卡变量名”), 没有下划线. 对于首字母缩写的单词, 更倾向于将它们视作一个单词进行首字母大写 (例如, 写作 StartRpc() 而非 StartRPC()).
AddTableEntry()
DeleteUrl()
OpenFileOrDie()
取值和设值函数的命名与变量一致. 一般来说它们的名称与实际的成员变量对应, 但并不强制要求. 例如 int count() 与 void set_count(int count).
命名空间以小写字母命名.
枚举的命名应当和 常量 或 宏 一致: kEnumName 或是 ENUM_NAME.
enum UrlTableErrors {
kOK = 0,
kErrorOutOfMemory,
kErrorMalformedInput,
};
enum AlternateUrlTableErrors {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};
新代码应该尽可能优先使用常量风格. 但是老代码没必要切换到常量风格, 除非宏风格确实会产生编译期问题.
通常 不应该 使用宏. 如果不得不用, 其命名像枚举命名一样全部大写, 使用下划线:
#define ROUND(x) ...
#define PI_ROUNDED 3.0
如果你命名的实体与已有 C/C++ 实体相似, 可参考现有命名策略.
注释固然很重要, 但最好的代码应当本身就是文档. 有意义的类型名和变量名, 要远胜过要用注释解释的含糊不清的名字.
// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature
int x = 0;
auto add_to_x = [&x](int n) { x += n; };
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 ... }
class Shape {
public:
virtual void Rotate(double radians) = 0;
};
class Circle : public Shape {
public:
void Rotate(double radians) override;
};
void Circle::Rotate(double /*radians*/) {}
MUST_USE_RESULT bool IsOK();
函数调用遵循如下形式:
bool retval = DoSomething(argument1, argument2, argument3);
如果同一行放不下, 可断为多行, 后面每一行都和第一个实参对齐, 左圆括号后和右圆括号前不要留空格:
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
参数也可以放在次行, 缩进四格:
if (...) {
...
...
if (...) {
DoSomething(
argument1, argument2, // 4 空格缩进
argument3, argument4);
}
如果一些参数本身就是略复杂的表达式, 且降低了可读性, 那么可以直接创建临时变量描述该表达式, 并传递给函数:
int my_heuristic = scores[x] * y + bases[x];
bool retval = DoSomething(my_heuristic, x, y, z);
或者放着不管, 补充上注释:
bool retval = DoSomething(scores[x] * y + bases[x], // Score heuristic.
x, y, z);
此外, 如果一系列参数本身就有一定的结构, 可以酌情地按其结构来决定参数格式:
// 通过 3x3 矩阵转换 widget.
my_widget.Transform(x1, x2, x3,
y1, y2, y3,
z1, z2, z3);
// 一行列表初始化示范. return {foo, bar}; functioncall({foo, bar}); pair<int, int> p{foo, bar}; // 当不得不断行时. SomeFunction( {"assume a zero-length name before {"}, // 假设在 { 前有长度为零的名字. some_other_function_parameter); SomeType variable{ some, other, values, {"assume a zero-length name before {"}, // 假设在 { 前有长度为零的名字. SomeOtherType{ "Very long string requiring the surrounding breaks.", // 非常长的字符串, 前后都需要断行. some, other values}, SomeOtherType{"Slightly shorter string", // 稍短的字符串. some, other, values}}; SomeType variable{ "This is too long to fit all in one line"}; // 字符串过长, 因此无法放在同一行. MyType m = { // 注意了, 您可以在 { 前断行. superlongvariablename1, superlongvariablename2, {short, interior, list}, {interiorwrappinglist, interiorwrappinglist2}};
倾向于不在圆括号内使用空格.
if (condition) { // 圆括号里没有空格.
... // 2 空格缩进.
} else if (...) { // else 与 if 的右括号同一行.
...
} else {
...
}
如果能增强可读性, 简短的条件语句允许写在同一行. 只有当语句简单并且没有使用 else 子句时使用:
if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();
Google 强调有一对 if-else 时, 不论有没有嵌套, 都要有大括号
// 只要其中一个分支用了大括号, 两个分支都要用上大括号.
if (condition) {
foo;
} else {
bar;
}
switch 语句中的 case 块可以使用大括号也可以不用, 取决于你的个人喜好. 如果用的话, 要按照下文所述的方法.
如果有不满足 case 条件的枚举值, switch 应该总是包含一个 default 匹配 (如果有输入值没有 case 去处理, 编译器将给出 warning). 如果 default 应该永远执行不到, 简单的加条 assert:
switch (var) {
case 0: { // 2 空格缩进
... // 4 空格缩进
break;
}
case 1: {
...
break;
}
default: {
assert(false);
}
}
空循环体应使用 {} 或 continue, 而不是一个简单的分号.
while (condition) {
// 反复循环直到条件失效.
}
for (int i = 0; i < kSomeNumber; ++i) {} // 可 - 空循环体.
while (condition) continue; // 可 - contunue 表明没有逻辑.
while (condition); // 差 - 看起来仅仅只是 while/loop 的部分之一.
x = *p;
p = &x;
x = r.y;
x = r->y;
// 好, 空格前置.
char *c;
const string &str;
// 好, 空格后置.
char* c;
const string& str;
int x, *y; // 不允许 - 在多重声明中不能使用 & 或 *
char * c; // 差 - * 两边都有空格
const string & str; // 差 - & 两边都有空格.
在单个文件内要保持风格一致, 所以, 如果是修改现有文件, 要遵照该文件的风格.
如果一个布尔表达式超过 标准行宽, 断行方式要统一一下.
下例中, 逻辑与 (&&) 操作符总位于行尾:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one) {
...
}
注意, 上例的逻辑与 (&&) 操作符均位于行尾. 这个格式在 Google 里很常见, 虽然把所有操作符放在开头也可以. 可以考虑额外插入圆括号, 合理使用的话对增强可读性是很有帮助的. 此外, 直接用符号形式的操作符, 比如 && 和 ~, 不要用词语形式的 and 和 compl
//-----------------------
return result; // 返回值很简单, 没有圆括号.
// 可以用圆括号把复杂表达式圈起来, 改善可读性.
return (some_long_condition &&
another_condition);
//-----------------
return (value); // 毕竟您从来不会写 var = (value);
return(result); // return 可不是函数!
即使预处理指令位于缩进代码块中, 指令也应从行首开始.
// 好 - 指令从行首开始 if (lopsided_score) { #if DISASTER_PENDING // 正确 - 从行首开始 DropEverything(); # if NOTIFY // 非必要 - # 后跟空格 NotifyClient(); # endif #endif BackToNormal(); } // 差 - 指令缩进 if (lopsided_score) { #if DISASTER_PENDING // 差 - "#if" 应该放在行开头 DropEverything(); #endif // 差 - "#endif" 不要缩进 BackToNormal(); }
访问控制块的声明依次序是 public:, protected:, private:, 每个都缩进 1 个空格.
class MyClass : public OtherClass { public: // 注意有一个空格的缩进 MyClass(); // 标准的两空格缩进 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_; };
// 如果所有变量能放在同一行: MyClass::MyClass(int var) : some_var_(var) { DoSomething(); } // 如果不能放在同一行, // 必须置于冒号后, 并缩进 4 个空格 MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) { DoSomething(); } // 如果初始化列表需要置于多行, 将每一个成员放在单独的一行 // 并逐行对齐 MyClass::MyClass(int var) : some_var_(var), // 4 space indent some_other_var_(var + 1) { // lined up DoSomething(); } // 右大括号 } 可以和左大括号 { 放在同一行 // 如果这样做合适的话 MyClass::MyClass(int var) : some_var_(var) {}
命名空间 不要增加额外的缩进层次, 例如:
namespace { void foo() { // 正确. 命名空间内没有额外的缩进. ... } } // namespace //------------------------ namespace { // 错, 缩进多余了. void foo() { ... } } // namespace
声明嵌套命名空间时, 每个命名空间都独立成行.
namespace foo {
namespace bar {
水平留白的使用根据在代码中的位置决定. 永远不要在行尾添加没意义的留白.
void f(bool b) { // 左大括号前总是有空格. ... int i = 0; // 分号前不加空格. // 列表初始化中大括号内的空格是可选的. // 如果加了空格, 那么两边都要加上. int x[] = { 0 }; int x[] = {0}; // 继承与初始化列表中的冒号前后恒有空格. class Foo : public Bar { public: // 对于单行函数的实现, 在大括号内加上空格 // 然后是函数实现 Foo(int b) : Bar(), baz_(b) {} // 大括号里面是空的话, 不加空格. void Reset() { baz_ = 0; } // 用空格把大括号与实现分开. ...
if (b) { // if 条件语句和循环语句关键字后均有空格.
} else { // else 前后有空格.
}
while (test) {} // 圆括号内部不紧邻空格.
switch (i) {
for (int i = 0; i < 5; ++i) {
switch ( i ) { // 循环和条件语句的圆括号里可以与空格紧邻.
if ( test ) { // 圆括号, 但这很少见. 总之要一致.
for ( int i = 0; i < 5; ++i ) {
for ( ; i < 5 ; ++i) { // 循环里内 ; 后恒有空格, ; 前可以加个空格.
switch (i) {
case 1: // switch case 的冒号前无空格.
...
case 2: break; // 如果冒号有代码, 加个空格.
// 赋值运算符前后总是有空格.
x = 0;
// 其它二元操作符也前后恒有空格, 不过对于表达式的子式可以不加空格.
// 圆括号内部没有紧邻空格.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);
// 在参数和一元操作符之间不加空格.
x = -5;
++x;
if (x && !y)
...
// 尖括号(< and >) 不与空格紧邻, < 前没有空格, > 和 ( 之间也没有.
vector<string> x;
y = static_cast<char*>(x);
// 在类型与指针操作符之间留空格也可以, 但要保持一致.
vector<char *> x;
这不仅仅是规则而是原则问题了: 不在万不得已, 不要使用空行. 尤其是: 两个函数定义之间的空行不要超过 2 行, 函数体首尾不要留空行, 函数体中也不要随意添加空行.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。