C++ Primer Plus(第6版)中文版

978-7-115-27946-0
作者: 【美】Stephen Prata
译者: 张海龙袁国忠
编辑: 傅道坤
分类: C++

图书目录:

详情

C++是在C语言基础上开发的一种集面向对象编程、通用编程和传统的过程化编程于一体的编程语言,是C语言的超集。本书通过大量短小精悍的程序详细而全面地阐述了C++的基本概念和技术。

我们针对图书内容开发了在线编程练习题,并提供了在线编程环境,您可以边读书,边练习,在线编程,双效合一! 购买【异步社区VIP会员】或直接购买以下图书的【e读版电子书】后,即可在线畅读全书,并可通过章节末尾入口进入在线编程练习!了解更多精品图书在线编程实验课:https://www.epubit.com/topicsDetails?id=877f112b-febb-49f4-b203-ec6432852759  

图书摘要

版权信息

书名:C++ Primer Plus(第6版)中文版

ISBN:978-7-115-27946-0

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] Stephen Prata 

  译    张海龙 袁国忠

  责任编辑 傅道坤

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Authorized translation from the English language edition, entitled C++ Primer Plus (sixth edition), 9780321776402 by Stephen Prata, published by Pearson Education, lnc., publishing as Addison-Wesley, Copyright 2011 Pearson Education, lnc.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education lnc. CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD., and POSTS & TELECOMMUNICATIONS PRESS Copyright 2012.

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签。无标签者不得销售。


C++是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言,是C语言的超集。本书是根据2003年的ISO/ANSI C++标准编写的,通过大量短小精悍的程序详细而全面地阐述了 C++的基本概念和技术,并专辟一章介绍了C++11新增的功能。

全书分18章和10个附录。分别介绍了C++程序的运行方式、基本数据类型、复合数据类型、循环和关系表达式、分支语句和逻辑运算符、函数重载和函数模板、内存模型和名称空间、类的设计和使用、多态、虚函数、动态内存分配、继承、代码重用、友元、异常处理技术、string类和标准模板库、输入/输出、C++11新增功能等内容。

本书针对C++初学者,书中从C语言基础知识开始介绍,然后在此基础上详细阐述C++新增的特性,因此不要求读者有C语言方面的背景知识。本书可作为高等院校教授C++课程的教材,也可供初学者自学C++时使用。


Stephen Prata在美国加州肯特菲尔得的马林学院教授天文、物理和计算机科学。他毕业于加州理工学院,在美国加州大学伯克利分校获得博士学位。他单独或与他人合作编写的编程图书有十多本,其中《New C Primer Plus》获得了计算机出版联合会1990年度最佳“How-to”计算机图书奖,《C++ Primer Plus》获得了计算机出版联合会1991年度最佳“How-to”计算机图书奖提名。


学习C++是一次探索之旅,因为这种语言容纳了好几种编程范式,其中包括面向对象编程、泛型编程和传统的过程化编程。本书第 5 版是基于 ISO C++标准编写的,该标准的官方名称为 C++99 和 C++03(C++99/C++03),其中2003标准主要是对1999标准的技术修正,并没有添加任何新功能。C++在不断发展,编写本书时,新标准获得了C++国际标准委员会的批准。在制定期间,该标准名为C++0x,但现已改名为C++11。大多数编译器都能很好地支持C++99/03,而本书的大多数示例都遵守该标准。有些实现中已显现了新标准的很多功能,而本书也对这些新功能进行了探索。

本书在介绍C++特性的同时,讨论了基本C语言,使两者成为有机的整体。书中介绍了C++的基本概念,并通过短小精悍的程序来阐明,这些程序都很容易复制和试验。书中还介绍了输入和输出,如何让程序执行重复性任务,如何让程序做出选择,处理数据的多种方式,以及如何使用函数等内容。另外,本书还讲述了C++在C语言的基础上新增的很多特性,包括:

初级教程方法

大约20年前,《C Primer Plus》开创了优良的初级教程传统,本书建立在这样的基础之上,吸收了其中很多成功的理念。

基于上述理念,本书帮助您理解这种用途广泛的语言,并学习如何使用它。

本书的作者和编辑尽最大的努力使本书简单、明了、生动有趣。我们的目标是,您阅读本书后,能够编写出可靠、高效的程序,并且觉得这是一种享受。

示例代码

本书包含大量的示例代码,其中大部分是完整的程序。和前一版一样,本书介绍的是通用C++,因此适用于任何计算机、操作系统和编译器。书中的示例在Windows 7系统、Macintosh OS X系统和Linux系统上进行了测试。

使用了C++11功能的程序要求编译器支持这些功能,但其他程序可在遵循C++ 99/03的任何系统上运行。

书中完整程序的源代码可从配套网站下载,详情请参阅封底的链接信息。

本书内容

本书分为18章和10个附录。

对教师的提示

本书宗旨之一是,提供一本既可用于自学又可用于教学的书籍。下面是本书在支持教学方面的一些特征。

本书约定

为区别不同类型的文本,我们使用了一些印刷上的约定。

旁注:提供更深入的讨论和额外的背景知识,帮助阐明主题。

提示:提供特定编程情形下很有帮助的简单指南。

警告:指出潜在的陷阱。

注意:提供不属于其他类别的各种说明。

开发本书编程示例时使用的系统

本书的C++11示例是使用Microsoft Visual C++ 2010和带Gnu g++ 4.5.0的Cygwin开发的,它们都运行在64位的Windows 7系统上。其他示例在这些系统上进行了测试,还在OS X 10.6.8系统和Ubuntu Linux系统上分别使用g++ 4.21和g++ 4.4.1进行了测试。大多数非C++11示例最初都是在Windows XP Professional系统上使用Microsoft Visual C++ 2003和Metrowerks CodeWarrior Development Studio 9开发的,并在该系统上使用Borland C++ 5.5命令行编译器和GNU gpp 3.3.3进行了测试;其次,在运行SuSE 9.0 Linux的系统上使用Comeau 4.3.3和GNU g++3.3.1进行了测试;最后,在运行OS 10.3的Macintosh G4上使用Metrowerks Development Studio 9进行了测试。

C++为程序员提供了丰富多彩的内容。祝您学习愉快!


本章内容包括:

欢迎进入C++世界!这是一种令人兴奋的语言,它在C语言的基础上添加了对面向对象编程和泛型编程的支持,在20世纪90年代便是最重要的编程语言之一,并在21世纪仍保持强劲势头。C++继承了C语言高效、简洁、快速和可移植性的传统。C++面向对象的特性带来了全新的编程方法,这种方法是为应付复杂程度不断提高的现代编程任务而设计的。C++的模板特性提供了另一种全新的编程方法——泛型编程。这三件法宝既是福也是祸,一方面让C++语言功能强大,另一方面则意味着有更多的东西需要学习。

本章首先介绍C++的背景,然后介绍创建C++程序的一些基本原则。本书其他章节将讲述如何使用C++语言,从最浅显的基本知识开始,到面向对象的编程(OOP)及其支持的新术语——对象、类、封装、数据隐藏、多态和继承等,然后介绍它对泛型编程的支持(当然,随着您对C++的学习,这些词汇将从花里胡哨的词语变为论述中必不可少的术语)。

C++融合了3种不同的编程方式:C语言代表的过程性语言、C++在C语言基础上添加的类代表的面向对象语言、C++模板支持的泛型编程。本章将简要介绍这些传统。不过首先,我们来看看这种传统对于学习C++来说意味着什么。使用C++的原因之一是为了利用其面向对象的特性。要利用这种特性,必须对标准C语言知识有较深入的了解,因为它提供了基本类型、运算符、控制结构和语法规则。所以,如果已经对C有所了解,便可以学习C++了,但这并不仅仅是学习更多的关键字和结构,从C过渡到C++的学习量就像从头学习C语言一样大。另外,如果先掌握了C语言,则在过渡到C++时,必须摈弃一些编程习惯。如果不了解C语言,则学习C++时需要掌握C语言的知识、OOP知识以及泛型编程知识,但无需摈弃任何编程习惯。如果您认为学习C++可能需要扩展思维,这就对了。本书将以清晰的、帮助的方式,引导读者一步一个脚印地学习,因此扩展思维的过程是温和的,不至于让您的大脑接受不了。

本书通过传授C语言基础知识和C++新增的内容,带您步入C++的世界,因此不要求读者具备C语言知识。首先学习C++与C语言共有的一些特性。即使已经了解C语言,也会发现阅读本书的这一部分是一次很好的复习。另外,本章还介绍了一些对后面的学习十分重要的概念,指出了C++和C之间的区别。在牢固地掌握了C语言的基础知识后,就可以在此基础上学习C++方面的知识了。那时将学习对象和类以及C++是如何实现它们的,另外还将学习模板。

本书不是完整的C++参考手册,不会探索该语言的每个细节,但将介绍所有的重要特性,包括模板、异常和名称空间等。

下面简要地介绍一下C++的背景知识。

在过去的几十年,计算机技术以令人惊讶的速度发展着,当前,笔记本电脑的计算速度和存储信息的能力超过了20世纪60年代的大型机。很多程序员可能还记得,将数叠穿孔卡片提交给充斥整个房间的大型计算机系统的时代,而这种系统只有100KB的内存,比当今智能手机的内存都少得多。计算机语言也得到了发展,尽管变化可能不是天翻地覆的,但也是非常重要的。体积更大、功能更强的计算机引出了更大、更复杂的程序,而这些程序在程序管理和维护方面带来了新的问题。

在20世纪70年代,C和Pascal这样的语言引领人们进入了结构化编程时代,这种机制把秩序和规程带进了迫切需要这种性质的领域中。除了提供结构化编程工具外,C还能生成简洁、快速运行的程序,并提供了处理硬件问题的能力,如管理通信端口和磁盘驱动器。这些因素使C语言成为20世纪80年代占统治地位的编程语言。同时,20世纪80年代,人们也见证了一种新编程模式的成长:面向对象编程(OOP)。SmallTalk和C++语言具备这种功能。下面更深入地介绍C和OOP。

20世纪70年代早期,贝尔实验室的Dennis Ritchie致力于开发UNIX操作系统(操作系统是能够管理计算机资源、处理计算机与用户之间交互的一组程序。例如,操作系统将系统提示符显示在屏幕上以提供终端式界面、提供管理窗口和鼠标的图形界面以及运行程序)。为完成这项工作,Ritchie需要一种语言,它必须简洁,能够生成简洁、快速的程序,并能有效地控制硬件。

传统上,程序员使用汇编语言来满足这些需求,汇编语言依赖于计算机的内部机器语言。然而,汇编语言是低级(low-level)语言,即直接操作硬件,如直接访问CPU寄存器和内存单元。因此汇编语言针对于特定的计算机处理器,要将汇编程序移植到另一种计算机上,必须使用不同的汇编语言重新编写程序。这有点像每次购买新车时,都发现设计人员改变了控制系统的位置和功能,客户不得不重新学习驾驶。

