The world is built on C++.
——Herb Sutter
the chairman of the ISO C++ standards committee
and chief native languages architect at Microsoft
前传 C++世界地图
如果我们要到某个陌生的地方去旅行,我们往往有很多疑问:这个地方有什么好玩的?在哪儿可以吃饭?在哪儿又可以住店?这时我们只需要一张内容详尽的旅行地图就可以解决这些问题。同样地,对于即将进入C++世界的我们,心中同样也有着很多疑问:C++是什么?C++是怎么来的?它能做什么?而我又如何才能完成我的第一个C++程序?面对这些问题,我们所需要的同样是一张C++世界地图。它可以解答我们的这些问题,让我们清晰地认识C++世界。同时,我们还可以通过这张C++世界地图更多地了解C++世界:有哪些好玩的地方,有什么有趣的故事,有哪些有用的知识与经验,又有什么危险而需要注意的陷阱。这张C++世界地图,将带领我们畅游整个C++世界。
还等什么?让我们带上它马上出发吧!
1.1 C++是什么
C++是什么?
这几乎是每个第一次来到C++世界的旅行者都会问的第一个问题。在百科全书上,它的解释是“C++是一种静态数据类型检查的、支持多种编程范式(面向过程与面向对象等)的通用程序设计语言”。虽然这里的静态数据类型、面向对象等修饰语我们还看不太懂,但这里我们至少知道了一点——C++是一种语言,更确切地说,是一种用于程序设计的语言。就像现实世界中的自然语言是用来表达我们的思想一样,C++作为一门程序设计语言,同样也是用来表达我们的思想的,只不过接受它的对象是计算机,所以它有着不同的语法和表达方式。只要我们学会了英语,就可以用英语跟老外交流。同样地,只要我们学会了C++,也同样可以用C++跟计算机交流,让计算机去帮助我们做一些事情。
既然C++是一门语言,那么它也就必然有着自己的语法规则,也有自己的基本词汇和句型,也可以分成不同的段落和篇章等等。用自然语言描述一个事物,是作文,而用程序设计语言描述一个事物,就是编程。总之,我们可以把C++当作我们的第二外语来学习和运用。
知道更多:编程是怎么回事?
语言,是用来描述和表达现实世界的,C++作为一门编程语言也不例外。作为自然语言,为了描述现实世界中的事物,我们需要一些名词(陈老师、曾学生)。这些名词在C++中就是变量(teChen、stZeng);同时这些名词都有自己的种类(老师、学生),表现在C++中就是变量的数据类型(Teacher、Student);为了表达事物之间的关系,我们需要一些动词(指导),然后用这些动词将各个事物连缀成句子(老师指导学生)。这些动词表现在C++中就是函数(Guide()),而由这些动词连缀而成的句子在C++中就成了表达式(huTeacher.Guide(huStudent););将多个句子按照一定的逻辑关系组合起来,就可以形成一篇文章。同样,在C++中利用一定的逻辑控制结构将多个表达式组合起来就形成了程序。
Teacher teChen; // 陈老师Student stZeng; // 曾学生teChen.Guide(stZeng); // 老师指导学生// 用if条件控制结构组织表达式if(stZeng.m_nScore >= 60) // 如果学生的成绩大于或等于60{ stZeng.m_bPass = true; // 学生及格}
通过C++编程语言与自然语言的对比,我们可以轻松地理解C++程序的含义。编程,就是将描述现实世界的自然语言翻译成C++语言的过程,如此而已。而这也提示我们,在编写程序之前,不妨先把程序所要描述的事物(通常是某个数据处理过程)先用自然语言把它描述一遍,然后再翻译成对应的C++编程语言,就得到了最后的C++程序。
1.2 C++的“前世今生”
读史可以使人明智。
C++作为一门高级程序设计语言,可说是历史悠久,算得上是程序设计语言中的“老革命”了。了解C++的发展历史,可以加深我们对这门语言的认识,了解C++的本质内涵,理解C++的文化,从而可以更好地学习和掌握这门语言。
传说,很久很久以前……
1.2.1 从B到C
1967年,著名的计算机科学家丹尼斯·里奇(Dennis Ritchie)进入美国AT&T的贝尔实验室工作。一开始,里奇和他的同事肯·汤普森(Ken Thompson)开始研究DEC PDP-7这种早期计算机,但是他们发现在这个机器上写程序很困难,只能使用繁琐的汇编语言编程。所谓的汇编语言(Assembly Language),是一种比较接近计算机底层的低级程序设计语言。在汇编语言中,它用助记符(MOV、PUSH、POP等)代替机器语言的操作码,用地址符号或者标号代替机器语言的地址码。在执行的时候,用汇编语言编写的程序并不能被计算机直接识别和执行,我们还需要通过一个叫汇编程序的工具将汇编语言重新翻译成机器语言,然后交由计算机执行。虽然,汇编语言借助助记符和地址符号在一定程度上降低了编写程序的难度,但是因为它接近计算机底层,因而它所编写出来的程序依然难以阅读和理解,程序的开发效率非常低下。
为了解决这个难题,汤普森设计了一种高级程序语言来代替汇编语言,并将其命名为B语言。但是由于B语言本身设计的缺陷,使得汤普森在内存的限制面前一筹莫展。到了1973年,里奇对B语言进行了改良,从而赋予了这门新语言强有力的系统控制能力,同时,新语言也做到了简洁而高效。里奇把它命名为C语言,意为B语言的下一代程序设计语言。
知道更多:B语言又是从哪里来的?
C语言来自B语言,那么B语言是不是来自A语言呢?B语言之前并不存在A语言,之所以取名为B语言,是作者为了纪念他的妻子,他妻子名字的第一个字母是B。
嗯,程序员中也有情圣啊!
1978年,里奇和另一位著名的计算机科学家布朗•克尼汉(Brian Kernighan)一起出版了著名的《The C Programming Language》一书,C语言随后逐渐成为世界上应用最广泛的高级程序设计语言,这个版本的C语言也被称为K&R C。1989年,C语言被ANSI(American National Standards Institute,美国国家标准学会,一个由公司、政府和其他成员组成的志愿组织。这个组织负责协商与标准有关的活动,并审议美国国家标准。)标准化(ANSI X3.159—1989)。在K&R C发布后,又不断有人为C语言添加新特性,但C语言的标准在一段相当长的时间内都保持不变,直到20世纪90年代,标准才被更新,这就是ISO 9899:1999(1999年发布)。这个版本就是通常提及的C99。ANSI于2000年3月采用了这个新标准。
1.2.2 从C到C++
语言的发展是一个逐步递进的过程。1979年4月,同样是来自贝尔实验室的本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)博士与同事接受了一项工作——尝试分析UNIX的内核。但当时没有合适的工具能够有效地完成这个任务,很难将其内核模块化,所以斯大叔(不是斯达舒哦)的工作进展很慢。同年10月,斯大叔设计了一个预处理程序,称之为“Cpre”。 所谓预处理程序,就是在源程序文件被最终编译之前,对其进行预先处理的程序。Cpre为C语言加上了类似Simula语言的类机制(类机制,一种抽象和封装的机制。它将描述一个事物的数据抽象成类的属性,而将对这些数据的操作抽象成类的方法,然后将属性和方法封装成类。在稍后的第6章中我们将重点介绍这个概念)。在这个过程中,斯大叔萌生了创建一门新语言的想法。贝尔实验室对这个想法很感兴趣,就让他组织一个开发小组,专门进行研究。
当时这门新语言并不是叫C++,而是叫C with class,它只是C语言的一个有效扩充,后来才更名为C++。当时C语言已经在所有程序设计语言中居于老大的地位,要想发展一种新的语言,最强大的竞争对手就是C语言了。C++当时面临两个挑战:第一,C++要在运行时间、代码紧凑性和数据紧凑性方面与C语言相媲美;第二,C++要尽量避免在语言应用领域的限制。在这种情况下,最简单的方法就是继承C语言的一些特性,让C++语言具备C语言的各种优点。同时,斯大叔为了突破C语言的种种局限,还借鉴了其他程序设计语言的优点,实践了编程界由来已久的“拿来主义”。例如:C++从Simula拿来了类的概念;从Algol68拿来了操作符重载、引用以及在任何地方声明变量的能力;从BCPL拿来了“//”注释;从Ada拿来了模板、名字空间;从Ada、Clu和ML拿来了异常处理等。通过这一系列的拿来动作,C++具备了多种程序设计语言的优秀基因,既系出名门,又博采众家之长,从而完成了从C到C++的进化。
其后,C++又经历了长期的发展,随着标准模板库(Standard Template Library,STL)的出现、泛型编程的发展,C++在2000年左右出现了其发展史上的一个高峰,而到了2011年,C++的最新标准C++11正式发布。这个新标准在C++的易用性和性能上作了大量改进,增加了线程库等现代软件开发所需要的内容,这也为C++的发展注入了新的动力。
很多朋友都是从C语言转入到C++语言的学习的,大家拥有了C语言的基础,同时又因为C语言和C++语言之间天然的血缘关系,这使得大家可以对C++语言轻松上手,以前的关于C语言的知识和编程经验在C++语言中也继续有效。但是,C语言和C++语言毕竟是两门不同的编程语言,它们虽然有一定的血缘关系,但是两者之间还是有本质的不同,这就是C++比C多出来的两个“+”号。如果说其中一个“+”号代表了C++比C语言多出来的体现面向对象思想的类机制,那么另外一个“+”号则代表了C++全新添加的标准模板库,正是这两个“+”号将两者区分开来。所以,如果我们是一个有C语言经验的程序员来学习C++语言,既需要复用自己以前的关于C语言的知识和经验,同时也应该更新观念,将学习和理解的重点放在面向对象思想的类机制和标准模板库这两个方面,这样才能学习到C++语言的精髓。
知道更多:C++大事记
1983年8月,C++首次投入使用,开天辟地。
1983年12月,Rick Mascitti建议将C with class更名为CPlusPlus,亦即C++。C++从此名正言顺。同年,C++吸收了很多新的特性,其中包括虚函数、函数名和操作符重载、常数、用户可控制的自由空间储存区、改良的类型检查及新的双斜线“//”单行注释风格。
1985年2月,C++ Release 1.0发布。
1985年10月,斯特劳斯特卢普博士完成了经典巨著《The C++ Programming Language》的第一版。
1989年,C++ Release 2.0发布。它引入了多重继承、抽象类、静态成员函数及成员访问保护等新特性。C++中面向对象的思想更加成熟。
1990年3月,第一次ANSI X3J16技术会议在美国新泽西州召开。
1990年7月,C++加入模板。
1990年11月,C++加入异常处理。
1991年6月,《The C++ Programming Language》第二版完成。
1991年6月,第一次ISO WG21会议在瑞典召开。
1994年8月,ANSI/ISO委员会草案登记。
1997年7月,《The C++ Programming Language》第三版完成。
1998年10月,ISO标准通过表决被接受。
1998年11月,ISO标准得到批准。同年,C++11标准公开,它是当时计划中的C++的新标准,将取代现行的C++标准ISO/IEC 14882。
2003年,在官方公布1998标准的5年之后,C++标准委员会处理缺陷报告,并于2003年发布了一个C++标准的修正版本,称为C++03。新的标准包含了核心语言的新功能,同时扩展了C++标准程序库,合并了大部分的C++ Technical Report 1程序库。
2005年,公布一份名为Library Technical Report 1(简称TR1)的技术报告。虽然它不属于官方标准,但它所提出的几个扩展建议有望成为新C++标准的一部分。目前,几乎所有流行的C++编译器都已经支持TR1。
2008年10月,C++11的最新报告N2800公开。
2011年8月,C++11(先前被称作C++0x)获得ISO/IEC一致通过;同年9月新的C++标准C++11正式出版,C++从此进入一个新的时代。
1.2.3 更简单、更高效:C++11让C++续写传奇
技术总是在不断进步,C++也总是在不断发展。自从斯大叔发明并实现了C++语言之后,在面向对象语言迅速发展的时代背景下,C++以其面向对象的语言特性、对C语言的良好兼容、以及极其接近C语言的性能效率,在工业界占据了相当大的份额,成为程序设计语言中的无冕之王。在其后的发展中,C++又不断引入新的内容。标准模板库和Boost程序库的出现、泛型程序设计的流行,使得C++牢牢占据了TIOBE编程语言排行榜前三名的位置,成为业界最流行的程序设计语言之一,成为众人传颂的一个传奇。
然而,随着硬件技术的不断发展,特别是多核技术的出现以及Java、C#等新语言的不断涌现,C++的发展受到了很大的冲击,在业界的应用范围不断萎缩。C++曾经是Visual Studio 6.0中的首选语言,但是在后继版本的Visual Studio中,特别是在微软推出.NET Framework之后,C++的地位不断下滑,被后来居上的C#抢了风头。很多钟情于C++的程序员不禁发出这样的感叹:“C++老矣,尚能编否?”
虽然C++在发展历程中经历了上述小小的波折,但是应当看到,世界上还有无数的C++代码在稳定地运行着,这些代码还需要维护和升级。另外,C++在某些领域(比如,操作系统编程、游戏开发、电信金融业务、服务器端开发等)仍具有不可替代的优势,无数基于C++的新项目正在进行着。为了应对现代程序设计语言的发展及业界的需求,C++也积极汲取现代程序设计语言的发展成果,C++的新标准C++11正是在这种背景之下应运而生的。
C++11是自1998年C++首次被ISO标准化以来变化最大的一个新标准,它主要在以下两个方面对C++进行了革命性的改进和增强:
一方面,C++11让C++更加易于使用。C++曾经以其语法的繁琐复杂而著称于世,因而可以用C++精确地描述现实世界。同时,C++也非常灵活而自由,我们几乎可以在C++中完成任何我们想要完成的事情。但繁复、自由和灵活是一把双刃剑,它让C++拥有无限的能力,但同时也让C++在程序员们的心目中成为一门难学难用难以掌握的编程语言,让一些初学者更是望而却步,从而严重阻碍了C++的进一步发展。为了改变这一现状,C++11引入了很多改善其易用性的语法特性,并从其他主流的编程语言(尤其是Java)中借鉴吸收了很多旨在改善C++易用性的语法特性。例如,C++11提供了auto这种特殊的数据类型,使用它作为变量的数据类型,编译器可以根据变量的初始值自动推断其合理的真实数据类型,省去了程序员确定复杂变量的数据类型的繁琐;C++11开始支持Lambda表达式,让C++中匿名函数的定义和使用成为可能;C++11从Java和C#中借鉴了序列for循环语句,让针对某个容器的循环遍历更加简单。
另一方面,C++11让C++的性能更高。相对于其他主流的高级编程语言,接近于低级语言的高性能表现,一直以来都是C++最大的优势。但是,C++11并不满足于C++现有的性能表现,通过增加新的语法特性、改写标准库等手段,想榨干C++身上最后的一滴性能血液。例如,C++11提供了对右值引用、移动语义的完全支持,解决了从函数返回一个大对象的资源浪费问题;利用新的语法特性对标准库进行了大规模的改写,极大地提高了标准库的性能表现;特别值得一提的是,为了适应当今越来越普及的并行计算开发,充分利用主流的多核CPU的计算资源,C++11在标准库中对并行计算提供了全面的支持,我们可以通过线程(thread)对象轻松完成线程的创建,也可以通过互斥(mutex)对象、条件变量(condition_variable)对线程的执行情况进行控制。对并行计算的完全支持,让C++11拥有了更加优异的性能表现。
C++11在这两个方面的大力改进,不仅进一步增强了C++在性能方面的优势,做到了扬长;同时也改善了C++的易用性,做到了避短。正是通过“扬长避短”,使得C++成为了一门“又快又好”的程序设计语言。这些新特性给C++注入了新的活力,使得C++重新焕发青春,带来C++的复兴。C++也必将续写它那不朽的传奇。
1.2.4 新兴的C#会不会革了C++的命?
自从微软推出全新的开发语言C#之后,关于C++与C#之间的争论就没有停止过。就像C++继承了C语言的许多特性而同时又增加了很多新特性一样,C#也同样继承了C++的许多特性,同时也增加了很多现代编程语言的新特性。配合强大的.NET Framework,C#下的软件开发就像搭积木一样简单,那些原来在C++下需要几十行代码才能完成的功能在C#下可能只需要几行代码就可以完成。极高的开发效率使得C#的应用越来越广泛,这使得C++的初学者常常会有这样的疑问:新兴的C#会不会革了C++的命?我们应该学习C++还是学习C#?
正所谓“成也萧何,败也萧何”。.NET Framework在给软件开发带来便利的同时,它也在C#和操作系统之间隔了一层,让我们无法了解C#背后的真相,从而处处受制于.NET Framework。例如,实现同样一个功能,使用C#我们可能只有一种方法,而使用C++,在我们明白了这个功能背后的实现机制之后,就可以用不同的方法应对不同的情况,从而实现最优的方案。总结起来,C#简单容易,但却在很多时候无法实现最佳方案,会牺牲一些性能和内存空间;C++稍显复杂,但它却往往能实现最佳方案。
从本质上讲,C++和C#之间的差异是两种不同的编程世界观之间的差异。在C++的世界观中,我们看到的是内存、指针、模板等基础设施,很多事情都还等着我们自己去完成建设,虽然辛苦一些,但是我们获得的却是更多的自由,更高的性能;而在C#的世界观中,我们看到的是强大的类库、垃圾回收机制等已经初具规模的设施,我们只需要使用这些设施来实现自己的功能就可以了。在这种世界观下,开发效率自然会提高,但是性能就不敢保证了。这就像盖房子,C++提供给我们的是砖头和沙子,整个房子都需要我们自己动手;而C#提供给我们的是半成品的一堵墙或者一个房顶,我们只需要将这些半成品垒成一个房子就可以了。用C++的方法,虽然麻烦一些,但是可以盖出各式各样独具个性的房子;用C#的方法,虽然省时省力,可是盖出来的房子都大同小异,没有什么个性可言。
语言无所谓好坏强弱,C#能做的,C++不一定都能做好,而C++能做的,C#也不一定都能做好。所以,讨论语言的好坏强弱没有任何意义。C++和C#各有各的特点,所以也都有各自的应用场景。根据应用场景的不同而选择合适的语言才是最重要的。最合适的语言就是最好的语言。虽然C#等现代语言的兴起部分地蚕食了C++原来的应用范围,但是只要这个世界还需要一门性能与开发效率并重的开发语言,只要这个世界还需要服务器端的开发、多媒体游戏的开发、图形图像处理的开发等,C++就不会被革命。
知道更多:将C++嫁接到.NET Framework
虽然C++在某些开发领域仍然保持着绝对的优势,但是以C#为代表的新兴语言,凭借着强大的类库,其极高的开发效率越来越成为程序员们的新宠。为了使C++能够应对这种新的开发趋势,微软把C++嫁接到强大的.NET Framework上,由此诞生了C++/CLI(Common Language Infrastructure,公共语言结构),从而允许大量只熟悉C++的开发人员可以继续在.NET Framework平台上使用C++开发应用,借助强大的.NET Framework来提高开发效率。
那么到底什么是C++/CLI?它跟传统的C++又有什么不同呢?
CLI指的是通用语言结构,一种支持动态组件编程模型的多重结构。在整个CLI结构中,最重要的是公共语言运行时(Common Language Runtime,CLR),它负责管理微软中间语言(Microsoft Intermediate Language,MSIL)代码的运行环境。CLR位于CLI的下半部分(如图1-1所示),主要包括类加载器(class loader)、实时编译器(IL to native compilers)和一个运行时环境的垃圾收集器(garbage collector)。CLI运行在底层操作系统与程序之间,为MSIL代码提供运行的环境,这使得CLI成为一个实时的软件层,一个有效的执行系统。我们可以将任何语言编写的代码通过特定的编译器转换为MSIL代码,然后在CLI上运行。
图1-1 C++/CLI的结构
当C++和CLI结合起来就成了一种可以经过特殊的编译器编译之后运行在CLI之上的C++语言。其中的斜杠“/”代表C++和CLI的捆绑,这个捆绑使得C++/CLI同时具备了C++和CLI这两个方面的特性。首先,C++/CLI继承了C++的大部分语法规则,使得我们可以轻松地将C++代码转换为C++/CLI代码。开发人员可以充分利用已有的C++编码经验,使用C++/CLI为.NET Framework平台开发新的应用程序。其次,当我们用C++/CLI编写的托管代码被运行时,代码将被CLR所管理,它提供了诸如垃圾收集等现代高级程序设计语言的特性,同时也实现了C++/CLI与.NET Framework支持的其他语言之间的互操作,让我们可以通过C++/CLI使用.NET Framework平台上丰富的组件,极大地提高了开发效率。可以说,通过将C++嫁接到强大的.NET Framework,C++/CLI使得C++这门“古老”的程序设计语言做到了与时俱进,能够高效率地开发面向未来的丰富应用。
// 用C++/CLI完成的Hello World程序using namespace System; // 使用System名字空间,这是C++中的编程经验int main(){ // 创建一个字符串指针str,当其使用完毕后, // 垃圾回收机制会自动释放这个字符串而无需程序员主动回收 String^ str = "Hello World!"; // 在控制台窗口输出“Hello World!”字符串和它的长度 // 这里使用的是.NET Framework所提供的控制台输出功能 Console::WriteLine("{0}的长度是{1}",str,str->Length); return 0;}
1.2.5 C++世界的“四大天王”
就像英语在不同的地域发展成了不同的美式英语和英式英语一样,C++自从1983年首次投入使用至今,在其30多年的发展过程中,为了适应不同的应用领域,它不断地吸收不同的开发思想而形成了不同的C++子语言。每个子语言各有所长,就像C++世界的“四大天王”,它们来自同一个语言家族,都是 C++语言,但是又各自拥有自己的特点,各自都有自己的擅长领域,各自都有自己众多的忠实追随者,如图1-1所示。
图1-2 C++世界的“四大天王”
1. C子语言
C++的发展渊源,使得C++支持几乎全部的C语言功能,在语法上与C语言仅有细微的差别。例如,C++中的语句、内建数据类型、数组、指针等等,全都是直接来自于C语言。很多人的开发仅仅用到了这些从C语言继承过来的内容,他们把C++当做一种经过扩展的C语言来使用,形成了一种独特的子语言。
2. 面向对象的C++
C++首先是作为一门面向对象的程序设计语言而闻名的。在C语言的基础上,C++添加了“类”的概念,从而可以很准确地表达出面向对象思想中封装、继承和多态的机制。所以,C++也可以用于面向对象程序设计,而这也是它最主要的应用状态。
3. 泛型编程语言
泛型编程是独立于流行的面向对象编程的一种新的开发方式,可以编写完全一般化并可重复使用的算法,其效率与针对特定数据类型而设计的算法的效率相近。所谓泛型(genericity),是指对多种数据类型皆可操作,与模板有些相似。简单地讲,也就是算法或者数据结构与具体的数据类型无关,任何数据类型(泛型)都可以操作。C++强大的模板机制为泛型编程提供了很好的支持,所以我们也可以将C++用于泛型编程。
4. STL
STL是C++泛型编程的一个杰出作品,随着C++的不断发展,STL也变得越来越强大,它已经逐渐成为C++程序设计中不可或缺的一部分。它将容纳数据的容器、访问数据的迭代器、以及对数据进行处理的算法非常优雅地整合在一起,其效率虽然比专门设计的C++代码稍低,但其安全性与规范性大受欢迎,在业界得到了广泛的应用,已逐渐发展成为一门独立于泛型编程之外的C++子语言。
就像在美式英语和英式英语中的语法规则稍有不同一样,在C++的不同子语言中,很多规则也有所不同。我们以函数间数据的传递为例,当我们工作在C子语言中时,对于内建的数据类型(比如int),传值通常比传指针更加高效;而到了面向对象的C++中时,对于用户自定义的数据类型而言,更高效的做法又变成了传引用。C++不是使用同一套规则的单一语言,它的每一种子语言几乎都有一套各自独立的应用规则。只要我们在头脑中对自己正在学习或使用的那一种C++子语言有一个清楚的概念,知道自己正在使用哪一种子语言,应用与之相对应的规则,我们会发现对C++的学习和使用会容易得多。