C++入门经典:第5版(txt+pdf+epub+mobi电子书下载)


发布时间:2021-04-20 07:48:08

点击下载

作者:(美)立波提,(美)卡登海德

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

C++入门经典:第5版

C++入门经典:第5版试读:

前言

祝贺您!当您阅读到这里时,离学习最重要的编程语言之一——C++又近了20秒。

如果您再花23小时59分40秒,就将掌握C++编程语言的基本知识。只需24个课程(每个课程不超过1小时),就将学会C++基本知识,如管理I/O、创建循环和数组、使用模板进行面向对象编程以及创建C++程序。

我们将所有这些主题组织成了结构完美、易于理解的课程。在每章中,都将通过项目、输出和代码分析,演示相关的主题。另外,还清楚地标出了语法示例,以方便参考。

每章末尾还列出了常见问题及其答案,帮助您更深入地掌握学到的知识。

本书针对的读者

通过阅读本书来学习C++时,读者不需要有任何编程经验。

本书从基本知识开始,既介绍C++语言,又讨论使用C++进行编程涉及的概念。无论读者是刚开始学习编程还是已经有一些编程经验,本书都将让您能够快速而轻松地学习C++语言。

是否要先学C语言

不需要先学 C 语言。Bjarne Stroustrup 创立的 C++语言是 C 语言的继任者,功能更强大,更多才多艺。先学C语言会让您养成一些编程习惯,在您使用C++编程时,这些习惯容易导致错误。本书不要求读者熟悉C语言。

为何要学习C++

您可学习众多其他的语言,但C++语言最值得学习,因为它经受了时间的考验,当前仍是一种深受欢迎的编程语言。

虽然面世于 1979 年,但鉴于 C++语言的强大功能和灵活性,当前仍被用于开发专业软件。新版本即将面世,其工作名为C++0x,这将让C++语言更有用。

诸如Java等其他语言的灵感来自C++语言,学习C++语言也将让您对这些语言有深入认识。掌握C++语言后,便可将这些技能用于当今的任何平台,从个人计算机到Linux和UNIX服务器,再到大型机和移动设备。

本书使用的约定

本书包含如下特殊元素。注意:提供与读者阅读的内容相关的额外信息。警告:提醒读者注意在特定情况下可能发生的问题或副作用。提示:突出那些可以使C++编程更有效的信息。第一部分 C++入门

第1章 编写第一个程序

第2章 程序的组成部分

第3章 创建变量和常量

第4章 使用表达式、语句和运算符

第5章 调用函数

第6章 控制程序流程

第7章 使用数组和字符串存储信息第1章 编写第一个程序

本章介绍如下内容:C++是如何发明的以及发明它的原因。如何寻找C++编译器?如何创建并编译第一个程序?如何链接并运行程序?1.1 使用C++

1979 年,美国贝尔实验室的一位丹麦计算机科学家开始着手改进 C 编程语言,Bjarne Stroustrop在其个人网站上解释说,他希望有一种可以高效而优雅地编写程序的语言。

这也是很多人的愿望。

Stroustrop将其作品取名为C++,几十年来,它一直占据着世界顶级编程语言的宝座。多年来,很多编程语言如流星般闪过,但对台式机、智能手机和MP3播放器等嵌入式设备以及众多其他计算环境的软件开发而言,C++始终是不错的选择。

C++是一种可移植的语言,适用于 Microsoft Windows、Apple Mac OS、Linux和 UNIX 系统。要学习这种语言,最佳的方式是编写程序,而不考虑运行程序的操作系统。

本书从实用的角度介绍C++,没有对使用的操作系统做任何假设。之所以能够做到这一点,是因为本书介绍的是标准 C++(也被称为 ANSI/ISO C++),这是国际上达成一致的版本,可移植到任何平台和开发环境。

本书的所有代码都是标准ANSI/ISO C++,可在任何遵循最新C++标准的开发环境中运行。

另外,也介绍了下一个版本(C++0x)的新功能。该版本将与2012年初发行,但有些流行的开发环境已经支持其中一些最有用的功能。

C++程序是使用一组系统工作的工具开发的,这些工具称为编译器和链接器。

编译器将C++转换成能够运行的形式,它将程序从适合人类阅读的形式(源代码)转换为机器能够运行的形式(机器代码)。编辑器生成目标文件,链接器则根据目标文件生成可执行文件。

有多种C++编程环境深受欢迎,您以前可能使用过或知道如何获取,这包括GCC(GNU编译器)、Microsoft Visual C++、NetBeans 和 Code::Blocks。

只要在计算机上安装了C++编译器并知道其基本用法,您就能毫无困难地完成本书的编程项目。

如果您没有C++编译器、不知道如何使用编译器或不知道如何寻找编译器,也不用担心,下一节将提供帮助。1.2 寻找编译器

本书的程序都是使用 GCC 创建和测试的,这是一组免费的开源编程工具,支持 C++软件开发。GCC 在 Linux 系统上非常流行,也有适用于 Windows 和 Mac OS 的版本。GCC 在命令行环境下工作,您通过输入命令让C++编译器和链接器创建程序。

有些计算机随操作系统一起安装了GCC。

如果您知道如何使用命令行,可输入如下命令核实是否安装了GCC:

G++是GCC中的C++编译器和链接器,如果看到如下消息,说明您的计算机安装了它:

上述版本消息显示了操作系统和编辑器的版本号。只需有G++便能创建本书的程序。

在 Microsoft Windows 系统上,可通过安装 MinGW(Minimalist GNU for Windows)来安装GCC,这是一组用于开发Windows软件的免费开发工具。

要了解该软件的更详细信息并下载它,请访问 MinGW 网站,其网址为http://www. mingw.org。单击主页上的Downloads链接(它可能在网页旁边),这将打开下载网页。注意:Apple可通过安装 XCode来获得 GCC,而XCode可在Mac OS X 安装盘上找到;也可前往http://developer.apple.com注册成为Apple开发人员来获得GCC。

下载页位于SourceForge,这是一个软件项目托管网站。单击相应的下载链接,将MinGW安装向导下载到计算机。

下载完毕后,打开相应的文件夹,并双击MinGW图标,这将运行安装向导。单击Next按钮。

按说明阅读软件授权协议,并决定如何安装程序。

在安装期间,向导会询问您要安装哪些组件,如图1.1所示。选中复选框MinGW base tools和 G++ compiler,再单击Next 按钮。图1.1 安装MinGW的C++编译器

向导将询问您要将软件安装到什么地方,默认为 C:\MinGW,您可以保留默认设置,也可选择其他文件夹。如果您选择的文件夹不存在,向导将创建它。单击Next按钮继续。

最后,向导将询问您要将MinGW快捷方式放在“开始”菜单的哪个文件夹中。您可以选择一个,也可以接受默认设置MinGW,然后单击Install按钮。这样就下载并安装了MinGW。

将出现一个进度条,它指出了安装进度。如果成功安装,您就可以打开Windows命令行,并验证是否安装了。

要打开命令行:在Windows XP或Windows 7中,选择“所有程序”>“附件”>“命令提示符”命令;在 Windows Vista 中,选择“程序”>“附件”>“命令提示符”命令。

命令窗口是一个文本窗口,其中显示了当前文件夹,右边是闪烁的光标。要切换到其他文件夹,可使用命令 CD,并在它后面输入空格以及要切换到的文件夹。下面的命令切换到MinGW G++工具所在的文件夹:

在这个文件夹中,可运行如下命令,看看G++是否运行正常:

在 Windows 计算机中,要使得在任何文件夹中都可运行 G++,可将其位置加入到 Path变量中。该变量存储了一系列文件夹,每当用户运行程序,而在当前文件夹中又找不到它时,都将检查这些文件夹。

要编辑Path变量,可按如下方式打开“环境变量”对话框。(1)右击桌面或“开始”菜单中的图标“我的电脑”,并选择“属性”命令,这将打开“系统属性”对话框。(2)单击“高级”标签或“高级系统设置”链接。(3)单击“环境变量”按钮,打开“环境变量”对话框。(4)选择Path并单击“编辑”按钮,打开“编辑系统变量”对话框。(5)在“变量值”文本框的末尾添加内容:;C:\MinGW\bin(开头的分号绝对不能少)。(6)依次单击“确定”按钮关闭每个对话框。

这样,您以后再打开命令窗口时,将能在任何文件夹中运行命令 g++ --version。切换到其他文件夹,看看能否运行该命令。

Microsoft Visual Studio 也支持 C++编程:最新版本的集成开发环境为 Visual Studio 2010。这个软件的安装太复杂,本书无法详细介绍,但提供了一些指南,以方便使用 Visual Studio学习C++的读者。1.3 编译和链接源代码

创建您的第一个C++程序之前,有必要了解整个创建过程。

C++程序一开始为源代码,即在Windows“写字板”、Gedit、Emacs或Vi等编辑器中输入的文本。虽然 Microsoft Word 和其他字处理器能够将文件存储为纯文本,但编程时应使用更简单的编辑器,因为您不需要字处理器的任何格式和显示功能。源代码由没有特殊格式的纯文本组成。

对于您创建的 C++源代码文件,可使用扩展名.cpp、.cxx、.cp 或.c。在本书中,所有源代码文件都使用扩展名.cpp,这是 C++程序员最常见的做法,也是有些编译器的默认做法。大多数C++都不关心源代码文件的扩展名,但使用.cpp有助于您识别源代码文件。

源代码是供人类阅读的C++程序,必须经过编译和链接才能运行。

编译源代码时,将生成一个目标文件,链接器将把它转换为可执行的程序。

创建C++程序时,将链接一个或多个目标文件以及一个或多个库。库是一系列可链接的文件,提供了有用的函数和类,可供您在程序中使用。函数是一个执行任务的代码块,如将两个数相乘或显示文本。类定义了一种新数据类型和相关的函数。

创建C++程序的步骤如下。(1)使用文本编辑器创建源代码。(2)使用编译器将源代码转换为目标文件。(3)使用链接器链接目标文件和必要的库,生成可执行的程序。(4)输入可执行文件的名称以运行它。

GCC编译器将编译和链接合而为一。1.4 创建您的第一个程序

介绍程序的创建过程后,该创建您的第一个程序并尝试使用编译器了。

运行您用于创建程序的文本编辑器并新建一个文件。创建您的第一个程序并在屏幕上显示文本。

在编辑器中输入程序清1.1所示的文本,但不要包含左边的行号和它后面的冒号。这些行号旨在方便引用代码。

输入时,务必正确地输入标点,如第5行的字符::和<<。

输入完毕后,将文件保存为Motto.cpp。

程序清1.1 Motto.cpp的完整代码

这里的目标是熟悉创建C++程序的步骤。如果您不知道每行代码的功能,也没有理由难过,第2章将介绍这些代码。

保存文件后,需要进行编译和链接。如果您使用的是GCC,可使用如下命令完成这两项任务:

这个命令让编译器G++编译文件Motto.cpp,并将其链接为可执行文件Motto.exe。如果编辑成功,将不会显示任何消息。该编辑器仅在有问题时出声:显示一条错误消息以及错误出现在哪行。

如果发生编译器错误,请逐行检查程序,确保没有遗漏任何标点,尤其是第5行和第6行末尾的分号。