然而,UNIX是为在不同的计算机(或平台)上工作而设计的,这意味着它是一种高级语言。高级(high-level)语言致力于解决问题,而不针对特定的硬件。一种被称为编译器的特殊程序将高级语言翻译成特定计算机的内部语言。这样,就可以通过对每个平台使用不同的编译器来在不同的平台上使用同一个高级语言程序了。Ritchie希望有一种语言能将低级语言的效率、硬件访问能力和高级语言的通用性、可移植性融合在一起,于是他在旧语言的基础上开发了C语言。

由于C++在C语言的基础上移植了新的编程理念,因此我们首先来看一看C所遵循的旧的理念。一般来说,计算机语言要处理两个概念——数据和算法。数据是程序使用和处理的信息,而算法是程序使用的方法(参见图1.1)。C语言与当前最主流的语言一样,在最初面世时也是过程性(procedural)语言,这意味着它强调的是编程的算法方面。从概念上说,过程化编程首先要确定计算机应采取的操作,然后使用编程语言来实现这些操作。程序命令计算机按一系列流程生成特定的结果,就像菜谱指定了厨师做蛋糕时应遵循的一系列步骤一样。

随着程序规模的扩大,早期的程序语言(如FORTRAN和BASIC)都会遇到组织方面的问题。例如,程序经常使用分支语句,根据某种测试的结果,执行一组或另一组指令。很多旧式程序的执行路径很混乱(被称为“意大利面条式编程”),几乎不可能通过阅读程序来理解它,修改这种程序简直是一场灾难。为了解决这种问题,计算机科学家开发了一种更有序的编程方法——结构化编程(structured programming)。C语言具有使用这种方法的特性。例如,结构化编程将分支(决定接下来应执行哪个指令)限制为一小组行为良好的结构。C语言的词汇表中就包含了这些结构(for循环、while循环、do while循环和if else语句)。

另一个新原则是自顶向下(top-down)的设计。在C语言中,其理念是将大型程序分解成小型、便于管理的任务。如果其中的一项任务仍然过大,则将它分解为更小的任务。这一过程将一直持续下去,直到将程序划分为小型的、易于编写的模块(整理一下书房。先整理桌子、桌面、档案柜,然后整理书架。好,先从桌子开始,然后整理每个抽屉,从中间的那个抽屉开始整理。也许我都可以管理这项任务)。C语言的设计有助于使用这种方法,它鼓励程序员开发程序单元(函数)来表示各个任务模块。如上所述,结构化编程技术反映了过程性编程的思想,根据执行的操作来构思一个程序。

图1.1 数据+算法=程序

虽然结构化编程的理念提高了程序的清晰度、可靠性,并使之便于维护,但它在编写大型程序时,仍面临着挑战。为应付这种挑战,OOP提供了一种新方法。与强调算法的过程性编程不同的是,OOP强调的是数据。OOP不像过程性编程那样,试图使问题满足语言的过程性方法,而是试图让语言来满足问题的要求。其理念是设计与问题的本质特性相对应的数据格式。

在C++中,类是一种规范,它描述了这种新型数据格式,对象是根据这种规范构造的特定数据结构。例如,类可以描述公司管理人员的基本特征(姓名、头衔、工资、特长等),而对象则代表特定的管理人员(Guilford Sheepblat、副总裁、$925 000、知道如何恢复Windows注册表)。通常,类规定了可使用哪些数据来表示对象以及可以对这些数据执行哪些操作。例如,假设正在开发一个能够绘制矩形的计算机绘图程序,则可以定义一个描述矩形的类。定义的数据部分应包括顶点的位置、长和宽、4条边的颜色和样式、矩形内部的填充颜色和图案等;定义的操作部分可以包括移动、改变大小、旋转、改变颜色和图案、将矩形复制到另一个位置上等操作。这样,当使用该程序来绘制矩形时,它将根据类定义创建一个对象。该对象保存了描述矩形的所有数据值,因此可以使用类方法来修改该矩形。如果绘制两个矩形,程序将创建两个对象,每个矩形对应一个。

OOP程序设计方法首先设计类,它们准确地表示了程序要处理的东西。例如,绘图程序可能定义表示矩形、直线、圆、画刷、画笔的类。类定义描述了对每个类可执行的操作,如移动圆或旋转直线。然后您便可以设计一个使用这些类的对象的程序。从低级组织(如类)到高级组织(如程序)的处理过程叫作自下向上(bottom-up)的编程。

OOP编程并不仅仅是将数据和方法合并为类定义。例如,OOP还有助于创建可重用的代码,这将减少大量的工作。信息隐藏可以保护数据,使其免遭不适当的访问。多态让您能够为运算符和函数创建多个定义,通过编程上下文来确定使用哪个定义。继承让您能够使用旧类派生出新类。正如接下来将看到的那样,OOP引入了很多新的理念,使用的编程方法不同于过程性编程。它不是将重点放在任务上,而是放在表示概念上。有时不一定使用自上向下的编程方法,而是使用自下向上的编程方法。本书将通过大量易于掌握的示例帮助读者理解这些要点。

设计有用、可靠的类是一项艰巨的任务,幸运的是,OOP语言使程序员在编程中能够轻松地使用已有的类。厂商提供了大量有用的类库,包括设计用于简化Windows或Macintosh环境下编程的类库。C++真正的优点之一是:可以方便地重用和修改现有的、经过仔细测试的代码。

泛型编程(generic programming)是C++支持的另一种编程模式。它与OOP的目标相同,即使重用代码和抽象通用概念的技术更简单。不过OOP强调的是编程的数据方面,而泛型编程强调的是独立于特定数据类型。它们的侧重点不同。OOP是一个管理大型项目的工具,而泛型编程提供了执行常见任务(如对数据排序或合并链表)的工具。术语泛型(generic)指的是创建独立于类型的代码。C++的数据表示有多种类型——整数、小数、字符、字符串、用户定义的、由多种类型组成的复合结构。例如,要对不同类型的数据进行排序,通常必须为每种类型创建一个排序函数。泛型编程需要对语言进行扩展,以便可以只编写一个泛型(即不是特定类型的)函数,并将其用于各种实际类型。C++模板提供了完成这种任务的机制。

与C语言一样,C++也是在贝尔实验室诞生的,Bjarne Stroustrup于20世纪80年代在这里开发出了这种语言。用他自己的话来说,“C++主要是为了我的朋友和我不必再使用汇编语言、C语言或其他现代高级语言来编程而设计的。它的主要功能是可以更方便地编写出好程序,让每个程序员更加快乐”。

Bjarne Stroustrup的主页


Bjarne Stroustrup设计并实现了C++编程语言,他是权威参考手册《The C++ Programming Language》和《The design and Evolution of C++》的作者。搜索Bjarne Stroustroup即可找到他的个人网站。

该网站包括了C++语言有趣的发展历史、Bjarne的传记材料和C++ FAQ。Bjarne被问得最多的问题是:Bjarne Stroustrup应该如何读。您可以访问Stroustrup的网站,阅读FAQ部分并下载.WAV文件,亲自听一听。

Stroustrup比较关心的是让C++更有用,而不是实施特定的编程原理或风格。在确定C++语言特性方面,真正的编程需要比纯粹的原理更重要。Stroupstrup之所以在C的基础上创建C++,是因为C语言简洁、适合系统编程、使用广泛且与UNIX操作系统联系紧密。C++的OOP方面是受到了计算机模拟语言Simula67的启发。Stroustrup加入了OOP特性和对C的泛型编程支持,但并没有对C的组件作很大的改动。因此,C++是C语言的超集,这意味着任何有效的C程序都是有效的C++程序。它们之间有些细微的差异,但无足轻重。C++程序可以使用已有的C软件库。库是编程模块的集合,可以从程序中调用它们。库对很多常见的编程问题提供了可靠的解决方法,因此能节省程序员大量的时间和工作量。这也有助于C++的广泛传播。

名称C++来自C语言中的递增运算符++,该运算符将变量加1。名称C++表明,它是C的扩充版本。

计算机程序将实际问题转换为计算机能够执行的一系列操作。OOP部分赋予了C++语言将问题所涉及的概念联系起来的能力,C部分则赋予了C++语言紧密联系硬件的能力(参见图1.2),这种能力上的结合成就了C++的广泛传播。从程序的一个方面转到另一个方面时,思维方式也要跟着转换(确实,有些OOP正统派把为C添加OOP特性看作是为猪插上翅膀,虽然这是头瘦骨嶙峋、非常能干的猪)。另外,C++是在C语言的基础上添加OOP特性,您可以忽略C++的面向对象特性,但将错过很多有用的东西。

在C++获得一定程度的成功后,Stroustrup才添加了模板,这使得进行泛型编程成为可能。在模板特性被使用和改进后,人们才逐渐认识到,它们和OOP同样重要——甚至比OOP还重要,但有些人不这么认为。C++融合了OOP、泛型编程和传统的过程性方法,这表明C++强调的是实用价值,而不是意识形态方法,这也是该语言获得成功的原因之一。

图1.2 C++的二重性

假设您为运行Windows 2000的老式奔腾PC编写了一个很好用的C++程序,而管理人员决定用使用不同操作系统(如Mac OS X或Linux)和处理器(如SPARC处理器)的计算机替换它。该程序是否可以在新平台上运行呢?当然,必须使用为新平台设计的C++编译器对程序重新编译。但是否需要修改编写好的代码呢?如果在不修改代码的情况下,重新编译程序后,程序将运行良好,则该程序是可移植的。

在可移植性方面存在两个障碍,其中的一个是硬件。硬件特定的程序是不可移植的。例如,直接控制IBM PC 视频卡的程序在涉及Sun时将“胡言乱语”(将依赖于硬件的部分放在函数模块中可以最大限度地降低可移植性问题;这样只需重新编写这些模块即可)。本书将避免这种编程。

可移植性的第二个障碍是语言上的差异。口语确实可能产生问题。约克郡的人对某个事件的描述,布鲁克林人可能就听不明白,虽然这两个地方的人都说英语。计算机语言也可能出现方言。Windows XP C++的实现与Red Hat Linux或Macintosh OS X实现相同吗?虽然多数实现都希望其C++版本与其他版本兼容,但如果没有准确描述语言工作方式的公开标准,这将很难做到。因此,美国国家标准局(American National Standards Institute,ANSI)在1990年设立了一个委员会(ANSI X3J16),专门负责制定C++标准(ANSI制定了C语言标准)。国际标准化组织(ISO)很快通过自己的委员会(ISO-WG-21)加入了这个行列,创建了联合组织ANSI/ISO,致力于制定C++标准。

经过多年的努力,制定出了一个国际标准ISO/IEC 14882:1998,并于1998年获得了ISO、IEC(International Electrotechnical Committee,国际电工技术委员会)和ANSI的批准。该标准常被称为C++98,它不仅描述了已有的C++特性,还对该语言进行了扩展,添加了异常、运行阶段类型识别(RTTI)、模板和标准模板库(STL)。2003年,发布了C++标准第二版(IOS/IEC 14882:2003);这个新版本是一次技术性修订,这意味着它对第一版进行了整理——修订错误、减少多义性等,但没有改变语言特性。这个版本常被称为C++03。由于C++03没有改变语言特性,因此我们使用C++98表示C++98/C++2003。

