第2章Qt概述

Qt(发单词“Cute”的音)是一个跨平台的C++开发框架,它包含一个功能丰富的C++类库以及一套简便易用的集成开发工具。Qt所支持的平台不但包括Linux,Windows以及Mac OS X等主流桌面操作系统,还包括诸如Symbian,Maemo以及MeeGo这样的嵌入式操作系。使用Qt编写的C++程序具有良好的跨平台特性,程序员几乎无须更改源代码,所编写的应用程序即可运行在各种操作系统中,这能大幅度缩短开发周期、降低开发成本。Qt的C++类库是完全面向对象的,经过精心的设计,该类库不但功能强大,而且方便易用。这些优点使得Qt被Adobe,Boeing,Google,IBM,Motorola,NASA,Skype等大型机构以及众多的中小公司采用。

1.Qt的历史

回顾Qt二十余年的发展历史,我们可以学习是哪些因素促成了Qt的成功。Qt的创始人是Haavard Nord和Eirik Chambe-Eng,二人后来分别成为Trolltech公司的首席执行官和总裁。1988年,受一个瑞典公司的委托Haavard开始开发一个C++图形库。1990年夏季,二人共同开发一个处理超声波图像的数据库系统时,需要一个能够运行在UNIX,Macintosh以及Windows上的跨平台C++图形库。一天,两人在公园长椅上享受阳光浴时,Haavard说:“我们需要一个面向对象的图形显示系统”,之后的讨论促成了Qt的诞生。这是Qt成功的首要因素:源于实际需求。

1991年—1993年,二人设计并实现了Qt库的图形核心库,一组控件以及“信号与槽”机制。1994年,二人创建了后来的Trolltech公司,并与1995年5月公开发布了Qt 0.90版。自发布之日起,Qt就提供了商业授权和开源软件授权两种方式。发布之后的10个月中,没有任何人购买Qt的商业授权。直到1996年3月,欧洲航天局终于购买了Qt的10份商业授权,Qt才得以逐步壮大。从这一阶段的历史,我们可以看出Qt成功的另外两个因素:开发团队精良的技术(比如提出并实现了“信号与槽”机制);欧洲人对知识产权的尊重(Qt创始人能够放心地发布Qt的源代码,而欧洲航天局在能够看到Qt所有源代码的条件下仍然购买Qt的商业授权)。

1997年,KDE项目的组织者Matthias Ettrich决定使用Qt构建KDE,使Qt实际成为Linux上开发C++图形程序的标准库。2001年,Qt 3.0发布,它的源代码已经超过50万行。2005年,Qt 4.0发布,包含500多个类,9000个函数。2008年,Nokia收购了Trolltech公司,将Qt作为该公司移动设备的主要开发平台。

2.Qt库的功能

虽然Qt库起初只是一个C++图形库,但是经过多年的演化,它已经成为一个功能丰富的通用C++类库。它集成了数据库、OpenGL、多媒体、脚本、XML、正则表达式、WebKit等模块等,其内核部分也加入了进程间通信、多线程等模块。

作为一个成熟的GUI框架,它定义了多种功能丰富的控件,实现了事件处理机制,可以实现普通菜单、上下文相关菜单、拖曳、可停靠工具栏等功能。Qt发明了“信号与槽”机制,各控件利用这一机制发送、处理消息,大幅降低了各控件的耦合度。其他GUI框架常常使用回调函数来实现控件之间的通信,相比之下,信号与槽机制更加安全。Qt库提供的Graphics/View框架以及Model/View框架可令程序员编写少量的代码,即可显示、编辑应用程序所要处理的数据。

整个Qt库支持Unicode编码,因而一个Qt应用程序可以轻易地同时显示英文、中文、日文、俄文等多种Unicode编码所支持的语言。Qt软件包还提供了诸如Qt Linguist这样的工具,便于程序员开发国际化软件产品。

Qt库的数据库模块内含以下数据库管理系统的驱动软件:Oracle,Microsoft SQL Server,Sybase Adaptive Server,IBM DB2,PostgreSQL,MySQL,Borland Interbase,SQLite,以及其他支持ODBC(Open Database Connectivity)接口的数据库管理系统。这意味着可以使用Qt访问各种平台上的多种数据库管理系统。除了使用SQL语句直接访问数据库外,Qt还提供了一些与数据库密切相关的控件,以简化数据的访问过程。

Qt库的XML模块包含了能够读取、解析、处理XML文档的类。该模块支持SAX(Simple API for XML)接口以及DOM(Document Object Model)规范。该模块易用、强大、功能完备。Qt库还允许应用程序使用正则表达式搜索、解析文档,或者在一个数据集中筛选符合某种条件的数据项。

