Google C++ 编程规范——学习笔记(一)

C++ 代码规范

头文件:

1. #define 保护

所有头文件都应该使用 #define 防止头文件被多重包含, 命名格式当是: <PROJECT>_<PATH

为保证唯一性头文件的命名应该依据所在项目源代码树的全路径例如项目 foo 中的头文件 foo/src/bar/baz.h 可按如下方式保护:

#ifndef FOO_BAR_BAZ_H_

#define FOO_BAR_BAZ_H_

#endif // FOO_BAR_BAZ_H_

2. 函数参数的顺序

定义函数时, 参数顺序依次为: 输入参数, 然后是输出参数.

3. #include 的路径及顺序

使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: C 库, C++ 库, 其他库的 .h, 本项目内的 .h.

总结:

    避免多重包含是学编程时最基本的要求;

    前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;

    内联函数的合理使用可提高代码执行效率;

    -inl.h 可提高代码可读性 (一般用不到吧:D);

标准化函数参数顺序可以提高可读性和易维护性 (对函数参数的堆栈空间有轻微影响我以前大多是相同类型放在一起)

包含文件的名称使用 和 .. 虽然方便却易混乱使用比较完整的项目路径看上去很清晰很条理包含文件的次序除了美观之外最重要的是可以减少隐藏依赖使每个头文件在 “最需要编译” (对应源文件处 :D) 的地方编译有人提出库文件放在最后这样出错先是项目内的文件头文件都放在对应源文件的最前面这一点足以保证内部错误的及时发现了.

作用域

2.3. 非成员函数、静态成员函数和全局函数

使用静态成员函数或名字空间内的非成员函数, 尽量不要用裸的全局函数.

2.4. 局部变量

将函数变量尽可能置于最小作用域内, 初始化

2.5. 静态和全局变量

禁止使用 class 类型的静态或全局变量: 它们会导致很难发现的 bug 和不确定的构造和析构函数调用顺序.

 cc中的匿名名字空间可避免命名冲突, 限定作用域, 避免直接使用 using 关键字污染命名空间;

 嵌套类符合局部使用原则, 只是不能在其他头文件中前置声明, 尽量不要 public;

 尽量不用全局函数和全局变量, 考虑作用域和命名空间限制, 尽量单独形成编译单元;

 多线程中的全局变量 (含静态成员变量) 不要使用 class 类型 (含 STL 容器), 避免不明确行为导致的 bug.

 作用域的使用, 除了考虑名称污染, 可读性之外, 主要是为降低耦合, 提高编译/执行效率.

3.1构造函数的职责

构造函数中只进行那些没什么意义的 (trivial, YuleFox 注: 简单初始化对于程序执行没有实际的逻辑意义, 因为成员变量 “有意义” 的值大多不在构造函数中确定) 初始化, 可能的话, 使用 Init() 方法集中初始化有意义的 (non-trivial) 数据.

3.5. 结构体 VS. 类

仅当只有数据时使用 struct, 其它一概使用 class.

3.10. 存取控制

将 所有 数据成员声明为 private, 并根据需要提供相应的存取函数. 例如, 某个名为 foo_ 的变量, 其取值函数是 foo(). 还可能需要一个赋值函数 set_foo().

3.11. 声明顺序

在类中使用特定的声明顺序: public: 在 private: 之前, 成员函数在数据成员 (变量) 前;

每个区段内的声明通常按以下顺序:

 typedefs和枚举

 常量

 构造函数

 析构函数

 成员函数, 含静态成员函数

 数据成员, 含静态数据成员

 

 不在构造函数中做太多逻辑相关的初始化;

 编译器提供的默认构造函数不会对变量进行初始化, 如果定义了其他构造函数, 编译器不再提供, 需要编码者自行提供默认构造函数;

 为避免隐式转换, 需将单参数构造函数声明为 explicit;

 为避免拷贝构造函数, 赋值操作的滥用和编译器自动生成, 可将其声明为 private 且无需实现;

 仅在作为数据集合时使用 struct;

 组合 > 实现继承 > 接口继承 > 私有继承, 子类重载的虚函数也要声明 virtual 关键字, 虽然编译器允许不这样做;

 避免使用多重继承, 使用时, 除一个基类含有实现外, 其他基类均为纯接口;

 接口类类名以 Interface 为后缀, 除提供带实现的虚析构函数, 静态成员函数外, 其他均为纯虚函数, 不定义非静态数据成员, 不提供构造函数, 提供的话,声明为 protected;

 为降低复杂性, 尽量不重载操作符, 模板, 标准类中使用时提供文档说明;

 存取函数一般内联在头文件中;

 声明次序: public -> protected -> private;

 函数体尽量短小, 紧凑, 功能单一;