C++在不断发展。ISO标准委员会于2011年8月批准了新标准ISO/IEC 14882:2011,该标准以前称为C++11。与C++98一样,C++11也新增了众多特性。另外,其目标是消除不一致性,让C++学习和使用起来更容易。该标准还曾被称为C++0x,最初预期x为7或8,但标准制定工作是一个令人疲惫的缓慢过程。所幸的是,可将0x视为十六进制数,这意味着委员会只需在2015年前完成这项任务即可。根据这个度量标准,委员会还是提前完成了任务。

ISO C++标准还吸收了ANSI C语言标准,因为C++应尽量是C语言的超集。这意味着在理想情况下,任何有效的C程序都应是有效的C++程序。ANSI C与对应的C++规则之间存在一些差别,但这种差别很小。实际上,ANSI C加入了C++首次引入的一些特性,如函数原型和类型限定符const。

在ANSI C出现之前,C语言社区遵循一种事实标准,该标准基于Kernighan和Ritchie编写的《The C Programming Language》一书,通常被称为K&R C;ANSI C出现后,更简单的K&R C有时被称为经典C(Classic C)。

ANSI C标准不仅定义了C语言,还定义了一个ANSI C实现必须支持的标准C库。C++也使用了这个库;本书将其称为标准C库或标准库。另外,ANSI/ISO C++标准还提供了一个C++标准类库。

最新的C标准为C99,ISO和ANSI分别于1999年和2000年批准了该标准。该标准在C语言中添加了一些C++编译器支持的特性,如新的整型。

Stroustrup编写的《The Programming Language》包含65页的参考手册,它成了最初的C++事实标准。

下一个事实标准是Ellis和Stroustrup编写的《The Annotated C++ Reference Manual》。

C++98标准新增了大量特性,其篇幅将近800页,且包含的说明很少。

C++11标准的篇幅长达1350页,对旧标准做了大量的补充。

当代的编译器都对C++98提供了很好的支持。编写本书期间,有些编译器还支持一些C++特性;随着新标准获批,对这些特性的支持将很快得到提高。本书反映了当前的情形,详尽地介绍了C++98,并涵盖了C++11新增的一些特性。在探讨相关的C++98主题时顺便介绍了一些C++新特性,而第18章专门介绍新特性,它总结了本书前面提到的一些特性,并介绍了其他特性。

在编写本书期间,对C++11的支持还不全面,因此难以全面介绍C++11新增的所有特性。考虑到篇幅限制,即使这个新标准获得了全面支持,也无法在一本书中全面介绍它。本书重点介绍大多数编译器都支持的特性,并简要地总结其他特性。

详细介绍C++之前,先介绍一些有关程序创建的基本知识。

假设您编写了一个C++程序。如何让它运行起来呢?具体的步骤取决于计算机环境和使用的C++编译器,但大体如下(参见图1.3)。

1.使用文本编辑器编写程序,并将其保存到文件中,这个文件就是程序的源代码。

2.编译源代码。这意味着运行一个程序,将源代码翻译为主机使用的内部语言——机器语言。包含了翻译后的程序的文件就是程序的目标代码(object code)。

3.将目标代码与其他代码链接起来。例如,C++程序通常使用库。C++库包含一系列计算机例程(被称为函数)的目标代码,这些函数可以执行诸如在屏幕上显示信息或计算平方根等任务。链接指的是将目标代码同使用的函数的目标代码以及一些标准的启动代码(startup code)组合起来,生成程序的运行阶段版本。包含该最终产品的文件被称为可执行代码。

图1.3 编程步骤

本书将不断使用术语源代码,请记住该术语。

本书的程序都是通用的,可在任何支持C++98的系统中运行;但第18章的程序要求系统支持C++11。编写本书期间,有些编译器要求您使用特定的标记,让其支持部分C++11特性。例如,从4.3版起,g++要求您编译源代码文件时使用标记-std=c++0x:

创建程序的步骤可能各不相同,下面深入介绍这些步骤。

本书余下的篇幅讨论源代码文件中的内容;本节讨论创建源代码文件的技巧。有些C++实现(如Microsoft Visual C++、Embarcadero C++ Builder、Apple Xcode、Open Watcom C++、Digital Mars C++和Freescale CodeWarrior)提供了集成开发环境(integrated development environments,IDE),让您能够在主程序中管理程序开发的所有步骤,包括编辑。有些实现(如用于UNIX和Linux的GNU C++、用于AIX的IBM XL C/C++、Embarcadero分发的Borland 5.5免费版本以及Digital Mars编译器)只能处理编译和链接阶段,要求在系统命令行输入命令。在这种情况下,可以使用任何文本编辑器来创建和修改源代码。例如,在UNIX系统上,可以使用vi、ed、ex或emacs;在以命令提示符模式运行的Windows系统上,可以使用edlin、edit或任何程序编辑器。如果将文件保存为标准ASCII文本文件(而不是特殊的字处理器格式),甚至可以使用字处理器。另外,还可能有IDE选项,让您能够使用这些命令行编译器。

给源文件命名时,必须使用正确的后缀,将文件标识为C++文件。这不仅告诉您该文件是C++源代码,还将这种信息告知编译器(如果UNIX编译器显示信息“bad magic number”,则表明后缀不正确)。后缀由一个句点和一个或多个字符组成,这些字符被称作扩展名(参见图1.4)。

图1.4 源文件的扩展名

使用什么扩展名取决于C++实现,表1.1列出了一些常用的扩展名。例如,spiffy.C是有效的UNIX C++源代码文件名。注意,UNIX区分大小写,这意味着应使用大写的C字符。实际上,小写c扩展名也有效,但标准C才使用小写的c。因此,为避免在UNIX系统上发生混淆,对于C程序应使用c,而对于C++程序则请使用C。如果不在乎多输入一两个字符,则对于某些UNIX系统,也可以使用扩展名cc和cxx。DOS比UNIX稍微简单一点,不区分大小写,因此DOS实现使用额外的字母(如表1.1所示)来区别C和C++程序。

表1.1 源代码文件的扩展名

C++实现

源代码文件的扩展名

UNIX

C、cc、cxx、c

GNU C++

C、cc、cxx、cpp、c++

Digital Mars

cpp、cxx

Borland C++

cpp

Watcom

cpp

Microsoft Visual C++

cpp、cxx、cc

Freestyle CodeWarrior

cp、cpp、cc、cxx、c++

最初,Stroustrup实现C++时,使用了一个C++到C的编译器程序,而不是开发直接的C++到目标代码的编译器。前者叫作cfront(表示C前端,C front end),它将C++源代码翻译成C源代码,然后使用一个标准C编译器对其进行编译。这种方法简化了向C的领域引入C++的过程。其他实现也采用这种方法将C++引入到其他平台。随着C++的日渐普及,越来越多的实现转向创建C++编译器,直接将C++源代码生成目标代码。这种直接方法加速了编译过程,并强调C++是一种独立(虽然有些相似)的语言。

编译的机理取决于实现,接下来的几节将介绍一些常见的形式。这些总结概括了基本步骤,但对于具体步骤,必须查看系统文档。

1.UNIX编译和链接

最初,UNIX命令CC调用cfront,但cfront未能紧跟C++的发展步伐,其最后一个版本发布于1993年。当今的UNIX计算机可能没有编译器、有专用编译器或第三方编译器,这些编译器可能是商业的,也可能是自由软件,如GNU g++编译器。如果UNIX计算机上有C++编译器,很多情况下命令CC仍然管用,只是启动的编译器随系统而异。出于简化的目的,读者应假设命令CC可用,但必须认识到这一点,即对于下述讨论中的CC,可能必须使用其他命令来代替。

请用CC命令来编译程序。名称采用大写字母,这样可以将它与标准UNIX C编译器cc区分开来。CC编译器是命令行编译器,这意味着需要在UNIX命令行上输入编译命令。

例如,要编译C++源代码文件spiffy.C,则应在UNIX提示符下输入如下命令:

CC spiffy.C

如果由于技巧、努力或是幸运的因素,程序没有错误,编译器将生成一个扩展名为o的目标代码文件。在这个例子中,编译器将生成文件spiffy.o。

接下来,编译器自动将目标代码文件传递给系统链接程序,该程序将代码与库代码结合起来,生成一个可执行文件。在默认情况下,可执行文件为a.out。如果只使用一个源文件,链接程序还将删除spiffy.o文件,因为这个文件不再需要了。要运行该程序,只要输入可执行文件的文件名即可:

a.out

注意,如果编译新程序,新的可执行文件a.out将覆盖已有的a.out(这是因为可执行文件占据了大量空间,因此覆盖旧的可执行文件有助于降低存储需求)。然而,如果想保留可执行文件,只需使用UNIX的mv命令来修改可执行文件的文件名即可。

与在C语言中一样,在C++中,程序也可以包含多个文件(本书第8~第16章的很多程序都是这样)。在这种情况下,可以通过在命令行上列出全部文件来编译程序:

CC my.C precious.C

如果有多个源代码文件,则编译器将不会删除目标代码文件。这样,如果只修改了my.C文件,则可以用下面的命令重新编译该程序:

CC my.C precious.o

这将重新编译my.C文件,并将它与前面编译的precious.o文件链接起来。

可能需要显式地指定一些库。例如,要访问数学库中定义的函数,必须在命令行中加上-lm标记:

CC usingmath.C -lm

2.Linux编译和链接

Linux系统中最常用的编译器是g++,这是来自Free Software Foundation的GNU C++编译器。Linux的多数版本都包括该编译器,但并不一定总会安装它。g++编译器的工作方式很像标准UNIX编译器。例如,下面的命令将生成可执行文件a.out

g++ spiffy.cxx

有些版本可能要求链接C++库:

g++ spiffy.cxx -lg++

要编译多个源文件,只需将它们全部放到命令行中即可:

g++ my.cxx precious.cxx

这将生成一个名为a.out的可执行文件和两个目标代码文件my.o和precious.o。如果接下来修改了其中的某个源代码文件,如my.cxx,则可以使用my.cxx和precious.o来重新编译:

g++ my.cxx precious.o

GNU编译器可以在很多平台上使用,包括基于Windows的PC和在各种平台上运行的UNIX系统。

3.Windows命令行编译器

要在Windows PC上编译C++程序,最便宜的方法是下载一个在Windows命令提示符模式(在这种模式下,将打开一个类似于MS-DOS的窗口)下运行的免费命令行编译器。Cygwin和MinGW都包含编译器GNU C++,且可免费下载;它们使用的编译器名为g++。

要使用g++编译器,首先需要打开一个命令提示符窗口。启动程序Cygwin和MinGW时,它们将自动为您打开一个命令提示符窗口。要编译名为great.cpp的源代码文件,请在提示符下输入如下命令:

g++ great.cpp

如果程序编译成功,则得到的可执行文件名为a.exe。

4.Windows编译器