Qt库集成了浏览器引擎WebKit,该引擎能够从服务器下载、解析、渲染、显示网页。由于该引擎执行速度快、运行稳定,已被用于Safari、Google Chrome等浏览器中。利用这个引擎,Qt应用程序可以在其界面中显示服务器网页。不但如此,Qt令WebKit引擎将网页的结构以及其中一些对象的细节呈现给Qt应用程序,使得程序中的其他控件可以直接和这些对象进行交互。这意味着Qt应用程序在显示网页时可以将一些Qt控件嵌入到网页中,也可以使用“信号与槽”机制,使得网页中某些对象的状态发生变化时,能够触发本地程序中某些控件的槽函数。

3.Qt的应用

作为一个成熟的C++开发框架,Qt已成为数以万计商业和开源应用程序的基础。在桌面应用领域,下面这些重量级的软件都用到了Qt:(1)Maya,是Autodesk公司出品的顶级三维动画制作软件。2011版开始使用Qt进行开发。(2)Mathematica,能够进行数学领域的数值和符号计算,是世界上最流行的数学软件之一。起初使用Motif开发,但是效率较低,难以移植到多个平台。为了能够在短时间内开发出能够运行在多个平台的版本,开发团队切换到了Qt。另外,通过使用Qt库中最新的控件,该软件的界面也更加符合时代潮流。(3)Google Earth,能够显示地球上任意一个角落的3D地形图,部分地区的精度高达0.6米。其图像来源于卫星照片、航空照相以及地面摄像。(4)Photoshop Elements,是Adobe公司出品的图像编辑软件,比专业版的Photoshop功能稍弱但价格相对很低。(5)Skype,国际上非常流行的网络电话软件。Qt被用来开发其客户端部分,使其能够运行在各种桌面环境中。(6)KDE,如前文所述,是一个能够运行在Linux、UNIX、FreeBSD等操作系统上的图形工作环境,也包含一个应用程序开发框架。(7)KOffice,类似于微软Office的一套办公软件。由于使用了Qt,该软件可以运行在Linux,Windows,Mac OS X等操作系统上。(8)VirtualBox,是运行在x86上的一款虚拟机软件,目前属于Oracle公司。运行该软件的操作系统被称为主操作系统。该软件运行时,向用户呈现一个虚拟的计算机,用户可以在这个虚拟计算机上安装一个从操作系统。

Qt也被一些知名厂商用来开发移动设备中的软件。三星用它来开发数字相框产品SPF-105V,中兴用它来开发智能手机ZTE U980。Qt也为Symbian、Maemo以及MeeGo等操作系统提供了优秀的C++应用程序开发框架。

对Qt感兴趣的读者可能会关心一个问题:“如果我使用Qt来开发商用软件,是否需要向Nokia公司付费?”。本章2.1节将讨论Qt的版权问题。读者在阅读本书时,常常希望能够运行、修改书中的例子,这需要在读者本地机器上安装、配置Qt的开发环境。由于Qt是一个跨平台软件,其安装、配置过程稍微有些复杂,2.2~2.3节介绍这个过程。为简单起见,本书举的一些例子只是运行在主控台模式下,不需要创建任何图形元素,2.4节介绍如何创建一个能够输入/输出Qt类型的主控台程序。

良好的编程规范(比如变量的命名等)是确保软件质量的一个重要条件,2.5节介绍Qt开发团队定义的编程规范。本书并不试图全面介绍Qt框架,读者在阅读本书时可以参考2.6节罗列的Qt/C++文献。

2.1 Qt版权

Qt的最近几个版本提供3种授权方式:GPL协议、LGPL协议以及商业授权。其中,GPL协议、LGPL协议是开源社区中广泛使用的两种协议。作为法律条文,最精确的阐述是该条文本身,任何解读都可能是不全面、不准确的,下面我们仅从Qt用户的角度介绍这两种协议。最后,我们讨论如何在这3种协议中进行选择。关于GPL、LGPL协议的官方文本以及详细的解读,请参www.gnu.org/licenses。

GPL(General Public License)协议并不是为了保护软件作者的利益,而是为了鼓励软件开发者相互共享各自的成果。该协议允许软件的用户享有以下权利:能够得到软件的源代码;修改软件,或者将软件的一部分用在用户自己开发的软件中;能够发行软件的复本,用户和原作者均可收费,即使一个软件的用户通常并不是该软件的作者。同时,该协议要求用户履行以下义务:一旦该用户所开发的一个软件用到了其他GPL软件,新开发的软件也必须遵循GPL协议,也就是说,新软件的用户也享有上述权利,这要求新软件的开发者在发布该软件时也必须发布源代码。

对于GPL协议下的软件,由于软件的用户也有权以收费或者免费的方式发布该软件,该软件的原作者实际上不能指望通过出售该软件本身赢利。然而,由于原作者对该软件最熟悉,能够提供强有力的技术支持,他们可以通过技术支持的方式获利,比如Redhat公司。

LGPL(Lesser GPL)协议一般适用于类库。该协议允许类库的用户享有以下权利:如果用户没有修改类库,而且是以动态链接的方式使用类库,用户在发布自己开发的软件时,可以不发布源代码。但是,如果用户修改了受LGPL协议保护的类库,或者在自己开发的软件中使用了类库中的源代码,则新开发的软件也必须遵循LGPL协议,也就是说,在发布新软件时,必须发布类库中被修改的源代码、新软件中相关部分的源代码。