解决潜在的问题后,尝试再次编译。如果仍有问题且找不出原因,可从本书配套网站(http://cplusplus.cadenhead.org)下载该程序的备份。

成功编译程序后,就可在计算机上运行Motto.exe,方法与运行其他程序一样:将其名称Motto.exe作为命令输入,再按回车键。

程序Motto的输出如下:

这是奥尔胡斯大学(Aarhus University)的校训。该大学位于丹麦的奥尔胡斯市,是一座公立大学,在校学生 3.8 万人,在丹麦排名第二。该校训是 Seek a firm footing in the depths的拉丁版。

奥尔胡斯大学的校友包括环境作家 Bjorn Lomborg、诺贝尔化学奖得主 Jens Christian Skou、丹麦王子 Fredrik,还有一个名为 Bjarne Stroustrop 的家伙。1.5 总结

祝贺您现在可将自己称为C++程序员了,但如果您到此止步不前,那么没有人会认为您是那种值得炫耀的C++程序员。

30多年来,C++一直是流行的软件开发语言。它有其独特之处,但熟悉程序的结构后,将很容易创建复杂的程序。

在接下来的几章中,您将学习C++的基本成分。在每章中,您都将创建多个程序,它们演示了C++语言和编程技巧。1.6 问与答

问:文本编辑器和字处理器有何不同?

答:文本编辑器生成纯文本文件,只包含字母、数字、空格和标点。它没有设置格式的命令,如粗体、斜体、行对齐、边距等。C++源代码不需要这些格式,如果使用字处理器,它可能在文件中存储编译器无法识别的东西。如果您无法编译程序Motto且使用的是字处理器,请尝试使用更简单的编辑器,如 Windows Notepad,看看能否解决问题。

问:我使用的编译器内置了编辑器,可使用它吗?

答:听起来您使用的是集成开发环境(IDE)——一种提高程序编写、调试和测试速度的图形界面工具。诸如 Microsoft Visual C++等复杂编译器包含完整的 IDE,让程序员能够访问帮助文件、就地编辑和编译代码以及在不离开IDE的情况下解决编译和链接错误。这是一种更好的C++程序编写方式,但仅当您知道如何使用IDE时才如此。在学习C++的同时,还要学习IDE的方方面面太难了。这就是本书选择使用GCC的原因,它简单、功能强大,还是免费的。

问:能否不理会编译器发出的警告消息?

答:绝对不能。当编译器发现代码的功能可能并非您的本意时,将发出警告。最好留意这些警告,并通过必要的修改消除它们。出现错误消息意味着编译器不知道如何将您编写的代码转换为机器语言,而警告意味着可以转换,但转换方式可能与您期望的不同。1.7 作业

输入、编译、链接并执行您的第一个程序后,您将能够回答几个问题并完成两个练习,以巩固编译器方面的知识。1.7.1 测验

1.哪种工具将C++源代码转换为目标代码?

A.编译器

B.链接器

C.集成开发环境

2.最常用的源代码文件扩展名是什么?

A.cpp

B.c

C.h

3.可使用哪些工具来编辑源代码?

A.文本编辑器

B.字处理器

C.两者都行1.7.2 答案

1.A。编译器接受C++源代码文件,并将其转换为目标代码。链接器将该目标文件和其他必要的目标文件链接起来,创建一个可执行程序。

2.A。编译器可处理任何源代码文件,而不是其扩展名是什么,但.cpp被广泛用作C++代码的文件扩展名。使用这种扩展名让您以后更容易识别程序的源代码。

3.C。可使用任何可将代码存储为纯文本的工具。您可使用操作系统自带的简单编辑器,如Notepad、Vi、Gedit或Emacs。1.7.3 练习

1.修改程序 Motto,使其显示文本“Saluton Mondo!”,这是问候语“Hello world!”的世界语版本。

2.如果您没有 C++ IDE且不习惯使用命令行,请看看 NetBeans(http://netbeans.org)或Code::Blocks(http://codeblocks.org)。它们都是免费的IDE,可进行配置使其与GCC协同工作。阅读本书时,您可能发现它们使用起来更容易。

这些练习的答案请参阅本书配套网站,其网址为http://cplusplus.cadenhead.org。第2章 程序的组成部分

本章介绍如下内容:为何使用C++?C++程序的组织结构。注释如何让程序更容易理解?函数的作用。

虽然面世30多年,但是C++编程语言的地位比20世纪70年代末出现的其他东西高得多。当前C++仍在风行,还是一种世界级编程语言。

造就它令人惊讶的生命力的原因在于,通过使用C++,只需编写少量的代码,就可创建快速执行的程序,且可在各种计算环境下运行。当今的C++编程功能让您能够生成功能强大的复杂应用程序,适用于商业、商务和开源开发。2.1 使用C++的原因

在计算时代持续的70年中,计算机编程语言得到了长足发展。C++被认为是对1972年面世的C语言的革命性改进。

最早的程序员使用最原始的计算机指令:机器语言,这些指令用一长串1和0表示。后来,人们发明了汇编语言,它们将机器指令映射到人类能阅读且易于管理的命令,如 ADD和MOV。

组成计算机程序的指令称为源代码。

随后出现了高级语言,如BASIC和COBOL,这些语言让程序员能够使用类似于实际单词或句子的语言编写程序,如 Let Gpa=2.25。然后,由称为解释器或编译器的工具将这些指令转换为机器语言。

诸如BASIC等基于解释器的语言每次读取一行代码,并将指令进行转换。

基于编译器的语言通过编译将程序转换为目标代码,这些代码存储在目标文件中。然后,由链接器将目标文件转换为可在操作系统上运行的可执行程序。

由于解释器在代码编写时读取它,并动态地执行代码,因此对程序员来说更容易使用。编译器要求程序员执行不那么方便的编译和链接步骤,但好处是运行速度比解释器运行的程序快得多。

多年来,程序员的目标是编写可快速执行的简短代码。程序必须很小,因为内存昂贵;其运行速度也必须快,因为处理时间也价格不菲。随着计算机价格越来越便宜、速度越来越快、处理能力越来越强大,而内存的容量越来越大、价格越来越便宜,这些因素已不再重要。

当前,最大的编程费用是程序员的时间成本。诸如C++等现代语言使得编写结构良好、易于维护的程序更容易,而这些程序还可扩展和改进。2.1.1 编程风格

随着编程语言的发展,出现了满足不同编程风格的语言。

在过程型编程中,程序被设计为一系列操作,这些操作对一组数据进行处理。结构化编程的出现为组织这些过程和管理大量数据提供了一种系统性方法。

结构化编程的主要思想是分而治之。对于程序要执行的任务,如果太复杂,则将其划分为一组更小的任务。如果这些任务还太复杂,则进一步划分成更小的任务。最终的目标是,任务足够小,足够独立,易于理解。

例如,假设有出版商请您编写一个程序,以跟踪其天才横溢、外形漂亮的计算机图书作者小组的平均收入,可将这项工作划分成如下子任务。(1)找出每位作者的收入。(2)计算出版商有多少作者。(3)计算这些作者的总收入。(4)将总收入除以作者数。

计算总收入又可做如下划分。(1)获取每位作者的个人记录。(2)获悉作者的预付款和版税。(3)扣除咖啡的费用以及治疗眼睛疲劳的费用。(4)将其收入加入总收入。(5)获取下一位作者的个人记录。

获取作者记录的任务又可进一步划分为如下子任务。(1)打开存储作者记录的文件夹。(2)选择正确的记录。(3)从磁盘读取数据。

虽然结构化编程得到了广泛应用,但这种方法也存在一些缺点。随着数据量的增加,将数据与操作数据的任务分开将更难。使用数据要做的事情越多,程序越令人感到迷惑。

过程型程序员经常发现自己为解决问题而重新寻求新的解决方案,而不是编写可重用的程序。重用性基于的理念是,创建程序组件,需要时将其插入程序中。这种方法模拟了现实世界。在现实世界中,使用已制造好的零件组装成设备,这些零件执行特定的任务,因此自行车设计师无需从空白开始制作刹车;相反,他可将现有的刹车融合到设计中,以利用其功能。

在面向对象编程面世前,计算机程序员没有类似的选择空间。2.1.2 C++和面向对象编程

从本质上说,面向对象编程就是将数据和操作数据的过程视为一个对象:一个有身份和特征的独立实体。

C++全面支持面向对象编程,包括面向对象开发的三个支柱概念:封装、继承和多态。

1.封装

前面说到的自行车工程师设计新车时,他将各个组件组合起来,如车架、手把、车轮和前灯。每个组件都有一些属性,能够完成一些行为。他不用了解前灯的工作原理就能使用,只要知道它是做什么用的即可。

为实现这个目标,前灯必须是独立的,它必须完成明确的任务,并且全面完成。全面完成一项任务称为封装。

前灯的所有属性都封装在headlight对象中,而不是遍布整辆自行车。

C++支持通过创建用户定义的类型来封装属性,这种类型称为类。定义良好的类是一个完全封装的实体,要么使用整个实体,要么不使用。使用定义良好的类时,程序不需要知道其工作原理,根据这一原则,应隐藏类的内部工作原理。程序员只需知道如何使用它即可。如何创建类将在第8章介绍。

2.继承和重用

下面更深入地了解自行车工程师,假设他名为 Penny Farthing。Penny要让新设计的自行车快速投放市场:他欠了一屁股赌债,这些债主可没有耐心。

鉴于时间紧迫,Penny 决定对一款现有的自行车进行改进,给它添加杯托和里程表等新部件,推出一款有额外功能的改进型新车。他重用了普通自行车的所有部件,同时添加了新部件,以拓展其用途。

C++通过继承来支持重用的概念。可将新类型声明为现有类型的扩展,新子类称为继承了现有类型。Penny 设计的自行车继承了老式普通自行车,因此具备其所有品质,但根据需要添加了新功能。有关继承及其应用将在第16章讨论。

3.多态

作为最后一个卖点, Penny Farthing 设计的 Amazo-Bicycle 牌自行车的铃铛的行为与众不同,不是像病鹅那样叫,而是轻按时像汽车喇叭,重按时像雾号。铃铛根据骑车人如何使用它发出正确的叫声。

为支持这种不同对象做相应事情的概念,C++使用了称为函数多态和类多态的功能。 多态指的是同一样东西有多种形态,这将在第17章讨论。

通过学习C++,您将全面了解面向对象编程。等您阅读完本书并开始开发C++程序时,将熟悉这些概念。

本书不介绍如何设计自行车,也不介绍从赌债缠身的状态下走出来。2.2 程序的组成部分

您在第1章创建的程序Motto.cpp包含C++程序的基本框架。程序清单2.1再次列出了该程序的源代码,以便详细探索。

在编辑器中输入该程序时,记住不要包含其中的编号,提供它们旨在方便引用代码行。

程序清2.1 Motto.cpp的完整源代码

这个程序生成一行输出,奥尔胡斯大学的校训:

Solidum petit in profundis!

在程序清单2.1中,第一行包含了一个名为iostream的文件,导致编译器认为在这个地方输入的是文件iostream的全部内容。2.2.1 预处理器编译指令

C++编译器执行的第一项操作是,调用另一个被称为预处理器的工具对源代码进行检查,这是在编译器每次运行时自动进行的。

在第1 行,第一个字符是符号#,它指出这行是一个将由预处理器处理的命令。这些命令称为预处理器编译指令。预处理器的职责是,阅读代码,查找编译指令并根据编译指令相应地修改代码。修改后的代码将提供给编译器。

预处理器相当于编译前的代码编辑,每条编译指令都是一个命令,告诉这位编辑如何做。

编译指令#include 告诉预处理器,将指定文件的全部内容加入到程序的指定位置。C++提供了一个标准源代码库,您可在程序中使用它们来执行有用的功能。文件iostream中的代码支持输入输出任务,如在屏幕上显示信息以及从用户那里接受输入。

文件名iostream前后的<>告诉预处理器,前往一组标准位置寻找该文件。由于这些尖括号,预处理器将前往为编译器存储头文件的目录中查找文件 iostream。这些文件也被称为包含文件,因为它们被包含在源代码中。

在第1行,将插入文件iostream的全部内容。注意:传统上,头文件的扩展名为.h,也被称为h文件,因此使用的编译指令类似于 include <iostream.h>。

较新的编译器不要求指定扩展名,但是,如果引用了使用扩展名的文件,编辑指令出于兼容性考虑仍然会奏效。本书在包含文件时,省略不必要的.h。

在第5行,命令cout使用了文件iostream的内容,该命令在屏幕上显示信息。

在上述源代码中,没有其他的编译指令,因此Motto.cpp的其他代码由编译器处理。2.2.2 源代码行

实际的程序从第3行开始,它声明了一个名为main()的函数。函数是执行一个或多个相关操作的代码块,它执行某些操作后返回到调用它的位置。

每个C++程序都包含一个main()函数,程序运行时将自动调用main()。

在C++中,所有函数都必须在完成任务后返回一个值。函数main()总是返回一个整数,这是使用关键字int指定的。

与C++程序中的其他代码块一样,函数也包含在{和}内。所有函数都以左大括号{开头,并以右大括号}结尾。

在程序Motto.cpp中,函数main()的大括号位于第4行和第7行。大括号内的所有代码都是函数的组成部分。

在第5行,使用命令cout在屏幕上显示了一条消息。在该对象前面,使用了std::对其进行限定,告诉编译器使用标准C++输入输出库。就现在而言,有关这方面的工作原理太复杂,如果在这里介绍,很可能导致您将本书丢得老远。为他人的安全着想,这些内容将在本书后面介绍。就现在而言,将std::cout视为在程序中用于处理输出的对象的名称,而将std::cin视为用于处理用户输入的对象即可。

在第5行,std::cout后面是<<,它被称为输出重定向运算符。运算符是代码行中根据某种信息执行操作的字符。运算符<<显示它后面的信息(仅限当前行)。在第5行,文本“Solidum petit in profundis!\n”用双引号括起了。这将在屏幕上显示一个字符串,末尾的特殊字符\n 表示换行符,导致接下来的程序输出从下一行开始。

在第6行,程序返回整数0。程序运行完毕后,操作系统将收到这个值。通常,程序返回0表示它运行成功,而其他数字表示出现了某种故障。

第7行的右大括号表示函数main()到此结束,而程序也到此结束。所有程序的基本框架都与这里演示的相同。2.3 注释

在您编写程序时,每行源代码的功能显而易见,但随着时间的流逝,再回过头来修复程序Bug或添加新功能时,常常会发现对自己以前做的工作感到一头雾水。

为避免这种困境,并帮助他人理解您编写的代码,可在源代码中添加注释。注释是阐述程序做什么的文本,编译器对其置之不理,因此只能给阅读代码的人带来好处。

在 C++中,有两种类型的注释。单行注释以两个斜杆(//)打头,导致编译器忽略从这里开始到行尾的全部内容,下面是一个例子:

多行注释以斜杠和星号(/*)打头,并以星号和斜杆(*/)结尾。/*和*/之间的所有内容都是注释,哪怕它们占据多行。如果程序中不存在与*/配套的/*,编译器将视之为错误。下面是一个多行注释:

在上述注释中,为提高可读性,让文本左对齐,但并非必须这样,因为编译器忽略/*和*/之间的所有内容。在这里,可包含任何内容:杂货清单、情诗、从未告诉过别人的秘密等。警告:关于多行注释,需要牢记的一个重点是,不能将其嵌套。如果您使用/*开始注释,并在几行后又使用了一个/* ,则编译器见到第一个*/后,将认为多行注释到此结束,这样第二个*/将导致编译器错误。大多数C++编辑器都以不同的颜色显示注释,让注释的开始和结束位置非常清晰。

在稍后您将创建的项目中,包含了这两种类型的注释。请在程序中添加大量注释,您为解释代码的功能而在编写注释上花的时间越多,几周、几月甚至几年后,代码就越容易理解。2.4 函数

main()是独特的C++函数,因为程序启动时将自动调用它。

程序从函数main()开头开始,逐行执行源代码。调用函数时,程序将转而执行该函数,函数执行完毕后,将返回到调用函数的代码行。函数可能返回值,也可能不返回,但函数main()是个例外,它总是返回一个整数。

函数由函数头和函数体组成,其中函数头包含以下三项内容。函数的返回类型。函数名。函数接受的参数。

函数名是一个简短的标识符,描述了函数的功能。

函数不返回值时,使用返回类型void,这表示空。

参数是传递给函数的数据,控制函数做什么,函数收到的参数称为实参。函数可接受零个、一个或多个参数,在接下来您将创建的程序中,有一个名为add()的函数,它将两个数相加。下面是这个函数的声明:

参数放在括号内,用逗号分隔,构成参数列表。在这个函数中,参数为x和y,它们的类型都为整型。

函数的名称、参数及其排列顺序被称为签名,就像人的签名,函数的签名也唯一地标识了它。

没有参数的函数包含一组空括号,如下例所示:

函数名不能包含空格,因此在函数getServerStatus()中,除第一个单词外,其他每个单词的首字母都大写。这种命名规则在C++程序员中很流行,本书也采用它。

函数体由左大括号、零或多条语句以及右大括号组成。返回值的函数使用return语句,如程序Motto中所示:

return语句导致函数结束。如果函数不包含return语句,将自动在函数体末尾返回void。在这种情况下,必须将函数的返回类型指定为void。

在函数中使用参数

在程序清单2.2所示的程序Calculator.cpp中,充实了前面提到的函数add(),使用它将两个数相加并显示结果。这个程序演示了如何创建一个函数,它接受两个整型参数并返回一个整型值。

程序清2.2 Calculator.cpp 的完整源代码

该程序的输出如下:

在第5行,程序Calculator包含一个单行注释,第12~第15行包含一个多行注释。所有注释都会被编译器忽略。

函数add()接受两个整型参数:x和y,并在一条return语句中将它们相加(第3~第8行)。

程序从main()函数开始执行,其中的第一条语句(第16行)使用对象std::cout和重定向运算符<<显示文本“What is 867 + 5309?”,再换行。

下一行代码显示文本“The sum is”,调用函数 add() 并给它传递参数 867 和 5309。这样,程序将执行函数 add(),从输出中的“Running calculator....”文本可以知道这一点。

显示这个函数返回的值后,换行两次。

第18~19行重复这个过程。

公式(x+y)是一个表达式,第4章将介绍如何创建表达式。2.5 总结

本章介绍了 C++是从其他计算机语言风格发展而来的,并支持被称为面向对象编程的方法。这种方法在计算领域获得了巨大成功,让C++在今天与在1979年面世时一样,一点也不落伍。

在本章开发的两个程序中,您使用了C++程序的三个组成部分:预处理器编译指令、注释和函数。

您将使用C++开发的所有程序使用的基本框架都与程序Motto和Calculator相同,只是随着使用的函数增多,它们将变得更为复杂,无论这些函数是您自己编写的,还是来自编译指令#include包含的头文件。2.6 问与答

问:在C++程序中,字符#有何用途?

答:#符号指出当前代码行是一个编译指令:需要在程序编译器处理的命令。编译指令#include 将指定文件的所有内容插入到当前位置。编译器看不到编译指令,相反,结果就像是在指定位置输入了文件的全部内容一样。

问:注释类型//和/*有何不同?

答:以//打头的注释是单行注释,到当前行行尾结束。以/*打头的注释是多行注释,到下一个*/处才结束。函数结束不会导致多行注释结束,只有添加了*/标志多行注释结束,否则编译器将报错。

问:形参和实参有何不同?

答:这些术语与过程相关:调用函数时,给它提供一项或多项数据,函数将使用它们来完成其工作。形参是传递给函数的信息,实参是函数收到的信息。调用函数时,提供的是形参,而在函数内部,这些收到的形参将称为实参。

问:什么是kludge?

答:kludge是一种糟糕的解决方案,以后将被更好的解决方案替代。这个术语在海军技师、计算机程序员和航空工程师中很流行,并传播到了其他技术领域。

在计算机程序中,kludge 是管用、但如果有足够的时间可设计得更好的源代码。kludge的寿命通常比预期的长。

阿波罗13号上的宇航员创建了史上最牛的kludge之一:使用胶布和袜子拼凑出一个过滤系统,用于过滤太空船空气中的二氧化碳,从而帮助他们回到地球。

1962 年,在《Datamation》杂志上发表的一篇文章中,Jackson W. Granholm首先使用了这个术语,并给它下了精彩的定义:将一系列不匹配的零件拼凑起来,构成一个令人苦恼的整体。这个定义经受住了时间的考验。2.7 作业

了解 C++程序的组成部分后,您将能够回答几个问题并完成两个练习,以巩固所学的知识。2.7.1 测验

1.函数main()的返回类型是什么?

A.void

B.int

C.它不返回值

2.在C++程序中,大括号有何作用?

A.指出函数的开头和结尾

B.指出程序的开头和结尾

C.强化程序的牙齿

3.下面哪项不是函数签名的组成部分?

A.函数名

B.参数

C.返回类型2.7.2 答案

1.B。函数main()返回一个整数。

2.A。大括号标识函数以及接下来几章将介绍的其他代码块的开头和结尾。

3.函数签名由函数名、参数及其排列顺序组成。2.7.3 练习

1.改写程序Motto,使其在一个函数中显示奥尔胡斯大学的校训。

2.改写程序Calculator,给函数add()添加第三个整型参数z,并调用这个函数两次。

这些练习的答案请参阅本书配套网站,其网址为http://cplusplus.cadenhead.org。第3章 创建变量和常量

本章介绍如下内容:如何创建变量和常量?如何给变量复制以及修改这些值?如何显示变量的值?如何获悉变量占据的内存量?3.1 变量是什么

变量是计算机内存中的一个位置,您可在这里存储和检索值。可将计算机内存视为一系列排成长队的文件架,并按顺序都为每个文件架进行了编号,而文件架的编号就相当于内存地址。

变量有地址,并赋予了描述其用途的名称。在游戏程序中,可创建一个名为 score 的变量,用于存储玩家的积分,还可创建一个名为zombies的变量,用于存储玩家打败了多少怪兽。变量名相当于文件架上的标签,这样无需知道变量的实际内存地址就能访问它。

图3.1显示了7个文件架,它们的地址为101~107。在文件架104中,变量zombies的值为17,而其他文件架是空的。图3.1 内存的可视化表示3.1.1 在内存中存储变量

在C++中,当您创建变量时,必须将变量的名称和存储的信息类型(如整数、字符或浮点数)告诉编译器,这就是变量的类型,有时也称为数据类型。通过变量的类型,编译器将知道需要预留多少内存空间,以存储变量的值。

内存中的每个文件架为1字节,如果变量长2字节,将需要2字节的内存。计算机使用字节来表示值,您必须熟悉这种概念。

短整型(在C++中用short表示)通常占用2字节,长整型(long)占用4字节,整型(int)可以为 2 或4 字节,长长整型(long long)为8 字节。

字符类型(在C++中用char表示)通常为1字节。在图3.1中,每个文件架表示1字节,因此一个短整型变量可能占据文件架106和107。

布尔值用bool类型变量存储,这种变量只能存储值true或false。

short的长度总是不超过int,而int的长度总是不超过long。浮点数类型与此不同,将在本章后面讨论。

前面讨论的常见变量类型的长度并不适用于所有系统,要获悉变量类型的长度,可使用函数sizeof(),并在括号内指定类型名,如下面的语句所示:

这条语句显示存储一个整型变量需要多少字节。函数sizeof()由编译器提供,不需要使用编译指令include。在程序清单3.1所示的程序Sizer中,使用了函数sizeof()指出常见的C++类型在您的计算机上占用多少内存。

程序清3.1 Sizer.cpp 的完成源代码

这个程序使用了 C++0x(下一个 C++版本)新增的功能。数据类型 long long int 用于存储非常大的整数,如果您的编译器报错,可能是它还不支持这种数据类型。删除第19~20行,看看是否还有问题。

编译后,在 Linux Ubuntu 9.10 系统上运行该程序时,输出如下:

请将在您的计算机上的输出与此进行比较。函数sizeof()指出作为参数传递给它的对象的长度,例如,在第16行将关键字float传递给了函数sizeof()。从输出可知,在Ubuntu计算机上,int变量和long变量的长度相同。3.1.2 无符号变量和带符号变量

所有整型变量都又分两种类型,这是使用一个关键字指定的。当这些变量只存储正值时,可使用关键字 unsigned 进行声明,而当它们可存储正值或负值时,使用关键字signed 进行声明。下面的语句创建一个 short int 变量,这个变量名为 zombies,不能存储负数:

给这个变量指定了初值 0。无符号整型变量和带符号整型变量都可存储 0。如果声明时没有指定,则默认为带符号的。

带符号和无符号整型变量占据的字节数相同,因此,无符号整型变量可存储的最大数是带符号整型变量可存储的最大正数的两倍。unsigned short 变量的取值范围为 0~65 535,在signed short变量可存储的数中,有一半是负数,因此其取值范围为−32 768~32 767。这两种变量可存储的值数都是 65 536 个。3.1.3 变量类型

除整型变量外,C++还支持浮点数类型和字符。

浮点变量可存储包含小数的值,而字符变量占用1字节,可存储ASCII字符集中的256个字符和符号之一。

表3.1列出了C++支持的变量类型,其中包含变量类型、通常占用的内存量以及取值范围。请将该表与在您的计算机上运行程序 Sizer 时得到的输出进行比较,看看占用的内存量有何不同。表3.1 变量类型

在C++中,short和long变量也称为short int和long int,在程序中这两种名称都可以使用。

从表 3.1 可知,unsigned short 变量可存储的最大值为 65 535,而 signed short 变量可存储的最大值为其一半。虽然 unsigned long long int变量可存储的最大值为 18.4 × 1018,但这仍有限,如果需要存储更大的值,必须使用类型 float 或 double,但代价是精度将降低。float 和double变量可存储非常大的值,但在大多数计算机上,只有前7位或19位有效,其他位将被四舍五入。

可将char变量用于存储很小的整数,但这是一种糟糕的编程习惯。每个字符都有相应的数值,即字符集中的ASCII码。例如,惊叹号(!)对应的值为33。3.2 定义变量

在C++中,变量是通过声明其类型和名称定义的,并以分号结束语句,如下例所示:

在一条语句中可定义多个变量,只要它们的类型相同。在这种情况下,应用逗号将变量名分隔,如下面的示例所示:

变量highScore和playerScore的类型都是unsigned int。第二条语句定义了三个long变量:area、width和length,由于这些变量的类型相同,因此可在一条语句中定义它们。

变量名可包含任何大小写字母、数字和下划线(_),但不能包含空格,x、driver8 和playerScore都是合法的变量。C++区分大小写,因此变量highScore不同于变量highscore或HIGHSCORE。

应使用描述性变量名,这样阅读程序的人将更容易理解(但对编译器没有影响)。在下面的两段示例代码中,哪段更容易理解呢?

示例1:

示例2:

程序员在给变量命名时遵守的规则各不相同,有些人喜欢使用小写,并用下划线分隔单词,如high_score和player_score;而有些人喜欢除第一个单词外,其他单词的首字母都大写,如highScore和playerScore(为增加一些编程知识,这种方式称为CamelCase,因为中间的大写字母看起来像驼峰)。

在UNIX环境下学习的程序员通常采用第一种规则,而Microsoft领域的程序员通常使用第二种。对编译器来说,这无关紧要。

本书的代码采用的是第二种规则。

通过精选变量名并提供大量注释,数月甚至数年后回过头来阅读时,将更容易理解您编写的代码。警告:有些编译器允许您禁用变量名区分大小写,请不要这样做。否则,您的程序在其他编译器中将无法通过编译,而其他C++程序员也会取笑您。

C++保留了一些单词,不能将它们用作变量名,因为它们是C++使用的关键字。 保留的关键字包括if、while、for和main。通常,任何合理的变量名都几乎不是关键字。

变量可将关键字作为其名称的组成部分,但不能将其作为完整的名称,因此变量名mainFlag和forward合法,但main和for非法。3.3 给变量赋值

要给变量赋值,可使用运算符=,它被称为赋值运算符。下面的语句演示了如何创建一个名为 highScore 的整型变量,并将其值设置为 13 000:

可在声明变量的同时给它赋初值:

这称为初始化变量。初始化看起来像赋值,但后面使用常量时,将看到有些变量必须初始化,因为不能给它们赋值。

在程序清单3.2所示的程序Rectangle中,使用了变量和赋值来计算矩形的面积并显示结果。

程序清3.2 Rectangle.cpp 的完整源代码

运行时,该程序的输出如下:

与前面编写的程序一样,程序Rectangle也使用编译指令#include将标准库iostream 加入,这使得可以使用std::cout来显示信息。

在该程序的函数main()中,第6行创建了变量width和length并给变量width赋初值5;在第7行,使用赋值运算符=将变量length的值指定为10。

在第11行,定义了一个名为area的int变量,并将其初始化为变量width和length的乘积。乘法运算符*将两个数相乘。

在第13~15行,显示了这三个变量的值。3.4 使用类型定义

当 C++程序包含大量变量时,不断输入 unsigned short 既繁琐又容易出错。要创建现有类型的简捷表示,可使用关键字 typedef,它表示类型定义(type definition)。

类型定义要求使用关键字typedef,后面跟现有类型及其新名称,如下例所示:

这条语句创建了一个名为 USHORT 的类型定义,可在程序的任何地方使用它来替代unsigned short。在程序清3.3所示的程序 NewRectangle 中使用这个类型定义重写了程序Rectangle。

程序清3.3 NewRectangle.cpp 的完整源代码

这个程序的输出与Rectangle相同:变量width、length和area的值,分别为5、10和50。

在第6 行,创建了类型定义 USHORT,将其作为 unsigned short 的简捷表示。在使用USHORT 的每个地方,都使用一个类型定义替换了底层定义 unsigned short。

第8章将介绍如何创建C++新类型,这不同于创建类型定义。注意:编译程序 NewRectangle 时,有些编译器会发出警告:转换可能降低精度,这是因为第14行将两个USHORT变量相乘时,结果可能超出unsigned short的取值范围。就这个程序而言,对这种警告置之不理不会有任何问题。3.5 常量

与变量一样,常量也是一个内存位置,可在其中存储值;不同的是,常量的值不会改变,您必须在创建常量时对其进行初始化。C++支持两种类型的常量:字面常量和符号常量。

字面常量是直接在需要的地方输入的值。例如,请看下面的语句:

这条语句将5赋给整型变量width,这里的5就是字面常量。您不能给5赋值,因为不能修改它的值。

可存储在布尔变量中的值true和false也是字面常量。

符号常量是用名称表示的常量,与变量类型相似。声明符号常量时,需要使用关键字const,并在后面跟类型、名称和初值。下面的语句指定杀死一头怪兽奖励的点数:

每杀死一头怪兽,玩家的积分都将增加指定的奖励:

如果您以后决定将奖金提高到 10 000 点,可修改常量 KILL_BONUS,这将影响整个程序。如果使用的是字面常量 5 000,找到所有使用它的地方并进行修改将难得多。这减少了潜在的错误。

适当命名符号常量也可让程序更容易理解。程序员通常将常量名全部大写,以区分于变量。C++并没有要求这样做,但常量的大小写必须一致,因为C++区分大小写。3.5.1 定义常量

还有另一种定义常量的方法,这起源于早期的C语言(C++的前身)版本。可使用编译指令#define来创建常量,方法是在它后面指定常量的名称和值,并用空格将它们分开:

这种常量不需要指定类型,如int或char。编译指令#define执行简单的文本替换,将代码中所有的 KILLBONUS 都替换为 5 000,编译器只能看到替换后的结果。

由于这种常量没有指定类型,因此编译器无法确保它们的值是合适的。3.5.2 枚举常量

枚举常量在一条语句中创建一组常量,它们是使用关键字 enum 定义的,后面跟一组用逗号分隔的名称,这些名称放在大括号内:

这条语句创建一组名为COLOR的枚举常量,其中包含5个名为RED、BLUE、GREEN、WHITE和BLACK的值。

枚举常量值以0(对应于第一个常量)打头,其他常量的值依次加1。因此,RED的值为0,BLUE为1,GREEN为2,WHITE为3,而BLACK为4。这些值都是整数。

也可以使用赋值运算符指定枚举常量的值:

这条语句将RED设置为100,将GREEN设置为500,并将BLACK设置为700。对于没有设置值的枚举常量,其值比它前面的成员大1,即BLUE为101,而WHITE为501。

这种方法的优点是,可使用符号名称,如BLACK和WHITE,而不是无意义的数字,如1或700。3.6 总结

本章介绍如何处理简单的信息,如整数、浮点数和字符。变量用于存储在程序运行过程中可改变的值,而常量用于存储不变的值,换句话说,它们是不可变的。

使用变量时,最大的挑战在于选择合适的类型。如果处理的无符号整数可能大于 65 000,就应将其存储在long变量而不是short变量中;如果它们可能大于21亿,对long类型来说,就太大了。如果数字值包含小数部分,就必须使用float或double变量来存储,这是C++支持的两种浮点类型。

使用变量时,要牢记的另一点是它们占用的字节数,这因系统而异。函数sizeof()提供了编译器返回的变量类型占用的字节数。3.7 问与答

问:既然使用short可能溢出,为何不总是使用类型long呢?

答:类型short和long都可能溢出,但导致long溢出的数要大得多。在大多数计算机上, long占用的内存是short的两倍。鉴于现代PC的内存很大,这已经不那么重要了。

问:如果将一个小数赋给整型变量而不是float或double变量,结果将如何?请看下面的代码:

答:有些编译器会发出警告,但C++允许将小数值赋给整型变量。小数将被截断为整数,因此上述语句相当于将整数 5 赋给变量 rating。在赋值过程中,精确的信息将丢失,因此如果您接着将rating赋给一个float变量,那么该float变量的值将为5。

问:为何要不怕麻烦使用符号常量呢?

答:在程序的多个地方使用同一个常量时,如果将其作为符号常量,只需修改该常量的初始化代码,就可修改所有值。符号常量还有解释作用,这类似于注释。如果语句将一个数字乘以360,那么与乘以值为360的常量degreesInACircle相比,后者将更容易理解。3.8 作业

学习变量和常量后,您能够回答几个问题并完成两个练习,以巩固有关变量和常量方面的知识。3.8.1 测验

1.为何使用无符号整型变量而不是带符号整型变量?

A.它们的取值范围更大

B.它们可存储的正数更多

C.没有理由这样做

2.变量ROSE、rose和Rose 指的是同一个变量吗?

A.是的

B.不是

C.不管您什么事

3.使用#define和const定义的常量有何不同?

A.只有一个由预处理器处理

B.只有一个指定了类型

C.上面两种说法都对3.8.2 答案

1.B。无符号整型常量可存储更多的正数,且不能用于存储负值。它们可能的取值数相同。

2.B。C++区分大小写,因此ROSE、rose和Rose不同,编译器将它们视为不同的变量。

3.C。预处理器编译指令#define将代码中每个常量名都替换为指定的值,它没有指定数据类型,编译器也看不到它们。使用关键字const创建的常量有数据类型,将由编译器处理。3.8.3 练习

1.创建一个程序,使用常量表示触地(6分)、射门(3分)、定踢射中(1分)和安全得分(2分),然后按上一季超级杯大赛中各队得分的顺序将它们相加,并显示最后的得分。

2.改进程序Rectangle,使其计算长方体的体积。要计算长方体的体积,可使用运算符*将长、宽、高相乘。

这些练习的答案请参阅本书配套网站,其网址为http://cplusplus.cadenhead.org。第4章 使用表达式、语句和运算符

本章介绍如下内容:如何编写语句?如何创建表达式?条件满足时如何运行代码?各种运算符的功能。4.1 语句

所有C++都由语句组成,语句是以分号结尾的命令。根据约定,每条语句占一行,但并非必须这样:可将多条语句放在一行,只要每条语句都以分号结尾即可。语句控制程序的执行流程、评估表达式甚至可以什么也不做(空语句)。一种常见的语句是赋值语句:

这条语句将 a + b 的结果赋给变量 x。赋值运算符将其右边的值赋给左边的变量,如果 a为4、b为13,那么执行这条语句后,x将为17。4.1.1 空白

在C++程序的源代码中,空格、制表符和换行符统称为空白。空白旨在让程序员方便阅读代码,编译器通常忽略它们。

上述赋值语句可写成如下两种形式,而效果保持不变:

编译器忽略空白。变量名不能包含空白,因此不能将变量 playerScore 写为 player Score。

在程序中起缩进作用的制表符和空格属于空白。正确地缩进有助于识别程序块或函数块的开始和结束位置。4.1.2 复合语句

可将多条语句编组,构成一条复合语句,这种语句以左大括号{开头,以右大括号}结束。可将复合语句放在任何可使用单条语句的地方。

复合语句中的每条语句都必须以分号结尾,但复合语句本身不能以分号结尾,如下例所示:

这条复合语句交换变量a和b的值,交换时使用变量temp临时存储了一个变量的值。4.2 表达式

表达式是语句中任何返回一个值的部分,下面是一个简单的示例:

这条语句让变量x的值为变量y的值加13。因此,如果y为20,那么x将为33。整条语句也返回一个值,即变量x的值,因此也是一个表达式。要更好地理解这一点,请看下面这条更复杂的语句:

这条语句包含三个表达式:表达式 y + 13,其值被存储在变量 x 中。表达式 x=y + 13,它返回变量 x 的值,而该返回值被存储在变量 z 中。表达式 z=x=y + 13,它返回变量 z的值,但该返回值没有存储到其他变量中。

赋值运算符=导致左操作数的值变为右操作数的值。

操作数是一个数学术语,指的是被运算符操作的表达式。

在程序清单4.1中,程序Expression显示了三个变量在用于复杂的多表达式语句前后的值。

程序清4.1 Expression.cpp 的完整源代码

该程序的输出如下:

首先声明并初始化了三个变量,然后第5~6 行显示它们的值。在第7 行,依次使用表达式给变量x和z赋值,第8~9行显示了这些变量的新值。4.3 运算符

运算符是导致编译器执行操作的符号,如赋值、执行乘法运算、除法运算或其他数学运算。4.3.1 赋值运算符

赋值表达式由赋值运算符、左操作数(也叫左值)和右操作数(也叫右值)组成。在表达式 grade=95 中,左值为 grade,右值为 95。

常量可作为右值,但不能作为左值。在 C++中,表达式 95=grade 非法,因为不能给常量95赋值。

这里介绍术语左值和右值的主要原因是,它们可能出现在编译器错误消息中。4.3.2 数学运算符

数学运算符有五个:加法运算符(+)、减法运算符(-)、乘法运算符(*)、除法运算符(/)和求模运算符(%)。与C语言一样,C++也没有乘方运算符(将一个值相乘指定次数),这种任务由函数完成。

加法、减法和乘法运算符与您预期的一致,但除法运算符更复杂。

整数除法与普通除法不同。将 21 除以 4 时,结果是一个带小数的实数。但整数除法的结果为整数,余数被丢弃,因此 21 / 4 返回5。

求模运算符%返回整数除法的余数,因此 21 % 4 的结果为 1,因为 21 / 4 的结果为 5,余数为1。注意:对于包含求模运算符的表达式,这样表述:对……以……进行求模,因此 21 % 4 表述为“对 21 以 4 进行求模”。求模运算符执行求模运算,结果为模数。

计算模数在编程中很有用。如果要在任务每执行 10 次就显示一条声明,就可以使用表达式Count % 10。在这里,模数的范围为0~9,每当模数为0,就说明任务执行的次数为10的整数倍。

浮点数除法与常规除法相同,表达式 21 / 4.0 的结果为 5.25。

C++根据操作数的类型决定执行哪种除法。只要有一个运算符为浮点数变量或浮点数字面量,就将执行浮点数除法;否则,执行整数除法。4.3.3 组合运算符

经常需要将一个变量与一个值相加,并将结果赋给这个变量。下面的表达式将变量score的值加10:

这个表达式将变量score的当前值与10相加,再将结果存储到变量score中。

通过使用自赋值加法运算符+=,可将上述表达式简化为:

自赋值加法运算符+=将右值与左值相加,然后将结果赋给左值。还有自赋值减法运算符(−=)、自赋值除法运算符(/=)、自赋值乘法运算符(*=)和自赋值求模运算符(%=)。

这些自赋值运算符与更长的表达式等效,因此可根据自己的判断使用任何一种形式。4.3.4 递增和递减运算符

将变量加1或减1很常见。将变量加1称为递增,而将变量减1称为递减。C++提供了递增运算符++和递减运算符--,用于完成这些任务:

这两条语句分别将score加1以及将zombies减1,它们与下述更冗长的语句等效:

运算符++读作“加加”,而−−读作“减减”。注意:了解递增运算符后,应对名称 C++有更深入的认识。发明者 Bjarne Stroustrup旨在将编程语言C++作为C语言的改进版。他给这种语言一个类似于表达式的名称—在其中包含递增运算符,这让很多人对此感到迷惑—为何不将其称为“C+”呢?4.3.5 前缀运算符和后缀运算符

递增运算符++和递减运算符−−可放在变量名前面,也可放在变量名后面,但效果不同。放在变量名前面称为前缀运算符,如下面的语句所示:

放在变量名后面称为后缀运算符:

上述两条简单语句的效果相同,都将变量count加1。

将变量递增或递减,再将结果赋给另一个表达式时,前缀运算符和后缀运算符的差别将显现出来:后缀运算符在赋值后执行。

为明白这一点,下面来看一个具体的例子:

上述语句执行后,变量x和sum的值都为6。表达式++x中的前缀运算符导致先将x的值从5递增到6,再将其值赋给变量sum。

请将其与下面的示例进行比较:

这导致sum为5,而x为6。后缀运算符导致先将x的值赋给sum,再将其值从5递增到6。

在程序清单4.2中,程序Years使用前缀和后缀运算符将变量year递增了多次。

程序清4.2 Years.cpp 的完整源代码

该程序的输出如下:

程序 Years 不断将年份递增,并预测西雅图水手队(Seattle Mariners)将于哪年首次获得职业棒球赛冠军。西雅图水手队是美国职业棒球大联盟(Major League Baseball)中从未夺冠的两支球队之一。在第5行,该程序首先将变量year设置为2010。

第6 行生成首项输出:“The year 2011 passes.”注意到这里显示的年份是 2011,而不是初始值2010,这是因为第6行使用的是前缀运算符,这导致先将year的值递增,再显示它。

多年过去后,第10行显示的年份为2013。

第13 行的输出为“The year 2013 passes.”由于后缀运算符导致先显示 year 的值,再将其递增,因此显示的年份仍为2013。注意:在 C++中,将变量a加 1 的方式有三种:a=a + 1、a +=1 和 a++。这带来了一些迷惑—使用哪种方式最好呢?不存在最好的方式,这三种方式都可接受,只要您明白代码的作用即可。4.3.6 运算符优先级

复杂表达式的结果取决于运算符优先级,即表达式的计算顺序。下面的复杂表达式包含三个运算符:

如果先执行加法运算,再执行乘法运算,则这个表达式将64赋给变量x,因为8乘8为64。如果先执行乘法再执行加法,x将为29,因为5加上24为29。

每个运算符都有优先级。乘法运算符的优先级高于加法运算符,因此上述表达式将x设置为29。表4.1列出了运算符优先级。表4.1 运算符优先级续表

本书后面将简要地介绍表中的大部分运算符。按该表从上到下的顺序执行运算符,如果运算符的优先级相同,就根据该表的指示按从左到右或从右到左的顺序执行。

从该表可知,乘法运算符*和除法运算符/的优先级高于加法运算符+和减法运算符-,因此先执行乘除运算,再执行加减运算。

如果两个数学运算符的优先级相同,就将按从左到右的顺序执行。下面的表达式包含两个乘法运算符和三个加法运算符:

由于乘法的优先级高于加法,而同样的运算符按从左到右的顺序执行,因此首先计算 8乘9,结果为72,如下所示:

接下来,计算6乘4,结果为24:

现在按从左到右的顺序执行加法运算。最终的结果是,x的值为104。

有些运算符(如赋值运算符)按从右到左的顺序执行:

首先计算表达式y+13,其结果被赋给x。接下来,将x的值赋给z。

当优先级顺序不符合要求时,可使用括号来改变顺序。括号内运算符的优先级比其他任何数学运算符都高:

这个表达式将 minutesWork 和 minutesTravel 相加,将结果乘以 60 ,再将结果赋给totalSeconds。

括号可以嵌套,在这种情况下,将首先计算最内面的括号内的表达式:注意:有疑问时,可使用括号让表达式更清晰。括号不会影响程序的性能,因此即使在不必要时使用它们,也不会带来任何坏处。4.3.7 关系运算符

关系运算符用于比较,以判断一个数是大于、等于还是小于另一个数。每个关系表达式都是要么返回true,要么返回false。表4.2列出了关系运算符。表4.2 关系运算符

如果有整型变量 myAge 和 yourAge,那么表达式 myAge==yourAge 将判断它们是否相等。下面的语句使用了这个表达式:

如果它们相等,这条语句将显示1,否则显示0。警告:很多C++新手将赋值运算符=和相等运算符==混为一谈,这可能在程序中引入难以找出的bug。当您在使用相等运算符更合理的地方使用赋值运算符时,编译器将发出警告,但有时直到程序不按预期执行时才能发现这种问题。4.4 if-else 条件语句

本书前面创建的程序都按从上到下的顺序执行,通过使用关键字 if,可在仅满足条件时执行代码,如变量大于指定的值或布尔变量的值为true。

下面的if语句仅在整型变量zombies满足指定条件时显示一条消息:

如果变量 zombies 为 0,上述代码将显示消息“No more zombies!”。其中括号内的表达式为条件,如果该表达式的结果为true,就将执行if语句后面的语句;否则将跳过。

例如,上述代码执行时变量zombies的值为25,则什么也不会显示。

仅当表达式为true时,条件代码才会执行。布尔变量的值为true或false,可将其用作条件:

仅当布尔变量run为true时,上述代码才会显示单词Running。4.4.1 else子句

程序可在if条件为true时执行一条语句,并在if条件为false时执行另一条语句。要指定if条件为false执行的语句,可使用关键字else:

程序清单4.3所示的程序Grader演示了条件语句的用法。

程序清4.3 Grader.cpp 的完整源代码

这个程序使用了第1行的编译指令包含的输入输出库的另一部分:函数std::cin,它接受一行用户输入。第6 行向用户询问:“Enter a grade (1-100).”,第7 行使用 std::cin 收集用户输入,并将其存储到整型变量grade中。

程序Grader的输出随用户输入而异,这是在第9~12行使用if-else条件实现的。

下面是该程序的一个示例输出:4.4.2 复合if语句

在可使用单条语句的任何地方,都可使用复合语句。if条件和if-else条件后面通常是复合语句:

当 zombies 为 0 时,上述代码执行两项操作:显示“No more zombies!”并将变量 score加上 5 000;如果 zombies 不为 0,将不会执行其中任何一项操作。

可将任何语句与if条件结合使用,这包括另一个if条件子句,甚至另一条if或else语句。

在程序清单4.4中,程序NewGrader扩展了程序Grader,在成绩为A、B和C时分别显示不同的消息。

程序清4.4 NewGrader.cpp 的完整源代码

程序NewGrader包含一个主if-else条件,用于处理用户输入的成绩是否不低于70。

第10~22行负责处理成绩不低于70的情形,其中有两条if语句分别负责处理成绩不低于 90 和 80 的情形:分别显示消息“Pass with an A grade”和“Pass with a B grade”。显示消息后,语句 return 0 立即结束函数 main(),从而终止程序。

如果程序能够执行到第21 行,将显示消息“Pass with a C grade”。

else子句与第9行的if语句配套,它处理成绩低于70的情形:显示消息“Fail”。

下面是该程序的一个输出示例:

在程序NewGrader中,只使用大括号将复合语句括起来了。else子句后面是单条语句,不需要大括号。

有些程序员总是使用大括号将条件和其他代码块括起,即使没有必要这样做:

编译器允许这样做,这也让if和else代码块更清晰。另外,这也可避免程序员将单条语句转换为复合语句时忘记添加大括号,进而导致bug。注意:别忘了,空白和缩进只对程序员来说有意义,对编译器来说则毫无意义,它并不关心if语句是否对齐了。4.5 逻辑运算符

在本章前面,所有if-else条件都只包含一个表达式。通过使用逻辑运算符,可测试多个条件,这包括与运算符&&和或运算符||,而逻辑运算符非!检查表达式是否为false。

表4.3列出了这些运算符。表4.3 逻辑运算符4.5.1 与运算符

逻辑运算符与连接两个表达式,如果它们都为 true,那么整个表达式的结果也为 true。请看下面的语句:

如果x和y都为5,该表达式就为true;如果x和y有一个不为5,该表达式就为false。与运算符两边的表达式都必须为true,整个表达式才为true。4.5.2 或运算符

逻辑运算符或连接两个表达式,只要这两个表达式之一为true,整个表达式就为true:

如果x和y至少有一个为5,该表达式就为true。事实上,如果x为5,编译器根本就不会检查y。4.5.3 非运算符

逻辑非运算符对表达式求反,在表达式为 false 时返回 true,而在表达式为 true 时返回false。下面是一条使用该运算符的语句:

如果grade不小于70,该表达式将返回true,否则将返回false。在程序Grader和NewGrader中,使用了表达式 grade >=70 来判断是否及格。上述表达式检查成绩是否不低于 70,与表达式等效。4.5.4 关系运算符和逻辑运算符的优先级

与其他运算符一样,返回true或false的关系运算符和逻辑运算符也有优先级,这决定了先计算哪个表达式。这在判断表达式的值时很重要,如下所示:

逻辑运算符与和或的优先级相同,因此按从左到右的顺序计算。要让这个表达式的结果为true,要么x和y都大于5,要么z大于5。

通过添加括号,可改变计算顺序:

要让这个表达式为true,x必须大于5,且y和z至少有一个大于5。注意:在复杂的表达式中,使用括号让语句的功能更清晰通常是个不错的主意。逻辑运算符按从左到右的顺序执行,这对编译器来说很容易理解,但对程序员来说并非总是那么清晰。编写程序的目的是让它管用且易于理解。4.6 棘手的表达式值

条件表达式的结果为true或false。在C++中,0也被认为是false,而其他值被认为是true。有些C++在if语句中利用了这个特征:

如果zombies为0,则该if表达式为false,因此不显示怪兽数;否则,该if表达式为true,显示怪兽数。上述代码与下面的代码等效:

这两条语句都合法,但后者更清晰。一种良好的编程习惯是,只将前者用于真值测试,而不将其用于非零测试。

下面两条语句也等效:

这两个if条件都在x为0时为true,但第二条语句更容易理解。4.7 总结

本章介绍了语句、表达式和运算符,它们是C++程序的基本组成部分。

语句是成行的代码,执行特定任务。程序可能包含数百、数千甚至数百万条语句,其中每条语句都以分号结尾。

表达式是生成一个值的语句或语句的组成部分,通过使用赋值运算符,可将表达式的值赋给变量。

运算符是导致编译器执行操作的符号。运算符可能赋值、执行数学运算(如加法或除法)、比较两个值以及进行逻辑运算。

条件语句if和else导致仅当指定条件为true时才执行相应的语句,这些条件通常是使用表达式指定的。4.8 问与答

问:优先级已决定了先执行哪些运算符,为何在不必要的情况下使用括号呢?

答:阅读代码的程序员并非总是像C++编译器那样明白优先级。通过使用括号,可让程序对阅读它的人来说更容易理解,从长远看,这是有好处的。

问:制表符、空格和换行符对程序有何影响?

答:这些字符统称为空白,将被编译器忽略,因此对程序没有影响。使用它们旨在让程序更容易理解。如果没有正确地缩进,将难以判断哪些语句属于if条件语句、复合语句的起始和终止位置等。

问:负数为true还是false?

答:除0之外的所有数字都被视为true,无论它是正数还是负数。4.9 作业

学习表达式和语句后,该回答几个问题并做两个练习了,这样可巩固有关这些主题的知识。4.9.1 测验

1.x++和++x有何不同?

A.没有差别

B.在C++中,后者非法

C.它们都将x的值递增,但时机不同

2.左值和右值有何不同?

A.在表达式中只能使用右值

B.有些右值不能用作左值,但所有左值都可用作右值

C.上述两种说法都正确

3.求模运算符有何功能?

A.它执行整数除法

B.其结果为整数除法运算的余数

C.它计算平方根4.9.2 答案

1 .C。前者使用的是后缀运算符,而后者使用的是前缀运算符。后缀运算符先获取x的值再将其递增,而前缀运算符先将x递增再获取其值。如果在表达式中使用了x的值,选择前缀还是后缀运算符将影响表达式的值。

2.B。左值位于赋值运算符=的左边,而右值位于右边。所有左值都可以出现在赋值运算符的右边,但有些右值(如字面量)不能用作左值。

3.B。它计算整数除法运算的余数。4.9.3 练习

1.修改程序NewGrader,使其不包含return语句(最后一条除外)。使用大量值对其进行测试,直到找出bug,并确定其原因。

2.编写一个程序,它要求用户输入1~100的成绩以及多高的成绩为及格,并指出用户是否及格了。

这些练习的答案请参阅本书配套网站,其网址为http://cplusplus.cadenhead.org。第5章 调用函数

本章介绍如下内容:函数的作用。如何声明和定义函数?如何调用带参数的函数?如何从函数返回值?5.1 函数是什么

函数是程序的一部分,可对数据执行操作并返回一个值。每个C++程序都至少有一个函数:程序运行时自动调用的函数 main()。这个函数可包含调用其他函数的语句,而这些函数中有些可能又调用其他函数,以此类推。

每个函数都有名称,可用于调用它。函数被调用时,将首先执行其第一条语句,再不断执行,直到到达最后一条语句,然后返回到调用该函数的地方执行。

设计良好的函数执行特定任务。对于复杂的任务,应将其划分成多个函数,然后依次调用它们,这让代码更容易理解和维护。5.2 声明和定义函数

编写函数的代码前,必须声明它。

函数声明将函数的名称、函数返回的数据类型以及函数参数的类型告诉编译器。函数声明也叫原型,不包含任何代码。

声明将函数的工作方式告诉编译器。函数原型是单条语句,以分号结尾。

参数列表列出了所有参数及其类型,并用逗号分隔它们。

下面使用参数length和width计算矩形面积的函数的声明:

该函数声明包含如下三部分。返回类型int。函数名findArea。两个参数的名称和类型:类型为int的参数length和width。

函数原型必须与函数的三个元素匹配,否则无法通过编译;唯一无需匹配的是参数名。在函数声明中,可根本不指定参数名,对于前面的声明,可将其重写为如下形式:

这虽然合法,但相对于使用参数名,函数原型不那么清晰。

函数名是一个简短的标识符,描述了它执行的任务。由于名称不能包含空格,因此一种常见的做法是,除第一个单词外,其他单词的首字母都大写,这就是名称findArea中的A为大写的原因。

所有函数的结构都与程序的main()函数相同:函数的所有语句都放在左大括号{和右大括号}内。如果函数返回一个值,那么它至少应包含一条return语句,该语句返回一个类型正确的字面量或变量。

函数可返回任何C++数据类型。如果函数不返回值,就应将返回类型声明为void。返回类型为void的函数不需要包含return语句,虽然也可以使用下面这样的return语句:

不同于函数声明,在函数定义中,指定函数名的语句不以分号结尾。

下面是函数 findArea( )定义,它将矩形的长和宽相乘,以计算矩形的面积:

该函数只有一条语句,它返回两个参数的乘积。

程序清单5.1所示的程序Area使用了这个函数。

程序清5.1 Area.cpp的完整源代码

编译并运行时,该程序的输出如下:

函数findArea()的原型位于第3行,而其代码位于第25~28行。请比较原型的名称、返回类型和参数类型:它们都相同,但在原型中,参数名为length和width,在函数定义中为l和w。这种差别无关紧要,因为参数类型匹配。注意:如果将第25~28行的函数定义移到调用它的代码前面,则不需要原型。在小型程序(如本书创建的程序)中,这样可行,但在大型编程项目中,确保所有函数在使用前都进行了定义很麻烦。通过使用原型声明所有函数,就不用考虑这个问题了。5.3 在函数中使用变量

函数以多种方式使用变量:调用函数时可将变量指定为参数;在函数内部可声明变量,这些变量在函数执行完毕后将消失;还可在函数和程序的其他部分之间共享变量。5.3.1 局部变量

在函数内创建的变量为局部变量,因为它只存在于函数内,函数返回后,其所有局部变量都不能供程序使用。

局部变量的创建方式与其他变量相同,函数收到的参数也被视为局部变量。在程序清单5.2中,程序Temperature使用局部变量将华氏温度转换为摄氏温度。

程序清5.2 Temperature.cpp 的完整源代码

下面是运行该程序三次,且分别输入华氏温度212、32和85得到的输出:

在第19~24 行,该程序定义了一个 convert( )函数,它接受一个名为 fahrenheit 的参数,其类型为float。

在第21行,声明了一个名为celsius的局部变量,而第22行给这个变量赋值。这个值是使用一个包含三步的公式计算得到的,这个公式将华氏温度转换为摄氏温度:将华氏温度减去32。将结果乘以5。再将结果除以9。

在第23行,函数将转换得到的值返回。该函数结束时,局部变量fahrenheit和celsius将消失,不能再使用。

在函数main()中,创建了一个名为fahrenheit的变量,用于存储用户输入的值。还创建了一个名为celsius的变量,用于存储转换后的温度。这些变量与函数convert()中的局部变量同名,但属于不同的变量。

其中的原因是,它们是在不同的作用域中创建的。变量的作用域指的是变量存在的程序部分,它决定了变量在多长时间内可供程序使用以及可在什么地方访问。在块中声明的变量的作用域为当前块,到达该代码块末尾的右大括号后,这些变量便不可用。

可在任何代码块中声明变量,如if条件语句和函数内。5.3.2 全局变量

在C++程序中,也可在函数(包括函数main())外面定义C++变量,这样的变量称为全局变量,因为它们在程序的任何地方都可用。

在函数外面定义的变量的作用域为全局,因此可在程序的任何函数(包括 main())内使用。

程序清单5.3所示的程序Global是程序Temperature的修订版,它使用了全局变量。

程序清5.3 Global.cpp 的完整源代码

编译并运行时,该程序执行的操作与程序 Temperature 完全相同,虽然代码有多个重要的差别。

第5~6行声明了float变量fahrenheit和celsius,位于函数main()和convert()的外面,这使得它们为全局变量,可在任何地方使用,而不用考虑作用域。

这些变量是全局的,函数 convert()没有接受任何参数,而直接使用全局变量 fahrenheit并将其转换为摄氏温度。这个函数也没有返回值(返回类型为 void),因为它将转换得到的温度直接存储在全局变量celsius中。

虽然在这个示例中,全局变量看起来很有用,但在更复杂的程序中,这样做无疑是自找麻烦。应避免使用全局变量,因为它们会导致错误难以查找。在程序的任何一条语句中,都可修改全局变量的值,如果发生错误,您将不得不逐行检查,直到找到错误为止。

变量作用域的优点在于,如果变量包含意外的值或被错误使用,可限定需要检查的程序部分。

在本书中,Global是唯一一个使用全局变量的程序。5.4 函数参数

函数以函数参数的方式接受信息。函数可包含多个参数,只要将它们用逗号分隔即可;调用函数时,还可不提供任何参数。传递给函数的参数类型不必相同,例如,调用函数时,可传递一个int参数、两个long参数和一个字符参数。

任何合法的C++表达式都可作为参数传递给函数,包括常量、数学和逻辑表达式以及返回值的其他函数。

传递给函数的参数将成为该函数的局部变量,即使在调用函数的语句所属的作用域内有同名变量。

请看下面的代码,这些代码看起来像是交换两个变量的值。

出乎意料的是,函数swap()没有交换变量的值,让x为13,而y为4。相反,这些变量的值保持不变。其中的原因是,函数swap()收到的参数为该函数内的局部变量,交换它们的值不会影响调用函数swap()前创建的同名变量。

对函数参数所做的修改不会影响调用它的函数内部的值,这称为按值传递。因为传递给函数的是值,这将创建每个参数的本地备份,这些本地备份与其他局部变量一样。

函数swap()交换的是通过参数收到的局部变量,而不影响用于调用函数swap()的变量。

由于变量是按值传递的,因此函数swap()不会按您期望的方式工作。

从第10章起,我们将介绍其他几种传递参数的方式,它们让函数能够修改传递给它的变量。5.5 从函数返回值

函数返回一个值或void(C++中表示无值的数据类型)。

要从函数返回一个值,可使用关键字 return,并在它后面指定要返回的值。这个值可以是字面量、变量或表达式,因为所有表达式都生成一个值。下面是一些示例:

这些都是合法的return语句(如果函数convert()返回一个值)。如果x不大于5,第二条return语句将返回false,否则将返回true。

到达return语句后,将立即返回到调用函数的语句处,而return语句后面的所有语句都不会执行。

在同一个函数中,可包含多条return语句,程序清单5.4所示的程序LeapYear演示了这一点。

程序清5.4 LeapYear.cpp 的完整源代码

程序LeapYear判断用户输入的年份是否是闰年。下面是连续运行该程序4次得到的输出,其中每次的用户输入都不同:

闰年包含366天而不是365天,这是根据下面两个条件进行判断的。能被4整除,但不能被100整除。能被4、100和400整除。

第21~45行的函数isLeapYear()使用了多条if和else语句,以判断是否满足上述条件。如果是闰年,这个函数将返回布尔值true,否则返回false。这个函数接受一个int参数:要检查的年份。

这个函数包含4条return语句,其中每条都在不同条件下结束函数。5.6 默认函数参数

在原型中将函数声明为接受一个或多个参数时,必须使用数据类型正确的参数来调用该函数。来看一个接受一个int参数的函数:

函数isLeapYear()必须接受一个int参数,编译器将对此进行检查。调用函数时,如果缺少参数或传递的值无效,将导致编译器错误。

对于这一规则,有一个例外:如果在函数声明中指定了参数的默认值,便可在调用该函数时不指定参数,在这种情况下,将使用参数的默认值。下面对函数isLeapYear()的原型进行修改,其中包含参数year的默认值:

如果调用函数isLeapYear()时没有指定参数year的值,将使用默认值2011。

在原型中声明了默认参数时,无需修改函数的定义。

如果函数有多个参数,将根据参数的排列顺序指定默认值。可给任何参数指定默认值,但有一项重要的限制:如果某个参数没有默认值,那么它前面的任何参数都不能有默认值。

下面的函数原型包含4个参数:

不能对其做如下修改:

原因是参数t没有默认值。下面是一个合法的原型:

可这样调用根据该原型创建的函数:

在这种情况下,参数x、y、z和t的值将分别为130、85、1和2000。在程序清单5.5中,程序AreaCube计算长方体的体积,对于表示宽度和高度的参数,使用了默认值。

程序清5.5 AreaCube.cpp 的完整源代码

这个程序的输出如下:

在第3行,原型findArea()指定该函数接受三个int参数,其中最后两个参数有默认值。

该函数计算长方体的体积。如果没有指定height,将使用默认值12;如果没有指定width,将使用默认width值20和默认height值12。调用该函数时,如果没有指定width,就不能指定height。

在第7~9 行,初始化了 length、height 和 width。在第12 行,将它们传递给了函数findArea()。第13行显示了结果。

在第15行,再次调用了findArea(),但没有提供height值,因此将使用默认值计算并显示长方体的体积。

在第18行调用findArea()时,既没有提供width,也没有提供height。这将第三次执行第25行(使用这两者的默认值计算体积)并显示结果。5.7 函数重载

在C++中,可以有多个同名函数,只要它们的参数不同即可,这被称为函数重载。重载函数必须满足如下条件:参数的数据类型不同、参数数量不同或两者兼而有之。下面是三个重载函数的原型:

函数 store()有三个重载版本,它们的参数列表各不相同。第一个和第二个版本的差别在于数据类型,而第三个版本的差别在于参数数量。

调用该函数时,指定的参数决定了将调用哪个版本。

判断重载版本是否不同时,不考虑返回类型。多个重载版本的返回类型可以相同(如上例所示),也可以不同。然而,不能通过修改返回类型来重载函数,相反,参数类型或参数数量必须不同。

函数重载也被称为函数多态。

通过重载,可创建对不同数据类型执行类似操作的函数,而不用给每个函数指定不同的名称。如果程序需要计算以不同格式表示的两个数的平均值,可将函数分别命名为averageInts()、averageDoubles()和averageFloats()。为简化工作,可使用一个名为average()的重载函数,它包含如下原型:

调用函数average()时,只需传递合适的数据,就像调用相应的重载版本。

内联函数

当您定义函数时,C++编译器只在内存中创建一组指令。每当调用该函数时,都将跳转到这些指令,而函数返回时,将跳转到调用代码的下一行。如果程序调用了函数10次,每次都将跳转到同一组指令,即只有1个函数指令备份,而不是10个。跳转到函数和返回有一定的开销,如果函数包含的语句很少,就可以通过避免跳转来提高效率。在这种情况下,通过避免函数调用,程序的运行速度将更快。

声明 C++函数时,如果使用了关键字 inline,编译器将不会创建该函数,而将代码直接复制到调用它的地方,就像您在那里输入了函数的语句一样。如果该内联函数被调用10次,内联代码将总共复制10次,细微的速度改善可能因可执行程序的增大而抵消。注意:关键字inline提示编译器,您希望将函数嵌入。编译器可以忽略这种提示,将其视为真正的函数。为提高C++代码的执行速度,最新的编译器做了大量工作,因此将函数声明为内联的通常所得有限。

要将函数声明为内联的,可在函数原型中使用关键字inline。

而不用修改函数本身:5.8 总结

函数是C++程序的主力,程序执行的每项任务都由函数表示。任务可划分为更小的任务时,每个更小的任务也可以是函数。

所有函数都必须使用函数原型进行声明,函数原型是这样的语句,即指出了函数的名称、参数的顺序和数据类型以及返回的数据类型。

在本章的程序中,函数名像是命令:findArea()、convert()和average()。函数就是命令,您通过它们让计算机计算面积、对值进行转换以及计算两个数的平均值。

您在程序中编写的每个函数都执行一项具体任务,任务越具体,函数就越短。

函数重载让多个相关的函数可使用相同的函数名,这些函数通过参数的数据类型和参数数量进行区分,而C++根据这种差别决定执行哪个函数。5.9 问与答

问:为何不将所有变量都声明为全局的?

答:虽然这种做法以前很常见,但随着程序变得越来越复杂,在使用全局变量的程序中查找bug将变得极其困难,原因是在程序的任何部分都可能误用数据。为最大程度降低出错的概率,应让变量的作用域尽可能小。

问:在函数内部对参数进行修改时,为何不会影响到调用它的函数?

答:默认情况下,参数是按值传递的,这意味着函数接受的参数实际上是原始值的备份,即使变量名相同。第10章介绍指针和引用后,您将学习如何将可修改的参数传递给函数。

问:如果程序中包含如下两个函数,结果将如何?它们是同一个函数的不同重载版本吗?它们的参数数量不同,但第一个函数包含一个默认参数。

答:声明将能够通过编译,但是如果使用一个参数调用了findArea(),就将出现编译错误,指出无法确定应调用findArea(int,int)还是findArea(int)。5.10 作业

学习函数后,您能够回答几个问题并完成两个练习,以巩固有关函数原型和函数参数方面的知识。5.10.1 测验

1.将变量作为参数传递给函数时将出现什么情况?

A.传递给函数的是该变量的备份

B.传递给函数的是该变量本身

C.出现编译器错误

2.在三个重载函数的名称相同时,C++如何知道该调用哪个?

A.函数的返回类型不同

B.函数参数不同

C.函数的排列顺序不同

3.一条return语句可返回多少个值?

A.一个

B.一个或多个

C.零或一个5.10.2 答案

1.A。将创建变量的备份,这被称为按值传递。在函数内对变量的修改不会影响原始变量。

2.B。重载函数的参数类型或参数数量必须不同,编译器根据这种差别确定应调用哪个函数。函数的返回值无关紧要。

3.C。函数可返回零或一个值,虽然函数可包含多条返回值的return语句。函数不返回任何值时,使用返回类型void指出这一点。5.10.3 练习

1.编写一个将摄氏温度转换为华氏温度的程序。转换公式如下:将摄氏温度乘以 9 再除以5,然后加上32。

2.编写一个程序,它使用名为 average()的重载函数计算两个整型、两个长整型和两个浮点数的平均值。

这些练习的答案请参阅本书配套网站,其网址为http://cplusplus.cadenhead.org。第6章 控制程序流程

本章介绍如下内容:什么是循环?如何使用它们?如何创建各种循环?使用switch-case进行复杂的条件测试。6.1 循环

计算机擅长的方面之一是重复做相同的事情,因为软件不会疲劳。

很多编程任务都可通过重复相同的事情指定次数或直到满足指定条件来完成。在程序中连续执行多次的代码块称为循环,其中每次循环都称为迭代。

您在本章学习while循环、do-while循环和for循环时,这些术语将派上用场。6.2 while 循环

while循环导致程序重复执行一组语句,直到开始条件为false。在关键字while后面,将一个表达式放在括号内,如果该表达式为 true,就执行循环块内的语句。这些语句将重复执行,直到表达式为false。

下面的while循环显示数字0~99:

关键字while后跟一个放在括号内的表达式。这条语句没有以分号结尾,而循环内的语句是一个放在{和}之间的语句块。

该循环包含条件表达式 x < 100。只要x 小于100,就执行循环体:显示 x 的值,并将其加1。

x为100后,该循环将结束。

如果没有使用递增运算符的语句x++,x的值将始终为0,而循环将永远不会结束,这称为无限循环。

在程序清单6.1中,程序Thirteens使用一个while循环显示可被13整除且小于500的所有数字。

程序清6.1 Thirteens.cpp 的完整源代码

该程序的输出如下:

程序Thirteens演示了while循环的基本原理:检查指定的条件,只要该条件为true,就不断执行循环体。第7行检查条件“变量counter是否小于500”,如果该条件为true,就执行循环体。

在第9行,将变量counter递增;在第10行,使用一条if语句检查counter的当前值能否被13整除,如果能,就显示它。

当counter不再小于500时,第7行的条件将为false,这导致while循环结束,而程序将跳过第8~14行,进入第16行继续执行。6.2.1 退出循环

break语句导致循环立即终止,而不等待条件为false。程序清单6.2所示的程序Fourteens演示了该语句,这个程序显示前20个可被14整除的数。

程序清6.2 Fourteens.cpp 的完整源代码

下面是该程序的部分输出:

这个程序与程序Thirteens类似,它将变量counter从0开始递增,如果它的值能被14整除(第11行),就显示它。

第8行的while条件使用了一个不同寻常的表达式:

由于只要条件为true,while循环就将不断执行,这个循环被设计成无限循环。

第18行的break语句用于终止循环。使用变量multiples跟踪显示了多少个可被14整除的数,如果该变量大于19,就终止循环。警告:如果退出条件得不到满足,诸如while(true)等无限循环可能导致程序永远执行下去。对于自身不能结束的程序,可按 Ctrl+C 组合键终止执行。使用while(true)时要特别小心,并仔细测试代码。6.2.2 跳到下一次循环

另一种改变循环行为的方式是使用continue语句。在循环中遇到continue语句时,将跳过余下的语句,开始下一次循环迭代。

程序清单6.3所示的程序Fifteens显示前20个可被15整除的数,它在while循环内部使用了一条continue语句。

程序清6.3 Fifteens.cpp 的完整源代码

该程序的输出如下:

程序Fifteens使用一个while循环将变量counter递增,这与本章前面的两个程序类似。第8行的while语句导致循环不断执行,直到显示了20个可被15整除的数。

第10行将变量counter递增。

第11行使用一条if语句检查变量counter能否被15整除。如果不能,就执行第13行,这导致跳过循环的其他部分,跳转到第8行继续执行。

如果变量counter能被15整除,就不执行continue语句,而执行循环中的第15~16行,即显示counter的值,并将变量multiples递增。

正如这些循环表明的,对于同一个任务,C++经常提供了多种完成任务的方式。您可根据喜好选择使用的方式,只要编写的程序能够满足需要。6.3 do-while 循环

while循环执行循环语句前检查条件表达式,如果条件不可能为true,循环语句就不会执行。

使用do-while语句将在循环末尾检查条件。

请看下面的循环:

仅当 x < 50 时这个循环的条件才为 true。由于 x 初始值为 60,因此这种条件不可能满足。

虽然如此,循环体还是执行了一次,并显示x的值60。这是因为do-while循环第一次不考虑条件,到循环语句执行完后才考虑。

do-while循环的循环体至少会执行一次。

在程序清单6.4中,程序Badger使用这种循环将一个单词显示用户指定的次数。

程序清6.4 Badger.cpp 的完整源代码

运行时,该程序提出问题“How many badgers?”,并按用户指定的次数显示单词 Badger。

如果运行该程序时输入0,将看到如下输出:

在第7行,提示用户输入要显示的次数,这个值存储在int变量badger中。在do-while循环中,检查条件前就进入了循环体,这确保循环体至少执行一次。第11 行显示单词“Badger”,第12行将计数器减1,而第13行检查条件。如果条件为true,就跳转到循环体开头(第11行)继续执行,否则跳转到第15行继续执行。

在do-while循环中,continue和break的工作原理与在while循环中完全相同,while循环和do-while循环的唯一差别在于何时检查条件。6.4 for循环

编写循环时,经常需要设置计数器变量、检查计数器变量是否满足条件并在每次循环迭代中修改该变量,如下面的while循环所示:

上述代码在一行中显示 X 13 次。for循环是一种复杂的循环,将这三个步骤合并到了一条语句中。该语句使用关键字for,后面是一对括号。在括号内,是三条用分号分隔的语句,它们分别初始化计数器、检查条件和修改计数器。

下面的代码是前述while循环的重写版本,其输出相同:

for循环的第一部分是初始化。可在这里放置任何C++语句,但通常创建并初始化计数器变量。

第二部分是检查,可以是任何合法的C++表达式,其作用与while或do-while循环中的条件相同。

第三部分是修改计数器的操作,这通常是一条将计数器递增或递减的语句,但可以是任何合法的C++语句。

在程序清单6.5中,程序MultTable使用for循环显示前10个可被用户指定的数字整除的数。

程序清6.5 MultTable.cpp 的完整源代码

下面是用户输入11时该程序的输出:

第11行的for语句初始化int变量counter、检查该变量是否小于11并递增变量counter,这些都是在一行中完成的。第13行为该for语句的循环体。警告:一种常见的错误是,使用逗号而不是分号来分隔for语句的各个部分,这将导致编译器错误。另一种常见错误是,在for语句的右大括号后面添加分号,这将导致只循环但什么都不错。有时候这样做是合理的,因此编译器不会报错。6.4.1 高级for循环

for循环功能强大而灵活。经常需要初始化多个变量、检查复合逻辑表达式并执行多条语句。

如果初始化和操作部分包含多条语句,就必须使用逗号分隔它们,如下例所示:

这个循环的初始化部分设置了两个int变量:x和y,注意到两个声明之间为逗号。

该循环的测试部分检查条件 x < 10。

该循环的操作部分递增两个int变量,并使用逗号分隔这两条语句。

这个循环的循环体显示两个变量的乘积。

for循环的每部分都可为空。分隔各个部分的分号必不可少,但有些部分可不包含任何代码,如下所示:6.4.2 嵌套循环

在循环体内,可包含另一个循环。外部循环每次迭代时,都将执行整个内部循环。

在程序清单6.6中,程序BoxMaker在一个for循环中嵌套了另一个for循环,这旨在显示一个由用户选择的字符组成的矩形,矩形的宽度和高度也由用户指定。

程序清6.6 BoxMaker.cpp 的完整源代码

运行时,该程序首先让用户指定矩形的行数和列数,然后询问用户要使用什么字符绘制矩形。

在下面的输出中,绘制了一个由星号组成的10×15矩形:

在第16行,第一个for循环将计数器变量i初始化为0,接下来是循环体。

第18行为外部for循环的第1行,它建立了内部for循环。该loop将计数器变量j初始化为0,然后是内部for循环的循环体。第20行打印指定的字符,然后回到内部for循环的开头。

内部 for 循环只有一条语句,它显示指定的字符。检查的条件为(j < columns),如果为 true,就将j递增并再次显示指定的字符。这个过程将不断重复下去,直到j等于columns。

内部for循环的条件不满足后(在前面的输出中,此时打印了15个星号),将跳到第22行继续执行:换行。然后返回到外部 for 循环的开头,对条件(i < rows)进行检查。如果为 true,就将i递增并执行循环体。

在外部for循环的第二次迭代中,将重新开始内部for循环,即将j重新初始化为0,并再次运行整个内部for循环。

使用嵌套循环时,在外部循环的每次迭代中,都将执行整个内部循环一次,因此在每行中打印columns个指定的字符。6.5 switch语句

对于同一个变量,使用一系列if或if-else条件时,C++代码将非常繁琐,且很容易让人感到迷惑。一种替代方案是使用switch语句,它检查一个表达式,并根据其值执行多个代码块中的一个。

switch语句由关键字switch、要检查的表达式、一个或多个case部分和可选的default部分组成,其中每个case部分都对应于表达式的一种可能取值。

下面的switch语句根据您杀死了多少怪兽决定是否在单词zombie后面加s。

其中的switch表达式为变量zombies,两个case部分对应于变量zombies的不同取值。如果变量 zombies 的值为 0,就使用复数,导致输出为“You have killed 0 zombies”;如果为 1,输出就将为“You have killed 1 zombie”(末尾没有s)。

default 部分处理变量 zombies 为其他取值的情形,它显示“You have killed”以及怪兽数和单词zombies。

在switch语句的case部分,只能进行相等比较,而不能进行关系运算和布尔运算。如果有case值与表达式匹配,将执行相应的语句,然后继续执行到switch块末尾或遇到的第一条break语句。如果没有匹配的case部分,将执行可选的default部分。如果没有匹配的case部分,也没有default部分,将跳到switch语句后面执行。提示:在switch语句中,总是应该包含default部分,即使没有理由使用它,这是一种良好的编程习惯。可使用 default 部分显示一条错误消息,它表明表达式的值出乎意料,不与任何case部分匹配。

在前面的示例中,每个case部分都以一条break语句结尾,用于退出switch语句。如果case部分末尾没有break语句,将继续执行下一个case部分。虽然在有些情况下,可利用这种方法来执行多个 case部分,但在大多数情况下,您都希望每个部分以 break语句结尾。

在程序清单6.7中,程序BadTeacher使用一条switch语句根据考试成绩给予学生相应的评语。

程序清6.7 BadTeacher.cpp 的完整源代码

这个程序让用户输入用字母表示的成绩:A、B、C、D或F,然后给予相应的评语。下面是该程序的三种输出:

程序要求用户输入一个字母。第8行的switch语句检查输入的字母,第10行的case 语句检查字符是否为A,如果是,就执行第11行:显示评语“Finally!”,而下一行的break语句结束switch语句。

还有其他4个case部分分别测试不同的成绩。如果没有case部分匹配,就执行第25~27行的default部分。6.6 总结

本章介绍了循环和条件,它们可极大地提高C++程序的性能。

while循环不断运行一个代码块,直到条件不再为true。如果条件永远不为true,代码块一次也不会执行。

相反,do-while循环至少运行代码块一次,即使检查的条件永远不为true。

for循环包含初始化部分、检查部分和操作部分。这些部分使得可在for语句中创建计数器变量以及检查和修改该变量的值。

使用continue和break语句可编写出复杂循环,continue语句直接进入下一次循环迭代,而break语句结束整个循环。

switch语句让检查同一个变量的多种可能取值更简单,虽然使用一系列if和if-else 条件也可实现这样的目标,但switch语句让代码更容易开发和调试。6.7 问与答

问:如何在if-else和switch之间做出选择?

答:如果多个else子句测试同一个表达式,就应考虑改用switch语句;如果需要进行比较测试,如 a > b,则不能使用 switch语句。

问:如何在while和do-while做出选择?

答:如果循环体至少需要执行一次,应考虑使用do-while循环;否则,尽可能使用while循环。

问:如何在while和for之间做出选择?

答:如果要初始化计数变量,且每次循环迭代都检查并递增该变量,应考虑使用 for 循环;如果变量已初始化或无需每次循环迭代都递增它,while循环可能是更好的选择。6.8 作业

本章介绍了一些复杂的程序流程,您现在应该能够回答几个问题并完成两个练习,以巩固这方面的知识。6.8.1 测验

1.在for语句中应使用哪种数据类型?

A.整型

B.整型或浮点数

C.任何数据类型都可以

2.哪种循环不能使用break或continue语句?

A.for

B.for和while

C.无

3.在switch语句中,break命令有何作用?

A.跳到下一个case

B.结束switch语句

C.跳到default部分6.8.2 答案

1.C。大多数程序员都在for语句中只使用整型变量,但C++没有这样的限制,您可使用浮点数变量、字符串变量或其他任何数据类型的变量。

2.C。break和continue可用于任何类型的循环中,虽然它们在for循环中不那么常见,原因是这些循环通常循环固定的次数。

3.B。在switch语句中,break导致跳转到switch语句外继续执行。如果没有它,将从满足条件的第一个case部分执行到switch语句末尾。6.8.3 练习

1.编写一个程序,显示前100个能被16整除的数。

2.修改程序BadTeacher,使其处理成绩为E、G和H的情形并显示相应的评语。

这些练习的答案请参阅本书配套网站,其网址为http://cplusplus.cadenhead.org。第7章 使用数组和字符串存储信息

本章介绍如下内容:如何在数组中存储相关的数据?如何声明数组?如何使用字符数组创建字符串?如何复制字符串?7.1 数组是什么

数组是一系列类型相同的相关数据,可将数组视为一系列数据存储单元,其中每个存储单元都是数组的一个元素。

要声明数组,可依次指定数据类型、数组名以及用方括号括起的元素数,如下所示:

数据peaks存储25个长整型,上述声明导致编译器预留能够存储25个元素的内存空间。由于每个长整型占用4字节,因此该声明预留100字节连续的内存。

数组元素的编号从0开始,因此peaks数组存储的元素的编号为0~24。每个元素都可通过放在方括号内的编号进行访问,下面的语句给数组peaks的第一个元素赋值:

下面的语句给最后一个元素赋值:

数组元素的编号也被称为下标。

数组元素从0开始编号,这可能令人迷惑:如果一个数组包含3个元素,那么元素编号为0、1和2,而不是1、2和3。

在程序清单7.1中,程序WeightGoals使用一个数组来计算节食者的减肥转折点。这个数组存储浮点值,分别表示达到减肥目标的10%、25%、50%和75%。

程序清7.1 WeightGoals.cpp 的完整源代码

这个程序询问用户的当前体重和目标体重,然后显示4个体重转折点:

这个程序将用户的当前体重和目标体重分别存储在float变量weight和target中。

数组goal存储4个值,用于计算体重转折点。第5行创建了这个数组,而第6~8行将其元素分别设置为0.9、0.75、0.5和0.25。

使用了一个for来遍历数组元素,并将为达到转折点需要减去的体重存储在变量loss中(第20行),这是将要减去的体重乘以相应的百分比得到的。

为显示转折点,将当前体重与要减去的体重相减,如第21行所示。注意:数组元素编号从0而不是1开始,这是导致C++新手编写的程序出现bug 的常见原因。使用数组时,记住在包含 10 个元素的数组中,元素编号为0~9。7.2 写入时超过数组末尾

当您给数组元素赋值时,编译器将根据元素长度以及当前元素的下标决定将值存储在什么地方。如果您给goal[3]赋值,编译器将偏移量3与元素长度(对长整型来说,为4字节)相乘,然后从数组开头向前移相应的字节数(12),并将值存储在这个位置。

在程序WeightGoals中,数组goal只有4个元素。如果试图将值存储到goal[4],编译器将忽略没有这个元素的事实,将指定的值存储到离第一个元素开头16字节的地方,并替换这个地方原有的数据。这可能是任何数据,因此超越数组边界写入值可能导致无法预期的结果,如程序立即崩溃或行为古怪。

程序运行时,这样的错误难以查找,因此访问数组时,要特别注意其长度。

超越数组边界写入值的错误很常见,导致这种错误有其专用名称:篱笆桩错误(fence post error)。它指的是这样的计数问题,即如果需要一个长 10 英尺的篱笆,而篱笆桩之间相差 1英尺,需要多少篱笆桩呢?有些人的答案是10个,但实际上是11个,如图7.1所示。图7.1 计算篱笆桩数量

对任何程序员来说,这种“相差一个”的错误都是致命的。但随着时间的推移,您将习惯这样的思维,即如果一个数组包含25个元素,那么最大的元素编号为24,因为编号从0开始。7.3 初始化数组

对于简单的内置类型(如整型和字符串)数组,可在声明时初始化,方法是在数组名后面加上等号以及初始值列表,将这些值用大括号括起并用逗号分隔:

上述语句将 post 声明为一个包含 10 个元素的整型数组,并将 post[0]设置为 0,post[1]设置为10,以此类推,直到将post[9]设置为90。

如果省略数组长度,它包含的元素数将等于初始值数。请看下面的语句:

这条语句创建了一个包含5个元素的int数组,其中post[0]的值为10,post[1]的值为20,以此类推。

要获悉数组包含多少个元素,可使用C++内置的函数sizeof():

这条语句将数组post的长度处于元素长度,以获悉它包含多少个元素。

指定的初始值数量不能超过声明的元素数,下面的语句将导致编译器错误:

这时数组只有5个元素,而提供的初始值有6个。初始化数组时,指定的值数可少于元素数,如下面的语句所示:7.4 多维数组

可将一维数组视为一行数据,二维数组可视为由行和列组成的数据网格,其中一维对应于行,另一维对应于列。三维数组可视为立方体,其中一维对应于宽,另一维对应于高,而第三维对应于深。甚至可以声明超过三维的数组,虽然难以将其与物理空间对应起来。

声明数组时,每一维都用一个下标表示,因此二维数组有两个下标:

而三维数组有三个下标:

数组可包含任意维数,但您创建的通常是一维或二维数组。

一个典型的二维数组是国际象棋棋盘,其中一维包含8 行,另一维包含8 列,如图7.2所示。图7.2 国际象棋棋盘是方形的二维数组

假设您有一个名为board的char数组,它表示国际象棋棋盘,其中每个元素的值要么为w(表示相应的方格为白色),要么为b(表示相应的方格为黑色),则可使用下面的语句来创建这个数组:

也可使用包含64个元素的一维数组来存储这些数据:

然而,与二维数组相比,这种数组与国际象棋棋盘的对应关系不那么紧密。刚开始下棋时,“王”位于第1行的第4列,考虑到编号从0开始,这个位置对应的元素为board[0][3] (这里假设第一个下标表示行,而第二个下标表示列)。整个棋盘的布局如图7.2所示。警告:多维数组很容易将可用内存消耗殆尽,创建大型多维数组时别忘了这一点。7.4.1 初始化多维数组

可以像初始化一维数组那样初始化多维数组,其赋值顺序如下:最后一个下标不断变化,而其他下标相对稳定,就像汽车的里程表那样,如下所示:

第一个值赋给 box[0][0],第二个值赋给 box[0][1],而第三个值赋给 box [0][2];再下一个值赋给box[1][0],然后赋给box[1][1]和box[1][2]。

程序清单7.2所示的程序Box演示了这一点。

程序清7.2 Box.cpp的完整源代码

这个程序显示每个数组元素的值,可将其与第5~6行的初始化语句进行比较:

box是一个二维int数组,其中第一维的长度为5,而第二维的长度为3,这将创建一个5 × 3 的元素网格。

使用了两个for循环来遍历该数组,以显示每个元素及其值。

为让代码更为清晰,可使用大括号将初始值编组,并让每组值占据一行:

编译器忽略内部的大括号,但这种大括号让数据分布情况更清晰。

无论是否使用大括号进行分组,所有值都必须用逗号分隔。整个初始组必须放在大括号内,并以分号结尾。7.4.2 内存简介

声明数组时,您是在告诉编译器,您要在数组中存储多少个元素。编译器将根据数据类型和元素数为数组预留合适的内存量。数组适合用于存储项数已知的数据,如国际象棋棋盘的方格(64)或一个世纪的年份(100)。

如果预先不知道需要多少个元素,必须使用更高级的数据结构。

本书后面将介绍指针数组、堆数组以及其他数据结构。第19章将介绍一种高级数据结构——链表。7.5 字符数组

熟悉数组后,便可使用比char数据类型表示的单个字符更长的文本了。字符串是一系列字符,前面使用的字符串都是用于std::cout 语句的字面字符串:

在C++中,字符串是以空字符结尾的字符数组,空字符是编码为'\0'的特殊字符。可像其他数组那样声明并初始化字符串:

最后的'\0'是用于结束字符串的空字符。

这种逐个字符输入的方法比较困难,很容易出错,C++提供了使用字面量初始化字符串的简捷方式:

这种初始化方法不需要使用空字符,而是由编译器自动添加。

字符串“Zombie Eat Brains”占用 18 个字节(包括空字符在内)。

也可创建未初始化的字符数组,这被称为缓冲区。与其他数组一样,确保加入的信息不超过缓冲区的可用空间很重要。

缓冲区可用于存储用户输入,本书前面创建的多个程序都使用了std::cin对象来收集用户输入并将其存储到变量中:

这种方法虽然可行,但是存在两个大问题。首先,如果用户输入的字符数超过了缓冲区的长度,cin写入时将跨过缓冲区边界,导致程序不能正确运行,还可能导致安全问题。其次,如果用户输入了空格,cin将认为字符串就此结束,不再将接下来的内容写入缓冲区。

为解决这些问题,必须调用cin对象的方法getline(),并提供两个参数:要填充的缓冲区。最多读取多少个字符。

下面的语句最多从用户输入中读取 18 个字符(包括空字符),并将其存储到字符数组yum中:

调用这个方法时,还可提供第三个参数——终止输入的分隔符:

这条语句在遇到空格后停止读取输入。如果省略了第三个参数,则将换行符('\n')作为分隔符。

在程序清单7.3中,程序BridgeKeeper提出了来自电影中的三个著名问题,并将用户的回答存储到缓冲区。

程序清7.3 BridgeKeeper.cpp 的完整源代码

这个程序的输出类似于下面这样:

第10行调用了cin的方法getLine(),并将第5行声明的缓冲区作为第一个参数传递给它。第二个参数指定了输入最多可包含多少个字符,由于缓冲区name可存储50个字符,因此该参数必须为49,以便为终止的空字符预留空间。无需通过第三个参数来指定分隔符,因为默认分隔符(换行符)足以满足需求。

这些问题来自电影《巨蟒与圣杯》(Monty Python and the Holy Grail)。在这部电影中,死亡之桥的守卫者要求过桥人回答这三个问题,如果回答错误,将走向死亡。

下面是这些问题的答案:不列颠国王亚瑟(It is Arthur, King of the Britons)。去寻找圣杯(To seek the Holy Grail)。您说的是非洲燕子还是欧洲燕子(What do you mean? An African or Europeanswallow?)?7.6 复制字符串

C++从C语言那里继承了一个处理字符串的函数库,要将这个库加入到程序中,可包含头文件string.h:

在这个库提供的众多函数中,有两个用于将一个字符串复制到另一个字符串中,它们是strcpy()和strncpy()。

函数 strcpy()将整个字符串复制到指定的缓冲区,程序清7.4所示的程序 StringCopier演示了这一点。

程序清7.4 StringCopier.cpp 的完整源代码

运行该程序将得到如下输出:

第6行创建了一个字符数组,并使用一个字符串字面量对其进行初始化。在第9行,函数strcpy()接受两个字符数组作为参数:接受备份的目标数组以及要复制的源数组。如果源数组比目标数组大,strcpy()写入数据时将超过该缓冲区的边界。

为防止这种情况发生,标准库还提供了函数 strncpy()。这个函数接受第三个参数,它指定了最多复制多少个字符:7.7 总结

软件如此有用的原因之一是,它能够处理大量类似的数据。数组是一系列类型相同的数据,本章只演示了存储简单数据类型的数组,但后面您将发现,数组也可用于存储复杂的数据。

在C++中,字符串不过是字符数组,却被称为字符串,因为它们有很多用途。字符串可用于收集用户输出、显示文本以及存储来自文件、Web文档和其他来源的文本数据。

在C++中,有很多其他表示数据的方式,它们比简单数据类型和数组要复杂得多。7.8 问与答

问:对于只有24个元素的数组,如果写入第25个元素,结果将如何?

答:这将写入其他变量占据的内容,给程序带来灾难。这可能覆盖程序使用的其他内容,导致程序不能正常运行。据安全专家说,恶意程序员利用最多的软件漏洞是,写入数据时超越缓冲区边界,并利用这种错误来执行新代码。新代码通常可执行任何操作,如修改或删除文件、将系统特权授予不受信任的用户以及复制病毒。

问:什么是未初始化的数组元素?

答:任何没有赋值的数组元素,其值将为相应内存中原有的值。使用未赋值的元素的结果是无法预测的。

问:能够合并数组吗?

答:可以。对于简单数组,可使用指针将数组合并成更大的数组;对于字符串,可使用内置函数(如strcat)来合并。7.9 作业

本章介绍了数组,现在回答一些问题并完成两个练习,以巩固这方面的知识。7.9.1 测验

1.数组的最小下标是什么?

A.0

B.1

C.没有最小下标

2.存储数据时,如果使用的数组下标超过了所允许的最大值,结果将如何?

A.编译器报错

B.这些数据将被忽略

C.写入的数据将超越数组边界

3.没有初始值的字符数组又叫什么?

A.字符串

B.缓冲区

C.空字符7.9.2 答案

1.A。所有数组的下标都从 0 开始。最后一个元素的下标为数组长度减 1,因此数组brains[50]的最大下标为49。

2.C。数据将写入数组边界右边的内存中。很难说清楚结果将如何。如果您走运,数据被存储到计算机不让您访问的内存,将导致错误;如果不走运,将修改另一个变量,导致难以调试的bug。

3.B。缓冲区可用于存储用户输入和其他字符数据。7.9.3 练习

1.编写一个程序,询问用户的姓名,并在问候语中显示它。

2.修改程序WeightGoals,添加两个转折点,它们分别完成了减肥目标的90%和95%。

这些练习的答案请参阅本书配套网站,其网址为http://cplusplus.cadenhead.org。第二部分 类

第8章 创建基本类

第9章 高级类

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载