您的位置:首页 > 编程语言 > C语言/C++

Google C++Style Guide【C++编程风格指南解读】——C++特性

2017-04-09 09:32 465 查看
非常重要的一节,让你的代码有明显的美观可读改善!

1:所有按引用传递的参数必须加上 const.

解读:函数传入的参数若想修改,则传入方式可以是指针或引用, 输入参数是值参或 const 引用, 输出参数为指针. 输入参数可以是 const 指针, 但决不能是非 const 的引用参数,除非用于交换,比如 swap().

好处:定义引用参数防止出现 (*pval)++ 这样丑陋的代码. 像拷贝构造函数这样的应用也是必需的. 而且更明确, 不接受 NULL 指针.

2:只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward.

解读:很少可以碰到~

3:若要用好函数重载,最好能让读者一看调用点(call site)就胸有成竹,不用花心思猜测调用的重载函数到底是哪一种。该规则适用于构造函数。

解读:如果您打算重载一个函数, 可以试试改在函数名里加上参数信息。例如,用 AppendString() 和 AppendInt() 等, 而不是一口气重载多个 Append().回头去找重载版本真的是比较的麻烦!

4:通常友元应该定义在同一文件内, 避免代码读者跑到其它文件查找使用该私有成员的类. 

解读:经常用到友元的一个地方是将 FooBuilder 声明为 Foo 的友元, 以便 FooBuilder 正确构造 Foo 的内部状态, 而无需将该状态暴露出来. 某些情况下, 将一个单元测试类声明成待测类的友元会很方便.

友元扩大了 (但没有打破) 类的封装边界. 某些情况下, 相对于将类成员声明为 public, 使用友元是更好的选择, 尤其是如果你只允许另一个类访问该类的私有成员时. 当然, 大多数类都只应该通过其提供的公有成员进行互操作.

5:禁止使用RTTI(运行时识别)和C++异常

解读:通常情况下是利大于弊的~

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

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

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

2:用 const_cast 去掉 const 限定符.

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

7:只在记录日志时使用流.

解读:这一点也是见仁见智了~

优点:

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

缺点:

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

结论:

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

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

个人觉得只要保持一致性即可~,不过大多数还是选择了printf+read/write

8:对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.

解读:可以见我另外一篇博客:http://blog.csdn.net/misayaaaaa/article/details/62893822

这里需要注意的是,迭代器和模版对象这两种一定要使用前置的++或--,效率会更高!

9:建议在任何可能的情况下都要使用 const.

解读:在声明的变量或参数前加上关键字 const 用于指明变量值不可被篡改 (如 const int foo ). 为类中的函数加上 const 限定符表明该函数不会修改类成员变量的状态 (如 class Foo { int Bar(char c) const; };).

1:如果函数不会修改传你入的引用或指针类型参数, 该参数应声明为 const.

2:尽可能将函数声明为 const. 访问函数应该总是 const. 其他不会修改任何数据成员, 未调用非 const 函数, 不会返回数据成员非 const 指针或引用的函数也应该声明成 const.

3:如果数据成员在对象构造之后不再发生变化, 可将其定义为 const.

然而, 也不要发了疯似的使用 const. 像 const int * const * const x; 就有些过了, 虽然它非常精确的描述了常量 x. 关注真正有帮助意义的信息: 前面的例子写成 const int** x 就够了.

个人习惯:将const的位置置前

10:在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化。

解读:C++ 内建整型中, 仅使用 int. 如果程序中需要不同大小的变量, 可以使用 <stdint.h> 中长度精确的整型, 如 int16_t.如果您的变量可能不小于 2^31 (2GiB), 就用 64 位变量比如 int64_t.此外要留意,哪怕您的值并不会超出
int 所能够表示的范围,在计算过程中也可能会溢出。所以拿不准时,干脆用更大的类型。

如果已知整数不会太大, 我们常常会使用 int, 如循环计数. 在类似的情况下使用原生类型 int. 你可以认为 int 至少为 32 位, 但不要认为它会多于 32 位. 如果需要 64 位整型, 用 int64_t 或 uint64_t.

对于大整数, 使用 int64_t.

11:使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之.

解读:

1:不要在 .h 文件中定义宏.

2:在马上要使用时才进行 #define, 使用后要立即 #undef.

3:不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;

4:不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为.

5:不要用 ## 处理函数,类和变量的名字。

12:整数用 0, 实数用 0.0, 指针用 nullptr 或 NULL, 字符 (串) 用 '\0'.

解读:整数用 0, 实数用 0.0, 这一点是毫无争议的.对于指针 (地址值), 到底是用 0, NULL 还是 nullptr. C++11 项目用 nullptr; C++03 项目则用 NULL, 毕竟它看起来像指针。实际上,一些 C++ 编译器对 NULL 的定义比较特殊,可以输出有用的警告,特别是 sizeof(NULL) 就和 sizeof(0) 不一样。

字符 (串) 用 '\0', 不仅类型正确而且可读性好.

13:用 auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方。

解读:特别是在遇到STL时,非常有用

sparse_hash_map<string, int>::iterator iter = m.find(val);//这样的代码看着很头疼~~


但是也可能会造成难以分辨类型的困扰啊~所以定义一下两准则~

1:auto 只能用在局部变量里用。别用在文件作用域变量,命名空间作用域变量和类数据成员里。永远别列表初始化 auto 变量。

2:auto 还可以和 C++11 特性「尾置返回类型(trailing return type)」一起用,不过后者只能用在 lambda 表达式里。

14:C++11 中,任何对象类型都可以被列表初始化。