Qt的商业授权方式允许Qt用户享有以下权利:能够对Qt进行任意的修改,以满足特定的需求,比如使Qt能够运行在某种智能手机平台上。用户可以不公布这些修改;能够在至多两个工作日内,答复用户任何类型的技术问题,不限制问题的总数量。关于这种授权方式的细节,可参见www.digia.com。

GPL或者LGPL授权的Qt是免费的,而商业授权的Qt则要求用户支付一定的费用。对于Qt用户,如果他希望对开源社区作贡献,可以选择GPL授权方式,但他在发布自己的软件时必须附上源代码。如果他在开发中不需要修改Qt库,也不需要任何技术支持,可以选择LGPL授权方式。如果他需要修改Qt库或者需要良好的技术支持,应该选择商业授权方式。

2.2 Qt库的编译

开发工具的选择。虽然目前支持Qt开发的工具较多,比如跨平台的Qt Creator、QDevelop、Eclipse等,但是在Windows平台上,经过笔者的评测,微软的Visual Studio仍然是最优选择。为了支持开发者使用Visual Studio开发Qt应用程序,Qt提供了一个插件“Visual Studio Add-in”。安装、执行该插件后,开发者在Visual Studio中能方便地创建Qt应用程序的项目文件(扩展名为“.vcproj”),也能把Qt项目文件(扩展名为“.pro”)转换为Visual Studio的项目文件。Visual Studio有多个版本,本书采用了中文版Visual Studio 2010(以下简称为VS 2010)。

Qt版本的选择。Qt总被不断地更新。本书采用的是2012年4月11日发布的“Qt SDK Version 1.2.1”。该软件包不但包含了Qt库4.8.1,还包括了其他开发工具,比如Qt Creator等。Qt的官方网站qt.nokia.com/downloads提供了该软件包的两种安装方式:(1)用户下载一个字节数较少的“安装程序”,用户运行该安装程序,选择所要安装的子模块,该程序再从Nokia服务器下载所选择的子模块并将它们安装到用户的本地机器中。(2)用户下载一个包含有所有子模块的安装程序,用户在本地机器中运行该程序,选择所要的子模块,完成后续的安装操作。本书采用了第二种方式,所要下载的安装程序“qtsdk-offline-win-x86-v1_2_1.exe”接近1.8G字节。

Qt开发环境的配置。老版本的Qt没有直接提供在Visual Studio环境下编程所需要的库文件,需要用户使用Visual Studio重新编译Qt的源代码以生成所需要的库文件。这个过程不但费时,而且可能出错。而Qt SDK Version 1.2.1直接提供了该库文件,简化了安装过程。我们可以按照以下步骤配置Qt的开发环境。

安装VS 2010,比如到默认的C:\Program Files\Microsoft Visual Studio 9.0目录下。

获取并安装Qt软件包。截止到2011年2月22日,Windows平台上最新版的Qt软件包为2010年5月发布的qt-sdk-win-opensource-2010.05.exe,其中Qt库的版本为4.7.1。读者可以从Qt的官方网站下载该软件包。该软件包运行时需要用户输入目标路径。本书设该路径为d:\qt\4.7.1。

安装完毕后,Qt 4.7.1的库文件存放在D:\qt\4.7.1\lib目录下,头文件存放在D:\qt\4.7.1\include目录下,可执行程序(比如Qt Assistant)以及动态链接库(扩展名为DLL)存放在D:\qt\4.7.1\bin目录下。Qt 4.7.1的库文件可以供VS 2010的C++程序直接使用,没有必要像老版本那样需要使用VS 2010的编译器对Qt的源代码重新进行编译。因此,进行到这个步骤,我们可以直接在VS 2010中编写Qt应用程序了。当然,为了能够更方便地在VS 2010中开发Qt应用程序,我们也可以使用2.3节介绍的工具Qt Visual Studio Add-in。

由于Qt 4.7.1软件包已经包含了VS 2010编译生成的库文件,一般情况下我们不需要重新编译Qt源代码。然而,在某些场合下我们需要重新编译Qt库。例如本书8.3节在讨论Qt应用程序的二进制代码兼容问题时就需要重新编译Qt库。这是一项耗时的工作,具体的步骤如下。

复制Qt软件包。本书8.3节会本着试验的目的修改Qt的源代码。完成试验后我们应该复原Qt的源代码。Qt 4.7.1的所有文件都存放在目录d:\qt\4.7.1下。为了能够精确复原Qt的源代码,我们令该目录为原始目录,不会对其进行任何修改。做实验时,该目录下的内容会被复制到一个工作目录,本书设为D:\qt\vc,其中的内容可以被修改。当我们想复原Qt源代码时,只需要将原始目录中的文件复制到工作目录即可。由于原始目录中含有大约3.6万个文件,这个复制操作需要运行一段时间。我们可以使用Windows的资源管理器来完成这个复制操作,也可以使用命令

      xcopy d:\qt\4.7.1d:\qt\vc/s

