这是一本好书, 可以让你认清自己对C++的掌握程度.
看完之后,给自己打分,我对C++了解多少? 答案是不足20分.
对于我自己是理所当然的问题, 就不提了, 记一些有启发的条目和细节:
(*号表示不能完全理解,实力升级了之后回头看)
一般性问题:
1. 不要在注释中重复写代码语义,这样很容易产生不一致. 应该编写的是解释方法和原理的说明性注释.
2. 不要对每个项目每个文件进行强制的排版格式规定, 在同一个文件中风格一致即可.
3. 匈牙利记法是混用了类型不安全语言中的设施, 在面向对象语言中可以存在, 但是有害无益, 在泛型编程中则根本不可行, 在规范中禁用该记法则是合理的.
4. 用提倡更短小更简单的函数来代替规定函数单入口单出口.
5. “定义了从未使用的变量”这个警告可以通过无意义地使用它一下而消除.(如果有必要的话)
6. “遗漏了return语句”可以通过在函数尾部加上一个ASSERT(!”should never get here!”);return -1;来消除警告(!”string”的结果为false)
7. 使用自动构建系统(自动的,可靠的,单操作的)
8. 使用版本控制系统(推荐CVS, 5人日以上的项目都推荐使用CVS)
9. 在代码审查上投入.(准备一份check list)
a) 良性压力
b) 提高质量
c) 思想交流
d) 快速培养入门者
e) 形成团队精神
f) 增强整体实力,提升自信心,动力和职业荣誉感
设计风格(依赖性管理):
10. 一个实体应该只有一个紧凑的职责(变量,类,函数,名称空间,模块和库),随着实体变大,其职责范围也会扩大,但不应该发散.
11. 正确,简单和清晰第一: 正确优于速度. 简单优于复杂. 清晰优于机巧. 安全优于不安全.
要避免使用程序设计语言中的冷僻特性. 应该使用最简单的有效技术
12. 编程中应知道何时和如何考虑可伸缩性
a) 使用灵活的,动态分配的数组
b) 了解算法的实际复杂性
c) 优先使用线性算法或者尽可能快的算法
d) 尽可能避免劣于线性复杂性的算法
e) 永远不要使用指数复杂性的算法
13. 不要进行不成熟的优化: 首先要保持代码的清晰可读, 易维护, 易重构.
14. 不要进行不成熟的劣化: 放松自己, 轻松编程. 不要悲观对自己没有信心.
a) 可以使用引用传递却用了值传递
b) 使用++前缀很适合却用了后缀
c) 在构造函数中赋值而不是使用初始化列表
d) 使用抽象和库.
15. 尽量减少全局和共享数据: 避免共享数据,尤其是全局数据. 共享数据会增加耦合度, 从而降低可维护性, 通常还降低性能. 为”无共享”而奋斗吧, 用通信方式(比如消息队列)来代替数据共享.
16. 隐藏信息: 不要公开提供抽象的实体的内部信息. 绝对不要将类的数据成员设为Public
17. 懂得何时和如何进行并发性编程.(这部分涉及的不多, 没有多少体会, 回头补看) 如果应用程序使用了多个线程或者进程, 应该知道如何尽量减少共享对象, 以及如何安全地共享必须共享的对象. 最重要的是避免死锁, 活锁和恶性的竞争条件.
18. 确保资源为对象所拥有. 使用显式的RAII(资源获取即初始化)和智能指针.
19. 不要在同一条语句内分配一个以上资源.
20. 宁可编译时和连接时错误, 也不要运行时错误(这条的论述不理解, 没概念, 回头重看.)
21. *积极使用const
22. 避免使用宏: 在C++中可以用const或者enum定义易于理解的常量,用inline避免函数调用的开销, 用template指定函数系列和类型系列, 用namespace避免名称冲突.(略去一大堆攻击宏的叙述.) 但#include #ifdef和assert仍不可避免.
23. 避免使用”魔数”: 用符号名称和表达式替换他们. 应该用符号常量代替直接写死的字符串,将字符串与代码分开(所有字符串放在一个CPP文件中), 这样非程序员也可以对其进行检查和更新.
24. 重要的特定于领域的常量可以放在名字空间一级.
25. 特定于类的常量, 可以在类定义中定义静态整数常量.(在.h中声明常量,在.cpp中定义值)
26. 尽可能局部地声明变量. 因为不能合理的初始化变量是c和c++错误的普遍来源. 但因为常量并不添加状态, 所以本条对常量并不适用.
27. 总是初始化变量 :未初始化的变量是C和C++中错误的常见来源。养成在使用内存之前先清除的习惯,可以避免这种错误,在变量定义的时候就初始化。
28. 使用默认初始值或者?:减少数据流和控制流的混合。
29. 用函数替代复杂的计算流。
30. 初始化数组,正确的初始化并不见得真的要对所有的数据进行操作。
比如:char path[MAX_PATH] = [‘/0’] 创建一个以零填充的空路径(c风格字符串)。
31. 避免函数过长,避免嵌套过深
a) 尽量紧凑,对一个函数只赋予一种职责
b) 不要自我重复
c) 优先使用&&(相对于if嵌套)
d) 不要过分使用try
e) 优先使用标准算法
f) 不要根据类型标签(type tag)进行分支(switch),优先使用多态函数
32. 避免跨编译单元的初始化依赖
33. 尽量减少定义性依赖。避免循环依赖。
a) 依赖倒置原理:不要让高层模块依赖于低层模块。应该让两者都依赖于抽象。
34. *头文件应该自给自足
35. 总是编写内部#include保护符,绝不要编写外部#include保护符。(好不容易才理解,累死了,就是说在头文件本身写保护符,而不是在包含的时候再用保护符。这样可以避免保护符名称不一致。在包含的时候就用不着保护符了。)
36. 正确的选择通过值、(智能)指针或者引用传递参数:分清输入参数、输出参数和输入/输出参数,分清值参数和引用参数。正确的传递参数。
37. 对于只做输入的参数:
a) 始终用const限制所有指向只输入参数的指针和引用。
b) 优先通过值来取得原始类型和复制开销比较低的值的对象。
c) 优先按const的引用取得其他用户定义类型的输入。
d) 如果函数需要其参数的副本,则可以考虑通过值传递代替通过引用传递。这在概念上等同于通过const引用传递加上一次复制,能够帮助编译器更好的优化掉临时变量。
38. 对于输出参数或者输入/输出参数:
a) 如果参数是可选的,或者函数需要保存这个指针的副本或者操控参数的所有权,那么优先通过(智能)指针传递。
b) 如果参数是必须的,而且函数无需保存指向参数的指针,或者无需操控其所有权,那么应该优先通过引用传递。这表明参数是必须的,而且调用者必须提供有效对象。
39. 保持重载操作符的自然语义
40. *优先使用运算操作符和复制操作符的标准形式。
41. 优先使用++和--的标准形式。优先调用前缀形式,因为其少创建了一个对象。这是成熟的优化。
42. 考虑重载以避免隐含类型转换。(不必要经历创建临时变量的麻烦)
43. 避免重载&&、||或,(逗号)(危害一大堆)
a) 内置的&&||符号是短路操作符,遇到false,&&会停止计算,遇到true,||会停止计算。
44. 不要编写依赖于函数参数求值顺序的代码:函数参数的求值顺序是不确定的。
45. *弄清所要编写的是那种类,然后根据具体情况编写。
分为:值类 基类 traits 策略类 异常类。
46. 用小类代替巨类。
47. 用组合代替继承:继承仅次于友元,是C++中第二紧密的耦合关系。紧密的耦合是一种不良现象,应该尽量避免。(后面还有好处一大堆,但是没提模式。)
48. 避免从并非要设计成基类的类中继承:要添加行为,应该添加非成员函数而不是成员函数,要添加状态,应该使用组合而不是继承。要避免从具体的基类继承。
49. 优先提供抽象接口:抽象接口有助于我们集中精力保证抽象的正确性,不至于受到实现或者状态管理细节的干扰。优先采用实现了(建模抽象概念的)抽象接口的设计层次结构。DIP(Dependency Inversioin Principle 依赖倒置原理)有三个基本的设计优点:更强壮,更灵活,更好的模块性。
50. 公用继承即可替换性。继承不是为了重用,而是为了被重用。
51. 使用继承的时候,不说“是一个”,而说“其行为像一个”,这样可以避免描述易于误解。
52. *实施安全的改写。
53. *考虑将虚拟函数声明为非公用的,将公用函数声明为非虚拟的。
54. *要避免提供隐式转换
55. *将数据成员设为私有的, 无行为的聚集(C语言形式的struct)
56. *不要公开内部数据
57. *明智地使用Pimpl
58. *优先编写非成员非友元函数
59. *总是一起提供new和delete(这里指重载它们之一时)
60. *如果提供类专门的new, 应该提供所有标准形式. (普通,就地和不抛出)
61. 以同样的顺序定义和初始化成员变量
62. 在构造函数中用初始化代替赋值.
63. 避免在构造函数和析构函数中调用虚拟函数(*可以使用”后构造函数”来实现类似目的)
64. *将基类析构函数设为公用且虚拟的, 或者保护且非虚拟的.
65. *析构函数,释放和交换绝对不能失败.
66. 一致地进行赋值和销毁.如果定义了赋值构造函数, 复制赋值操作符或者析构函数中的任何一个, 那么可能也需要定义另一个或另外两个.
67. 显式地启用或者禁止复制.
a) 如果赋值对你的类型没有意义, 那就通过将它们声明为私有的未实现函数来禁止复制构造和复制赋值.
b) 如果复制和复制赋值适合于T对象,但是正确的复制行为又不同于编译器生成的版本, 那么就自己编写函数并将他们设为私有的.
c) 使用编译器生成的版本,最好是加上一个明确的注释.
68. *避免切片. 在基类中考虑用克隆代替复制.
69. *使用标准的赋值形式. 在实现operator=时, 应该使用标准形式—具有特定签名的非虚拟形式.
70. *只要可行,就提供不会失败的swap (而且要正确的提供)
71. 将类型及其非成员函数接口置于同一名字空间中.
72. *应该将类型和函数分别置于不同的名字空间中,除非有意想让它们一起工作.
73. 不要在头文件中或者#include之前编写名字空间using
74. 要避免在不同的模块中分配和释放内存
75. *不要在头文件中定义具有链接的实体
76. *不要允许异常跨越模块边界传播.
77. *在模块的接口中使用具有良好可移植性的类型
78. *理智的结合静态多态性和动态多态性
79. 多态性的优势在于,同一段代码能够操作不同类型,甚至可以是在编写代码时不知道的类型.
80. *有意的进行显式自定义
81. *不要特化模板函数
82. 不要无意地编写不通用的代码
a) 使用!=代替<对迭代器进行比较
b) 使用迭代替代索引访问
c) 使用empty()代替size()==0
d) 使用层次结构中最高层的类提供需要的功能
e) 编写常量正确的代码
83. 广泛的使用断言记录内部假设和不变式
84. 千万不要在assert语句中编写具有副作用的表达式
85. 建立合理的错误处理策略, 并严格遵守. 包括
a) 鉴别: 哪些情况属于错误
b) 严重程度: 每个错误的严重或者紧急性
c) 检查: 哪些代码负责检查错误
d) 传递: 用什么机制在模块中报告和传递错误通知
e) 处理: 哪些代码负责处理错误
f) 报告: 怎样将错误记入日志, 或通知用户.
86. *区别错误与非错误
87. *设计和编写错误安全代码
88. *优先使用异常报告错误
89. 通过值抛出, 通过引用捕获
90. *正确地报告, 处理和转换错误
91. *避免使用异常规范
92. 默认时使用vector. 否则, 选择其他适合的容器
a) 编程时正确,简单和清晰是第一位的
b) 编程时只在必要时才考虑效率
c) 尽可能编写事务性的. 强错误安全的代码
93. vector具有如下性质:
a) 保证所有容器中最低的空间开销
b) 保证具有所有容器中对所存放元素进行存取的速度最快
c) 保证与生俱来的引用局部性,也就是说容器中相邻对象保证在内存中也相邻,这是其它标准容器无法保证的
d) 保证具有C语言兼容的内存布局,这与其它标准容器也不同
e) 保证具有所有容器中最灵活的迭代器(随机访问迭代器)
f) 几乎肯定具有最快的迭代器(指针,或性能向但与类,在非调试模式下迭代 器类经常可以被编译成与指针具有相同的速度),比其他所有容器的迭代器都快.
94. 用vector和string代替数组
a) 它们能够自动管理内存
b) 它们具有丰富的接口
c) 它们与C内存模型兼容
d) 它们能够提供更大范围的检查
e) 它们支持上述特性并未牺牲太多效率
f) 它们有助于优化
95. *使用vector(和string::c_str)与非C++API交换数据
96. *在容器中只存储值和智能指针
97. *用push_back代替其它扩展序列的方式
98. 多用范围操作, 少用单元素操作
99. *使用公认的惯用法真正的压缩容量,真正地删除元素
100. 算法即循环—只是更好. 算法是循环的”模式”, 在只用for语句实现的原始循环上增加了很多语义内容和其他丰富内容.
101. *使用带检查的STL实现
102. *用算法调用代替手工编写的循环
a) 提高交流层次
b) 正确性, 效率较好.
103. *使用正确的STL排序算法
a) Partition, stable_partition和nth_element算法是线性时间的.
b) Partial_sort, sort, stable_sort和nth_element需要随机访问迭代器.
104. *使谓词成为纯函数
a) 谓词就是返回是或否的函数对象. 从数学意义上来说, 如果函数的结果只取决于其参数,则该函数就是一个纯函数.
105. *算法和比较器的参数应多用函数对象少用函数
106. *正确编写函数对象
107. 避免使用类型分支,多使用多态
108. *依赖类型, 而非其表示方式
a) 不要对对象在内存中的准确表示方式做任何假设. 相反应该让类型决定如何在内存中读写其对象.
109. 避免使用reinterpret_cast
110. 避免对指针使用static_cast
a) 用dynamic_cast, 重构, 甚至重新设计来代替它.
111. 避免强制转换const
112. *不要使用c风格的强制转换
113. 不要对非POD进行memcpy或者memcmp操作
114. *不要使用联合重新解释表示方式
115. 不要使用可变长参数(…)
116. 不要使用失效对象. 不要使用不安全函数
a) 已销毁对象
b) 语义失效对象
c) 从来都有效的对象
117. *不要多态地处理数组