C++代码风格指南
代码风格的重要性
今天我收到thougthwork笔试没过的消息, 心里确实很难受, 然后师兄说我代码写得很糟糕
细想一下, 我写代码确实是随心所欲, 并没有遵循什么规范; 所以现在下定决心痛改前非;
- 首先第一步是代码都自己一个字一个字的敲, 尽量减少
Ctrl+C
和Ctrl+V
操作 - 以后禁止使用
tab
键进行缩进,难道手敲四个空格会死么? - 避免代码冗长
- Google开源项目C++代码风格指南
头文件
- 头文件包含顺序
- 优先位置的包含文件
- C系统文件
- C++系统文件
- 其他库的.h文件
- 本库的.h文件
作用域
- 禁止使用 using 指示(using-directive)
- 禁止使用内联命名空间(inline namespace)
- 因为内联的命名空间会内部的标识符放到外层作用域
- 内联命名空间主要用来保持跨版本的 ABI 兼容性
- 在头文件中使用匿名空间导致违背 C++ 的唯一定义原则 (One Definition Rule (ODR)).
- 不应该使用 using 指示引入整个命名空间的标识符号 ---- 会污染命名空间
- 不要在命名空间 std 内声明任何东西, 包括标准库的类前置声明. 在 std 命名空间声明实体是未定义的行为, 会导致如不可移植. 声明标准库下的实体, 需要包含对应的头文件.
- 匿名命名空间和静态空间(内部链接属性)
- 在 .cc 文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为
static
; 但是不要在.h
文件中这么做 - 所有置于匿名命名空间的声明都具有内部链接性,函数和变量可以经由声明为
static
拥有内部链接性,这意味着你在这个文件中声明的这些标识符都不能在另一个文件中被访问。即使两个文件声明了完全一样名字的标识符,它们所指向的实体实际上是完全不同的。
- 在 .cc 文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为
- 静态成员函数, 全局函数, 和非成员函数
- 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.
- 局部变量
- 将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化.
- 提倡在尽可能小的作用域中声明变量, 离第一次使用越近越好.
- 这样做的好处是阅读代码可以更容易定位变量的声明位置, 了解变量的初始值和类型,特别是利用初始化的方式代替声明再赋值的方式.
- 警告: 如果变量是一个对象, 每次进入作用域都要调用其构造函数, 每次退出作用域都要调用其析构函数. 这会导致效率降低.
- 静态和全局变量
- 禁止定义静态储存周期非POD变量(原生数据类型 (POD : Plain Old Data)),禁止使用含有副作用的函数初始化POD全局变量,因为多编译单元中的静态变量执行时的构造和析构顺序是未明确的,这将导致代码的不可移植.
- 静态变量的构造函数和析构函数在C++中只有部分明确确定, 甚至随着构建变化而变化, 导致难以发现的bug.
- 尽量不要使用函数的返回值来初始化一个变量
- 同一个编译单元内是明确的,静态初始化优先于动态初始化,初始化顺序按照声明顺序进行,销毁则逆序。
- 不同的编译单元之间初始化和销毁顺序属于未明确行为 (unspecified behaviour)。
- 全局和静态变量在程序中断时会被析构, --- 析构顺序和构造函数的调用顺序相反。
- quick_exit() 来代替 exit() 并中断程序。quick_exit 不会执行任何析构,也不会执行 atexit() 所绑定的任何 handlers.
- 只允许 POD(原生数据类型) 类型的静态变量
- static 对象, 可以考虑在 main() 函数或 pthread_once() 内初始化一个指针且永不回收, 只能使用raw指针.
- 别用智能指针,毕竟后者的析构函数涉及不定顺序问题
- 匿名命名空间说白了就是文件作用域,就像 C static 声明的作用域一样,后者已经被 C++ 标准提倡弃用
- 局部变量在声明的同时进行显式值初始化,比起隐式初始化再赋值的两步过程要高效,同时也贯彻了计算机体系结构重要的概念「局部性(locality)」
类
- 不要在构造函数中调用虚函数, 也不要在无法报出错误时进行可能失败的初始化.
- 构造函数的地址是无法被取得的, 因此, 举例来说, 由构造函数完成的工作是无法以简单的方式交给其他线程的.
- 不要定义隐式类型转换. 对于转换运算符和单参数构造函数, 请使用 explicit 关键字.
- 在类型定义中, 类型转换运算符和单参数构造函数都应当用 explicit 进行标记. 一个例外是, 拷贝和移动构造函数不应当被标记为 explicit, 因为它们并不执行类型转换. 对于设计目的就是用于对其他类型进行透明包装的类来说, 隐式类型转换有时是必要且合适的. 这时应当联系项目组长并说明特殊情况.
- 仅当只有数据成员时使用 struct, 其它一概使用 class.
- 为了和 STL 保持一致, 对于仿函数等特性可以不用 class 而是使用 struct.
- 不在构造函数中做太多逻辑相关的初始化;
- 编译器提供的默认构造函数不会对变量进行初始化, 如果定义了其他构造函数, 编译器不再提供, 需要编码者自行提供默认构造函数;
- 为避免隐式转换, 需将单参数构造函数声明为 explicit;
- 为避免拷贝构造函数, 赋值操作的滥用和编译器自动生成, 可将其声明为 private 且无需实现;
- 仅在作为数据集合时使用 struct;
- 组合 > 实现继承 > 接口继承 > 私有继承, 子类重载的虚函数也要声明 virtual 关键字, 虽然编译器允许不这样做;
- 避免使用多重继承, 使用时, 除一个基类含有实现外, 其他基类均为纯接口;
- 接口类类名以 Interface 为后缀, 除提供带实现的虚析构函数, 静态成员函数外, 其他均为纯虚函数, 不定义非静态数据成员, 不提供构造函数, 提供的话, 声明为 protected;
- 为降低复杂性, 尽量不重载操作符, 模板, 标准类中使用时提供文档说明;
- 存取函数一般内联在头文件中;
- 声明次序: public -> protected -> private;
- 函数体尽量短小, 紧凑, 功能单一;
函数
- 在排列参数顺序时, 将所有的输入参数置于输出参数之前. 特别要注意, 在加入新参数时不要因为它们是新参数就置于参数列表最后, 而是仍然要按照前述的规则, 即将新的输入参数也置于输出参数之前.
- 倾向于编写简短, 凝练的函数.
- 可以思索一下能不能在不影响程序结构的前提下对其进行分割.
- 所有按引用传递的参数必须加上 const.
- 定义引用参数可以防止出现 (*pval)++ 这样丑陋的代码. 引用参数对于拷贝构造函数这样的应用也是必需的. 同时也更明确地不接受空指针.
- 引用在语法上是值变量却拥有指针的语义.
- 输入参数是值参或 const 引用, 输出参数为指针. 输入参数可以是 const 指针, 但决不能是非 const 的引用参数, 除非特殊要求, 比如 swap().
- 只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致.
- 对于虚函数, 不允许使用缺省参数, 因为在虚函数中缺省参数不一定能正常工作.
- 只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法. --- 后置返回类型是显式地指定 Lambda 表达式 的返回值的唯一方式.
来自Google的奇技
- Google用了很多自己实现的技巧/工具C++代码更加健壮
- 所有权与智能指针: 动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权.
- 智能指针是重载了
*
和->
运算符以表现得像指针一样的类-
unique_ptr
将所有权移动给新所有主 -
shared_ptr
表示动态分配对象的所有权, 但可以被共享, 也可以被复制
-
- 如果没有很好的理由就不要使用共享版所有权, 是为了避免开销昂贵的拷贝操作.
- 不要使用
auto_ptr
, 使用shared_ptr
代替它. - 使用cpplint.py检查代码风格, cpplint.py是一个用来分析源文件, 能检查出多种风格错误的工具.
- scoped_ptr 和 auto_ptr 已过时. 现在是 shared_ptr 和 uniqued_ptr 的天下了.
- AUR有对cpplint打包.
其他C++特性
- 引用参数: 所有按引用传递的参数必须加上
const
- 引用在语法上是值变量却拥有指针的语义.
- 输入参数是值参或 const 引用, 输出参数为指针. 输入参数可以是 const 指针, 但决不能是非 const 的引用参数,除非用于交换.
- 有时候,在输入形参中用 const T* 指针比 const T& 更明智.
- 右值的引用: 只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward.
- 右值引用是一种只能绑定到临时对象的引用的一种, 其语法与传统的引用语法相似.
- 用于定义移动构造函数 (使用类的右值引用进行构造的函数) 使得移动一个值而非拷贝之成为可能.
- 要高效率地使用某些标准库类型, 例如 std::unique_ptr, std::move 是必需的.
- 函数重载: 若要用好函数重载,最好能让读者一看调用点(call site)就胸有成竹,不用花心思猜测调用的重载函数到底是哪一种。该规则适用于构造函数.
- 模板化代码需要重载, 同时为使用者带来便利.
- 缺省参数: 我们不允许使用缺省函数参数,少数极端情况除外。尽可能改用函数重载.
- 缺省参数会干扰函数签名(function signature), 往往对不上所实际要调用的函数签名
- 其一,位于 .cc 文件里的静态函数或匿名空间函数,毕竟都只能在局部文件里调用该函数了。
- 其二,可以在构造函数里用缺省参数,毕竟不可能取得它们的地址。
- 其三,可以用来模拟变长数组。
- 变长数组和alloca()
- 我们不允许使用变长数组和 alloca().
- 变长数组具有浑然天成的语法. 变长数组和 alloca() 也都很高效; 但是不是标准C++语法的一部分.
- 改用更安全的分配器(allocator),就像 std::vector 或 std::unique_ptr<T[]>
- 友元(friend)
- 我们允许合理的使用友元类及友元函数.
- 通常友元应该定义在同一文件内, 避免代码读者跑到其它文件查找使用该私有成员的类.
- 将一个单元测试类声明成待测类的友元会很方便.
- 友元扩大了 (但没有打破) 类的封装边界. 某些情况下, 相对于将类成员声明为 public, 使用友元是更好的选择, 尤其是如果你只允许另一个类访问该类的私有成员时. 当然, 大多数类都只应该通过其提供的公有成员进行互操作.
- 异常
- 我们不使用 C++ 异常.
- 异常允许应用高层决定如何处理在底层嵌套函数中「不可能发生」的失败(failures),不用管那些含糊且容易出错的错误代码.
- 异常是处理构造函数失败的唯一途径.
- 异常会彻底扰乱程序的执行流程并难以判断,函数也许会在您意料不到的地方返回.
- 对于异常处理, 显然不是短短几句话能够说清楚的, 以构造函数为例, 很多 C++ 书籍上都提到当构造失败时只有异常可以处理, Google 禁止使用异常这一点, 仅仅是为了自身的方便, 说大了, 无非是基于软件管理成本上, 实际使用中还是自己决定
- 运行时类型识别(run time type information RTTI)
- RTTI 允许程序员在运行时识别 C++ 类对象的类型. 它通过使用 typeid 或者 dynamic_cast 完成.
- 在单元测试中可以使用 RTTI, 但是在其他代码中请尽量避免. 尤其是在新代码中, 使用 RTTI 前务必三思. 如果你的代码需要根据不同的对象类型执行不同的行为的话, 请考虑用以下的两种替代方案之一查询类型.
- 虚函数可以根据子类类型的不同而执行不同代码.
- 类型转型
- 使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式
- C++ 采用了有别于 C 的类型转换机制, 对转换操作进行归类.
- 用 static_cast 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时.
- 用 const_cast 去掉 const 限定符.
- 用 reinterpret_cast 指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你对所做一切了然于心时使用.
- 用 dynatic_cast 将派生类指针转换为基类类型, 或将基类指针转换为派生类指针.
- 流
- 只在记录日志时使用流.
- 流用来替代 printf() 和 scanf().
- 有了流, 在打印时不需要关心对象的类型.
- 流的构造和析构函数会自动打开和关闭对应的文件.
- 使用流还有很多利弊, 但代码一致性胜过一切. 不要在代码中使用流.
- 回想一下唯一性原则 (Only One Way): 我们希望在任何时候都只使用一种确定的 I/O 类型, 使代码在所有 I/O 处都保持一致.
- 简单性原则告诫我们必须从中选择其一, 最后大多数决定采用 printf + read/write.
- 前置自增或自减
- 对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.
- 不考虑返回值的话, 前置自增 (++i) 通常要比后置自增 (i++) 效率更高. 因为后置自增 (或自减) 需要对表达式的值 i 进行一次拷贝.
- 对简单数值 (非对象), 两种都无所谓. 对迭代器和模板类型, 使用前置自增 (自减).
- const 用法
- 我们强烈建议你在任何可能的情况下都要使用 const. 此外有时改用 C++11 推出的 constexpr 更好.
- 在声明的变量或参数前加上关键字 const 用于指明变量值不可被篡改 (如 const int foo ). 为类中的函数加上 const 限定符表明该函数不会修改类成员变量的状态
- 大家更容易理解如何使用变量. 编译器可以更好地进行类型检测, 相应地, 也能生成更好的代码.
- 如果函数不会修改传你入的引用或指针类型参数, 该参数应声明为 const.
- 尽可能将函数声明为 const. 访问函数应该总是 const. 其他不会修改任何数据成员, 未调用非 const 函数, 不会返回数据成员非 const 指针或引用的函数也应该声明成 const.
- 如果数据成员在对象构造之后不再发生变化, 可将其定义为 const.
- 关键字 mutable 可以使用, 但是在多线程中是不安全的, 使用时首先要考虑线程安全.
- 保持代码的一致性: 也就是不要在一些地方把 const 写在类型前面, 在其他地方又写在后面, 确定一种写法, 然后保持一致.
- constexpr 用法
- 在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化
- 变量可以被声明成 constexpr 以表示它是真正意义上的常量,即在编译时和运行时都不变。函数或构造函数也可以被声明成 constexpr, 以用来定义 constexpr 变量
- 靠 constexpr 特性,方才实现了 C++ 在接口上打造真正常量机制的可能。好好用 constexpr 来定义真・常量以及支持常量的函数。避免复杂的函数定义,以使其能够与constexpr一起使用。 千万别痴心妄想地想靠 constexpr 来强制代码「内联
- constexpr关键字详解
- constexpr编译时常量
- 整数
- C++ 内建整型中, 仅使用 int. 如果程序中需要不同大小的变量, 可以使用 <stdint.h> 中长度精确的整型, 如 int16_t.如果您的变量可能不小于 2^31 (2GiB), 就用 64 位变量比如 int64_t. 此外要留意,哪怕您的值并不会超出 int 所能够表示的范围,在计算过程中也可能会溢出。所以拿不准时,干脆用更大的类型
- C++ 没有指定整型的大小. 通常人们假定 short 是 16 位, int 是 32 位, long 是 32 位, long long 是 64 位.
- C++ 中整型大小因编译器和体系结构的不同而不同.
- 应该使用断言来保护数据.
- 使用断言来指出变量为非负数, 而不是使用无符号型
- 64位下的可移植性
- 代码应该对 64 位和 32 位系统友好. 处理打印, 比较, 结构体对齐时应切记
- 持久化 - 将数据按字节流顺序保存在磁盘文件或数据库中.
- gcc 中可使用
__attribute__((packed))
使结构体对齐
- 预处理宏
- 使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之.
- 宏意味着你和编译器看到的代码是不同的. 这可能会导致异常行为, 尤其因为宏具有全局作用域.
- 下面给出的用法模式可以避免使用宏带来的问题; 如果你要宏, 尽可能遵守:
- 不要在 .h 文件中定义宏.
- 在马上要使用时才进行 #define, 使用后要立即 #undef.
- 不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;
- 不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为.
- 不要用 ## 处理函数,类和变量的名字
- 0 , NULL 和 nullptr
- 整数用 0, 实数用 0.0, 指针用 nullptr 或 NULL, 字符 (串) 用 '\0'.
- 对于指针 (地址值), 到底是用 0, NULL 还是 nullptr. C++11 项目用 nullptr; C++03 项目则用 NULL, 毕竟它看起来像指针。实际上,一些 C++ 编译器对 NULL 的定义比较特殊,可以输出有用的警告,特别是 sizeof(NULL) 就和 sizeof(0) 不一样。
- 字符 (串) 用 '\0', 不仅类型正确而且可读性好.
- sizeof
- 尽可能用 sizeof(varname) 代替 sizeof(type).
- auto
- 用 auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方
- C++11 中,若变量被声明成 auto, 那它的类型就会被自动匹配成初始化表达式的类型
- 代码要避免无所谓的重复
- auto 只能用在局部变量里用。别用在文件作用域变量,命名空间作用域变量和类数据成员里。永远别列表初始化 auto 变量
- auto 还可以和 C++11 特性「尾置返回类型(trailing return type)」一起用,不过后者只能用在 lambda 表达式里
- 初始化列表
- C++11 中,该特性得到进一步的推广,任何对象类型都可以被列表初始化
- 用户自定义类型也可以定义接收 std::initializer_list 的构造函数和赋值运算符,以自动列表初始化
- 列表初始化也适用于常规数据类型的构造,哪怕没有接收 std::initializer_list 的构造函数
- Lambda表达式
- 适当使用 lambda 表达式。别用默认 lambda 捕获,所有捕获都要显式写出来
- Lambda 表达式是创建匿名函数对象的一种简易途径,常用于把函数当参数传
- Lambdas, std::functions 和 std::bind 可以搭配成通用回调机制(general purpose callback mechanism);写接收有界函数为参数的函数也很容易了
- 按 format 小用 lambda 表达式怡情。
- 禁用默认捕获,捕获都要显式写出来。打比方,比起 = {return x + n;}, 您该写成 n {return x + n;} 才对,这样读者也好一眼看出 n 是被捕获的值。
- 匿名函数始终要简短,如果函数体超过了五行,那么还不如起名(acgtyrant 注:即把 lambda 表达式赋值给对象),或改用函数。
- 如果可读性更好,就显式写出 lambd 的尾置返回类型,就像auto.
- 模板编程
- 不要使用复杂的模板编程
- 模板编程指的是利用c++ 模板实例化机制是图灵完备性, 可以被用来实现编译时刻的类型判断的一系列编程技巧
- 模板编程的优缺点
- Boost库
- 只使用 Boost 中被认可的库.
- C++11
- 适当用 C++11(前身是 C++0x)的库和语言扩展,在贵项目用 C++11 特性前三思可移植性
命名规约
- 最重要的一致性规则是命名管理.
- 类型, 变量, 函数, 常量, 宏, 等等, 甚至. 我们大脑中的模式匹配引擎非常依赖这些命名规则.
- 命名规约的一致性更重要, 所以无论你认为它们是否重要, 规则总归是规则.
- 通用命名规则
- 函数命名, 变量命名, 文件命名要有描述性; 少用缩写.
- 尽可能使用描述性的命名, 别心疼空间, 毕竟相比之下让代码易于新读者理解更重要. 不要用只有项目开发者能理解的缩写, 也不要通过砍掉几个字母来缩写单词.
- 文件命名
- 文件名要全部小写, 可以包含下划线 () 或连字符 (-), 依照项目的约定. 如果没有约定, 那么 “” 更好.
- 类型命名
- 类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.
- 所有类型命名 —— 类, 结构体, 类型定义 (typedef), 枚举, 类型模板参数 —— 均使用相同约定, 即以大写字母开始, 每个单词首字母均大写, 不包含下划线.
- 变量命名
- 变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接.
- 类的成员变量以下划线结尾, 但结构体的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_.
- 普通变量一律小写
- 不管是静态的还是非静态的, 结构体数据成员都可以和普通变量一样, 不用像类那样接下划线
- 常量命名
- 声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合.
- 函数命名
- 常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配
- 一般来说, 函数名的每个单词首字母大写 (即 “驼峰变量名” 或 “帕斯卡变量名”), 没有下划线. 对于首字母缩写的单词, 更倾向于将它们视作一个单词进行首字母大写 (例如, 写作 StartRpc() 而非 StartRPC()).
- 命名空间命名
- 命名空间以小写字母命名.
- *命名空间的名字取决于项目名称.
- 要注意避免嵌套命名空间的名字之间和常见的*命名空间的名字之间发生冲突.
- 注意 不使用缩写作为名称 的规则同样适用于命名空间. 命名空间中的代码极少需要涉及命名空间的名称, 因此没有必要在命名空间中使用缩写.
- 枚举命名
- 枚举的命名应当和 常量 或 宏 一致: kEnumName 或是 ENUM_NAME.
- 单独的枚举值应该优先采用 常量 的命名方式. 但 宏 方式的命名也可以接受.
- 宏命名
- 宏命名像这样命名: MY_MACRO_THAT_SCARES_SMALL_CHILDREN.
- 通常 不应该 使用宏. 如果不得不用, 其命名像枚举命名一样全部大写, 使用下划线
- 感觉 Google 的命名约定很高明, 比如写了简单的类 QueryResult, 接着又可以直接定义一个变量 query_result, 区分度很好; 再次, 类内变量以下划线结尾, 那么就可以直接传入同名的形参, 比如 TextQuery::TextQuery(std::string word) : word_(word) {} , 其中 word_ 自然是类内私有成员.
注释
- 注释虽然写起来很痛苦, 但对保证代码可读性至关重要.
- 注释固然很重要, 但最好的代码应当本身就是文档. 有意义的类型名和变量名, 要远胜过要用注释解释的含糊不清的名字.
- 注释风格
- 使用
//
或/* ···*/
, 统一风格就好.
- 使用
- 文件注释:
- 在每个文件开头加入版权公告, 文件注释描述了该文件的内容. 如果一个文件只声明, 或实现, 或测试了一个对象, 并且这个对象已经在它的声明处进行了详细的注释, 那么就没必要再加上文件注释. 除此之外的其他文件都需要文件注释.
- 不要在 .h 和 .cc 之间复制注释, 这样的注释偏离了注释的实际意义.
- 类注释:
- 每个类的定义都要附带一份注释, 描述类的功能和用法, 除非它的功能相当明显.
- 描述类用法的注释应当和接口定义放在一起, 描述类的操作和实现的注释应当和实现放在一起.
- 函数注释:
- 函数声明处的注释描述函数功能; 定义处的注释描述函数实现.
- 基本上每个函数声明处前都应当加上注释, 描述函数的功能和用途. 只有在函数的功能简单而明显时才能省略这些注释(例如, 简单的取值和设值函数).
- 让代码自文档化.
- 注意标点, 拼写和语法; 写的好的注释比差的要易读的多.
- TODO注释风格
- 关于注释风格, 很多 C++ 的 coders 更喜欢行注释, C coders 或许对块注释依然情有独钟, 或者在文件头大段大段的注释时使用块注释;
- 文件注释可以炫耀你的成就, 也是为了捅了篓子别人可以找你;
- 注释要言简意赅, 不要拖沓冗余, 复杂的东西简单化和简单的东西复杂化都是要被鄙视的;
- 对于 Chinese coders 来说, 用英文注释还是用中文注释, it is a problem, 但不管怎样, 注释是为了让别人看懂, 难道是为了炫耀编程语言之外的你的母语或外语水平吗;
- 注释不要太乱, 适当的缩进才会让人乐意看. 但也没有必要规定注释从第几列开始 (我自己写代码的时候总喜欢这样), UNIX/LINUX 下还可以约定是使用 tab 还是 space, 个人倾向于 space;
- TODO 很不错, 有时候, 注释确实是为了标记一些未完成的或完成的不尽如人意的地方, 这样一搜索, 就知道还有哪些活要干, 日志都省了.
格式
- 每个人都可能有自己的代码风格和格式, 但如果一个项目中的所有人都遵循同一风格的话, 这个项目就能更顺利地进行. 每个人未必能同意下述的每一处格式规则, 而且其中的不少规则需要一定时间的适应, 但整个项目服从统一的编程风格是很重要的, 只有这样才能让所有人轻松地阅读和理解代码.
- 行长度:
- 每行最多80个字符
- 尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码.
- 我们使用空格缩进. 不要在代码中使用制表符. 你应该设置编辑器将制表符转为空格.
- 使用好的参数名.
- 只有在参数未被使用或者其用途非常明显时, 才能省略参数名.
- 如果返回类型和函数名在一行放不下, 分行.
- 如果返回类型与函数声明或定义分行了, 不要缩进.
- 左圆括号总是和函数名在同一行.
- 函数名和左圆括号间永远没有空格.
- 圆括号与参数间没有空格.
- 左大括号总在最后一个参数同一行的末尾处, 不另起新行.
- 右大括号总是单独位于函数最后一行, 或者与左大括号同一行.
- 右圆括号和左大括号间总是有一个空格.
- 所有形参应尽可能对齐.
- 缺省缩进为 2 个空格.
- 换行后的参数保持 4 个空格的缩进.
- 水平留白的使用根据在代码中的位置决定. 永远不要在行尾添加没意义的留白.