解读:

// Vector 接收了一个初始化列表。
vector<string> v{"foo", "bar"};

// 不考虑细节上的微妙差别,大致上相同。
// 您可以任选其一。
vector<string> v = {"foo", "bar"};

// 可以配合 new 一起用。
auto p = new vector<string>{"foo", "bar"};

// map 接收了一些 pair, 列表初始化大显神威。
map<int, string> m = {{1, "one"}, {2, "2"}};

// 初始化列表也可以用在返回类型上的隐式转换。
vector<int> test_function() { return {1, 2, 3}; }

// 初始化列表可迭代。
for (int i : {-1, -2, -3}) {}

// 在函数调用里用列表初始化。
void TestFunction2(vector<int> v) {}
TestFunction2({1, 2, 3});


但是:千万别直接列表初始化 auto 变量,因为看不懂啊~~~

15:适当使用 lambda 表达式。别用默认 lambda 捕获,所有捕获都要显式写出来。

好处:

1:传函数对象给 STL 算法,Lambdas 最简易,可读性也好。

2:Lambdas, std::functions 和 std::bind 可以搭配成通用回调机制(general purpose callback mechanism);写接收有界函数为参数的函数也很容易了。

用法:

1:按 format 小用 lambda 表达式怡情。

2:禁用默认捕获,捕获都要显式写出来。打比方,比起 [=](int x) {return x + n;}, 您该写成
(int x) {return x + n;} 才对,这样读者也好一眼看出 n 是被捕获的值。

3:匿名函数始终要简短,如果函数体超过了五行,那么还不如起名(acgtyrant 注:即把 lambda 表达式赋值给对象),或改用函数。

4:如果可读性更好,就显式写出 lambd 的尾置返回类型,就像auto.

16:不要使用复杂的模板编程

解读:

1:模板编程有时候能够实现更简洁更易用的接口, 但是更多的时候却适得其反. 因此模板编程最好只用在少量的基础组件, 基础数据结构上, 因为模板带来的额外的维护成本会被大量的使用给分担掉

2:在使用模板编程或者其他复杂的模板技巧的时候, 你一定要再三考虑一下. 考虑一下你们团队成员的平均水平是否能够读懂并且能够维护你写的模板代码.或者一个非c++ 程序员和一些只是在出错的时候偶尔看一下代码的人能够读懂这些错误信息或者能够跟踪函数的调用流程. 如果你使用递归的模板实例化, 或者类型列表, 或者元函数, 又或者表达式模板, 或者依赖SFINAE, 或者sizeof 的trick 手段来检查函数是否重载, 那么这说明你模板用的太多了, 这些模板太复杂了, 我们不推荐使用

3:如果你使用模板编程, 你必须考虑尽可能的把复杂度最小化, 并且尽量不要让模板对外暴漏. 你最好只在实现里面使用模板, 然后给用户暴露的接口里面并不使用模板, 这样能提高你的接口的可读性. 并且你应该在这些使用模板的代码上写尽可能详细的注释. 你的注释里面应该详细的包含这些代码是怎么用的, 这些模板生成出来的代码大概是什么样子的. 还需要额外注意在用户错误使用你的模板代码的时候需要输出更人性化的出错信息. 因为这些出错信息也是你的接口的一部分, 所以你的代码必须调整到这些错误信息在用户看起来应该是非常容易理解,
并且用户很容易知道如何修改这些错误

这东西还是难的呀~

17:只使用 Boost 中被认可的库.Boost
库集 是一个广受欢迎, 经过同行鉴定, 免费开源的 C++ 库集.

优点:

Boost代码质量普遍较高, 可移植性好, 填补了 C++ 标准库很多空白, 如型别的特性, 更完善的绑定器, 更好的智能指针。

缺点:

某些 Boost 库提倡的编程实践可读性差, 比如元编程和其他高级模板技术, 以及过度 “函数化” 的编程风格.

译者(acgtyrant)笔记

1:实际上,缺省参数会改变函数签名的前提是改变了它接收的参数数量,比如把 void a() 改成 void a(int b = 0), 开发者改变其代码的初衷也许是,在不改变「代码兼容性」的同时,又提供了可选 int 参数的余地,然而这终究会破坏函数指针上的兼容性,毕竟函数签名确实变了。

2:此外把自带缺省参数的函数地址赋值给指针时,会丢失缺省参数信息。

3:我还发现 滥用缺省参数会害得读者光只看调用代码的话,会误以为其函数接受的参数数量比实际上还要少。

4:friend 实际上只对函数/类赋予了对其所在类的访问权限,并不是有效的声明语句。所以除了在头文件类内部写 friend 函数/类,还要在类作用域之外正式地声明一遍,最后在对应的 .cc 文件加以定义。

5:本风格指南都强调了「友元应该定义在同一文件内,避免代码读者跑到其它文件查找使用该私有成员的类」。那么可以把其声明放在类声明所在的头文件,定义也放在类定义所在的文件。

6:由于友元函数/类并不是类的一部分,自然也不会是类可调用的公有接口,于是我主张全集中放在类的尾部,即的数据成员之后,参考 声明顺序 。

7:对使用 C++ 异常处理应具有怎样的态度? 非常值得一读。

8:注意初始化 const 对象时,必须在初始化的同时值初始化。

9:用断言代替无符号整型类型,深有启发。

10:auto 在涉及迭代器的循环语句里挺常用。

11:Should the trailing return type syntax style become the default for new C++11 programs?
讨论了 auto 与尾置返回类型一起用的全新编码风格,值得一看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息