的方式来完成,这种方式可以显示复制的过程。

配置适用于VS 2010的环境变量。对Qt软件包进行编译时,需要以命令行方式使用VS 2010的编译器,这需要配置一些环境变量。用一个文本编辑软件打开文件C:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools\vsvars32.bat,使环境变量PATH包含d:\qt\vc、INCLUDE包含d:\qt\vc\include、LIB包含d:\qt\vc\lib。保存该批处理文件后,在命令行窗口运行该批处理文件。随书光盘Z:\misc\vsvars32.bat是一个修改后的文件,读者可以参考该文件进行修改。读者也可以直接运行该文件,跳过以上编辑过程。为了验证是否对上述环境变量做了正确的设置,可以在命令行窗口运行set命令,并检查屏幕的输出。

生成供VS 2010编译器使用的配置文件。在命令行窗口中,将当前目录切换到d:\qt\vc,运行:

      configure-platform win32-msvc2008

命令将生成一个配置文件,供步骤6中的编译程序nmake使用。Qt库由corelib,GUI,network等多个模块组成,上述命令表示即将编译所有模块。如果开发者只需要使用部分模块,可以使用下面的命令略过其他模块的编译,以节省编译时间:

      configure -no-sql-sqlite -no-qt3support -platform win32-msvc2008-no-libtiff
      -no-dbus -no-phonon -no-phonon-backend -no-webkit

该命令表示不编译sql-sqlite、qt3support、libtiff、dbus、phonon、phonon-backend及webkit模块。configure命令运行时会询问Qt的版本是商业版还是免费开源版,选择免费开源版。该命令大约运行10分钟。

运行nmake进行编译。在命令行窗口中,将当前目录切换到d:\qt\vc,运行命令nmake,对Qt的源代码进行编译。该命令运行2~3个小时。为了验证Qt库是否已经被正确编译,用户可以查看目录D:\qt\vc\lib。如果该目录包含qtcore4.lib、qtgui4.lib等库文件,而且这些库文件的更新时间正好为运行nmake命令的时间,则表示Qt库已被成功编译。编译完毕后d:\qt\vc目录占用了大约10GB的空间,其中大部分是.obj文件。用户可以在该目录下运行命令“del *.obj /s”删除这些文件,以节省磁盘空间。缩减后的目录大约占用2.6GB空间。

2.3 开发环境的设置

在VS 2010中开发Qt应用程序需要做以下设置。

1.安装插件Qt Visual Studio Add-in。为了便于在VS 2010环境下使用Qt库,可以从qt.nokia.com/download下载、安装插件Qt Visual Studio Add-in。安装时选用默认的安装参数即可。安装完毕后,运行VS 2010,会有一个名为Qt的菜单项。执行其中的Qt Options\Qt Versions\Add,在Version Name域输入任意名字比如QT 4.8.1,在Path域中输入d:\qtsdk\desktop\qt\4.8.1\msvc2010。至此,该插件和VS 2010无缝地集成在一起了。

2.指定Qt库的头文件位置以及库文件。设我们已经按照1.4节所述将目录“d:\qtsdk\desktop\qt\4.8.1\msvc2010”映射为盘符“q:\”,则所有Qt库的头文件都存放在q:\include目录下。Qt库包含多个子模块,每个子模块的头文件存放在一个单独的子目录下。例如,q:\include\QtCore子目录存放Qt核心模块的所有头文件,q:\include\QtGui子目录存放模型界面子模块的所有头文件,q:\include\QtWebKit存放Web开发子模块的所有头文件。在每个子目录下,所有头文件会被包含到一个“汇总”头文件中。例如,q:\include\QtGui目录下有一个名为“QtGui”的头文件,它包含了这个目录下的所有其他头文件。通过包含这个汇总头文件,我们就可以间接地包含这个子目录下的所有其他头文件,不必再写一大堆的include语句,使得我们的程序看起来更加简洁。

Qt应用程序通常以相对路径而不是绝对路径的方式包含Qt库的头文件,比如使用“#include <QtGui>”而不是使用“#include <q:\include\QtGui>”。当编译器看到这个预处理命令时,需要知道去哪个目录寻找所包含的头文件。

对于VS 2010,具体操作步骤如下。首先创建一个项目(或者打开一个已经存在的项目),然后执行“项目\属性\通用属性\VC++目录”,在“包含目录”中添加:“q:\include;q:\include\QtCore;q:\include\QtGui”。这样,我们就可以在C++程序中通过“#include<QtGui/QColor>”或者更简单的“#include <QColor>”方式,包含子目录QtGui下的头文件QColor。如果读者用到了Qt库的其他子模块,也应该将它们的路径添加到“包含目录”中。