Windows产品很多且修订频繁,无法对它们分别进行介绍。当前,最流行是Microsoft Visual C++ 2010,可通过免费的Microsoft Visual C++ 2010学习版获得。虽然设计和目标不同,但大多数基于Windows的C++编译器都有一些相同的功能。

通常,必须为程序创建一个项目,并将组成程序的一个或多个文件添加到该项目中。每个厂商提供的IDE(集成开发环境)都包含用于创建项目的菜单选项(可能还有自动帮助)。必须确定的非常重要的一点是,需要创建的是什么类型的程序。通常,编译器提供了很多选择,如Windows应用程序、MFC Windows应用程序、动态链接库、ActiveX控件、DOS或字符模式的可执行文件、静态库或控制台应用程序等。其中一些可能既有32位版本,又有64位版本。

由于本书的程序都是通用的,因此应当避免要求平台特定代码的选项,如Windows应用程序。相反,应让程序以字符模式运行。具体选项取决于编译器。一般而言,应选择包含字样“控制台”、“字符模式”或“DOS可执行文件”等选项。例如,在Microsoft Visual C++ 2010中,应选择Win32 Console Application(控制台应用程序)选项,单击Application Settings(应用程序设置),并选择Empty Project(空项目)。在C++ Builder中,应从C++ Builder Projects(C++ Builder项目)中选择Console Application(控制台应用程序)。

创建好项目后,需要对程序进行编译和链接。IDE通常提供了多个菜单项,如Compile(编译)、Build(建立)、Make(生成)、Build All(全部建立)、Link(链接)、Execute(执行)、Run(运行)和Debug(调试),不过同一个IDE中,不一定包含所有这些选项。

如果程序违反了语言规则,编译器将生成错误消息,指出存在问题的行。遗憾的是,如果不熟悉语言,将难以理解这些消息的含义。有时,真正的问题可能在标识行之前;有时,一个错误可能引发一连串的错误消息。

提示:


改正错误时,应首先改正第一个错误。如果在标识为有错误的那一行上找不到错误,请查看前一行。

需要注意的是,程序能够通过某个编译器的编译并不意味着它是合法的C++程序;同样,程序不能通过某个编译器的编译也并不意味着它是非法的C++程序。与几年前相比,现在的编译器更严格地遵循了C++标准。另外,编译器通常提供了可用于控制严格程度的选项。

提示:


有时,编译器在不完全地构建程序后将出现混乱,它显示无法改正的、无意义的错误消息。在这种情况下,可以选择Build All,重新编译整个程序,以清除这些错误消息。遗憾的是,这种情况和那些更常见的情况(即错误消息只是看上去无意义,实际上有意义)很难区分。

通常,IDE允许在辅助窗口中运行程序。程序执行完毕后,有些IDE将关闭该窗口,而有些IDE不关闭。如果编译器关闭窗口,将难以看到程序输出,除非您眼疾手快、过目不忘。为查看输出,必须在程序的最后加上一些代码:

cin.get( )语句读取下一次键击,因此上述语句让程序等待,直到按下了Enter键(在按下Enter键之前,键击将不被发送给程序,因此按其他键都不管用)。如果程序在其常规输入后留下一个没有被处理的键击,则第二条语句是必需的。例如,如果要输入一个数字,则需要输入该数字,然后按Enter键。程序将读取该数字,但Enter键不被处理,这样它将被第一个cin.get( )读取。

5.Macintosh上的C++

当前,Apple随操作系统Mac OS X提供了开发框架Xcode,该框架是免费的,但通常不会自动安装。要安装它,可使用操作系统安装盘,也可从Apple网站免费下载(但需要注意的是,它超过4GB)。Xcode不仅提供了支持多种语言的IDE,还自带了两个命令行编译器(g++和clang),可在UNIX模式下运行它们。而要进入UNIX模式,可通过实用程序Terminal。

提示:


为节省时间,可对所有示例程序使用同一个项目。方法是从项目列表中删除前一个示例程序的源代码文件,并添加当前的源代码。这样可节省时间、工作量和磁盘空间。

随着计算机的功能越来越强大,计算机程序越来越庞大而复杂。为应对这种挑战,计算机语言也得到了改进,以便编程过程更为简单。C语言新增了诸如控制结构和函数等特性,以便更好地控制程序流程,支持结构化和模块化程度更高的方法;而C++增加了对面向对象编程和泛型编程的支持,这有助于提高模块化和创建可重用代码,从而节省编程时间并提高程序的可靠性。

C++的流行导致大量用于各种计算平台的C++实现得以面世;而ISOC++标准(C++98/03和C++11)为确保众多实现的相互兼容提供了基础。这些标准规定了语言必须具备的特性、语言呈现出的行为、标准库函数、类和模板,旨在实现该语言在不同计算平台和实现之间的可移植性。

要创建C++程序,可创建一个或多个源代码文件,其中包含了以C++语言表示的程序。这些文件是文本文件,它们经过编译和链接后将得到机器语言文件,后者构成了可执行的程序。上述任务通常是在IDE中完成的,IDE提供了用于创建源代码文件的文本编辑器、用于生成可执行文件的编译器和链接器以及其他资源,如项目管理和调试功能。然而,这些任务也可以在命令行环境中通过调用合适的工具来完成。


本章内容包括:

要建造简单的房屋,首先要打地基、搭框架。如果一开始没有牢固的结构,后面就很难建造窗子、门框、圆屋顶和镶木地板的舞厅等。同样,学习计算机语言时,应从程序的基本结构开始学起。只有这样,才能一步一步了解其具体细节,如循环和对象等。本章对C++程序的基本结构做一概述,并预览后面将介绍的主题,如函数和类。(这里的理念是,先介绍一些基本概念,这样可以激发读者接下去学习的兴趣。)

首先介绍一个显示消息的简单C++程序。程序清单2.1使用C++工具cout生成字符输出。源代码中包含一些供读者阅读的注释,这些注释都以//打头,编译器将忽略它们。C++对大小写敏感,也就是说区分大写字符和小写字符。这意味着大小写必须与示例中相同。例如,该程序使用的是cout,如果将其替换为Cout或COUT,程序将无法通过编译,并且编译器将指出使用了未知的标识符(编译器也是对拼写敏感的,因此请不要使用kout或coot)。文件扩展名cpp是一种表示C++程序的常用方式,您可能需要使用第1章介绍的其他扩展名。

程序清单2.1 myfirst.cpp

程序调整


要在自己的系统上运行本书的示例,可能需要对其进行修改。有些窗口环境在独立的窗口中运行程序,并在程序运行完毕后自动关闭该窗口。正如第1章讨论的,要让窗口一直打开,直到您按任何键,可在return语句前添加如下语句:

对于有些程序,要让窗口一直打开,直到您按任何键,必须添加两条这样的语句。第4章将更详细地介绍cin.get( )。

如果您使用的系统很旧,它可能不支持C++98新增的特性。

有些程序要求编译器对C++11标准提供一定的支持。对于这样的程序,将明确指出这一点,并在可能的情况下提供非C++11代码。

将该程序复制到您选择的编辑器中(或使用本书配套网站的源代码,详情请参阅封底)后,便可以C++编译器创建可执行代码了(参见第1章的介绍)。下面是运行编译后的程序时得到的输出:

C语言输入和输出


如果已经使用过C语言进行编程,则看到cout函数(而不是printf( )函数)时可能会小吃一惊。事实上,C++能够使用printf( )、scanf( )和其他所有标准C输入和输出函数,只需要包含常规C语言的stdio.h文件。不过本书介绍的是C++,所以将使用C++的输入工具,它们在C版本的基础上做了很多改进。

您使用函数来创建C++程序。通常,先将程序组织为主要任务,然后设计独立的函数来处理这些任务。程序清单2.1中的示例非常简单,只包含一个名为main( )的函数。myfirst.cpp示例包含下述元素。

下面详细介绍这些元素。先来看看main( )函数,因为了解了main( )的作用后,main( )前面的一些特性(如预处理器编译指令)将更易于理解。

去掉修饰后,程序清单2.1中的示例程序的基本结构如下:

这几行表明有一个名为main( )的函数,并描述了该函数的行为。这几行代码构成了函数定义(function definition)。该定义由两部分组成:第一行int main( )叫函数头(function heading),花括号({和})中包括的部分叫函数体。图2.1对main( )函数做了说明。函数头对函数与程序其他部分之间的接口进行了总结;函数体是指出函数应做什么的计算机指令。在C++中,每条完整的指令都称为语句。所有的语句都以分号结束,因此在输入示例代码时,请不要省略分号。

图2.1 main( )函数

main( )中最后一条语句叫作返回语句(return statement),它结束该函数。本章将讲述有关返回语句的更多知识。

语句和分号


语句是要执行的操作。为理解源代码,编译器需要知道一条语句何时结束,另一条语句何时开始。有些语言使用语句分隔符。例如,FORTRAN通过行尾将语句分隔开来,Pascal使用分号分隔语句。在Pascal中,有些情况下可以省略分号,例如END前的语句后面,这种情况下,实际上并没有将两条语句分开。不过C++与C一样,也使用终止符(terminator),而不是分隔符。终止符是一个分号,它是语句的结束标记,是语句的组成部分,而不是语句之间的标记。结论是:在C++中,不能省略分号。

1.作为接口的函数头

就目前而言,需要记住的主要一点是,C++句法要求main( )函数的定义以函数头int main( )开始。本章后面的“函数”一节将详细讨论函数头句法,然而,为满足读者的好奇心,下面先预览一下。

通常,C++函数可被其他函数激活或调用,函数头描述了函数与调用它的函数之间的接口。位于函数名前面的部分叫作函数返回类型,它描述的是从函数返回给调用它的函数的信息。函数名后括号中的部分叫作形参列表(argument list)或参数列表(parameter list);它描述的是从调用函数传递给被调用的函数的信息。这种通用格式用于main( )时让人感到有些迷惑,因为通常并不从程序的其他部分调用main( )。

然而,通常,main( )被启动代码调用,而启动代码是由编译器添加到程序中的,是程序和操作系统(UNIX、Windows 7或其他操作系统)之间的桥梁。事实上,该函数头描述的是main( )和操作系统之间的接口。

来看一下main( )的接口描述,该接口从int开始。C++函数可以给调用函数返回一个值,这个值叫作返回值(return value)。在这里,从关键字int可知,main( )返回一个整数值。接下来,是空括号。通常,C++函数在调用另一个函数时,可以将信息传递给该函数。括号中的函数头部分描述的就是这种信息。在这里,空括号意味着main( )函数不接受任何信息,或者main( )不接受任何参数。(main( )不接受任何参数并不意味着main( )是不讲道理的、发号施令的函数。相反,术语参数(argument)只是计算机人员用来表示从一个函数传递给另一个函数的信息)。

简而言之,下面的函数头表明main( )函数可以给调用它的函数返回一个整数值,且不从调用它的函数那里获得任何信息:

很多现有的程序都使用经典C函数头:

在C语言中,省略返回类型相当于说函数的类型为int。然而,C++逐步淘汰了这种用法。

也可以使用下面的变体:

