从《C++ Primer 第四版》入手学习C++
为什么要学习C++?
2009 年本书作者Stan Lippman 先生来华参加上海祝成科技举办的C++技术大会,他表示人
们现在还用C++的惟一理由是其性能。相比之下,Java/C#/Python等语言更加易学易用并且
开发工具丰富,它们的开发效率都高于C++。但C++目前仍然是运行最快的语言1,如果你
的应用领域确实在乎这个性能,那么 C++ 是不二之选。
这里略举几个例子2。对于手持设备而言,提高运行效率意味着完成相同的任务需要更少的
电能,从而延长设备的操作时间,增强用户体验。对于嵌入式3设备而言,提高运行效率意
味着:实现相同的功能可以选用较低档的处理器和较少的存储器,降低单个设备的成本;
如果设备销量大到一定的规模,可以弥补C++开发的成本。对于分布式系统而言,提高10%的性能就意味着节约10%的机器和能源。如果系统大到一定的规模(数千台服务器),值
得用程序员的时间去换取机器的时间和数量,可以降低总体成本。另外,对于某些延迟敏
感的应用(游戏4,金融交易),通常不能容忍垃圾收集(GC)带来的不确定延时,而C++可
以自动并精确地控制对象销毁和内存释放时机5。我曾经不止一次见到,出于性能原因,用
C++重写现有的Java或C#程序。
C++之父Bjarne Stroustrup把C++定位于偏重系统编程(system programming) 6的通用程序设计
语言,开发信息基础架构(infrastructure)是C++的重要用途之一7。Herb Sutter总结道8,C++
注重运行效率(efficiency)、灵活性(flexibility)9和抽象能力(abstraction),并为此付出了生产力(productivity)方面的代价10。用本书作者的话来说,C++ is about efficient programming with abstractions。C++的核心价值在于能写出“运行效率不打折扣的抽象11”。
1见编程语言性能对比网站 shootout./ 和Google 员工写的语言性能对比论文
/sites/days2011/files/ws3-1-Hundt.pdf
2 C++之父Bjarne Stroustrup维护的C++用户列表:search.att/~bs/applications.html
3初窥C++在嵌入式系统中的应用,请见aristeia/TalkNotes/MISRA_Day_2010.pdf
4 Milo Yip在《C++强大背后》提到大部分游戏引擎(如Unreal/Source)及中间件(如Havok/FMOD)是C++
实现的。wwwblogs/miloyip/archive/2010/09/17/behind_cplusplus.html
5孟岩《垃圾收集机制批判》:C++利用智能指针达成的效果是,一旦某对象不再被引用,系统刻不容缓,立刻回收内存。这通常发生在关键任务完成后的清理(clean up)时期,不会影响关键任务的实时性,同时,内存里所有的对象都是有用的,绝对没有垃圾空占内存。
blog.csdn/myan/article/details/1906
6有人半开玩笑地说“所谓系统编程,就是那些CPU时间比程序员的时间更重要的工作。”
7《Software Development for Infrastructure》 search.att/~bs/Computer-Jan12.pdf
8 Herb Sutter在C++ and Beyond 2011会议上的开场演讲《Why C++?》
channel9.msdn/posts/C-and-Beyond-2011-Herb-Sutter-Why-C
9这里的灵活性指的是编译器不阻止你干你想干的事情,比如为了追求运行效率而实现即时编译(just-
in-time compilation)。
c语言如何去学10我曾向Stan Lippman介绍目前我在Linux下的工作环境(编辑器、编译器、调试器),他表示这跟
他在1970年代的工作环境相差无几,可见C++在开发工具方面的落后。另外C++的编译运行调试周
期也比现代的语言长,这多少影响了工作效率。
11可参考Ulrich Drepper在《Stop Underutilizing Your Computer》中举的SIMD例子。
dhat/f/pdf/summit/udrepper_945_stop_underutilizing.pdf
要想发挥C++的性能优势,程序员需要对语言本身及各种操作的代价有深入的了解12,特别要避免不必要的对象创建13。例如下面这个函数如果漏写了&,功能还是正确的,但性能将会大打折扣。编译器和单元测试都无法帮我们查出此类错误,程序员自己在编码时须得小心在意。
inline int find_longest(const std::vector<std::string>& words)
{
// std::max_element(words.begin(), d(), LengthCompare());
}
在现代CPU体系结构下,C++ 的性能优势很大程度上得益于对内存布局(memory layout )的
精确控制,从而优化内存访问的局部性14(locality of reference)并充分利用内存阶层(memory hierarchy )提速15,这一点优势在近期内不会被基于GC的语言赶上16。
C++的协作性不如C、Java、Python,开源项目也比这几个语言少得多,因此在TIOBE语言
流行榜中节节下滑。但是据我所知,很多企业内部使用C++来构建自己的分布式系统基础
架构,并且有替换Java开源实现的趋势。
学习C++只需要读一本大部头
C++不是特性(features)最丰富的语言,却是最复杂的语言,诸多语言特性相互干扰,使其复杂度成倍增加。鉴于其学习难度和知识点之间的关联性,恐怕不能用“粗粗看看语法,就撸起袖子开干,边查Google边学习17”这种方式来学习C++,那样很容易掉到陷阱里或养成坏的编程习惯。如果想成为专业
C++开发者,全面而深入地了解这门复杂语言及其标准库,你需要一本系统而权威的书,这样的书必定会是一本八九百页的大部头18。
兼具系统性和权威性19的C++教材有两本,C++之父Bjarne Stroustrup的代表作《The C++ Programming Language》和Stan Lippman的这本《C++ Primer》。侯捷先生评价道:“泰山北斗已现,又何必案牍劳形于墨瀚书海之中!这两本书都从C++盘古开天以来,一路改版,斩将擎旗,追奔逐北,成就一生荣光20。”
从实用的角度,这两本书读一本即可,因为它们覆盖的C++知识点相差无几。就我个人的阅读体验而言,Primer更易读一些,我十年前深入学习C++正是用的《C++ Primer第三版》。
12《Technical Report on C++ Performance》 /jtc1/sc22/wg21/docs/18015.html
13可参考Scott Meyers的《Effective C++ in an Embedded Environment》
www.artima/shop/effective_cpp_in_an_embedded_environment
14我们知道std::list的任一位置插入是O(1)操作,而std::vector的任一位置插入是O(N)操作,但由于std::vector的元素布局更加紧凑(compact),很多时候std::vector的随机插入性能甚至会高于std::list。
见ecn.channel9.msdn/events/GoingNative12/GN12Cpp11Style.pdf 这也佐证std::vector是首
选容器。
15可参考Scott Meyers的技术报告《CPU Caches and Why You Care》和任何一本现代的计算机体系结
构教材 aristeia/TalkNotes/ACCU2011_CPUCaches.pdf
16 Bjarne Stroustrup有一篇论文《Abstraction and the C++ machine model》对比了C++和Java的对象内
存布局。 search.att/~bs/abstraction-and-machine.pdf
17语出孟岩《快速掌握一个语言最常用的50%》 blog.csdn/myan/article/details/3144661
18同样篇幅的Java/C#/Python教材可以从语言、标准库一路讲到多线程、网络编程、图形编程。
19“权威”的意思是说你不用担心作者讲错了,能达到这个水准的C++图书作者全世界也屈指可数。20侯捷《大道之行也——C++ Primer 3/e译序》 jjhou.boolan/cpp-primer-foreword.pdf
这次借评注的机会仔细阅读了《C++ Primer第四版》,感觉像在读一本完全不同的新书。第四版内容组织及文字表达比第三版进步很多21,第三版可谓“事无巨细、面面俱到”,第四版重点突出详略得当,甚至篇幅也缩短了,这多半归功于新加盟的作者Barbara Moo。
《C++ Primer 第四版》讲什么?适合谁读?
这是一本C++语言的教程,不是编程教程。本书不讲八皇后问题、Huffman编码、汉诺塔、约瑟夫环、大整数运算等等经典编程例题,本书的例子和习题往往都跟C++本身直接相关。本书的主要内容是精解C++语法(syntax)与语意(semantics),并介绍C++标准库的大部分内容(含STL)。“这本书在全世界C++教学领域的突出和重要,已经无须我再赘言22。”
本书适合C++语言的初学者,但不适合编程初学者。换言之,这本书可以是你的第一本C++ 书,但恐怕不能作为第一本编程书。如果你不知道什么是变量、赋值、分支、条件、循环、函数,你需要一本更加初级的书23,本书第1章可用作自测题。
如果你已经学过一门编程语言,并且打算成为专业C++开发者,从《C++ Primer 第四版》入手不会让你走弯路。值得特别说明的是,学习本书不需要事先具备C语言知识。相反,这本书教你编写真正的C++程序,而不是披着C++ 外衣的C程序。
《C++ Primer 第四版》的定位是语言教材,不是语言规格书,它并没有面面俱到地谈到C++的每一个角落,而是重点讲解C++程序员日常工作中真正有用的、必须掌握的语言设施和标准库24。本书的作者一点也不炫耀自己的知识和技巧,虽然他们有十足的资本25。这本书用语非常严谨(没有那些似是而非的比喻),用词平和,讲解细致,读起来并不枯燥。特别是如果你已经有一定的编程经验,在阅读时不妨思考如何用C++来更好地完成以往的编程任务。
尽管本书篇幅近900页,其内容还是十分紧凑,很多地方读一个句子就值得写一小段代码
去验证。为了节省篇幅,本书经常修改前文代码中的一两行,来说明新的知识点,值得把
每一行代码敲到机器中去验证。习题当然也不能轻易放过。
《C++ Primer 第四版》体系了现代C++教学与编程理念:在现成的高质量类库上构建自己的程序,而不是什么都从头自己写。这本书在第三章介绍了string和vector这两个常用的类,立刻就能写出很多有用的程序。但作者不是一次性把string的上百个成员函数一一列举,而是有选择地讲解了最常用的那几个函数。
《C++ Primer 第四版》的代码示例质量很高,不是那种随手写的玩具代码。第10.4.2节实现了带禁用词的单词计数,第10.6利用标准库容器简洁地实现了基于倒排索引思路的文本检索,第15.9节又用面向对象方法扩充了文本检索的功能,支持布尔查询。值得一提的是,
21 Bjarne Stroustrup在《Programming--- Principles and Practice Using C++》的参考文献中引用了本书,
并特别注明 use only the 4th edition.
22侯捷《C++ Primer 4/e译序》
23如果没有时间精读注21中提到的那本大部头,短小精干的《Accelerated C++》亦是上佳之选。另
外如果想从C语言入手,我推荐裘宗燕老师的《从问题到程序:程序设计与C语言引论(第2版)》
24本书把iostream的格式化输出放到附录,彻底不谈locale/facet,可谓匠心独运。
25 Stan Lippman曾说:Virtual base class support wanders off into The material is simply
too esoteric to
这本书讲解继承和多态时举的例子符合Liskov替换原则,是正宗的面向对象。相反,某些
教材以复用基类代码为目的,常以“人、学生、老师、教授”或“雇员、经理、销售、合
同工”为例,这是误用了面向对象的“复用”。
《C++ Primer 第四版》出版于2005年,遵循2003年的C++语言标准26。C++新标准已于2011年定案(称为C++11),本书不涉及TR127和C++11,这并不意味着这本书过时了28。
相反,这本书里沉淀的都是当前广泛使用的C++编程实践,学习它可谓正当时。评注版也
不会越俎代庖地介绍这些新内容,但是会指出哪些语言设施已在新标准中废弃,避免读者
浪费精力。
《C++ Primer 第四版》是平台中立的,并不针对特定的编译器或操作系统。目前最主流的
C++编译器有两个, GNU G++和微软Visual C++。实际上,这两个编译器阵营基本上“模塑29”了C++语言的行为。理论上讲,C++语言的行为是由C++标准规定的。但是C++不像其他很
多语言有“官方参考实现30”,因此C++的行为实际上是由语言标准、几大主流编译器、现
有不计其数的C++产品代码共同确定的,三者相互制约。C++编译器不光要尽可能符合标准,同时也要遵循目标平台的成文或不成文规范和约定,例如高效地利用硬件资源、兼容操作
系统提供的C语言接口等等。在C++标准没有明文规定的地方,C++编译器也不能随心所欲
自由发挥。学习C++的要点之一是明白哪些行为是由标准保证的,哪些是由实现(软硬件
平台和编译器)保证的31,哪些是编译器自由实现,没有保证的;换言之,明白哪些程序行
为是可依赖的。从学习的角度,我建议如果有条件不妨两个编译器都用32,相互比照,避免
把编译器和平台特定的行为误解为C++语言规定的行为。尽管不是每个人都需要写跨平台
的代码,但也大可不必自我限定在编译器的某个特定版本,毕竟编译器是会升级的。
本着“练从难处练,用从易处用”的精神,我建议在命令行下编译运行本书的示例代码,
并尽量少用调试器。另外,值得了解C++的编译链接模型,这样才能不被实际开发中遇到
的编译错误或链接错误绊住手脚。(C++不像现代语言那样有完善的模块(module)和包(package)设施,它从C语言继承了头文件、源文件、库文件等古老的模块化机制,这套机
制相对较为脆弱,需要花一定时间学习规范的做法,避免误用。)
就学习C++语言本身而言,我认为有几个练习非常值得一做。这不是“重复发明轮子”,
而是必要的编程练习,帮助你熟悉掌握这门语言。一是写一个复数类或者大整数类33,实现
基本的运算,熟悉封装与数据抽象。二是写一个字符串类,熟悉内存管理与拷贝控制。三
是写一个简化的vector<T>类模板,熟悉基本的模板编程,你的这个vector应该能放入int
26基本等同于1998年的初版C++标准,修正了编译器作者关心的一些问题,与普通程序员基本无关。
27 TR1是2005年C++标准库的一次扩充,增加了智能指针、bind/function、哈希表、正则表达式等。
28作者正在编写《C++ Primer 第五版》,会包含C++11的内容。
29 G++统治了Linux平台,并且能用在很多Unix平台上;Visual C++统治了Windows平台。其他C++
编译器的行为通常要向它们靠拢,例如Intel C++在Linux上要兼容G++,而在Windows上要兼容
Visual C++。
30曾经是Cfront,本身作者正是其主要开发者。/projects/c_plus_plus 31包括C++标准有规定,但编译器拒绝遵循的。stackoverflow/questions/3931312/value-initialization-and-non-pod-types
32 G++ 是免费的,可使用较新的4.x版,最好32-bit和64-bit一起用,因为服务端已经普及64位。
微软也有免费的编译器,可考虑Visual C++ 2010 Express,建议不要用老掉牙的Visual C++ 6.0作为学
习平台。
33大整数类可以以std::vector<int>为成员变量,避免手动资源管理。
和string等元素类型。四是写一个表达式计算器,实现一个节点类的继承体系(右图),体会面向对象编程。前三个练习是写独立的值语义的类,第四个练习是对象语义,同时要考虑类与类之间的关系。
表达式计算器能把四则运算式3+2*4解析为左图的表达式树34,对根节点调用calculate()虚函数就能算出表达式的值。做完之后还可以再扩充功能,比如支持三角函数和变量。
在写完面向对象版的表达式树之后,还可以略微尝试泛型编程。比如把类的继承体系简化为下图,然后用BinaryNode<std::plus<double> >和BinaryNode<std:: multiplies<double> >来具现化BinaryNode<T>类模板,通过控制模板参数的类型来实现不同的运算。
在表达式树这个例子中,节点对象是动态创建的,值得思考:如何才能安全地、不重不漏地释放内存。本书第15.8节的Handle可供参考。(C++的面向对象基础设施相对于现代的语言而言显得很简陋,现在C++也不再以“支持面向对象”为卖点了。)
C++难学吗?“能够靠读书看文章读代码做练习学会的东西没什么门槛,智力正常的人只要愿意花功夫,都不难达到(不错)的程度。”35C++好书很多,不过优秀的C++开源代码很少,而且风格迥异36。我这里按个人口味和经验列几个供读者参考阅读:Google的protobuf、leveldb、PCRE的C++ 封装,我自己写的muduo网络库。这些代码都不长,功能明确,阅读难度不大。我不建议一开始就读STL或Boost的源码,因为编写通用C++模板库和编写C++
应用程序的知识体系相差很大。另外可以考虑读一些优秀的C开源项目,并思考是否可以
用C++更好地实现或封装之。
34“解析”可以用数据结构课程介绍的逆波兰表达式方法,也可以用编译原理中介绍的递归下降法,还可以用专门的Packrat算法。可参考lisoft/book/lang/poly/3tree.html
35孟岩《技术路线的选择重要但不具有决定性》 blog.csdn/myan/article/details/3247071
36从代码风格上往往能判断项目成型的时代。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论