前言
据2011年12月Tiobe网站(www.tiobe.com)的排名,最流行的前5个编程语言依次是Java、C、C++、C#以及Objective-C。排名的依据是熟练使用一种语言的人数,与该语言相关的课程数量以及支持该语言的第三方供应商的数量。自2001年这个排名标准诞生以来,C++几乎总是处于第3名。虽然Perl、Visual Basic以及PHP也曾占据过这个位置,但是它们只能在这个位置上维持几个月。
软件行业的骨架是由C++(及其兄弟C)搭建起来的。
● 三大桌面操作系统Windows,MacOS以及Chrome OS使用了C++。
● 运行在苹果公司iPhone,iPod,iTouch以及iPad上的操作系统,Windows Mobile和Symbian OS使用了C++。
● 在关系数据库管理系统方面,主流的产品Oracle Database,MySQL,IBM DB2,Microsoft SQL Server,IBM Informix,SAP DB/MaxDB都使用了C++。
● Web浏览器方面,依据网络分析公司StatCounter 2011年11月发布的数据,全球用户数量最多的前5款浏览器依次为微软的IE,Google Chrome,Mozilla Firefox,Safari以及Opera,它们全部使用了C++。
● 流行的办公套件Microsoft Office,Sun Open Office,Corel Office也都使用了C++,其中Corel Office在开发过程中曾使用过Java,但由于速度太慢最终转回C/C++。
● 甚至,出乎一般人的预料,C++也被用来开发网站。Google的网站采用C++(及汇编),eBay和Amazon采用C++与Java,Facebook采用LAMP(Linux+Apache+Mysql+PHP)外加C++。
C++的语言特性
C++的地位是由其鲜明的语言特性决定的。
(1)它兼容C,意味着C++项目可以复用过去40年来积累的C函数库以及C源代码。
(2)对复杂系统的抽象表达能力。C++和其他面向对象编程语言一样,使用“类”封装底层的数据和相关的操作,使用“继承”描述基类和派生类的共性,使用“多态性”描述不同子类的差异。一个复杂的系统总会被表示为一组具有清晰逻辑结构的类。为了解决复杂软件系统中名字冲突的问题,C++引入了函数名重载(function overloading)、名字空间(namespace)机制。
(3)执行速度快。C++和其他面向对象编程语言不同,并不追求形式上的简单。为了提高程序执行速度,C++不惜将自己变得更加复杂。模板技术以及基于该技术的泛型编程思想在编译阶段处理多态性问题,使得一个类模板或者函数模板能够处理各种类型。而其他编程语言采用虚函数与多态性解决类似的问题,相比之下,C++模板的执行速度更快。
(4)C++遵循“用时付费”原则。例如,有些编程语言将垃圾回收(gabage collection)作为语言内在的功能,无论一个程序是否需要,该机制在程序运行期间总会占用一定的计算资源。对于C++,即使演化到最新的C++0x11标准,它仍然坚持不纳入垃圾回收机制,因为它认为需要该功能的程序可以采用第三方库,而不需要该功能的程序不应花费任何额外计算资源。C++程序被编译为可执行代码后可以直接运行,不像其他一些语言那样需要一个虚拟机来解释执行。在对速度要求极高的场合,我们可以在C++程序中嵌入汇编代码。这些语言特性都能提高C++程序的执行速度。
本书特点
然而,也正因为C++的这些语言特性,使其成为所有面向对象语言中最复杂的一个。许多学习者抱怨C++语言“难学难精”。为了降低学习难度,提高学习效率,C++学习者至少应该阅读两种类型的教材:
● 讲述“C++是什么”的教材,如Thinking in C++以及 C++ Primer等。
● 讲述“如何使用C++”的教材,如Effective C++以及The C++ Programming Language(每章的Advice部分与Part Ⅳ)。
阅读第一种类型的教材时,学习者通常不会遇到什么障碍,然而在阅读第二种类型的教材时,学习者有时会觉得其中一些C++编程经验听起来有道理,但是在编程实践中并不一定能够灵活运用它们。有这种感觉并不奇怪,因为这些编程经验是C++高手们从几十年的编程实践中总结出来的,要真正理解它们,需要读者也能以某种方式重演这些经验背后的编程实践。
一种最有效的方式就是选择一个优秀的C++案例,研读它、模仿它,也就是说,C++学习者还应该阅读第三种类型的教材:案例教材。
目前有如下两种类型的案例教材。
(1)偏重介绍如何使用某种开发框架(如MFC)的教材。学习者看完这些教材,的确可以编写出一些具有图形界面的程序,但是无法体会如何使用C++语言的特性来解决复杂的设计问题、性能问题。更值得注意的是,这些教材中的案例往往都是为了书籍的出版而特意设计的,并非来源于实际软件项目。其系统设计、代码质量未经同行审阅,可能会误导学习者。
(2)深度剖析C++标准模板库(STL)的教材。由于STL本身精巧的设计,阅读这类教材的确可以帮助学习者掌握C++(尤其是模板技术)的精髓,但是这类教材抽象而深奥,令大多数学习者望而却步。
读者希望临摹如下这样的案例。
(1)案例具有良好的设计、高质量的代码,已被成功地广泛使用。
(2)案例不要涉及太多、太深其他领域的知识。
(3)案例可以比较复杂(以展现C++对复杂问题的处理能力)但是不要过于抽象,这样,读者可以通过形象、鲜活、具体的例子,体会C++语言特性的应用。
(4)案例应该全面覆盖C++的各种语言特性。
寻找这样一个案例并非易事。在众多的开源项目中,我们最终选择了Qt。Qt是一个跨平台的C++开发框架,它包含一个功能丰富的C++类库以及一套简便易用的集成开发工具。Qt所支持的平台不但包括Linux,Windows以及Max OS X等主流桌面操作系统,还包括诸如Symbian,Maemo以及MeeGo这样的嵌入式操作系统。
使用Qt编写的C++程序具有良好的跨平台特性,程序员几乎无须更改源代码,所编写的应用程序即可运行在各种操作系统中,这能大幅度缩短开发周期、降低开发成本。Qt的C++类库是完全面向对象的,该类库不但功能强大,而且设计精良、方便易用。这些优点使得Qt被Adobe®,Boeing®,Google®,IBM,Motorola®,NASA,Skype®等大型机构以及众多的中小公司采用。
Qt类库非常复杂,仅本书剖析的两个子模块QtCore以及QtGui就有大约22万行代码,其复杂程度足以展现C++对复杂问题的处理能力。该类库不但全面覆盖了C++的各种语言特性,还用到了MVC(Model-View-Control)框架、隐式共享、信号与槽、命令模式、抽象工厂模式、观察者模式等精妙的设计技巧,是一个值得学习者临摹的案例。
本书内容
本书共18章。第1章讲述为什么会从众多的开源C++项目中选择Qt。读者可以借鉴其中的方法选择其他C++案例,或者在学习其他编程语言时,使用其中的方法选择对应的案例。而且,读者还可以使用其中的工具CppDepend剖析其他软件的结构与质量。这一章还介绍了本书对术语、UML类图方面的约定。在阅读后续章节前,读者应该首先阅读这一章。
本书不但剖析Qt的源代码,有的章节还涉及修改Qt的源代码,此时需要重新编译整个Qt库。第2章简要介绍Qt,并讲述如何在Visual Studio 2010开发环境下安装、编译Qt库。Qt库多处用到了类模板特化技术。考虑到一般的C++教科书不会详细讲解这个话题,故第3章阐述该技术的概念和基本应用,第6章及第9章用到了该技术。
绝大多数C++程序都会涉及字符串的处理,因而在Qt提供的所有功能中,我们首先在第4章讲述Qt对字符串的处理思路。和C++标准库不同,Qt不再区分单字节字符串、宽字节字符串,而是使用类QString表示所有类型的字符串,每个字符用类QChar表示,至少占用2个字节。这一章分析比较了C++标准库、Qt对字符串的两种处理思路。
数据的输入输出也是绝大多数C++程序需要使用的功能。第5章~第7章分析比较C++标准库、Qt对数据输入/输出的不同处理思路。Qt的流框架功能丰富,易于使用但是可扩展性差。而C++标准库的流框架具有良好的可扩展性,但是其本身的功能偏弱,不便于使用。
如果一个类的某些对象的数据成员具有完全相同的取值,我们可以令它们共享一块内存以节省空间。只有当程序需要修改其中某个对象时,我们再为其分配新的内存。这种技术被称为隐式共享,在Qt库中被广泛使用。第8章介绍该技术,并剖析了QString的部分源代码以演示该技术的具体实现。除此之外,这一章还介绍了d-pointer技术,该技术将一个类的所有数据成员分离出来,定义在另外一个类中,并在原先的类中定义一个指针,指向另外那个类。这种技术能维持Qt库的二进制数据兼容性、提高Qt库的编译速度。
起初开发Qt时,C++的标准模板库STL尚未成为业界标准,部分编译器根本没有配备STL。为了实现良好的跨平台特性,Qt的设计者干脆开发了一个类似于STL的模块,将其集成在Qt软件体系中,本文将其称为QTL(Qt Template Library)。Qt其他模块大量使用了QTL,使其成为Qt软件体系的基石。正如设计精良的STL能够引来人们为其出书立著一样,QTL的设计也有可圈可点的特点。第9章介绍函数的概念及其在QTL中的应用,以及QTL是如何使用模板特化技术优化QList性能的。
目前的C++标准并不支持并发处理,但是并发处理的确可以提高程序性能、改善用户体验。因此,Qt封装了不同操作系统的细节,向程序员提供了一组和平台无关的类来处理并发问题。第10章介绍如何使用QThread创建一个线程,如何使用互斥体QMutex,信号量QSemaphore及条件量QWaitCondition实现线程同步。以上这些技术运行速度慢、开销大。这一章以多线程环境下singleton模式的实现为例,讨论了如何在C++程序中嵌入汇编代码,实现一个原子操作,并巧妙地利用这个原子操作,以很小的开销、非常快的速度同步多个线程。
Qt使用信号与槽机制进行对象间的通信。第11章阐述Qt为什么使用信号与槽机制而不是传统的回调函数进行对象间的通信。信号与槽机制大幅降低了各对象之间的耦合度,各对象的代码可以被独立地开发、测试,也更容易被复用。这种机制是Qt开发框架区别于其他框架的典型特征。
Qt的Graphics/View框架被用来存放、显示二维图形元素,处理那些对图形元素进行操作的交互命令。第12章介绍Qt图形系统的基本知识,Graphics/View框架如何处理图形元素,程序员如何定义新的派生类,以配合该框架实现动画效果。通过学习本章,读者将体会到虚函数的作用:一个开发框架实现基本、通用的功能。通过重载开发框架提供的虚函数,应用程序的代码能够和框架中的代码协同工作,达到复用框架代码的目的。
模型-视图-控制器架构(Model-View-Control structure,MVC)是一种流行的软件体系架构,它将数据及其显示分离开来,使用模型存取数据,使用视图显示数据,使用控制器处理用户的交互命令。第13章详细阐述Qt如何实现这个框架。该框架涉及Qt库的36个类,结构复杂、设计精良。这一章的篇幅最长,读者将从本章体会到一个复杂面向对象系统背后的设计理念。
Qt库用到多种设计模式。第14章~第16章阐述Qt如何实现命令模式(command pattern)、抽象工厂模式(abstract factory pattern)以及观察者模式(observer pattern)。对于每一种模式,我们先简要介绍它的一般定义,再讨论Qt使用哪些类/机制实现这种模式,最后给出一个例子演示该模式的作用。
Qt库的另外一个显著特征是构建了一个比标准C++的运行时类型信息RTTI(Run-time Type Information)系统功能更强大的元对象系统(meta-object system)。第17章回顾了标准C++中的RTTI系统,讨论了它的局限性,阐述了Qt的元对象系统以及如何使用该系统来获取对象的运行时信息。
C++的指针是一把双刃剑:一方面,它可被用来构建复杂的数据结构。但是另一方面,对指针的错误使用将导致内存泄漏以及野指针等问题。第18章介绍Qt提供的一些智能指针。这些智能指针对普通C++指针进行封装,能够有效解决上述问题。
本书虽然侧重于剖析Qt,但不是系统讲授如何使用Qt开发GUI应用程序。然而,本书的确详细阐述了Qt的一些基本组成部分,比如输入/输出流、隐式共享、信号与槽、Graphics/View框架、Model/View框架等,理解这些组成部分可以帮助读者尽快掌握Qt的使用。
本书读者对象
本书面向以下读者:软件学院或者计算机学院的学生,将本书作为学习“C++程序设计”或者“面向对象软件设计”课程的参考书;上述课程的教师,将本书的内容融入他们的主讲或者试验环节;软件行业的开发者,与本书一起领略Qt的精妙。
笔者由衷地感谢与敬佩Qt的创始人Haavard Nord以及Eirik Chambe-Eng。正是他们为C++世界带来了如此精彩的一个开发框架,并且无私地公开了整个框架的源代码,允许成千上万的C++程序员以GPL授权方式自由地使用、修改、发布这个库。另外,本书源于南开大学以及南开大学软件学院资助的一个教学改革项目,在此一并表示感谢。最后,感谢董颖、于洋等人对本书草稿的校对。
虽然笔者付出了近两年时间剖析Qt、撰写此书,但由于C++以及Qt的复杂,本书未能涵盖Qt的所有技术亮点。对本书的批评与建议请发送至zhangbo_nk@163.com。关于本书的勘误、讨论以及其他信息,请访问笔者个人博客blog.csdn.net/zhangbo_nk。
张波
2012年3月1日于南开大学