在括号中使用关键字void明确地指出,函数不接受任何参数。在C++(不是C)中,让括号空着与在括号中使用void等效(在C中,让括号空着意味着对是否接受参数保持沉默)。

有些程序员使用下面的函数头,并省略返回语句:

这在逻辑上是一致的,因为void返回类型意味着函数不返回任何值。该变体适用于很多系统,但由于它不是当前标准强制的一个选项,因此在有些系统上不能工作。因此,读者应避免使用这种格式,而应使用C++标准格式,这不需要做太多的工作就能完成。

最后,ANSI/ISO C++标准对那些抱怨必须在main( )函数最后包含一条返回语句过于繁琐的人做出了让步。如果编译器到达main( )函数末尾时没有遇到返回语句,则认为main( )函数以如下语句结尾:

这条隐含的返回语句只适用于main( )函数,而不适用于其他函数。

2.为什么main( )不能使用其他名称

之所以将myfirst.cpp程序中的函数命名为main( ),原因是必须这样做。通常,C++程序必须包含一个名为main( )的函数(不是Main( )、MAIN( )或mane( )。记住,大小写和拼写都要正确)。由于myfirst.cpp程序只有一个函数,因此该函数必须担负起main( )的责任。在运行C++程序时,通常从main( )函数开始执行。因此,如果没有main( ),程序将不完整,编译器将指出未定义main( )函数。

存在一些例外情况。例如,在Windows编程中,可以编写一个动态链接库(DLL)模块,这是其他Windows程序可以使用的代码。由于DLL模块不是独立的程序,因此不需要main( )。用于专用环境的程序—如机器人中的控制器芯片—可能不需要main( )。有些编程环境提供一个框架程序,该程序调用一些非标准函数,如_tmain( )。在这种情况下,有一个隐藏的main( ),它调用_tmain( )。但常规的独立程序都需要main( ),本书讨论的都是这种程序。