可供Qt应用程序链接的Qt库实际上是由多个库文件组成的,这些库文件都存放在q:\lib目录下,每个子模块对应两个库文件,其中一个的扩展名为“.lib”,供静态链接模式下使用,另外一个的扩展名为“.DLL”,供动态链接模式下使用。

一个Qt应用程序必定会调用某些库文件中的某些函数,VS 2010链接器需要知道到哪个目录去寻找这些库文件,因此我们需要做以下配置。执行“项目\属性\通用属性\VC++目录”,在“库目录”中添加“q:\lib”。再执行“项目\属性\通用属性\连接器\输入”,在其中的“附加依赖项”中加入库文件的名称。例如,如果一个Qt应用程序只用到QtCore以及QtGui模块,而且这个应用程序以动态链接方式和Qt库相链接,那么所要加入的库文件应该为“qtmaind.lib QtGuid4.lib QtCored4.lib”。

以动态方式链接的Qt应用程序在运行的时候需要使用q:\bin目录下的动态链接库(DLL文件),所以用户应该将该目录加入到Windows系统的环境变量PATH中。具体来说,执行显示桌面\我的电脑\右键\属性\高级\环境变量\系统变量\PATH,在其末尾加上q:\bin。

3.编写测试程序。安装完毕Qt Visual Studio Add-in,我们可以尝试在VS 2010环境下编写一个小测试程序。运行文件\新建\项目,在弹出的对话框中选择Qt4 Projects。有多种项目类型可供选择,本书仅涉及其中两个。

(1)Qt Concole Application,即主控台程序。这种类型的程序不使用Qt GUI模块的任何类或者函数,运行时没有图形界面,只有一个能够显示文字的窗口,允许用户通过键盘输入一些信息,程序将运行结果以文字形式显示在该窗口中。该窗口通常被称为命令行窗口。

(2)Qt Application,即一般的图形界面程序。这种类型的程序使用Qt GUI模块的函数,显示一个具有图形界面的窗口。以上两种类型相互排斥,不要试图创建一个既具有命令行窗口又具有图形界面窗口的程序。如果一个程序使用了Qt GUI模块的任何类或者函数,就只能创建图形界面程序。2.4节将介绍如何创建主控台程序,本节仅介绍如何创建图形界面程序。

在“名称”域输入hello,在“位置”域输入一个目录名,以存放Qt插件自动生成的源文件、头文件。单击“确认”按钮后会启动一个创建项目的向导,其中有一页罗列了Qt的多个软件模块,供程序员选择,以和应用程序链接。我们暂时选择默认配置即可。项目创建完毕后,运行“生成\生成解决方案”对项目进行编译。成功后,执行“调试\开始执行(不调试)”,一个标题为“hello”的空白窗口弹出,表明该应用程序运行成功。下面我们来看看Qt是如何实现这个简单程序的。

从VS 2010的解决方案浏览器可以看到Qt插件为我们创建了源文件(source files)hello.cpp,main.cpp,头文件(header files)hello.h、资源文件(resource files)hello.qrc,以及若干生成文件(generated files)。目前,我们不必弄清楚每个文件、每一行的含义,我们只关心其中部分内容。hello.h的主要内容如下。

      #include <QtGui/QMainWindow>         ①
      #include "ui_hello.h"
      class hello : public QMainWindow     ②
      {
            Q_OBJECT
      public:
            hello(QWidget *parent = 0, Qt::WFlags flags = 0);
            ~hello();
      private:
            Ui::helloClass ui;
      };

由于该头文件使用了类QMainWindow,所以行①包含了头文件QMainWindow。Qt的每个类均对应着一个与该类同名的头文件。行②从类QMainWindow派生新类hello。类QMainWindow实现了一个应用程序主窗口的功能。它能够在操作系统的图形环境中显示自己,接收鼠标、键盘等消息进行处理。菜单、滚动条等部件常常作为子对象,被添加到该类的对象中。

用户在声明类hello时用到了宏Q_OBJECT,应该将声明该类的头文件hello.h加入到VS 2010项目的“Header Files”部分,否则将产生编译错误,具体原因我们将在17.2中阐述。

项目中main.cpp的内容如下。

      #include "hello.h"
      #include <QtGui/QApplication>
      int main(int argc, char *argv[])
      {
            QApplication a(argc, argv);     ③
            hello w;                         ④
            w.show();                        ⑤
            return a.exec();                 ⑥
      }

行③定义了一个QApplication对象,用于管理程序命令行等信息。行④定义一个hello对象,行⑤显示该对象。行⑥启动Qt的事件处理机制,处理用户的鼠标输入、键盘输入等事件。

4.Qt文档。Qt软件包提供了丰富的文档,介绍Qt库中各个类的用法、Qt开发环境(比如Qt Creator)的用法等内容。这些文档存放在目录d:\qtsdk\documentation下。为了阅读这些文档,读者应该运行“Qt助手”,也就是q:\bin目录下的assistant.exe,然后执行“编辑\首选项\文档\添加”,选择d:\qtsdk\documentation目录下的某个文件,比如介绍Qt库用法的qt.qch,以阅读该文件内容。

