务实,就要观其大略,莫纠缠于细节。
务实,就是用20%的时间学到80%有用的知识。
本系列以专题划分,各个专题并非一蹴而就,而是随学习的深入不断增添新内容,以备用时参考。
本系列侧重于“能做什么”而非“不能做什么”,侧重于“应该怎样做”而非“不该怎样做”。
如果您想通过本系列学习,最好具备以下条件:
1 学过C++
2 即将或正在使用C++编程
OK. Go!
下面是从一个好心肠的高手那里抄来的C++学习方法,值得时常回过头来看:
C++的复杂性有两种分类办法,一是分为非本质复杂性和本质复杂性;其中非本质复杂性分为缺陷和陷阱两类。另一种分类办法是按照场景分类:库开发场景下的复杂性和日常编码的复杂性。从从事日常编码的实践者的角度来说,采用后一种分类可以让我们迅速掌握80%场景下的复杂性。
二八法则
以下通过列举一些常见的例子来解释这种分类标准:
80%场景下的复杂性:
1. 资源管理(C++日常复杂性的最主要来源):深拷贝&浅拷贝;类的四个特殊成员函数;使用STL;RAII惯用法;智能指针等等。
2. 对象生命期:局部&全局对象生存期;临时对象销毁;对象构造&析构顺序等等。
3. 多态
4. 重载决议
5. 异常(除非你不用异常):栈开解(stack-unwinding)的过程;什么时候抛出异常;在什么抽象层面上抛出异常等等。
6. undefined&unspecified&implementation defined三种行为的区别:i++ + ++i是undefined behavior(未定义行为——即“有问题的,坏的行为,理论上什么事情都可能发生”);参数的求值顺序是unspecified(未指定的——即“你不能依赖某个特定顺序,但其行为是良好定义的”);当一个double转换至一个float时,如果double变量的值不能精确表达在一个float中,那么选取下一个接近的离散值还是上一个接近的离散值是implementation defined(实现定义的——即“你可以在实现商的编译器文档中找到说明”)。这些问题会影响到你编写可移植的代码。
(注:以上只是一个不完全列表,用于演示该分类标准的意义——实际上,如果我们只考虑“80%场景下的复杂性”,记忆和学习的负担便会大大减小。)
20%场景下的复杂性:
1. 对象内存布局
2. 模板:偏特化;非类型模板参数;模板参数推导规则;实例化;二段式名字查找;元编程等等。
3. 名字查找&绑定规则
4. 各种缺陷以及缺陷衍生的workarounds(C++书中把这些叫做“技术”):不支持concepts(boost.concept_check库);类型透明的typedef(true-typedef惯用法);弱类型的枚举(强枚举惯用法);隐式bool转换(safe-bool惯用法);自定义类型不支持初始化列表(boost.assign库);孱弱的元编程支持(type-traits惯用法;tag-dispatch惯用法;boost.enable_if库;boost.static_assert库);右值缺陷(loki.mojo库);不支持可变数目的模板参数列表(type-list惯用法);不支持native的alignment指定。
(注:以上只是一个不完全列表。你会发现,这些细节或技术在日常编程中极少用到,尤其是各种语言缺陷衍生出来的workarounds,构成了一个巨大的长尾,在无论是C++的书还是文献中都占有了很大的比重,作者们称它们为技术,然而实际上这些“技术”绝大多数只在库开发当中需要用到。)
非本质复杂性&本质复杂性
此外,考虑另一种分类办法也是有帮助的,即分为非本质复杂性和本质复杂性。
非本质复杂性(不完全列表)
1. 缺陷(指能够克服的问题,但解决方案很笨拙;C++的书里面把克服缺陷的workarounds称作技术,我觉得非常误导):例子在前面已经列了一堆了。
2. 陷阱(指无法克服的问题,只能小心绕过;如果跌进去,那就意味着你不知道这个陷阱,那么很大可能性你也不知道从哪去解决这个问题):一般来说,作为一个合格的程序员(不管是不是C++程序员),80%场景下的语言陷阱是需要记住才行的。比如深拷贝&浅拷贝;基类的析构函数应当为虚;缺省生成的类成员函数;求值顺序&序列点;类成员初始化顺序&声明顺序;导致不可移植代码的实现相关问题等。
本质复杂性(不完全列表)
1. 内存管理
2. 对象生命期
3. 重载决议
4. 名字查找
5. 模板参数推导规则
6. 异常
7. OO(动态)和GP(静态)两种范式的应用场景和交互
总而言之,这一节的目的是要告诉你从一个较高的层次去把握C++中的复杂性。其中最重要的一个指导思想就是在学习的过程中注意你正学习的技术或细节到底是80%场景下的还是20%场景下的(一般来说,读完两本书——后面会提到——之后你就能够很容易的对此进行判断了),如果是20%场景下的(有大量这类复杂性,其中尤数各种各样的workarounds为巨),那么也许最好的做法是只记住一个大概,不去作任何深究。此外,一般来说,不管使用哪门语言,认识语言陷阱对于编程来说都是一个必要的条件,语言陷阱的特点是如果你掉进去了,那么很大可能意味着你本来就不知道这有个陷阱,后者很大可能意味着你不知道如何解决。