C++注释以双斜杠(//)打头。注释是程序员为读者提供的说明,通常标识程序的一部分或解释代码的某个方面。编译器忽略注释,毕竟,它对C++的了解至少和程序员一样,在任何情况下,它都不能理解注释。对编译器而言,程序清单2.1就像没有注释一样:

C++注释以//打头,到行尾结束。注释可以位于单独的一行上,也可以和代码位于同一行。请注意程序清单2.1的第一行:

本书所有的程序都以注释开始,这些注释指出了源代码的文件名并简要地总结了该程序。在第1章中介绍过,源代码的文件扩展名取决于所用的C++系统。在其他系统中,文件名可能为myfirst.C或myfirst.cxx。

提示:


应使用注释来说明程序。程序越复杂,注释的价值越大。注释不仅有助于他人理解这些代码,也有助于程序员自己理解代码,特别是隔了一段时间没有接触该程序的情况下。


C-风格注释


C++也能够识别C注释,C注释包括在符号//之间:

由于C-风格注释以*/结束,而不是到行尾结束,因此可以跨越多行。可以在程序中使用C或C++风格的注释,也可以同时使用这两种注释。但应尽量使用C++注释,因为这不涉及结尾符号与起始符号的正确配对,所以它产生问题的可能性很小。事实上,C99标准也在C语言中添加了//注释。

下面简要介绍一下需要知道的一些知识。如果程序要使用C++输入或输出工具,请提供这样两行代码:

可使用其他代码替换第2行,这里使用这行代码旨在简化该程序(如果编译器不接受这几行代码,则说明它没有遵守标准C++98,使用它来编译本书的示例时,将出现众多其他的问题)。为使程序正常工作,只需要知道这些。下面更深入地介绍一下这些内容。

C++和C一样,也使用一个预处理器,该程序在进行主编译之前对源文件进行处理(第1章介绍过,有些C++实现使用翻译器程序将C++程序转换为C程序。虽然翻译器也是一种预处理器,但这里不讨论这种预处理器,而只讨论这样的预处理器,即它处理名称以#开头的编译指令)。不必执行任何特殊的操作来调用该预处理器,它会在编译程序时自动运行。

程序清单2.1使用了#include编译指令:

该编译指令导致预处理器将iostream文件的内容添加到程序中。这是一种典型的预处理器操作:在源代码被编译之前,替换或添加文本。

这提出了一个问题:为什么要将iostream文件的内容添加到程序中呢?答案涉及程序与外部世界之间的通信。iostream中的io指的是输入(进入程序的信息)和输出(从程序中发送出去的信息)。C++的输入/输出方案涉及iostream文件中的多个定义。为了使用cout来显示消息,第一个程序需要这些定义。#include编译指令导致iostream文件的内容随源代码文件的内容一起被发送给编译器。实际上,iostream文件的内容将取代程序中的代码行#include <iostream>。原始文件没有被修改,而是将源代码文件和iostream组合成一个复合文件,编译的下一阶段将使用该文件。

注意:


使用cin和cout进行输入和输出的程序必须包含文件iostream。

像iostream这样的文件叫作包含文件(include file)—由于它们被包含在其他文件中;也叫头文件(header file)—由于它们被包含在文件起始处。C++编译器自带了很多头文件,每个头文件都支持一组特定的工具。C语言的传统是,头文件使用扩展名h,将其作为一种通过名称标识文件类型的简单方式。例如,头文件math.h支持各种C语言数学函数,但C++的用法变了。现在,对老式C的头文件保留了扩展名h(C++程序仍可以使用这种文件),而C++头文件则没有扩展名。有些C头文件被转换为C++头文件,这些文件被重新命名,去掉了扩展名h(使之成为C++风格的名称),并在文件名称前面加上前缀c(表明来自C语言)。例如,C++版本的math.h为cmath。有时C头文件的C版本和C++版本相同,而有时候新版本做了一些修改。对于纯粹的C++头文件(如iostream)来说,去掉h不只是形式上的变化,没有h的头文件也可以包含名称空间—本章的下一个主题。表2.1对头文件的命名约定进行了总结。

表2.1 头文件命名约定

头文件类型

约 定

示 例

说 明

C++旧式风格

以.h结尾

iostream.h

C++程序可以使用

C旧式风格

以.h结尾

math.h

C、C++程序可以使用

C++新式风格

没有扩展名

iostream

C++程序可以使用,使用namespace std

转换后的C

加上前缀c,没有扩展名

cmath

C++程序可以使用,可以使用不是C的特性,如namespace std

由于C使用不同的文件扩展名来表示不同文件类型,因此用一些特殊的扩展名(如.hpp或.hxx)表示C++头文件是有道理的,ANSI/ISO委员会也这样认为。问题在于究竟使用哪种扩展名,因此最终他们一致同意不使用任何扩展名。

如果使用iostream,而不是iostream.h,则应使用下面的名称空间编译指令来使iostream中的定义对程序可用:

这叫作using编译指令。最简单的办法是,现在接受这个编译指令,以后再考虑它(例如,到第9章再考虑它)。但这里还是简要地介绍它,以免您一头雾水。

名称空间支持是一项C++特性,旨在让您编写大型程序以及将多个厂商现有的代码组合起来的程序时更容易,它还有助于组织程序。一个潜在的问题是,可能使用两个已封装好的产品,而它们都包含一个名为wanda( )的函数。这样,使用wanda( )函数时,编译器将不知道指的是哪个版本。名称空间让厂商能够将其产品封装在一个叫作名称空间的单元中,这样就可以用名称空间的名称来指出想使用哪个厂商的产品。因此,Microflop Industries可以将其定义放到一个名为Microflop的名称空间中。这样,其wanda( )函数的全称为Microflop::wanda( );同样,Piscine公司的wanda( )版本可以表示为Piscine::wanda( )。这样,程序就可以使用名称空间来区分不同的版本了:

按照这种方式,类、函数和变量便是C++编译器的标准组件,它们现在都被放置在名称空间std中。仅当头文件没有扩展名h时,情况才是如此。这意味着在iostream中定义的用于输出的cout变量实际上是std::cout,而endl实际上是std::endl。因此,可以省略编译指令using,以下述方式进行编码:

然而,多数用户并不喜欢将引入名称空间之前的代码(使用iostream.h和cout)转换为名称空间代码(使用iostream和std::cout),除非他们可以不费力地完成这种转换。于是,using编译指令应运而生。下面的一行代码表明,可以使用std名称空间中定义的名称,而不必使用std::前缀:

这个using编译指令使得std名称空间中的所有名称都可用。这是一种偷懒的做法,在大型项目中是一个潜在的问题。更好的方法是,只使所需的名称可用,这可以通过使用using声明来实现:

用这些编译指令替换下述代码后,便可以使用cin和cout,而不必加上std::前缀:

然而,要使用iostream中的其他名称,必须将它们分别加到using列表中。本书首先采用这种偷懒的方法,其原因有两个。首先,对于简单程序而言,采用何种名称空间管理方法无关紧要;其次,本书的重点是介绍C++的基本方面。本书后面将采用其他名称空间管理技术。

现在来看一看如何显示消息。myfirst.cpp程序使用下面的C++语句:

双引号括起的部分是要打印的消息。在C++中,用双引号括起的一系列字符叫作字符串,因为它是由若干字符组合而成的。<<符号表示该语句将把这个字符串发送给cout;该符号指出了信息流动的路径。cout是什么呢?它是一个预定义的对象,知道如何显示字符串、数字和单个字符等(第1章介绍过,对象是类的特定实例,而类定义了数据的存储和使用方式)。

马上就使用对象可能有些困难,因为几章后才会介绍对象。实际上,这演示了对象的长处之一—不用了解对象的内部情况,就可以使用它。只需要知道它的接口,即如何使用它。cout对象有一个简单的接口,如果string是一个字符串,则下面的代码将显示该字符串:

对于显示字符串而言,只需知道这些即可。然而,现在来看看C++从概念上如何解释这个过程。从概念上看,输出是一个流,即从程序流出的一系列字符。cout对象表示这种流,其属性是在iostream文件中定义的。cout的对象属性包括一个插入运算符(<<),它可以将其右侧的信息插入到流中。请看下面的语句(注意结尾的分号):

它将字符串“Come up and C++ me some time.”插入到输出流中。因此,与其说程序显示了一条消息,不如说它将一个字符串插入到了输出流中。不知道为什么,后者听起来更好一点(参见图2.2)。

图2.2 使用cout显示字符串

初识运算符重载


如果熟悉C后才开始学习C++,则可能注意到了,插入运算符(<<)看上去就像按位左移运算符(<<),这是一个运算符重载的例子,通过重载,同一个运算符将有不同的含义。编译器通过上下文来确定运算符的含义。C本身也有一些运算符重载的情况。例如,&符号既表示地址运算符,又表示按位AND运算符;* 既表示乘法,又表示对指针解除引用。这里重要的不是这些运算符的具体功能,而是同一个符号可以有多种含义,而编译器可以根据上下文来确定其含义(这和确定“sound card”中的“sound”与“sound financial basic”中的“sound”的含义是一样的)。C++扩展了运算符重载的概念,允许为用户定义的类型(类)重新定义运算符的含义。

1.控制符endl

现在来看看程序清单2.1中第二个输出流中看起来有些古怪的符号:

endl是一个特殊的C++符号,表示一个重要的概念:重起一行。在输出流中插入endl将导致屏幕光标移到下一行开头。诸如endl等对于cout来说有特殊含义的特殊符号被称为控制符(manipulator)。和cout一样,endl也是在头文件iostream中定义的,且位于名称空间std中。

打印字符串时,cout不会自动移到下一行,因此在程序清单2.1中,第一条cout语句将光标留在输出字符串的后面。每条cout语句的输出从前一个输出的末尾开始,因此如果省略程序清单2.1中的endl,得到的输出将如下:

从上述输出可知,Y紧跟在句点后面。下面来看另一个例子,假设有如下代码:

其输出将如下:

同样,每个字符串紧接在前一个字符串的后面。如果要在两个字符串之间留一个空格,必须将空格包含在字符串中。注意,要尝试上述输出示例,必须将代码放到完整的程序中,该程序包含一个main( )函数头以及起始和结束花括号。

2.换行符

C++还提供了另一种在输出中指示换行的旧式方法:C语言符号\n:

\n被视为一个字符,名为换行符。

显示字符串时,在字符串中包含换行符,而不是在末尾加上endl,可减少输入量:

另一方面,如果要生成一个空行,则两种方法的输入量相同,但对大多数人而言,输入endl更为方便:

本书中显示用引号括起的字符串时,通常使用换行符\n,在其他情况下则使用控制符endl。一个差别是,endl确保程序继续运行前刷新输出(将其立即显示在屏幕上);而使用“\n”不能提供这样的保证,这意味着在有些系统中,有时可能在您输入信息后才会出现提示。

换行符是一种被称为“转义序列”的按键组合,转义序列将在第3章做更详细的讨论。

有些语言(如FORTRAN)是面向行的,即每条语句占一行。对于这些语言来说,回车的作用是将语句分开。然而,在C++中,分号标示了语句的结尾。因此,在C++中,回车的作用就和空格或制表符相同。也就是说,在C++中,通常可以在能够使用回车的地方使用空格,反之亦然。这说明既可以把一条语句放在几行上,也可以把几条语句放在同一行上。例如,可以将myfirst.cpp重新格式化为如下所示:

这样虽然不太好看,但仍然是合法的代码。必须遵守一些规则,具体地说,在C和C++中,不能把空格、制表符或回车放在元素(比如名称)中间,也不能把回车放在字符串中间。下面是一个不能这样做的例子:

然而,C++11新增的原始(raw)字符串可包含回车,这将在第4章简要地讨论。

1.源代码中的标记和空白

一行代码中不可分割的元素叫作标记(token,参见图2.3)。通常,必须用空格、制表符或回车将两个标记分开,空格、制表符和回车统称为空白(white space)。有些字符(如括号和逗号)是不需要用空白分开的标记。下面的一些示例说明了什么情况下可以使用空白,什么情况下可以省略:

图2.3 标记和空白

2.C++源代码风格

虽然C++在格式方面赋予了您很大的自由,但如果遵循合理的风格,程序将更便于阅读。有效但难看的代码不会令人满意。多数程序员都使用程序清单2.1所示的风格,它遵循了下述规则。

前三条规则旨在确保代码清晰易读;第四条规则帮助区分函数和一些也使用圆括号的C++内置结构(如循环)。在涉及其他指导原则时,本书将提醒读者。

C++程序是一组函数,而每个函数又是一组语句。C++有好几种语句,下面介绍其中的一些。程序清单2.2提供了两种新的语句。声明语句创建变量,赋值语句给该变量提供一个值。另外,该程序还演示了cout的新功能。

程序清单2.2 carrot.cpp

空行将声明语句与程序的其他部分分开。这是C常用的方法,但在C++中不那么常见。下面是该程序的输出:

下面探讨这个程序。

计算机是一种精确的、有条理的机器。要将信息项存储在计算机中,必须指出信息的存储位置和所需的内存空间。在C++中,完成这种任务的一种相对简便的方法,是使用声明语句来指出存储类型并提供位置标签。例如,程序清单2.2中包含这样一条声明语句(注意其中的分号):

这条语句提供了两项信息:需要的内存以及该内存单元的名称。具体地说,这条语句指出程序需要足够的存储空间来存储一个整数,在C++中用int表示整数。编译器负责分配和标记内存的细节。C++可以处理多种类型的数据,而int是最基本的数据类型。它表示整数—没有小数部分的数字。C++的int类型可以为正,也可以为负,但是大小范围取决于实现。第3章将详细介绍int和其他基本类型。

完成的第二项任务是给存储单元指定名称。在这里,该声明语句指出,此后程序将使用名称carrots来标识存储在该内存单元中的值。carrots被称为变量,因为它的值可以修改。在C++中,所有变量都必须声明。如果省略了carrots.cpp中的声明,则当程序试图使用carrots时,编译器将指出错误。事实上,程序员尝试省略声明,可能只是为了看看编译器的反应。这样,以后看到这样的反应时,便知道应检查是否省略了声明。

为什么变量必须声明?


有些语言(最典型的是BASIC)在使用新名称时创建新的变量,而不用显式地进行声明。这看上去对用户比较友好,事实上从短期上说确实如此。问题是,如果错误地拼写了变量名,将在不知情的情况下创建一个新的变量。在BASIC中,ss程序员可能编写如下语句:

由于CastleDank是拼写错误(将r拼成了n),因此所作的修改实际上并没有修改CastleDark。这种错误很难发现,因为它没有违反BASIC中的任何规则。然而,在C++中,将声明CastleDark,但不会声明被错误拼写的CastleDank,因此对应的C++代码将违反“使用变量前必须声明它”的规则,因此编译器将捕获这种错误,发现潜在的问题。

因此,声明通常指出了要存储的数据类型和程序对存储在这里的数据使用的名称。在这个例子中,程序将创建一个名为carrots的变量,它可以存储整数(参见图2.4)。

图2.4 变量声明

程序中的声明语句叫作定义声明(defining declaration)语句,简称为定义(definition)。这意味着它将导致编译器为变量分配内存空间。在较为复杂的情况下,还可能有引用声明(reference declaration)。这些声明命令计算机使用在其他地方定义的变量。通常,声明不一定是定义,但在这个例子中,声明是定义。

如果您熟悉C语言或Pascal,就一定熟悉变量声明。不过C++中的变量声明也可能让人小吃一惊。在C和Pascal中,所有的变量声明通常都位于函数或过程的开始位置,但C++没有这种限制。实际上,C++通常的做法是,在首次使用变量前声明它。这样,就不必在程序中到处查找,以了解变量的类型。本章后面将有一个这样的例子。这种风格也有缺点,它没有把所有的变量名放在一起,因此无法对函数使用了哪些变量一目了然(C99标准使C声明规则与C++非常相似)。

提示:


对于声明变量,C++的做法是尽可能在首次使用变量前声明它。

赋值语句将值赋给存储单元。例如,下面的语句将整数25赋给变量carrots表示的内存单元:

符号=叫作赋值运算符。C++(和C)有一项不寻常的特性—可以连续使用赋值运算符。例如,下面的代码是合法的:

赋值将从右向左进行。首先,88被赋给steinway;然后,steinway的值(现在是88)被赋给baldwin;然后baldwin的值88被赋给yamaha(C++遵循C的爱好,允许外观奇怪的代码)。

程序清单2.2中的第二条赋值语句表明,可以对变量的值进行修改:

赋值运算符右边的表达式carrots – 1是一个算术表达式。计算机将变量carrots的值25减去1,得到24。然后,赋值运算符将这个新值存储到变量carrots对应的内存单元中。

到目前为止,本章的示例都使用cout来打印字符串,程序清单2.2使用cout来打印变量,该变量的值是一个整数:

程序没有打印“carrots”,而是打印存储在carrots中的整数值,即25。实际上,这将两个操作合而为一了。首先,cout将carrots替换为其当前值25;然后,把值转换为合适的输出字符。

如上所示,cout可用于数字和字符串。这似乎没有什么不同寻常的地方,但别忘了,整数25与字符串“25”有天壤之别。该字符串存储的是书写该数字时使用的字符,即字符2和5。程序在内部存储的是字符2和字符5的编码。要打印字符串,cout只需打印字符串中各个字符即可。但整数25被存储为数值,计算机不是单独存储每个数字,而是将25存储为二进制数(附录A讨论了这种表示法)。这里的要点是,在打印之前,cout必须将整数形式的数字转换为字符串形式。另外,cout很聪明,知道carrots是一个需要转换的整数。

与老式C语言的区别在于cout的聪明程度。在C语言中,要打印字符串“25”和整数25,可以使用C语言的多功能输出函数printf( ):

撇开printf( )的复杂性不说,必须用特殊代码(%s和%d)来指出是要打印字符串还是整数。如果让printf( )打印字符串,但又错误地提供了一个整数,由于printf( )不够精密,因此根本发现不了错误。它将继续处理,显示一堆乱码。

cout的智能行为源自C++的面向对象特性。实际上,C++插入运算符(<<)将根据其后的数据类型相应地调整其行为,这是一个运算符重载的例子。在后面的章节中学习函数重载和运算符重载时,将知道如何实现这种智能设计。

cout和printf( )


如果已经习惯了C语言和printf( ),可能觉得cout看起来很奇怪。程序员甚至可能固执地坚持使用printf( )。但与使用所有转换说明的printf( )相比,cout的外观一点也不奇怪。更重要的是,cout还有明显的优点。它能够识别类型的功能表明,其设计更灵活、更好用。另外,它是可扩展的(extensible)。也就是说,可以重新定义<<运算符,使cout能够识别和显示所开发的新数据类型。如果喜欢printf( )提供的细致的控制功能,可以使用更高级的cout来获得相同的效果(参见第17章)。

再来看几个C++语句的例子。程序清单2.3中的程序对前一个程序进行了扩展,要求在程序运行时输入一个值。为实现这项任务,它使用了cin,这是与cout对应的用于输入的对象。另外,该程序还演示了cout对象的多功能性。

程序清单2.3 getinfo.cpp

程序调整


如果您发现在以前的程序清单中需要添加cin.get( ),则在这个程序清单中,需要添加两条cin.get( )语句,这样才能在屏幕上看到输出。第一条cin.get( )语句在您输入数字并按Enter键时读取输入,而第二条cin.get( )语句让程序暂停,直到您按Enter键。

下面是该程序的运行情况:

该程序包含两项新特性:用cin来读取键盘输入以及将四条输出语句组合成一条。下面分别介绍它们。

上面的输出表明,从键盘输入的值(12)最终被赋给变量carrots。下面就是执行这项功能的语句:

从这条语句可知,信息从cin流向carrots。显然,对这一过程有更为正式的描述。就像C++将输出看作是流出程序的字符流一样,它也将输入看作是流入程序的字符流。iostream文件将cin定义为一个表示这种流的对象。输出时,<<运算符将字符串插入到输出流中;输入时,cin使用>>运算符从输入流中抽取字符。通常,需要在运算符右侧提供一个变量,以接收抽取的信息(符号<<和>>被选择用来指示信息流的方向)。

与cout一样,cin也是一个智能对象。它可以将通过键盘输入的一系列字符(即输入)转换为接收信息的变量能够接受的形式。在这个例子中,程序将carrots声明为一个整型变量,因此输入被转换为计算机用来存储整数的数字形式。

getinfo.cpp中的另一项新特性是将4条输出语句合并为一条。iostream文件定义了<<运算符,以便可以像下面这样合并(拼接)输出:

这样能够将字符串输出和整数输出合并为一条语句。得到的输出与下述代码生成的相似:

根据有关cout的建议,也可以按照这样的方式重写拼接版本,即将一条语句放在4行上:

这是由于C++的自由格式规则将标记间的换行符和空格看作是可相互替换的。当代码行很长,限制输出的显示风格时,最后一种技术很方便。

需要注意的另一点是:

在同一行中。

这是因为前面指出过的,cout语句的输出紧跟在前一条cout语句的输出后面。即使两条cout语句之间有其他语句,情况也将如此。

看了足够多的cin和cout示例后,可以学习有关对象的知识了。具体地说,本节将进一步介绍有关类的知识。正如第1章指出的,类是C++中面向对象编程(OOP)的核心概念之一。

类是用户定义的一种数据类型。要定义类,需要描述它能够表示什么信息和可对数据执行哪些操作。类之于对象就像类型之于变量。也就是说,类定义描述的是数据格式及其用法,而对象则是根据数据格式规范创建的实体。换句话说,如果说类就好比所有著名演员,则对象就好比某个著名的演员,如蛙人Kermit。我们来扩展这种类比,表示演员的类中包括该类可执行的操作的定义,如念某一角色的台词,表达悲伤、威胁恫吓,接受奖励等。如果了解其他OOP术语,就知道C++类对应于某些语言中的对象类型,而C++对象对应于对象实例或实例变量。

下面更具体一些。前文讲述过下面的变量声明:

上面的代码将创建一个类型为int的变量(carrots)。也就是说,carrots可以存储整数,可以按特定的方式使用—例如,用于加和减。现在来看cout。它是一个ostream类对象。ostream类定义(iostream文件的另一个成员)描述了ostream对象表示的数据以及可以对它执行的操作,如将数字或字符串插入到输出流中。同样,cin是一个istream类对象,也是在iostream中定义的。

注意:


类描述了一种数据类型的全部属性(包括可使用它执行的操作),对象是根据这些描述创建的实体。

知道类是用户定义的类型,但作为用户,并没有设计ostream和istream类。就像函数可以来自函数库一样,类也可以来自类库。ostream和istream类就属于这种情况。从技术上说,它们没有被内置到C++语言中,而是语言标准指定的类。这些类定义位于iostream文件中,没有被内置到编译器中。如果愿意,程序员甚至可以修改这些类定义,虽然这不是一个好主意(准确地说,这个主意很糟)。iostream系列类和相关的fstream(或文件I/O)系列类是早期所有的实现都自带的唯一两组类定义。然而,ANSI/ISO C++委员会在C++标准中添加了其他一些类库。另外,多数实现都在软件包中提供了其他类定义。事实上,C++当前之所以如此有吸引力,很大程度上是由于存在大量支持UNIX、Macintosh和Windows编程的类库。

类描述指定了可对类对象执行的所有操作。要对特定对象执行这些允许的操作,需要给该对象发送一条消息。例如,如果希望cout对象显示一个字符串,应向它发送一条消息,告诉它,“对象!显示这些内容!”C++提供了两种发送消息的方式:一种方式是使用类方法(本质上就是稍后将介绍的函数调用);另一种方式是重新定义运算符,cin和cout采用的就是这种方式。因此,下面的语句使用重新定义的<<运算符将“显示消息”发送给cout:

在这个例子中,消息带一个参数—要显示的字符串(参见图2.5)。

图2.5 向对象发送消息

由于函数用于创建C++程序的模块,对C++的OOP定义至关重要,因此必须熟悉它。函数的某些方面属于高级主题,将在第7章和第8章重点讨论函数。然而,现在了解函数的一些基本特征,将使得在以后的函数学习中更加得心应手。本章剩余的内容将介绍函数的一些基本知识。

C++函数分两种:有返回值的和没有返回值的。在标准C++函数库中可以找到这两类函数的例子,您也可以自己创建这两种类型的函数。下面首先来看一个有返回值的库函数,然后介绍如何编写简单的函数。

有返回值的函数将生成一个值,而这个值可赋给变量或在其他表达式中使用。例如,标准C/C++库包含一个名为sqrt( )的函数,它返回平方根。假设要计算6.25的平方根,并将这个值赋给变量x,则可以在程序中使用下面的语句:

表达式sqrt(6.25)将调用sqrt( )函数。表达式sqrt(6.25)被称为函数调用,被调用的函数叫作被调用函数(called function),包含函数调用的函数叫作调用函数(calling function,参见图2.6)。

圆括号中的值(这里为6.25)是发送给函数的信息,这被称为传递给函数。以这种方式发送给函数的值叫作参数。(参见图2.7。)函数sqrt( )得到的结果为2.5,并将这个值发送给调用函数;发送回去的值叫作函数的返回值(return value)。可以这么认为,函数执行完毕后,语句中的函数调用部分将被替换为返回的值。因此,这个例子将返回值赋给变量x。简而言之,参数是发送给函数的信息,返回值是从函数中发送回去的值。

图2.6 调用函数

图2.7 函数调用的句法

情况基本上就是这样,只是在使用函数之前,C++编译器必须知道函数的参数类型和返回值类型。也就是说,函数是返回整数、字符、小数、有罪裁决还是别的什么东西?如果缺少这些信息,编译器将不知道如何解释返回值。C++提供这种信息的方式是使用函数原型语句。

注意:


C++程序应当为程序中使用的每个函数提供原型。

函数原型之于函数就像变量声明之于变量—指出涉及的类型。例如,C++库将sqrt( )函数定义成将一个(可能)带小数部分的数字(如6.25)作为参数,并返回一个相同类型的数字。有些语言将这种数字称为实数,但是C++将这种类型称为double(将在第3章介绍)。sqrt( )的函数原型像这样:

第一个double意味着sqrt( )将返回一个double值。括号中的double意味着sqrt( )需要一个double参数。因此该原型对sqrt( )的描述和下面代码中使用的函数相同:

原型结尾的分号表明它是一条语句,这使得它是一个原型,而不是函数头。如果省略分号,编译器将把这行代码解释为一个函数头,并要求接着提供定义该函数的函数体。

在程序中使用sqrt( )时,也必须提供原型。可以用两种方法来实现:

第二种方法更好,因为头文件更有可能使原型正确。对于C++库中的每个函数,都在一个或多个头文件中提供了其原型。请通过手册或在线帮助查看函数描述来确定应使用哪个头文件。例如,sqrt( )函数的说明将指出,应使用cmath头文件。(同样,可能必须使用老式的头文件math.h,它可用于C和C++程序中。)

不要混淆函数原型和函数定义。可以看出,原型只描述函数接口。也就是说,它描述的是发送给函数的信息和返回的信息。而定义中包含了函数的代码,如计算平方根的代码。C和C++将库函数的这两项特性(原型和定义)分开了。库文件中包含了函数的编译代码,而头文件中则包含了原型。

应在首次使用函数之前提供其原型。通常的做法是把原型放到main( )函数定义的前面。程序清单2.4演示了库函数sqrt( )的用法,它通过包含cmath文件来提供该函数的原型:

程序清单2.4 sqrt.cpp

注意:


如果使用的是老式编译器,则必须在程序清单2.4中使用#include <math.h>,而不是#include<cmath>。


使用库函数


C++库函数存储在库文件中。编译器编译程序时,它必须在库文件搜索您使用的函数。至于自动搜索哪些库文件,将因编译器而异。如果运行程序清单2.4时得到一条消息,指出_sqrt是一个没有定义的外部函数(似乎应当避免),则很可能是由于编译器不能自动搜索数学库(编译器倾向于给函数名添加下划线前缀—提示它们对程序具有最后的发言权)。如果在UNIX实现中遇到这样的消息,可能需要在命令行结尾使用-lm选项:

在Linux系统中,有些版本的Gnu编译器与此类似:

只包含cmath头文件可以提供原型,但不一定会导致编译器搜索正确的库文件。

下面是该程序的运行情况:

由于sqrt( )处理的是double值,因此这里将变量声明为这种类型。声明double变量的句法与声明int变量相同:

double类型使得变量area和side能够存储带小数的值,如1 536.0和39.191 8。将看起来是整数(如1536)的值赋给double变量时,将以实数形式存储它,其中的小数部分为.0。在第3章将指出,double类型覆盖的范围要比int类型大得多。

C++允许在程序的任何地方声明新变量,因此sqrt.cpp在要使用side时才声明它。C++还允许在创建变量时对它进行赋值,因此也可以这样做:

这个过程叫作初始化(initialization),将在第3章更详细地介绍。

cin知道如何将输入流中的信息转换为double类型,cout知道如何将double类型插入到输出流中。前面讲过,这些对象都很智能化。

有些函数需要多项信息。这些函数使用多个参数,参数间用逗号分开。例如,数学函数pow( )接受两个参数,返回值为以第一个参数为底,第二个参数为指数的幂。该函数的原型如下:

要计算5的8次方,可以这样使用该函数:

另外一些函数不接受任何参数。例如,有一个C库(与cstdlib或stdlib.h头文件相关的库)包含一个rand( )函数,该函数不接受任何参数,并返回一个随机整数。该函数的原型如下:

关键字void明确指出,该函数不接受任何参数。如果省略void,让括号为空,则C++将其解释为一个不接受任何参数的隐式声明。可以这样使用该函数:

注意,与其他一些计算机语言不同,在C++中,函数调用中必须包括括号,即使没有参数。

还有一些函数没有返回值。例如,假设编写了一个函数,它按美元、美分格式显示数字。当向它传递参数23.5时,它将在屏幕上显示$23.50。由于这个函数把值发送给屏幕,而不是调用程序,因此不需要返回值。可以在原型中使用关键字void来指定返回类型,以指出函数没有返回值:

由于它不返回值,因此不能将该函数调用放在赋值语句或其他表达式中。相反,应使用一条纯粹的函数调用语句:

在有些语言中,有返回值的函数被称为函数(function);没有返回值的函数被称为过程(procedure)或子程序(subroutine)。但C++与C一样,这两种变体都被称为函数。

标准C库提供了140多个预定义的函数。如果其中的函数能满足要求,则应使用它们。但用户经常需要编写自己的函数,尤其是在设计类的时候。无论如何,设计自己的函数很有意思,下面来介绍这一过程。前面已经使用过好几个用户定义的函数,它们都叫main( )。每个C++程序都必须有一个main( )函数,用户必须对它进行定义。假设需要添加另一个用户定义的函数。和库函数一样,也可以通过函数名来调用用户定义的函数。对于库函数,在使用之前必须提供其原型,通常把原型放到main( )定义之前。但现在您必须提供新函数的源代码。最简单的方法是,将代码放在main( )的后面。程序清单2.5演示了这些元素。

程序清单2.5 ourfunc.cpp

main( )函数两次调用simon( )函数,一次的参数为3,另一次的参数为变量count。在这两次调用之间,用户输入一个整数,用来设置count的值。这个例子没有在cout提示消息中使用换行符。这样将导致用户输入与提示出现在同一行中。下面是运行情况:

1.函数格式

在程序清单2.5中,simon( )函数的定义与main( )的定义采用的格式相同。首先,有一个函数头;然后是花括号中的函数体。可以把函数的格式统一为如下的情形:

注意,定义simon( )的源代码位于main( )的后面。和C一样(但不同于Pascal),C++不允许将函数定义嵌套在另一个函数定义中。每个函数定义都是独立的,所有函数的创建都是平等的(参见图2.8)。

2.函数头

在程序清单2.5中,simon( )函数的函数头如下:

开头的void表明simon( )没有返回值,因此调用simon( )不会生成可在main( )中将其赋给变量的数字。因此,第一个函数调用方式如下:

由于simon( )没有返回值,因此不能这样使用它:

图2.8 函数定义在文件中依次出现

括号中的int n表明,使用simon( )时,应提供一个int参数。n是一个新的变量,函数调用时传递的值将被赋给它。因此,下面的函数调用将3赋给simon( )函数头中定义的变量n:

当函数体中的cout语句使用n时,将使用函数调用时传递的值。这就是为什么simon(3)在输出中显示3的原因所在。在示例运行中,函数调用simon(count)导致函数显示512,因为这正是赋给count的值。简而言之,simon( )的函数头表明,该函数接受一个int参数,不返回任何值。

下面复习一下main( )的函数头:

开头的int表明,main( )返回一个整数值;空括号(其中可以包含void)表明,main( )没有参数。对于有返回值的函数,应使用关键字return来提供返回值,并结束函数。这就是在main( )结尾使用下述语句的原因:

这在逻辑上是一致的:main( )返回一个int值,而程序员要求它返回整数0。但可能会产生疑问,将这个值返回到哪里了呢?毕竟,程序中没有哪个地方可以看出对main( )的调用:

答案是,可以将计算机操作系统(如UNIX或Windows)看作调用程序。因此,main( )的返回值并不是返回给程序的其他部分,而是返回给操作系统。很多操作系统都可以使用程序的返回值。例如,UNIX外壳脚本和Windows命令行批处理文件都被设计成运行程序,并测试它们的返回值(通常叫做退出值)。通常的约定是,退出值为0则意味着程序运行成功,为非零则意味着存在问题。因此,如果C++程序无法打开文件,可以将它设计为返回一个非零值。然后,便可以设计一个外壳脚本或批处理文件来运行该程序,如果该程序发出指示失败的消息,则采取其他措施。

关键字


关键字是计算机语言中的词汇。本章使用了int、void、return、double等C++关键字。由于这些关键字都是C++专用的,因此不能用作他用。也就是说,不能将return用作变量名,也不能把double用作函数名。不过可以把它们用作名称的一部分,如painter(其中包含int)或return_aces。附录B提供了C++关键字的完整列表。另外,main不是关键字,由于它不是语言的组成部分。然而,它是一个必不可少的函数的名称。可以把main用作变量名(在一些很神秘的以至无法在这里介绍的情况中,将main用作变量名会引发错误,由于它在任何情况下都是容易混淆的,因此最好不要这样做)。同样,其他函数名和对象名也都不能是关键字。然而,在程序中将同一个名称(比如cout)用作对象名和变量名会把编译器搞糊涂。也就是说,在不使用cout对象进行输出的函数中,可以将cout用作变量名,但不能在同一个函数中同时将cout用作对象名和变量名。

我们再深入一步,编写一个使用返回语句的函数。main( )函数已经揭示了有返回值的函数的格式:在函数头中指出返回类型,在函数体结尾处使用return。可以用这种形式为在英国观光的人解决重量的问题。在英国,很多浴室中的体重秤都以英石(stone)为单位,不像美国以磅或公斤为单位。一英石等于14磅,程序清单2.6使用一个函数来完成这样的转换。

程序清单2.6 convert.cpp

下面是该程序的运行情况:

在main( )中,程序使用cin来给整型变量stone提供一个值。这个值被作为参数传递给stonetolb( )函数,在该函数中,这个值被赋给变量sts。然后,stonetolb( )用关键字return将14*sts返回给main( )。这表明return后面并非一定得跟一个简单的数字。这里通过使用较为复杂的表达式,避免了创建一个新变量,将结果赋给该变量,然后将它返回。程序将计算表达式的值(这里为210),并将其返回。如果返回表达式的值很麻烦,可以采取更复杂的方式:

这两个版本返回的结果相同,但第二个版本更容易理解和修改,因为它将计算和返回分开了。

通常,在可以使用一个简单常量的地方,都可以使用一个返回值类型与该常量相同的函数。例如,stonetolb( )返回一个int值,这意味着可以以下面的方式使用该函数:

在上述任何一种情况下,程序都将计算返回值,然后在语句中使用这个值。

这些例子表明,函数原型描述了函数接口,即函数如何与程序的其他部分交互。参数列表指出了何种信息将被传递给函数,函数类型指出了返回值的类型。程序员有时将函数比作一个由出入它们的信息所指定的黑盒子(black boxes)(电工用语)。函数原型将这种观点诠释得淋漓尽致(参见图2.9)。

图2.9 函数原型和作为黑盒的函数

函数stonetolb( )短小、简单,但包含了全部的函数特性:

可以把stonetolb( )看作函数设计的标准格式。第7章和第8章将更详细地介绍函数。而本章的内容让读者能够很好地了解函数的工作方式及其如何与C++匹配。

在程序清单2.5中,两个函数中都包含下面一条using编译指令:

这是因为每个函数都使用了cout,因此需要能够访问位于名称空间std中的cout定义。

在程序清单2.5中,可以采用另一种方法让两个函数都能够访问名称空间std,即将编译指令放在函数的外面,且位于两个函数的前面:

当前通行的理念是,只让需要访问名称空间std的函数访问它是更好的选择。例如,在程序清单2.6中,只有main( )函数使用cout,因此没有必要让函数stonetolb( )能够访问名称空间std。因此编译指令using被放在函数main( )中,使得只有该函数能够访问名称空间std。

总之,让程序能够访问名称空间std的方法有多种,下面是其中的4种。

命名约定


C++程序员给函数、类和变量命名时,可以有很多种选择。程序员对风格的观点五花八门,这些看法有时就像公共论坛上的圣战。就函数名称而言,程序员有以下选择:

Myfunction( )
myfunction( )
myFunction( )
my_function( )
my_funct( )

选择取决于开发团体、使用的技术或库以及程序员个人的品位和喜好。因此凡是符合第3章将介绍的C++规则的风格都是正确的,都可以根据个人的判断而使用。

撇开语言是否允许不谈,个人的命名风格也是值得注意的—它有助于保持一致性和精确性。精确、让人一目了然的个人命名约定是良好的软件工程的标志,它在整个编程生涯中都会起到很好的作用。

C++程序由一个或多个被称为函数的模块组成。程序从main( )函数(全部小写)开始执行,因此该函数必不可少。函数由函数头和函数体组成。函数头指出函数的返回值(如果有的话)的类型和函数期望通过参数传递给它的信息的类型。函数体由一系列位于花括号({})中的C++语句组成。

有多种类型的C++语句,包括下述6种。

类是用户定义的数据类型规范,它详细描述了如何表示信息以及可对数据执行的操作。对象是根据类规范创建的实体,就像简单变量是根据数据类型描述创建的实体一样。

C++提供了两个用于处理输入和输出的预定义对象(cin和cout),它们是istream和ostream类的实例,这两个类是在iostream文件中定义的。为ostream类定义的插入运算符(<<)使得将数据插入到输出流成为可能;为istream类定义的抽取运算符(>>)能够从输入流中抽取信息。cin和cout都是智能对象,能够根据程序上下文自动将信息从一种形式转换为另一种形式。

C++可以使用大量的C库函数。要使用库函数,应当包含提供该函数原型的头文件。

至此,读者对简单的C++程序有了大致的了解,可以进入下一章,了解程序的细节。

在附录J中可以找到所有复习题的答案。

1.C++程序的模块叫什么?

2.下面的预处理器编译指令是做什么用的?

3.下面的语句是做什么用的?

4.什么语句可以用来打印短语“Hello,world”,然后开始新的一行?

5.什么语句可以用来创建名为cheeses的整数变量?

6.什么语句可以用来将值32赋给变量cheeses?

7.什么语句可以用来将从键盘输入的值读入变量cheeses中?

8.什么语句可以用来打印“We have X varieties of cheese,”,其中X为变量cheeses的当前值。

9.下面的函数原型指出了关于函数的哪些信息?

10.定义函数时,在什么情况下不必使用关键字return?

11.假设您编写的main( )函数包含如下代码:

而编译器指出cout是一个未知标识符。导致这种问题的原因很可能是什么?指出3种修复这种问题的方法。

1.编写一个C++程序,它显示您的姓名和地址。

2.编写一个C++程序,它要求用户输入一个以long为单位的距离,然后将它转换为码(一long等于220码)。

3.编写一个C++程序,它使用3个用户定义的函数(包括main( )),并生成下面的输出:

其中一个函数要调用两次,该函数生成前两行;另一个函数也被调用两次,并生成其余的输出。

4.编写一个程序,让用户输入其年龄,然后显示该年龄包含多少个月,如下所示:

{:--}

5.编写一个程序,其中的main( )调用一个用户定义的函数(以摄氏温度值为参数,并返回相应的华氏温度值)。该程序按下面的格式要求用户输入摄氏温度值,并显示结果:

下面是转换公式:

华氏温度 = 1.8×摄氏温度 + 32.0

6.编写一个程序,其main( )调用一个用户定义的函数(以光年值为参数,并返回对应天文单位的值)。该程序按下面的格式要求用户输入光年值,并显示结果:

天文单位是从地球到太阳的平均距离(约150000000公里或93000000英里),光年是光一年走的距离(约10万亿公里或6万亿英里)(除太阳外,最近的恒星大约离地球4.2光年)。请使用double类型(参见程序清单2.4),转换公式为:

1光年=63240天文单位

7.编写一个程序,要求用户输入小时数和分钟数。在main( )函数中,将这两个值传递给一个void函数,后者以下面这样的格式显示这两个值:


相关图书

代码审计——C/C++实践
代码审计——C/C++实践
CMake构建实战:项目开发卷
CMake构建实战:项目开发卷
C++ Templates(第2版)中文版
C++ Templates(第2版)中文版
C/C++代码调试的艺术(第2版)
C/C++代码调试的艺术(第2版)
计算机图形学编程(使用OpenGL和C++)(第2版)
计算机图形学编程(使用OpenGL和C++)(第2版)
Qt 6 C++开发指南
Qt 6 C++开发指南

相关文章

相关课程