2.4 主控台的输入与输出

本书一些例子需要从键盘接收输入,并向屏幕输出文本信息。如果输入/输出数据是C++的基本类型,我们仍然可以使用C++标准库定义的cin与cout。如果输入/输出数据是Qt库中某些类的对象,我们就不能使用标准库中的cin与cout,因为Qt为了支持Unicode,设计了自己的输入/输出架构。

第7章将详细阐述Qt的输入/输出流框架,本节简要介绍如何使用该框架。类似于C语言,Qt也将键盘、屏幕看作特殊的文件。为了对文件进行输入/输出操作,Qt定义了类QFile以及QTextStream。QFile是对C语言文件操作函数的简单封装,实现底层的文件操作。而QTextStream实现二进制数据与Unicode文本数据之间的转换。

对文件操作时,一个QTextStream对象和一个QFile对象绑定。当需要输出程序中的二进制数据时,QTextStream将二进制数据转换为Unicode文本格式,再由QFile将数据写入文本文件。读取数据时,QFile从文本文件读取数据,由QTextStream完成文本格式到二进制格式的转换。如果该文本文件恰好是C++中预先定义的全局变量FILE * stdin,则从键盘接收数据;如果该文本文件是stdout,则向屏幕输出。

我们也可以使用Qt提供的类QDebug来输出数据,该类支持C++基本数据类型、Qt中的大部分数据类型。代码段2-1演示了以上两种方式。行①、②中的对象虽然与C++标准库中的cin,cout同名,但二者的类型是QTextStream,因而可以对一个类型为QString的对象进行操作。行③的函数qDebug()返回一个全局默认的QDebug对象,该对象支持QRect类型对象的输出。

代码段2-1,使用Qt进行主控台输入与输出,取自z:\examples\qt_console\main.cpp

      QTextStream cin(stdin, QIODevice::ReadOnly);      ①
      QTextStream cout(stdout, QIODevice::WriteOnly);   ②
      int main(int argc, char *argv[])
      {
            QString str;
            cin >> str;
            cout << "the string is "<< str << endl;
            qDebug() << "Qt types " << QRect(0,0,10,10) << endl;     ③
      }

创建上述主控台应用程序的步骤如下。在VS 2010中执行File\New\Project,在Project Types中选择Qt4 Projects,在Templates中选择Qt Console Application。在Name域中输入一个项目名称比如qt_console,在Location域输入一个已经存在的目录名。不选“Create directory for solution”选择框,以默认配置执行后续步骤,即可创建一个主控台应用程序。

关于选择框“Create directory for solution”的细节如下:VS 2010的一个solution可以包含多个projects,每个project可以有自己独立的子目录。每个solution对应着一个描述文件,描述该solution包含哪些projects,而每个project也会对应着一个描述文件,描述该project包含哪些C++源文件、头文件等。如果一个solution只含有一个project,可以不选Create directory for solution选项,此时该solution以及该project共享一个目录,二者的描述文件都被存放在该目录下。如果选择了这个选项,VS 2010会为二者各自创建一个目录,将二者的描述文件放在各自的目录下。

2.5 Qt风格的编程规范

笔者的一位朋友曾在北京一家电信软件公司担任技术总监。当我问他每天都在忙碌什么时,他半开玩笑的回答“我们公司的软件系统经过这么多年的开发,体系架构、性能等都已经比较成熟,并不需要大幅度的重构。我每天做的工作也就是修改函数、参数的名字,使程序更加易读”。的确,在传统的大学课程中,算法、系统架构、设计等会被强调,而诸如函数命名这样的编程规范常被忽略。良好的编程规范可以大幅提高一个程序的可读性、可维护性。

软件开发领域并没有一个公认的、统一的编程规范,不同的软件开发项目或者组织会采用不同的编程规范,如Google公司采用参考文献[4]作为该公司所有开源项目的编程规范。Qt的开发团队采用参考文献[5]作为编程规范。该规范使得Qt的API更容易被程序员理解和使用,缩短了开发人员的学习周期,是Qt得以广泛流行的原因之一。学习并遵循该规范,可以使我们开发的Qt应用程序也更易读、更易维护。下面我们介绍该规范的主要内容。

1.API的设计原则。(1)精炼。软件系统应该尽量少定义类,每个类中应该尽量少定义成员。(2)清晰而简单的语义。用户应该能够轻易地使用一个API来解决那些常见的任务。对于不常见、复杂的任务,用户也能通过该API来实现,但是这不应该成为API设计的重点。另外,API不能过度追求通用性,它应该首先被用来解决具体的、常见的问题。(3)直观。使得那些没有阅读过一个API文档的程序员也能够读懂使用该API开发出来的程序。(4)容易记忆。应该使用精确、统一的命名规则来对类、函数、参数等进行命名。