其他 C++ 特性

5.1. 引用参数

所以按引用传递的参数必须加上 const

5.1. 引用参数

所以按引用传递的参数必须加上 const

5.4. 变长数组和 alloca()

我们不允许使用变长数组和 alloca().

优点:

变长数组具有浑然天成的语法. 变长数组和 alloca() 也都很高效.

缺点:

变长数组和 alloca() 不是标准 C++ 的组成部分. 更重要的是, 它们根据数据大小动态分配堆栈内存, 会引起难以发现的内存越界 bugs: “在我的机器上运行的好好的, 发布后却莫名其妙的挂掉了”.

结论:

使用安全的内存分配器, 如 scoped_ptr / scoped_array

 

.

5.8. 类型转换

使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式;

定义:

C++ 采用了有别于 C 的类型转换机制, 对转换操作进行归类.

优点:

C 语言的类型转换问题在于模棱两可的操作; 有时是在做强制转换 (如 (int)3.5), 有时是在做类型转换 (如 (int)"hello"). 另外, C++ 的类型转换在查找时更醒目.

缺点:

恶心的语法.

结论:

不要使用 C 风格类型转换. 而应该使用 C++ 风格.

 用 static_cast 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时.

 用 const_cast 去掉 const 限定符.

 用 reinterpret_cast 指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你对所做一切了然于心时使用.

 dynamic_cast测试代码以外不要使用. 除非是单元测试, 如果你需要在运行时确定类型信息, 说明有 设计缺陷.

5.9. 流

只在记录日志时使用流.

定义:

流用来替代 printf() 和 scanf().

优点:

有了流, 在打印时不需要关心对象的类型. 不用担心格式化字符串与参数列表不匹配 (虽然在 gcc 中使用 printf 也不存在这个问题). 流的构造和析构函数会自动打开和关闭对应的文件.

缺点:

流使得 pread() 等功能函数很难执行. 如果不使用 printf 风格的格式化字符串, 某些格式化操作 (尤其是常用的格式字符串 %.*s) 用流处理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而这一点对于软件国际化很有用.

结论:

不要使用流, 除非是日志接口需要. 使用 printf 之类的代替.

使用流还有很多利弊, 但代码一致性胜过一切. 不要在代码中使用流.

拓展讨论:

对这一条规则存在一些争论, 这儿给出点深层次原因. 回想一下唯一性原则 (Only One Way): 我们希望在任何时候都只使用一种确定的 I/O 类型, 使代码在所有 I/O 处都保持一致. 因此, 我们不希望用户来决定是使用流还是 printf + read/write. 相反, 我们应该决定到底用哪一种方式. 把日志作为特例是因为日志是一个非常独特的应用, 还有一些是历史原因.

流的支持者们主张流是不二之选, 但观点并不是那么清晰有力. 他们指出的流的每个优势也都是其劣势. 流最大的优势是在输出时不需要关心打印对象的类型. 这是一个亮点. 同时, 也是一个不足: 你很容易用错类型, 而编译器不会报警. 使用流时容易造成的这类错误:

cout << this;   // Prints the address

cout << *this;  // Prints the contents

 

由于 << 被重载, 编译器不会报错. 就因为这一点我们反对使用操作符重载.

有人说 printf 的格式化丑陋不堪, 易读性差, 但流也好不到哪儿去. 看看下面两段代码吧, 实现相同的功能, 哪个更清晰?

cerr << "Error connecting to '" << foo->bar()->hostname.first

     << ":" << foo->bar()->hostname.second << ": " << strerror(errno);

 

fprintf(stderr, "Error connecting to '%s:%u: %s",

        foo->bar()->hostname.first, foo->bar()->hostname.second,

        strerror(errno));

 

你可能会说, “把流封装一下就会比较好了”, 这儿可以, 其他地方呢? 而且不要忘了, 我们的目标是使语言更紧凑, 而不是添加一些别人需要学习的新装备.

每一种方式都是各有利弊, “没有最好, 只有更适合”. 简单性原则告诫我们必须从中选择其一, 最后大多数决定采用 printf + read/write.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值