2.可读性比精炼更重要。例如,语句:

      QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");

虽然精炼但是可读性差。类QSlider的API应该被改为:

      QSlider *slider = new QSlider(Qt::Vertical);
      slider->setRange(12, 18);
      slider->setPageStep(3);
      slider->setValue(13);
      slider->setObjectName("volume");

又比如,当我们调用一个控件的成员函数repaint()重新绘制该控件时,有时我们希望先擦除其背景再绘制,有时却又希望直接绘制以提供速度。一种做法是令该函数包含一个布尔类型的参数,表示是否需要擦除背景。这种做法虽然只用了一个函数来处理两种情形,看起来比较简洁,但是可能导致下面的代码:

      widget->repaint(false);

API的初级用户可能将其理解为“不重新绘制控件”。因此,为了提高代码的可读性,更好的一个做法是用两个函数来实现上述两种情形:成员函数repaint()擦除背景再绘制控件,而成员函数repaintWithoutErasing()只绘制控件、不擦除背景。

3.相似的类应该具有相似的接口。例如,Qt中的QTextStream负责以文本形式输入或者输出程序中的数据,而QDataStream负责以二进制方式输入或者输出。由于二者具有相似的功能,它们都重载了运算符“<<”以及“>>”,使得熟悉其中一个类的用户能够迅速学会另外一个类的使用方法,缩短了学习周期。

4.命名原则。Qt推荐使用以下命名原则。

● 不要使用缩写。即使对于“previous→prev”这样广为接受的缩写形式,Qt也不予采纳,因为这要求程序员记忆哪些单词被缩写、哪些没有被缩写。

● 如果一个标识符含有多个单词,则第2个单词之后所有单词的首字母应该大写,比如“installSceneEventFilter”。有的编程规范建议使用下画线分割各个单词,但这会增加标识符的长度。

● 类的名字以大写字母开始。其中绝大多数以字母Q开始,表示该类是Qt软件包中定义的类,此时,第二个字母也应该是大写的。而且,类的名字应该是一个名词,比如QFileSystemWatcher。

● 函数名字以小写字母开始,应该是一个动词或者含有动词的短语,比如collidesWithItem()。

● 全局常量名字中的字母都应该大写,比如Q_BIG_ENDIAN。枚举常量的名字应该含有枚举类型的信息。这是由于枚举常量在C++程序中可被直接使用,如果其名字过于简单,可能会导致歧义。例如,设有:

      enum CaseSensitivity { Insensitive, Sensitive };

由于有上下文,枚举常量Insensitve及Sensitive在被定义时并没有任何歧义。然而,当它们在程序其他地方被引用时,程序的读者难以理解它们的含义。因此,应该在它们的名字中嵌入枚举类型的信息,比如

      enum CaseSensitivity { CaseInsensitive, CaseSensitive };

● 如果某个数据成员具有布尔类型,读取该数据的成员函数应该以“is”打头,比如isEmpty()、isMovingEnabled()。然而,如果该数据成员的名字是复数形式的,则不需要加任何前缀,比如scrollBarsEnabled()。

2.6 与Qt及C++相关的文献资源

1.与Qt相关的文献资源。developer.qt.nokia.com/books列举了有关Qt的书籍。初始学习阶段,读者可以阅读书籍Foundations of Qt Development[6],An Introduction to Design Patterns in C++with Qt4[7]以及C++GUI Programming with Qt 4[8]。也可以在Qt官方网站qt.nokia.com上观看Qt专家的讲座录像、Qt培训资料、电子杂志“Qt Quarterly”。在开发过程中,可以参考Qt联机文档,查看类的使用方法。如果想学习深层的编程技巧,可以参考 Advanced Qt Programming[9]。该书的作者正是负责撰写Qt联机文档的Mark Summerfield。Qt的创始人之一Eirik Chambe-Eng为该书写了序言。

英文社区中,网站blog.qt.nokia.com对Qt进行一般性的讨论,网站labs.qt.nokia.com的blog部分含有一些技术水准较高的文章。developer.qt.nokia.com/forums讨论与Qt相关的技术问题。中文社区中,网站www.cuteqt.com,www.qtcn.org以及qt.csdn.net讨论Qt的使用与开发。

开发Qt应用程序时,开发者可以使用Qt软件包中的Qt Assisant阅读Qt的联机文档。该文档介绍类库中各个类的功能,类之间的继承关系,编程工具Qt Designer、Qt Linguist、QMake的使用等。这些文档的格式是Qt特有的,程序员只能使用Qt Assitant来查看。阅读文档内容时,用户可以使用Ctrl+鼠标滚轮更改字体大小,还可以在多个标签页中显示具有不同主题的内容。

2.与C++相关的文献资源。关于C++的书籍、论文、论坛非常多,即使那些被C++程序员奉为经典的书籍也多达十几本。以下是笔者推荐的一些书籍、网站。其中大多数文献都有对应的中文版,本文不再一一列举。对于具有一定英文基础的读者,笔者建议直接阅读英文原版文献,因为同一个C++概念在不同的中文版图书中会有不同的翻译,会增加阅读、交流难度。对于C++的初学者,如果他以前学习过C语言,可以选择下面的Thinking in C++一书,否则,可以选择下面的C++ Primer一书。

C++Primer[10],详细、系统地阐述了C++语言的各种语言特性,文笔流畅,通俗易懂。该书涵盖了C语言的内容,因而适用于那些从未接触过C语言的读者。该书的作者之一Stanley B.Lippman曾和C++创始人Bjarne Stroustrup在Bell实验室一起工作,实现了cfront。另外一个作者Josée Lajoie是IBM加拿大实验室C++编译器项目组成员,自1990年起成为C++标准委员会的成员,曾担任该委员会的副主席。虽然他们对C++语言有着深刻的理解,但是他们在本书中却常常使用一些通俗易懂的小例子,来讲述为什么要使用某个语言特性以及如何使用它。

Thinking in C++[11],假设读者已经掌握了C语言。在批判C语言缺点的过程中,逐步引入C++的各个语言特性,借此证明这些语言特性的作用。书中例子短小精炼,仅用来说明某个语言特性,不涉及任何实际问题。该书分为两卷,第一卷介绍基本的C++语言特性,文笔流畅而严谨,第二卷介绍C++标准库、多继承、运行时类型信息等内容,虽然仍然保持着第一卷通俗易懂的风格,但是极少数内容似乎写得比较仓促。由于国内许多大学将C语言作为一门独立的课程,那些掌握了C语言的学生可以将该书作为学习C++语言的入门教材。

以上两本教材主要讨论了“C++是什么”。要想在编程实践中“正确、有效地使用C++”,可以阅读下面的Effective C++一书以及本书。Effective C++(以及其姊妹篇More Effective C++)将这些知识总结为85条编程准则,而本书结合Qt这一具体开源项目,以具体而鲜活的形式诠释这些知识。

Effective C++[2],文笔流畅、精炼、幽默。随着C++的演化,作者出版了1992、1998、2005年三个版本。这组书籍讨论如何更加有效地利用C++的各种语言特性,以设计出更好的面向对象程序。作者以50余个短小、具体、易于记忆的准则,总结了C++程序员应该做什么(以及原因)、不应该做什么(以及原因)。其中一些准则和设计策略相关,比如,是使用继承还是使用模板,是使用函数名重载还是使用默认参数,一个类的成员函数何时应该被定义为虚函数,何时应该被定义为普通函数。另外一些准则和某个具体的语言特性相关,比如,不要像C语言那样在函数体的开始部分定义所有变量,而是应该尽量推迟对象的定义。该书的作者还于1996年出版了另外一本风格相似的书More Effective C++,给出了一些更深层次的准则,比如如何提升程序的性能,如何定义智能指针等。

对于那些具有一定开发经验、想要全面、准确地把握C++语言的程序员,可以参考下面的文献。

The C++Programming Language[12],由“C++之父”Bjarne Stroustrup撰写,具有1986、1991、1997、2000年四个版本,讨论如何使用C++的语言特性设计、编写更好的面向对象程序,该书最后一部分甚至专门从整体的软件开发与设计这一角度讨论C++的各种语言特性。该书系统、准确,书中的观点是作者几十年软件开发实践中宝贵经验的结晶,值得C++程序员仔细品位、借鉴。但是,作者有意将该书的读者定位为具有一定开发经验的C++程序员,加之文笔稍显晦涩,学术味重,使得该书不适合初学者。

要想开发一个高质量的软件系统,仅了解C++语言本身是不够的,开发人员还应该从软件工程这一更高的角度来看待软件开发。虽然这一领域的理论、文献更加繁多,但是笔者建议读者至少要系统地阅读下面这本教材。

Design patterns:elements of reusable object-oriented software[13],设计模式领域的经典之作。针对软件开发中经常出现的一些问题,该书以类图的形式给出每个问题的解决思路,阐述类和类之间的协作关系,讨论这种设计对系统的可复用性、性能或者其他方面的影响,并给出C++或者Smalltalk语言的具体实现。学习这些设计模式,读者一方面可以直接将它们应用在自己的设计中,另一方面可以揣摩如何利用C++语言的各种特性来解决实际设计问题。这些设计模式是该书作者们从大量设计优良的软件系统中总结而来的,因而在一个规模稍大、质量较高的软件系统中,我们可以找到这些模式的具体应用。Qt就是一个例子,本书将阐述Qt是如何使用其中一些设计模式来解决具体问题的。

关于C++的网络资源也非常丰富。导航网站www.robertnz.net/cpp_site.html含有多个链接,指向与C++相关的文献、库、编程规范、新闻组等。随书光盘“Z:\misc\Sites of interest to C++ users.mht”保存了该网站的内容,供读者参考。另外一个类似的导航网站是C++ FAQ(www.parashift.com/c++-faq-lite)。