C++程序设计(txt+pdf+epub+mobi电子书下载)


发布时间:2020-10-13 01:46:17

点击下载

作者:宁涛

出版社:辽宁科学技术出版社

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

C++程序设计

C++程序设计试读:

前言

随着信息技术的飞速发展,尤其是高级编程语言的发展和普及,面向对象的软件开发方法越来越重要。使用C++作为软件开发工具的企业越来越多,计算机硬件的发展也在很大程度上提高了C++的运行效率。如何更有效地利用C++开发出灵活、易用的软件产品成为能否迅速占领用户市场的关键问题。

正是在这种背景下,根据多年的教学经验并结合学生的特点和需求,编写了本书。本书主要讲述了C++面向过程部分的语法结构,以及面向对象封装、继承和多态等机制。

本书是大连交通大学计算机软件专业、软件工程专业和数字媒体技术学生的必修课程之一。本书由浅入深地介绍了C++程序设计语言的语法结构和用法,充分考虑应用型本科学生培养目标和教学的特点,注重基本概念的同时,重点介绍实用性较强的内容。

本书参考了国内多所院校及机构应用多年的教材内容,结合作者所在学校学生的实际情况和教学经验,有取舍地改编和扩充了原教材的内容,使本书更适合于作者所在学校本科学生的特点,具有更好的实用性和扩展性。

本书共分14章,全面、系统、深入地讲解了C++程序设计语言的语法结构、函数和数组的使用、指针和引用的用法、类和对象的概念以及继承、多态机制。同时,每一章都有大量典型应用实例和课后习题。其中,第1~4章由张振琳编写;第5、6、7、9、10、11章由宁涛编写;第8、12、13、14章由刘瑞杰编写。

本书在编写过程中力求符号统一,图表准确,语言通俗,结构清晰。本书可作为高等院校或大专院校计算机软件专业、软件工程专业和数字媒体技术专业学生的教材,也可作为广大程序开发人员自学不可缺少的参考书之一。

如需本书课件和习题答案,请来信索取,地址:mozi4888@126.com宁涛第1章 C++编程基础

引言

C++是在C的基础上发展起来的,是兼顾面向过程和面向对象特点的程序设计语言。本章从计算机程序设计语言的发展历程出发,介绍程序设计语言的不同发展阶段和特点,介绍C++语言的产生以及C++与C之间的关系。通过完整的C++程序实例,使读者熟悉C++的开发环境,领会C++的编程风格。

学习目标(1)了解计算机程序设计语言的发展。(2)了解面向过程程序设计语言和面向对象程序设计语言的特点和区别。(3)了解面向对象程序设计的基本概念和思想。(4)熟悉C++语言的特点和开发过程。(5)掌握C++语言的结构和组成元素。(6)掌握C++语言的编程规范。(7)能够自主编写可运行的简单C++程序。1.1 计算机程序设计语言的发展

程序设计语言(Program Design Language,简称PDL)是一组由程序员编写,用来定义计算机程序的语法规则。它是一种被标准化的交流方式,通过向计算机发出指令来描述解决问题的方法。1.1.1 机器语言与汇编语言

最原始的程序设计语言是机器语言,它可以被计算机直接理解执行。机器语言是由二进制的0和1表示。机器语言实现100与200相加的计算如下:

1101 1000 0110 0100 0000 0000(B86400)

0000 0101 1100 1000 0000 0000(05C800)

由于机器语言是由一串二进制数组成,这使程序员读起来感觉十分晦涩,这也使得软件开发的难度大,周期长,后期维护困难。

为了克服机器语言编程的缺点,不久,能够将机器指令映射为类似英文缩写的、能被人读懂的助记符出现了,这就是汇编语言(assembly language)。如ADD、SUB助记符分别表示加、减运算指令,汇编语言实现100与200相加的计算如下:

MOV AX,100

ADD AX,200

程序员运行汇编程序将用助记符写成的源程序通过汇编器(Assembler)转换成机器指令,然后再运行机器指令程序。但是由于汇编语言的抽象层次太低,过多地依赖于具体的硬件系统,一个简单的操作可能需要大量的语句实现,使得汇编语言编程的难度无法降低,这决定了汇编语言和机器语言一样,同属于低级语言。1.1.2 高级语言

为了进一步提高编程效率,增加程序的可读性,人们逐渐开发了Fortran、Basic、C等高级语言。早期的计算机主要用于数学计算,随着计算机处理问题的复杂和计算机硬件、软件成本的下降,可读性、易维护、可移植成为程序设计的首要目标。20世纪60年代产生了结构化的程序设计思想,结构化程序设计的方法主要是:自顶向下、逐步求精;程序按功能分成树状结构的若干模块;模块间的依赖尽可能简单,功能相对独立;每个功能模块均由顺序、选择和循环3种基本结构组成。

结构化编程语言提高了语言的层次性,使编程语言更加接近人类的自然语言。C语言是结构化编程语言的代表,已经成为广泛的系统软件和应用软件开发语言。但是结构化编程语言是一种面向过程的语言,它把数据和数据处理过程分离成相互独立的部分,程序的可重用性较差。为了能更加直观地描述客观世界及它们间的联系,20世纪80年代由Xerox公司推出了第一个真正的面向对象编程语言——Smalltalk-80。面向对象编程语言将现实世界中的客观事物描述成具有属性和行为的对象,通过对象抽象出同一类对象共同特征成为类,这使得程序模块间的关系更加简单化,程序模块保持了独立性。面向对象编程语言实现了程序能够较直接反映问题本身,使程序员能够使用人类观察事物的一般思维方式进行软件开发。1.1.3 面向对象程序设计语言

面向对象程序设计的本质是把数据和对数据的操作看做一个整体——对象。

面向对象程序设计语言与面向过程程序设计语言的不同在于其出发点是能够更加直接地描述现实世界中存在的客观事物(对象)及它们之间的关系。下面介绍面向对象程序设计的基本概念,在后续章节会对它们作深入剖析和综合运用。

1.对象

对象是现实世界中存在的客观事物,可以是有形的(一部手机),也可以是无形的(一项计划)。对象是构成现实世界的独立单位,具有静态特性(属性)和动态特性(操作)。

面向对象程序设计方法中的对象是用来描述客观事物的实体,它是构成系统的基本单位。对象由一组属性和对这组属性的操作构成。属性是用来描述对象静态特性的数据项,操作是用来描述对象动态特性的行为序列。

2.类

把具有共同属性和操作的对象归纳、划分成一些集合是人们认识客观世界的基本方法。这样的集合我们称之为类,划分类的原则就是抽象,即忽略事物的非本质特性,只关心那些与当前目标有关的本质特性,从而总结出事物的共性,把具有共性的事物划分成一类,得出一个抽象的概念。

3.封装与数据隐藏

封装是面向对象设计方法的一个重要原则。它有两重含义:第一重含义是把对象属性和操作结合成一个独立的系统单位(对象);第二重含义是尽可能隐藏对象的内部细节,对系统外形成一个边界,只保留有限的接口与外部发生联系,这种无须知道系统内部如何工作就能使用的思想称为数据隐藏。例如,电脑技术人员组装电脑的时候,如果需要声卡,会直接去购买他所需要的某种功能的声卡,而不去关心声卡内部的工作原理,声卡本身是一个独立单元。声卡的所有属性都封装在声卡中,不会扩展到声卡之外。

C++通过建立用户定义类型(类)支持封装性和数据隐藏。完好定义的类一旦建立,就可以看成是完全封装的实体。类的实际内部工作隐藏起来,用户使用完好定义的类时,不需要知道类是如何工作的,只需要知道如何使用它即可。

4.继承与重用

特殊类的对象具有一般类的全部属性和操作,这称作特殊类对一般类的继承。例如,要制造新的电脑,可以有两种方法:一种是从草图开始全新设计,另一种是对现有型号电脑加以改进。现有型号的某些功能可能已经开发成熟,无须从头开始,因此设计人员往往会在原有型号基础上增加一组芯片来完成新功能的添加。这样设计出的新电脑被赋予新的型号后,新型电脑就产生了。这种机制就是继承与重用。

C++使用继承支持重用的思想,程序可以在扩展现有类型的基础上声明新的类型。新的类是从原有类派生出来的。上面实例中,新型电脑在原有型号电脑上增加若干功能而得到,那么,新型电脑是原有电脑的派生,继承了原有电脑的所有属性和操作,并在此基础上增加了新的属性和操作。

5.消息

消息是面向对象发出操作的请求,系统通过消息完成对象间的通信,消息包含提供服务的对象标识、操作标识、输入信息和回答信息。在面向对象程序设计中,消息是通过函数调用实现的。

6.多态性

通过继承的方法构造类,采用多态性可以为每个类指定其具体行为。多态性是在一般类中定义的属性和行为,被特殊类继承后,可以具有不同的数据类型或表现出不同的行为。这使同一个属性或行为在一般类及各个特殊类中具有不同的语义。例如,学生类应该有计算成绩的操作。学生类包括小学生、中学生和大学生。大学生是中学生的延伸,对于中学生而言,计算成绩的操作主要包括语文、数学和英语等课程的计算,而对于延伸的大学生而言,计算成绩的操作包括英语、高等数学、物理、计算机等课程计算。大学生计算成绩的操作便表现出多态性。

通过继承,类似的对象可以共享许多相似的特性,而通过多态性,每个对象可以有独特的属性和操作。1.2 C++语言概述

C++语言是在C语言的基础上发展起来的为支持面向对象而设计的编程语言。C++对C的扩充是Bjarne Stroustrup博士于1980年在美国新泽西州玛瑞惠尔的贝尔实验室提出来的,起初,他把这种语言称为“带类的C”,到1983年更名为C++。C++语言的标准化工作从1989年开始,于1994年制定了ANSI C++标准草案。

C++语言在保留C语言原来优点的基础上,吸收了面向对象的思想。C++语言包括过程化语言部分和类部分。过程化语言部分与C语言差别不大,类部分是C语言所没有的,它是面向对象程序设计的主体。因为C++语言和C语言在过程化语言部分共有,C++语言分享了C语言的许多技术风格,所以学习过C语言对学习C++语言有一定的促进作用,而没有学习过C语言的人也可以直接学习C++语言。C++语言的程序结构清晰,易于扩展,易于维护,改善了C语言的安全性。与C语言相比,C++语言具有三方面的优越特征。(1)支持抽象数据类型(Abstract Data Type, ADT)。(2)多态性,C++语言既支持早期联编又支持后期联编,而C语言仅支持早期联编。(3)继承性,这既保证了代码重用,确保软件的质量,又支持了类的概念,使对象成为具体实例。

C++语言对C语言完全兼容,这使许多C语言代码不经修改就能在C++编译器下通过,C语言编写的库函数和实用软件可方便地移植到C++里。因此,C++语言不是一门纯粹的面向对象的程序设计语言,它既支持面向对象程序设计方法,又支持面向过程程序设计方法。1.3 C++程序开发过程

C++程序开发包括6个阶段:编辑、预处理、编译、连接、运行与调试。编辑阶段的任务是编辑源程序,C++源程序文件一般带有.h、.c、.cpp扩展名。.cpp是标准的C++源程序扩展名;程序员通过编译器对编辑好的源程序进行编译,生成目标文件,目标文件的扩展名为.obj。该目标文件为源程序的目标代码,即机器语言指令。但目标代码只是一个个的程序块,仍然不是可执行的程序,为将其转换为可执行程序,必须进行连接(link)。连接是通过连接器实现,它的功能是将目标同函数的代码连接起来,生成可执行代码,存储成可执行文件,可执行文件的扩展名为.exe。

程序员首先在开发环境中编辑源程序,然后在开发环境中启动编译程序将源程序转化成目标文件。编译完成后,若出现编译错误,则回到编辑状态重新开始编辑和编译操作;最后对编译成功的各程序块进行连接,连接阶段若出现连接错误,则再次回到编辑状态修改程序。程序连接通过后,便生成可执行文件,运行过程中,可执行文件被操作系统装入内存,通过CPU从内存中读取程序执行。目前的C++系统产品,如Microsoft Visual C++和Borland C++,除了将程序的编辑、编译和连接集成在一个环境中,还提供源代码级的调试工具,可以直接对源程序进行调试。

在程序开发的各个阶段都可能产生不同的错误,编译阶段出现的错误称为编译错误;连接阶段出现的错误称为连接错误;程序运行过程中出现的错误称为运行错误或逻辑错误。C++系统提供了调试工具debug帮助发现程序的逻辑错误,然后进一步修改源程序,改正错误。C++程序开发过程如图1-1所示。图1-1 C++程序开发步骤1.4 C++程序举例

下面通过一个简单的程序来熟悉和分析C++程序的基本构成。【例1-1】本程序是一个完整的可运行的程序,它的输出结果显示:“I love C++!”。1 /**************************************************2 程序文件名:Ex1_1.cpp3 本程序结果:4 I love C++!5 设计者:Mr Ning6 3-21-20117 **************************************************/8 #include<iostream> //加载头文件9 using namespace std; //使用命名空间10 void main() //程序入口11 {12 cout<<"I love C++!"<<endl;13 }

C++的程序结构由注释块、编译预处理和程序主体组成。【程序解释】

◇程序中的第1~7行为注释块。注释块中通常表明程序的名称、程序完成的功能、程序设计人员姓名、程序最后修改时间。注释块在预处理时被过滤,不被编译器编译,不形成目标代码,编译器把每个注释都视为空格处理。关于C++中的注释格式,下面会详细介绍,这里不再赘述。

◇程序的第8行语句以“#”开头,是编译预处理行。C++规定,每个以“#”开头的行,都称为编译预处理行。如本例中“#include”称为文件包含预处理命令。它在编译时由预处理器执行,其作用是将<>中指定位置的头文件iostream加载到源程序,iostream的位置由编译器设定。它是系统定义的一个“头文件”,设置了C++的I/O环境,定义输入输出流对象cout与cin等。它们通常放在指定位置的include子目录下。预处理命令在编译时由预处理器处理,没有编译成执行指令,也称作伪指令。预处理命令结尾处没有“;”。

◇程序第9行表示使用std命名空间。

◇程序第10行main()表示主函数,每个C++程序必须有且只有一个main()函数。它作为程序的入口,如果一个程序包含多个源程序模块,则只允许一个模块包含main()函数。void在此处表示main()函数没有返回值。函数名main全部由小写英文字母构成,C++程序的命名是严格区分大小写的,其规则在第2章中详细介绍。

◇第11行与第13行表示的是一对花括号{},C++中用一对花括号将多条语句括起来构成函数体,描述一个函数所执行算法的过程称为函数定义。本例中,void main()函数头和函数体构成了一个完整的函数定义。

◇第12行cout<<"I love C++!"<<endl;中,cout(全是小写英文字母)代表标准输出流对象,它是C++预定义的对象(定义在iostream中),与显示设备相连。<<是插入操作符,endl为换行符,双引号括起的数据“I love C++!”称为字符串常量,“;”是一个语句的结束标记。整个语句的功能是将“I love C++!”字符串与endl依次插入cout中。

◇C++程序中的语句必须以“;”结尾。1.5 注释方法

C++的注释方法有两种形式:一种方法是块注释“/**/”格式,这种风格与C语言的注释方法相同。C++是C语言的扩充,因此支持C的注释方法。注释以“/*”开头,以“*/”结束,编译器忽略这两个符号间的所有语句(同行语句或多行语句),如例1-1中的第1~7行采用的就是这种方法。另一种方法是行注释“//”双斜线格式,这种风格是C++区别于C语言特有的注释方法,在当前行双斜线之后的部分都会被注释,如例1-1中第8行。注释是程序员用来说明程序或解释代码的,是程序的组成部分,虽然编译器编译时忽略注释,但注释有非常重要的作用。注释通常用于:(1)版本、版权声明。(2)函数接口说明。(3)重要的代码行或段落提示。

规范的程序编写过程应该有专门的时间来添加注释。假如程序设计人员花费数月时间编写了几万行的C++代码,调试成功后交给客户。如果客户需要对代码进行维护或修改,看到数万行没有任何注释的代码时,就既不能在短时间内搞清楚它们的功能,又不能很好地理解开发人员的设计意图;即使是程序设计人员在间隔很长一段时间后再来阅读曾经编写过的代码,也会有很多理解上的困难。C++程序中正确的注释在代码阅读、验收尤其是后期代码维护阶段可以起到事半功倍的效果。但是不必为程序每一行的代码进行注释,也不必为那些一目了然的代码添加注释。1.6 C++的编程风格

C++程序员使用标识符、空格、空行、标点符号、代码缩进排列和注释等来安排原代码的方式构成了编程风格的重要组成部分。编程风格的好坏不会影响程序的执行效率和结果,但是阅读者很难读懂书写不规范的程序,例1-2是编写不规范的程序段。【例1-2】编写不规范难以理解的程序。1 /**************************************************/2 程序文件名:Ex1_2.cpp3 编写不规范的程序4 设计者:Mr Ning5 3-21-20116 **************************************************/7 #include<iostream>8 using namespace std;9 int main(){int a, b;cout<<"输入变量a和b:";cin //晦涩难懂的编程风格10>>a>>b;cout<<"a+b="<<a+b<<endl;return 0;

尽管编程风格的自由度很大,但是大多程序应该遵循程序设计的国际常规。追求清晰、美观是程序风格的重要构成因素。可以把程序的风格比喻为“书法”,好的“书法”让人一目了然,兴致勃勃;差的“书法”让人看得索然无味,更令维护者烦恼有加。1.6.1 代码行规范

代码行的编写规范包括下列9条规则。(1)一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且便于添加注释。(2)if、for、while、do等语句独占一行,执行语句不得紧跟其后。无论执行语句有多少行(包括0行和1行),都要加{}。下面对不同编码风格的相同程序段进行对比。【例1-3】不同编码风格的程序段对比。int width, height, depth; //宽度、高度、深度x=a+b;y=c+d;z=e+f;if(width<height)function1();for(first;second;update)function1();other();

例1-3(a)风格不良的程序段

int width;int height; //高度int depth; //深度x=a+b;y=c+d;z=e+f;if(with<height){function1();}for(first;second;update){function1();} //空行other();

例1-3(b)风格良好的程序段(3)关键字之后要留空格。如const、virtual、inline、case等关键字之后至少要留一个空格,否则无法辨析。如if、for、while等关键字之后应留一个空格再跟左括号“(”,以突出关键字。(4)函数名之后不要留空格,紧跟左括号“(”,以与关键字区别。(5)“(”向后紧跟,“)”、“,”、“;”向前紧跟,紧跟处不留空格。(6)“,”之后要留空格,如function(x, y,z)。如果“;”不是一行的结束符,其后要留空格,如for(initialization;condition;update)。(7)赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“=”、“+=”“>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。(8)一元操作符如“!”、“~”、“++”、“——”、“&”(地址运算符)等前后不加空格。(9)像“[]”、“.”、“->”这类操作符前后不加空格。1.6.2 修饰符和注释符规范

修饰符既可以靠近数据类型,又可以靠近变量名。若靠近数据类型,例如int*x;从语义上讲这种写法比较直观,即x是int类型的指针。但这种写法容易引起误解,例如int*x, y;此处y容易被误解为指针变量,因此修饰符应符合“紧靠变量名”的规则。

例如:

char*name;

int*x, y; //此行y不易被误解为指针

C++语言中,程序块的注释采用“/*……*/”,行注释一般采用“//……”。注释的编码风格应满足如下规则:(1)注释是对代码的“提示”,而不是文档。程序中的注释不可太多,注释的花样要少。(2)如果代码本来就是清楚的,则不必添加注释。例如,i++;//i加1,此处注释多余。(3)边写代码边注释,修改代码的同时修改相应的注释,以保证注释与代码的一致性。(4)避免在注释中使用缩写,尤其是不常用的缩写。(5)注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在其下方。(6)若代码比较长,尤其有多重嵌套时,应当在一些段落的结束处加注释。1.6.3 类版式的规范

类可以将数据和函数封装在一起,其中函数表示了类的行为。类提供关键字public、protected和private,分别用于声明数据和函数的公有、受保护或者私有的性质。这样可以实现数据隐藏的目的。

类的版式主要有两种方式:(1)将private类型的数据写在前面,而将public类型的函数写在后面,如例1-4(a)。这种方法是“以数据为中心”,重点关注类的内部结构。(2)将public类型的函数写在前面,而将private类型的数据写在后面,如例1-4(b)。这种方法是“以行为为中心”,重点关注类应该提供什么样的接口。

本教材建议采用“以行为为中心”的书写方式,即首先考虑类应该提供什么样的函数。这样不仅可以使自己设计类时思路清晰,而且方便用户阅读,因为用户最关心的是接口,而不是数据成员。

class X{private:int i, j;float x, y;……public:void function1(void);void function2(void);……}

例1-4(a)以数据为中心的方法class X{public:void function1(void);void function2(void);……private:int i, j;float x, y;……}

例1-4(b)以行为为中心的方法1.7 C++的输入输出简介

C++程序没有输入/输出语句,它的输入/输出功能由函数(scanf、printf)或输入输出流(I/O流)控制来实现。C++定义了运算符“<<”和“>>”的iostream类。运算符和类的概念在后面章节会详细介绍。当使用C++的标准输入输出时,必须包含头文件iostream。

1.输入

C++中使用对象cin作为标准输入流对象,cin与键盘操作符>>连用,格式为:

cin>>对象1>>对象2>>……>>对象n;

表示从标准输入流提取键盘输入的n个数据分别赋给对象1、对象2、……对象n。

例如:#include<iostream>using namespace std;void main(){int a=5,b;cin>>a>>b;}

上述程序段的意思是从输入流提取键盘上输入的两个数据,分别赋给变量a和变量b。

2.输出

C++中使用对象cout作为标准输出流对象,cout与键盘操作符<<连用,格式为:

cout<<对象1<<对象2<<……<<对象n;

表示依次将对象1、对象2、……对象n插入到标准输出流中,以实现对象在显示器上的输出。例如:#include<iostream>using namespace std;void main(){cout<<“Hello.\n”;}

上述程序段的意思是使用插入操作符“<<”向输出流cout中插入字符串“Hello.\n”(转义字符“\n”表示回车换行符),并把它在屏幕上显示输出。

在C++程序中,cin与cout允许将任何基本数据类型的名字或值传给流,而且书写格式较灵活,可以在同一行中串连书写,也可以分写在几行,提高可读性。例如:cout<<“hello”;cout<<3;cout<<endl;等价于:cout<<“hello”<<3<<endl;1.8 小结(1)计算机编程语言经历了从机器语言、汇编语言、高级语言到面向对象语言的过程,编程语言的发展与人类的自然语言越来越接近,提高了编程效率。(2)面向对象的基本概念:对象、类、封装和数据隐藏、继承与重用、消息、多态性。(3)C++语言具有兼容C语言的面向过程化的特点,又支持面向对象的设计方法。(4)C++程序设计的步骤包括编辑、编译、连接和运行。(5)C++包括两类注释方法:行注释与块注释。(6)为提高程序的可读性和保证程序的易维护性,C++程序应该遵循良好的编程风格。(7)C++通过标准输入输出流进行输入输出。1.9 习题1

选择题(1)C++语言属于()。

A.机器语言

B.汇编语言

C.低级语言

D.高级语言(2)C++语言程序能够在不同的操作系统下编译、运行,因为C++具有良好的()。

A.适应性

B.兼容性

C.可读性

D.可移植性(3)#include语句()。

A.总是在程序运行时最先执行

B.按照在程序中的位置顺序执行

C.在程序运行前已经被执行

D.在最后执行(4)C++程序的入口是()。

A.程序的第一条语句

B.预处理命令后的第一条语句

C. main()

D.预处理指令(5)下列说法正确的是()。

A.用C++语言书写程序时,不区分大小写字母

B.用C++语言书写程序时,每行必须有行号

C.用C++语言书写程序时,一个语句可分几行写

D.用C++语言书写程序时,一行只能写一个语句第2章 基本数据类型及表达式

引言

C++数据的处理是通过表达式完成的,根据数据所占内存单元以及表达含义的不同,分为不同类型的数据。不同类型的数据通过各种运算符进行连接组成表达式,实现数据处理。本章主要介绍C++语句的基础部分:数据类型、变量和常量、运算符和表达式。

学习目标(1)掌握C++的基本数据类型。(2)掌握各种类型数值的表示。(3)理解变量的含义以及标识符的命名规则。(4)区分不同类型的常量。(5)掌握常用运算符的优先级、结合性和使用特点。(6)理解表达式的构成规则。(7)掌握自动类型转换和强制类型转换的使用方式。2.1 C++的数据类型

一个完整的C++程序由若干程序段组成,程序段包含若干条语句,语句则由C++不同类型的数据组成,数据类型是程序中最基本的元素。如果把C++程序看做一篇文章,各个程序段就可看做文章的各个自然段,程序段中的语句可以看做文章中的句子,不同类型的数据可以看做句子中的字词。数据类型决定了变量和常量(下文会详细介绍)的空间大小分配。为了更好地描述客观世界的不同事物以及满足程序处理对象的不同需要,C++提供了严格的数据类型检查机制,这也是C++超过C语言的优点之一,C++的数据类型如图2-1所示。图2-1 C++的数据类型2.1.1 字符集与关键字

字符集是C++程序的最小元素,C++程序中的语句只能由字符集中的字符组成。C++的字符集由下列字符组成:(1)26个小写英文字母。

a b c d e f g h i j k l m n o p q r s t u v w x y z(2)26个大写英文字母。

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z(3)10个数字。

0 1 2 3 4 5 6 7 8 9(4)特殊符号。

空格+-*/,._:;?\“‘~|#%&()[]{}^<>

C++包含一些特殊的、预先定义的标识符,这些标识符称作关键字(保留字)。表2-1列出了ANSI C规定的32个关键字和ANSI C++补充的29个关键字。

C++程序不允许出现与关键字同名的标识符。2.1.2 基本数据类型

C++的数据类型分为基本数据类型、构造数据类型和抽象数据类型。基本数据类型是C++内部预定义的数据类型,包括字符型(char)、整数型(int)、实数型(float)、空值型(void)和布尔型(bool),其中布尔型也称为逻辑型,是C语言不具备的、C++扩充的新数据类型。表2-2列出了C++的基本数据类型。

在大多计算机中,short int表示2个字节长,short只能修饰int, short int可以省略为short;long只能修饰int和double,修饰long int时,表示4个字节,修饰long double时,表示10个字节。

在C++中可以使用sizeof确定某数据类型的字节长度。例如下列语句:

cout<<"size of double is"<<sizeof(double)<<endl;

在16位计算机上运行上面语句的结果为:

size of double is 82.2 变量定义

变量是存储数据的内存区域,变量名是所对应内存区域的标识符。标识符是用来标识程序中实体的名字,函数名、变量名、类名和对象名等都属于标识符。变量的“变”体现为在程序运行过程中,变量所标识的内存区域中的数据是可以改变的。比如某图书馆的座位号是551,座位是不变的,而不同时间座位上的人是可以变化的,那么与变量的定义相对应,则座位相对于变量,座位号相对于变量名,而座位上的人相对于存储在变量的数据。2.2.1 变量的命名

C++是区分字母大小写的。例如变量名virtual、Virtual、VIRTUAL和VirTual是不同的。在C++中,变量名要遵循标识符的命名规则,具体如下:(1)不能是C++的关键字。(2)只能以英文字母或下划线开头。(3)名字中间不能有空格。(4)名字只能由26个大小写英文字母、数字和下划线组成。(5)大写字母和小写字母表示不同的标识符。(6)变量名不能与C++的库函数名、类名或对象名相同。

例如,下面的变量名定义:

const, double, cout //不合法,C++的关键字

8myprograme,98umk9_i //不合法,以数字开头

Myproga&ia, adder@12 //不合法,包含特殊字符

_myprogame, myfirst12a //合法

变量名通常是描述性的标识符,为了提高程序代码的可读性,给变量命名时,尽量做到“见名知义”。例如,有变量名myAge,可以使人自然想到这个变量表示人的年龄;而变量名8fde_de虽然是合法的,但是没有明确的含义,所以是风格很差的变量名。若变量名的描述涉及两个或两个以上单词时,建议使用下划线或大小写对单词加以区分。例如,变量名my_first_car和myFirstCar要好于变量名myfirstcar。2.2.2 变量的定义格式

变量在使用前必须先声明其类型,编译器根据不同类型分配相应的内存空间。变量定义的一般格式为:

★数据类型变量名1[,变量名2][,变量名3][,……][,变量名n];

C++允许在同一语句中定义类型相同的多个变量,多个变量名之间用逗号分隔,但同一语句中不能混合定义不同类型的变量。例如,下列语句分别定义了3个字符型变量和2个整型变量:

char a, b,c; //定义了3个字符型变量,变量名分别为a, b,c

int myAge,_num; //定义了2个整型变量,变量名分别为myAge,_num2.2.3 变量的赋值与初始化

C++使用赋值运算符“=”给变量赋值。【例2-1】给整型变量myAge赋值18。int myAge; //定义整型变量myAgemyAge=18; //将整数18赋给变量myAgecout<<myAge<<endl;

这时名字为myAge的整型变量中存储的数据为18,程序运行结果显示如图2-2所示。图2-2 例2-1运行结果

如果此时增加一条语句myAge=19;则上例变为:

int myAge;myAge=18;cout<<myAge<<endl;myAge=19; //将整数19赋给变量myAgecout<<myAge<<endl;

程序运行结果如图2-3所示。

从运行结果可以知道,当给变量myAge重新赋值19时,原来存储在变量myAge中的18不复存在,已经被19取代。图2-3 例2-1修改后运行结果

C++允许定义变量的同时给变量赋初始值,这叫变量的初始化。变量初始化的一般格式如下:

★数据类型变量名1=初值1[,变量名2=初值2][,……][,变量名n=初值n];

例如:int sum=10; //定义整型变量sum,并将其初始化为10char n1='a',n2='b'; //定义字符型变量n1,n2,并将其初始化为‘a’,‘b’double height=110. 333; //定义双精度变量height,并将其初始化为110.333

整型变量在没有被初始化的情况下,系统默认其初始值为0。

C++提供了另外一种初始化的方法就是在定义变量时,将初始值写在紧跟变量的一对括号中。格式如下:

★数据类型变量名1(初值1)[,变量名2(初值2)][,……][,变量名n(初值n);

例如,上述初始化语句可等价表示如下:int sum(10);char n1('a'),n2('b');double height(110. 333);2.3 常量定义

常量是在程序运行过程中值不能改变的量。根据定义和使用方式不同,可分为文字常量、符号常量和常变量。2.3.1 文字常量

能够在程序中直接使用的常量叫文字常量。文字常量存储在代码区,按其取值方式和表达方法的不同,可分为整型常量、实型常量、字符型常量和字符串常量。

1.整型常量

整型常量一般有3种表示形式:(1)10进制整数,如456、-321、0。(2)8进制整数,它是以0开头,包含数字0~7。如034表示8进制整数(34),等价于10进制整数28。8(3)16进制整数,它是以0X或0x开头,包含数字0~9、A~F(或a~f)。如0X34或0x34表示16进制整数(34),等价于10进16制整数52。

在一个常数后加U或u表示该常数是无符号型的整数,如12U、012u、0xA2U。但8进制整数和16进制整数只能表示无符号整数。在一个常数后加L或l表示该常数是长整型(long int)的数,如12L、0xA2l。

2.实型常量

实型常量就是浮点数,在内存中以浮点形式存放,并且只能由10进制表示,没有进制的区分。实型常量有两种表示:(1)小数形式,由数字和小数点组成(必须写出小数点)。如0.34、.345、2.等都是合法的实型常量。(2)指数形式,也叫做科学表示法形式。如0.34×10 5可表示为0.34E5或0.34e5。注意E或e前必须有数字,并且E或e后面的数字必须是整数。E2、0.34e3.2、.e6和e等都是不合法的指数形式。

C++中的实型常量如果没有特殊说明,均表示double型;要表示float型,则必须在实数后面加上F或f;要表示long double型,则必须在实数后面加上L或l。如:0.34 F(等价于0.34f) //float型实数0.34  //默认为double型实数0.34 L(等价于0.34l) //long double型实数0.34 e3F(等价于0.34e3f) //float型实数0.34 e3L(等价于0.34e3l) //long double型实数3.字符型常量

字符型常量是用单引号括起来的一个字符,如‘a’、‘B’、‘?’、‘&’等都是字符型常量。在内存中存放字符所对应的ASCII码值,数据类型为char。除能够直接在键盘输入表示的字符外,C++还提供一种使用特殊形式表示的字符常量,这些字符常量以反斜线“\”开头,改变了原来字符常量的含义,称之为“转义”字符。如‘n’表示字母n,而‘\n’作为一个独立的字符,表示换行符。表2-3列出了C++中定义的转义字符。

4.字符串常量

字符串常量是用双引号括起来的字符序列。如“hello”,“I love Xiaoxin!”,“a2”都表示字符串常量。字符串常量在内存中是按顺序逐个存储串中的字符,并在末尾加上结束标志‘\0’,‘\0’称为串结束符。字符串的长度是串中‘\0’之前所有字符的个数,字符串常量占用的字节数是字符串长度加1。例如,字符串“hello”,它的字符串长度为5,在内存中表示6个连续的内存单元,如2-4图所示。图2-4 字符串的内存表示

注意,虽然一个字符和具有单个字符的字符串在输出表示上没有差别,例如:cout<<'a'<<endl; //输出结果为:acout<<"a"<<endl; //输出结果为:a

但是由于字符与字符串的类型、占用内存大小以及处理和使用方式的不同,决定了它们本质上的区别,‘a’在内存中占1个字节,“a”在内存中则占2个字节。2.3.2 符号常量

在程序中,如果直接使用文字常量,就可能会带来编写烦琐和可维护性差等问题。例如,假设程序中有实型常量3.1415926,如果程序中多处使用这一常量,则会带来重复编写的烦琐;如果需要对程序中的这一常量进行精度上的修改,例如将3.1415926表示为3.14,则需要在出现的所有位置逐个修改。为了简化这种操作上的烦琐,C++提供了允许用一个特定符号代表一个确定常量的机制,这就是符号常量。

符号常量是指程序中符号化的常量,即用一个标识符表示一个常量。在程序的开头定义一个符号常量,令其代表一个常量数值,在下面的程序中直接使用该符号常量即可。符号常量的定义格式如下:

★#define符号常量名常量数值

例如:#define PI 3. 1415926 //定义符号常量PI(1)

式(1)中,用标识符PI表示实型常量3.1415926,若要将程序中的3.1415926都修改为3.14,则只需在符号常量定义处直接修改,而不需要重新修改程序中的其他代码。如式(2):#define PI 3. 14 //修改符号常量PI(2)

通常情况下,使用大写英文字母给符号常量命名。2.3.3 常变量

符号常量虽然使用比较方便,但是编译器在编译时不会对其进行类型检查,而C++是一种强类型检查机制的语言,为了程序的安全性和稳定性,C++提供了一种使常量具有数据类型的机制,称之为常变量。常变量的定义格式可使用如下两种方式:

★const数据类型符号常量名=数值;

等价于

★数据类型const符号常量名=数值;

例如:const double pi=3. 14; //定义一个值为3.14的实型常量,名为pi(3)const int a=123; //定义一个值为123的整型常量,名为a(4)

上例中,式(3)和式(4)的常变量pi和a的值在程序运行过程中不能被修改,也就是在使用过程中,不能对常变量进行赋值。若式(3)修改为:const double pi;pi=3. 14; //error,不能对常变量pi赋值

则会产生语法错误,并且常变量在定义的同时必须被初始化。常变量定义中初始化的值可以是具体数值,也可以是一个常量表达式。例如:const int height=300 *sizeof(int); //ok,300 *sizeof(int)是常量表达式

与符号常量相比,常变量与变量的定义格式类似,常变量可以根据需要选择定义为不同的数据类型,既节省了内存空间,又保证了程序的安全性。虽然文字常量、符号常量和常变量统称为常量,但它们之间在处理和使用上有本质的区别,文字常量和符号常量不占用内存空间,而常变量与变量一样被保存在专门的内存空间。2.4 运算符与表达式

对变量或常量进行运算和处理的符号统称为运算符,参与运算的对象称为操作数。C++提供了丰富的运算符,依据不同的划分原则,运算符可以进行不同的分类。按要求操作数的多少,可分为单目运算符、双目运算符和三目运算符;按运算符的运算性质可分为算术运算符、关系运算符、逻辑运算符、赋值运算符和位运算符等。

表达式是将运算符与操作数连接起来的式子,单独的常量和变量也可称为表达式。2.4.1 算术运算符

算术运算符是C++中最常用的运算符,C++提供7种基本的算术运算符:+(加)、-(减)、*(乘)、/(除)、%(取余)、+(正号)和-(负号),其中前5个运算符是二目运算符,后2个运算符是单目运算符。(1)算术运算符的优先级与数学中相应符号的优先级相一致,+(正号)和-(负号)的优先级高于*、/、%的优先级,+(加)、-(减)的优先级最低。

例如:a=8;b=6;a+b*-3; //结果为-10

由于负号优先级最高,因此首先对3进行取负运算,然后做乘法得-18,最后进行加法,最终结果为-10。(2)根据操作数的不同,除法的运算规则也不相同。如果进行两个整数相除,则计算结果取商的整数部分。

例如:5/2 //结果为25/4 //结果为1

如果除数或被除数中至少有一个为浮点数,则进行通常意义的除法。例如:5.0 /2 //结果为2.55/2. 0 //结果为2.55.0 /2.0 //结果为2.5(3)%作为取余运算符,要求两个操作数的值必须为整数或字符型数,如果对浮点数操作,则会引起编译错误。5%2 //结果为15.0 %2 //编译错误2.4.2 关系运算符

关系运算符也叫做比较运算符,关系运算的结果是bool型值:true(即非0)或false(即0)。关系运算符是二目运算符,C++提供了6种关系运算符:>(大于)、<(小于)、>=(大于等于)、<=(小于等于)、==(等于)、!=(不等于)。

关系运算符的两个操作数可以是任意基本类型的数据。当比较结果成立,则结果为true;否则,结果为false。

例如:int a=1,b=3,c=5;a>b; //结果为falseb<a+c; //结果为true

由于浮点数在计算机中只是近似地表示一个数,因此一般不直接比较两个浮点数。如果要对两个浮点数进行==或!=比较时,通常指定一个极小的精度值,如果两个操作数的差位于精度之内,则认为两个浮点数相等。

例如:float a, b;a==b //通常写作fabs(a-b)<1e-6a!=bfabc()的是取浮点数的绝对值函数,第4章会详细介绍。2.4.3 逻辑运算符

逻辑运算符用于进行复杂的逻辑判断,一般以关系运算的结果作为操作数,计算结果的类型也是bool类型。C++提供了3种逻辑运算符,按照优先级由高到低,依次为:!(取反) //单目运算符&&(与) //双目运算符||(或) //双目运算符

例如:

!a的含义是,当a为true时,表达式的值为false,否则为true。

a&&b的含义是,当a、b均为true时,表达式的值为true,否则为false。

a||b的含义是,当a、b均为false时,表达式的值false,否则为true。

逻辑运算符的操作数为bool型,当操作数是其他数据类型时,将其转换为bool型值后参加运算。如果操作数的值非0,逻辑运算符都把其当做true进行计算。

例如:int a=10,b=6,c=-2,d=0;!a; //结果为false!d; //结果为truea&&b; //结果为truea||b;

在逻辑运算符的使用过程中,如下几点需要注意:(1)C++规定,作为逻辑运算的操作数,任何非0值都表示true,0值表示false,如:a=-6;b=3.4;则表达式a&&b的结果为true。(2)如果要表示数学关系0≤a≤10时,应该表示为0<=a&&a<=10,不能表示为0<=a<=10。例如,如果a=-2时,数学关系0≤a≤10显然不成立;而作为C++的表达式0<=a<=10的运算过程为:按照自左向右运算,0<=a的结果为0(即false),再计算0<=10,结果为1(即true),这与数学关系不一致。而对于表达式0<=a&&a<=10,先计算0<=a,结果为0,再计算0&&a<=10,结果为0(即false),这与数学关系一致。(3)在进行逻辑表达式计算时,从左向右扫描表达式,一旦根据某部分的值能够确定整个表达式的值,则不再进行计算,这是逻辑表达式运算的优化,或称为求值“短路”。

例如:int a=10,b=10,c=10,d=0;d=(a<b)&&(a==c); //d=0(5)d=(a==c)||(a<b);(6)

式(5)中,先计算a<b,结果为0,因为其后紧跟的是&&运算符,这时已能确定整个逻辑表达式的值为0,不再继续a==c的计算。

式(6)中,先计算a==c,结果为1,因为其后紧跟的是||运算符,这时已能确定整个逻辑表达式的值为1,不再继续a<b的计算。2.4.4 赋值运算符

将数据存储到变量所标识内存单元的操作叫做赋值。除了在定义时给变量赋值外,还可以用赋值操作以新值取代旧值。因为常量的值在使用过程中不能改变,所以不能对常量进行赋值操作。C++的赋值运算符是“=”,含义是将赋值号右边的值送到左边变量标识的内存单元。赋值操作格式如下:

★<变量>=<表达式>

赋值操作的求解过程为:先计算右边<表达式>的值,再将结果赋给左边<变量>。【例2-2】给变量i、x赋值。int i=100; //定义时给整型变量i赋值为100char x='A'; //定义时给字符变量x赋值为’A’cout<<"i="<<i<<"x="<<x<<endl;//结果为i=100 x=Ai=200; //给变量i赋新值为200,取代了旧值100x='B'; //给变量x赋新值为’B’,取代了旧值’A’cout<<"i="<<i<<"x="<<x<<endl;//结果为i=200 x=B

C++除了提供一般形式的赋值运算符“=”外,还允许所有的双目算术运算符和双目位运算符与赋值运算符结合组成一个复合赋值运算符。C++中的复合赋值运算符及其功能如表2-4所示。

复合赋值运算符所表示的表达式比一般形式的赋值运算符简练,生成的目标代码比较少,从这个角度讲,在C++程序中应该尽量多地使用复合赋值运算符;但是,由于复合赋值运算符的易读性低于一般形式的表达(如:a+=b和a=a+b),因此从易读性的角度讲,在C++程序中应该避免过多地使用复合赋值运算符。2.4.5 自增、自减运算符

C++提供了另外两个使用方便并可以改变变量值的单目运算符:++(自增)和——(自减)。它们的作用分别是给变量的值加1和减1。这两个运算符有前置和后置两种形式。例如:++i //前置++j—— //后置——

根据自增、自减运算符的前后置形式不同,具体的操作完全不同。以++为例进行说明,——操作类似。++i表示先对变量i的值加1,然后使用i的值参加运算;i——表示先使用i的值参加运算,然后对i的值加1。【例2-3】自增、自减运算符的前置、后置操作。1 int i=3,a, b;2 a=++i; //先对i加1,再进行赋值操作3 cout<<i<<a<<endl; //结果为i=4,a=44 b=i++; //先进行赋值操作,再对i加15 cout<<i<<b<<endl; //结果为i=5,b=4【程序解释】

◇例2-3中,第2行的操作:首先执行++i,相当于执行i=i+1操作,此时,i的值已经自增1变为4,然后将自增后的结果4赋值给变量a,结果i=4,a=4。

◇第4行的操作:首先执行b=i操作,也就是将i的值4先赋给变量b,然后对变量i执行自增操作i++,相当于i=i+1,结果i=5,b=4。

◇因为自增、自减运算内含了赋值运算,所以其运算对象只能赋值,不能作用于常量和表达式,表达式3++和++(x+y)等都是非法的。2.4.6 位运算符

C++保留了低级语言的二进制位运算符,位运算分为移位运算和按位逻辑运算。

1.左移(<<)

将左操作数向左移动其右操作数所指定的位数,移出的位补0。其一般格式为:

★a<<n(7)

式(7)中,a、n为整型量,其含义为:将a按二进制位向左移动n位,移出的最高n位舍弃,最低位补n个0。例如:

short int a=42,b=a<<1;

从例中可知,变量b左移1位的结果为01010110,即十进制的84。一般来说,将一个数左移n位,相当于将该数乘以2n。

2.右移(>>)

将左操作数向右移动其右操作数所指定的位数,移出的位补0。其一般格式为:

★a>>n(8)

式(8)中,a、n为整型量,其含义为:将a按二进制位向右移动n位,移出的最低n位舍弃,最高位补n个0。例如:

short int a=42,b=a>>1;

从例中可知,变量b右移1位的结果为00010101,即十进制的21。一般来说,将一个数左移n位,相当于将该数乘以2-n(即除以2n)。

3.按位取反(~)

将运算量的每个二进制位取反,即0变为1,1变为0。例如:

int a=42,b=~a;

4.按位与(&)

将两个运算量的对应二进制位逐一按位进行逻辑与运算。运算规则为:对应位都是1,结果为1,否则为0。例如:

int a=42,b=23,c=a&b;

运算结果:变量c的值为2。

5.按位或(|)

将两个运算量的对应二进制位逐一按位进行逻辑或运算。运算规则为:对应位都是0,结果为0,否则为1。例如:

int a=42,b=23,c=a|b;

运算结果:变量c的值为63。

6.按位异或(^)

将两个运算量的对应二进制位逐一按位进行逻辑异或运算。运算规则为:若对应位不同,结果为1,否则为0。例如:

int a=42,b=23,c=a^b;

运算结果:变量c的值为61。2.4.7 其他运算符

1.逗号运算符

逗号运算符是所有运算符中级别最低的,其求解过程为从左向右依次计算各个表达式,并将最后一个表达式的值作为整个逗号表达式的值。逗号运算符的一般格式为:

★表达式1,表达式2,……,表达式n;

例如:

a=a+b, b=b+c, c=c+a;

设a=1,b=2,c=3,该逗号表达式依次计算a=3,b=5,c=6,整个逗号表达式的值为6。

若取

i=(a=a+b, b=b+c, c=c+a);//i的值为6

2.求字节数运算符(sizeof)

sizeof运算符是单目运算符,用来计算某种类型或变量所占字节数。其一般格式为:

★sizeof(类型说明符|变量名|常量)

例如:1 double x=100.3;

2 sizeof(double); //结果为8

3 sizeof(x); //结果为8

上例中的第2行和第3行的结果8均表示double型在机器内存中所占的字节数为8。2.4.8 运算符的优先级(1)运算符的优先级按单目、双目、三目和赋值的顺序依次降低。(2)运算符按算术、移位、关系、按位和逻辑运算的顺序依次降低。

表2-5表示了本章介绍的各运算符的优先级,表中数字越小,优先级别越高。2.5 类型转换

C++遇到两种不同类型的数值运算时,会将两个数进行适当的类型转换后再运算。表达式的类型转换分为两种方式:自动类型转换和强制类型转换。自动类型转换出现在算术表达式的运算中,而大多数的类型转换使用强制类型转换。2.5.1 自动类型转换

自动类型转换又称为隐式类型转换。双目运算中的算术运算符、关系运算符、逻辑运算符和位运算符组成的表达式要求两个操作数的类型一致,如类型不一致则转换为高一级的类型。(1)实型数据赋给整型变量时,将整数部分赋给整型变量而舍弃小数部分,不进行四舍五入。如:

int i=2. 6;

则i的值为2而不是3。(2)整型数据赋给实型变量时,数值不变,有效数字位数增加。如:

double i=46;

则i的值为46.0而不是46。(3)所有浮点型的运算都是以双精度类型进行的,如果某表达式仅含有float型单精度运算,也必须先转换成double型后,再运算。(4)bool型、char型和short型参与运算时,应该先转换成int型。如:

char c=-3;

int i=2;

i=c;

则i的值为-3。(5)位运算的操作数必须是整数,当双目位运算的操作数是不同类型的整数时,也首先自动类型转换。(6)如果在一个表达式中出现不同类型的数据进行混合运算时,则C++首先使用特定的转换规则将两个不同类型的操作数转换成同一类型的操作数再进行运算。不同类型的转换规则如下所示:【例2-4】不同类型数据的混合运算。

char c='b';int i=6;float f=4. 56;double db=8. 86;则c*i+f*3.0-db=592.82

其计算过程为:(1)将c转换为int型,计算c*i=98 *6=588。(2)将f转换为double型,计算f*3.0=4.56 *3.0=13.68。(3)将(1)的计算结果转换为double型,计算588.0+13.68=601.68。(4)计算601.68-db=601.68-8.86=592.82。

当参与运算的两个操作数中至少有一个是float型,并且另一个不是double型,则运算结果为float型。2.5.2 强制类型转换

强制类型转换又称为显式类型转换,其作用是将某种类型仅在当前运算中强制转换成指定的数据类型,运算结束后,原类型保持不变。强制类型转换格式为在一个数值或表达式前加上带括号的类型名。格式如下:

★(类型说明符)变量\数值

例如:

int i;float j=3. 64;i=(int)j; //将j转换为整数类型(或理解为取j的整数部分)

结果i=3,j仍为float类型,其值为3.64。这种格式在C语言和C++语言中都被认为是合法的。

C++还允许在类型名后跟带括号的数值或表达式的格式,形如:

★类型说明符(变量\数值)

例如:int i;float j=3. 64;i=int(j); //将j转换为整数类型(或理解为取j的整数部分)

结果i=3,j仍为float类型,其值为3.64。但这种格式在C语言中被认为是不合法的。标准C++还提供了新式的强制类型转换运算,格式如下:

★static_cast<类型说明符>(表达式)

用于一般表达式的类型转换。

★reinterpret_cast<类型说明符>(表达式)

用于非标准的指针数据类型转换,如将void*转换成int*。

★const_cast<类型说明符>(表达式)

将const表达式转换成非常量类型,常用于将限制const成员函数的const定义删除。

★dynamic_cast<类型说明符>(表达式)

用于进行对象指针的类型转换。2.6 实例应用与剖析【例2-5】逗号表达式的使用。1 /**************************************************/2 程序文件名:Ex2_5.cpp3 逗号表达式的使用4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int x, a;10 x=(a=3 *5,a*4),a+5;11 cout<<"x="<<x<<"a="<<a<<endl;12 }

程序运行结果如图2-5所示。图2-5 例2-5运行结果【程序解释】

◇第9行定义了两个整型变量x和a。

◇第10行中x=(a=3 *5,a*4),a+5包含两层逗号表达式,第一层表达式是a=3 *5,a*4,第二层表达式是x=(a=3 *5,a*4),a+5。

◇程序首先执行逗号表达式(a=3 *5,a*4),逗号表达式的结果为60,a的值为15。

◇接下来进行第二层逗号表达式的计算,由于赋值运算符的优先级高于逗号运算符,因此x=(a=3 *5,a*4)是第二层表达式的第一部分,a+5是表达式的第二部分。

◇计算x=(a=3 *5,a*4)后,x的值为60,而整个第二层表达式的值为a+5,即15+5=20,a的值为15。思考:如果上例的第10行修改为x=((a=3 *5,a*4),a+5),那么x的值是多少?【例2-6】关系运算符与逻辑运算符的使用。1 /**************************************************/2 程序文件名:Ex2_6.cpp3 关系运算符与逻辑运算符的使用4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int i=1,j=2,k=3,x=345;10 cout<<((k=i>j)&&++x)<<endl;11 }

程序运行结果如图2-6所示。图2-6 例2-6运行结果【程序解释】

◇第9行定义了3个整型变量。

◇第10行中使用了逻辑与运算符连接了k=i>j和++x。

◇因为关系运算符的优先级高于赋值运算符,计算i>j的值为0,所以k=0;根据&&的特点,编译器不再执行++x操作,整个表达式的值为0,x的值仍为345。思考:(1)如果上例的第10行中i>j改为i<j,那么运算结果是多少?x的值是否仍是345?(2)如果上例的第10行中&&改为||,运算结果和x的值又是多少?2.7 小结(1)C++的基本数据类型包括整型、实型、布尔型和字符型,表示为int、float\double、bool、char,各数据类型的表示范围不同。(2)常量包括文字常量、符号常量和常变量。(3)根据类型的不同,变量内存单元的大小也不相同。(4)C++包含算术运算符、关系运算符、逻辑运算符和位运算符等多种运算符,根据优先级高低,运算符的顺序为:算术运算符、移位运算符、关系运算符、按位运算符和逻辑运算符;单目运算符优先于双目运算符,双目运算符优先于三目运算符。(5)不同类型的数据可以出现在同一表达式中,不同类型的数据可以进行自动类型转换或强制类型转换。2.8 习题2

1.选择题(1)下列选项中,均为合法的标识符的是()。

A. design a&b 2he

B. cc@126 c++a_b

C.π常量a@b

D. aLine_35a4 at3(2)上述选项中,均为不合法的标识符的是()。(3)设i为int型,f为float型,则36+i+'f'的数据类型为()。

A. int

B. float

C. double

D. char(4)设变量int i=3;float f=4.6;执行i=f;则i的值为()。

A. 3

B. 4

C. 4.0

D. 5.0(5)下列运算要求操作数必须整型的是()。

A./

B.++

C.%

D.!=(6)6种基本数据类型的长度排列正确的是()。

A. bool=char<int?long=float<double

B. char<bool=int?long=float<double

C. bool<char<int<long<float<double

D. bool<char<int<long=float<double(7)下列运算符中优先级最高的是()。

A.<

B.+

C.&&

D.!=(8)设变量a=5,b=6;连续执行运算a=a+b;b=b-a;a=a-b;后,a和b的值分别为()。

A. 11,5

B. 11,-5

C. 16,-5

D. 16,5

2.已知int i=6,j=5;下面表达式运算结束后,写出i、j以及表达式的值。(1)i=3 *5,i*4(2)j=(i=3,4 *3)(3)i=i>j&&j, j+1(4)i=(i>j||++j, j+1)(5)i=i>j&&++j, j+3(6)i=(i<j&&++j, j+3)第3章 语句与控制结构

引言

C++程序由一系列的语句组成,语句按照一定的控制结构组织起来,逐行顺序执行。程序根据不同的要求,选择相应的控制结构,或者是经过不同选择后的计算或处理,或者是相同语句的反复执行。C++提供了判断、循环和转移等控制语句来实现对程序的控制。

学习目标(1)掌握if、switch分支语句的使用。(2)掌握for、while、do……while循环语句的使用和不同环境下的相互替换,并能根据不同要求灵活选择不同的循环语句,能够熟练使用循环的嵌套。(3)掌握break、continue语句的联系和区别,根据不同场合正确地使用。(4)了解goto语句的转移用法。3.1 语句格式

C++的语句类似于自然语言中的句子,它由不同类型的数据组成。数据类型是程序中最基本的元素,而语句则是程序中可以独立执行的最小单元。C++的语句以分号作为结束标记,相当于自然语言中的句号。语句通常由表达式末尾加上分号构成,C++认为不执行任何操作的空语句是合法的。

例如:; //不执行任何操作的空语句a+b; //一般表达式语句a=a+b;

空语句一般在循环结构中用来延迟一段时间,空语句的分号是必不可少的。分号的存在是语句成立的必要条件。

在C++中,声明变量的语句称为声明语句,声明语句可以出现在程序中任何可以出现语句的地方,这提高了变量声明的灵活性。但是在C语言中,变量的声明不是语句,只能在模块的首部集中声明。

某些情况下,可以用一对花括号{}将多条语句括起来组成一个块语句。

例如:{int a=6,b=3;a=(a+b)/2;cout<<"a="<<a<<endl;}

块语句的右括号后没有分号,块语句一般存在于选择和循环结构中。3.2 控制结构

C++是解决客观世界实际问题的工具,它对每个具体问题的解决是通过不同算法实现的。例如,计算1+2+……+100的累加和。int i=1,sum=0;for(i=1;i<=100;i++){sum=sum+i;}

上例是使用循环结构进行100次累加的算法。算法是解决问题的步骤序列,合法的算法具有如下特征:(1)有穷性:一个算法必须保证执行有限步之后结束。(2)确定性:算法的每一步必须有确切的定义,不能出现二义性。(3)输入:一个算法有0个或多个输入。(4)输出:一个算法有1个或多个输出,没有输出的算法是毫无意义的。(5)可行性:算法原则上能够精确地运行。

可以使用多种方法对算法进行描述,如自然语言、流程图、判定表、判定树、伪代码等。其中流程图是使用最广泛的表示方法,它具有简洁、直观、准确的特点,常用的流程图符号如图3-1所示。图3-1 流程图的常用符号

C++的控制结构包括顺序结构、选择结构和循环结构。(1)顺序结构是最简单的结构,其特点是各个块按照先后次序依次执行。(2)选择结构是先对条件进行判断,然后选择执行相应语句。C++的选择结构主要是if语句和switch……case语句。(3)循环结构也是先对条件进行判断,然后执行语句,并且在执行完语句后继续进行下一次的条件判断,直到条件为假时才不执行结构中的语句。C++的循环结构主要包括for语句、while语句和do……while语句。3.3 if语句

if语句属于判断选择结构的语句,C++的判断选择结构也称为条件分支结构,一般包括if语句和switch语句。3.3.1 基本if语句

基本if语句是最简单的条件语句,其功能是根据指定的条件判断是否执行相应的语句。语法格式如下:

★if(条件表达式)

语句;

其中,条件表达式一般是一个关系或者逻辑表达式,其值或者为true,或者为false,并且必须写在一对圆括号中;语句可以是单语句也可以是多条语句组成的语句块(块语句),多条语句组成的语句块必须包含在一对花括号中,单条语句可以包含在花括号中也可以没有花括号。但是为了程序编写的整洁、规范,建议即使是单语句也将其包含在一对花括号中。

If语句执行过程为:首先计算条件表达式的值,如果值为true,则执行语句;否则跳过语句,执行if语句的后继语句,执行过程如图3-2所示。条件表达式也可以是常量,0表示false,非0表示true。图3-2 基本if语句流程图【例3-1】从键盘输入学生成绩,如果大于或等于60分,输出“通过”。1 /**************************************************2 程序文件名:Ex3_1.cpp3 基本if语句的使用4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int n;10 cout<<"Please enter the score:"<<endl;11 cin>>n;12 if(n>=60) //条件表达式为n>=6013 {14 cout<<"通过"<<endl;15 }16 }

程序运行结果如图3-3所示。图3-3 例3-1运行结果

例3-1中的整型变量n用来保存输入的成绩,12行语句if(n>=60)的判断条件是表达式n>=60,如果成立,则从13行开始执行语句,输出“通过”;否则从12行直接跳转到16行。3.3.2 if……else语句

if……else语句的语法格式如下:

★if(条件表达式)

语句1;

else

语句2;

其执行过程为:首先计算条件表达式的值,如果值为true,则执行语句1;否则执行语句2。执行过程如图3-4所示。

else前面的部分叫if分支子句,else及其后的部分叫else分支子句;虽然if分支的语句1和else分支的语句2在形式上是独立的两条语句,但是在语法上,从“if”开始到“语句2;”结束的整体是一条不可分割if语句。图3-4 if……eIse语句流程图【例3-2】从键盘输入学生成绩,如果大于或等于60分,输出“通过”,否则输出“不通过”。1 /**************************************************2 程序文件名:Ex3_2.cpp3 if……else语句的使用4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int n;10 cout<<"Please enter the score:"<<endl;11 cin>>n;12 if(n>=60)13 { //第13~15行是语句114 cout<<"通过"<<endl;15 }16 else17 { //第17~19行是语句218 cout<<"不通过"<<endl;19 }20 }

程序运行结果如图3-5所示。图3-5 例3-2运行结果

在例3-2中,第13~15行语句块充当了语句1;第17~19行的语句块充当了语句2。如果条件表达式n>=60成立,则从第13行开始执行语句1,输出“通过”;否则从第17行开始执行语句2,输出“不通过”。if……else语句中的关键字“else”是必不可少的。3.3.3 嵌套if语句

如果遇到的问题有多重选择,比如在十字路口,仅使用if语句或if……else语句无法解决。为了解决多重选择问题,C++提供了嵌套的if语句。在if语句中,如果分支子句也是if语句,就构成了嵌套的if语句。

在形式上,嵌套的if语句有两种形式:一种是嵌套在else分支中,另一种是嵌套在if分支中。

1.else分支的嵌套

else分支嵌套的if语句语法格式为:

★if(条件表达式1)

语句1;

else if(条件表达式2)

语句2;

……

else if(条件表达式n)

语句n;

else

语句n+1;

其语义可用流程图如图3-6所示。图3-6 eIse分支的嵌套【例3-3】用嵌套if语句将百分制成绩按等级输出。1 /**************************************************2 程序文件名:Ex3_3.cpp3 嵌套if语句的使用4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int n; //变量n用来存储从键盘输入的成绩10 char grade;11 cout<<"Please enter the score:"<<endl;12 cin>>n;13 if(n<60)14 grade='E';15 else if(n<70)16 grade='D';17 else if(n<80)18 grade='C';19 else if(n<90)20 grade='B';21 else if(n<=100)22 grade='A';23 else24 grade='F';25 cout<<"The grade is"<<grade<<endl;26 }

在例3-3中,首先在第13行判断关系表达式n<60,如果结果为真,则执行第14行,把字符‘E’赋给grade,然后跳过其他语句,直接执行第25行的语句。这种嵌套的if语句,只允许选择满足条件的一个分支,其他分支不再判断执行。如果第13行结果为假,也就是n≥60,则跳过第14行而执行第15行的判断。第24行表示如果输入的成绩大于100,grade的值为‘F’。

2.if分支的嵌套

if分支嵌套的if语句语法格式为:

★if(条件表达式1)

if(条件表达式2)

语句1;

else

语句2;

else

语句3;

其语义可用流程图如图3-7所示。

使用if分支的嵌套形式修改例3-3中相应的语句为:

if(n>=60)

if(n>=70)

if(n>=80)

if(n>=90)

grade='A';

else

grade='B';

else图3-7 if分支的嵌套

grade='C';

else

grade='D';

else

grade='E';

在使用if分支嵌套时,要注意else与if的匹配原则:else与上方离它最近的未匹配过的if相匹配。3.3.4 条件运算符

当if……else语句是简单语句时,可用条件运算符“?:”来进行简化,其语法格式为:

★(条件表达式)?语句1:语句2

例如,求两个数a和b中较大的值,使用if语句:if(a>b)max=a; //语句1elsemax=b; //语句2

将if语句替换成等价的条件运算符为:

max=(a>b)?a:b;3.4 switch语句

嵌套的if语句能够实现多分支选择,C++还提供了另外一种实现多分支选择的结构——switch语句。switch语句也称为开关语句,它根据给定的条件,从多个分支中选择一条语句作为执行的入口。switch语句虽然与嵌套的if语句功能类似,但是switch语句每次都计算同一表达式的值,而嵌套的if语句要分别计算各个if分支语句表达式的值,所以在许多处理多分支选择问题时,swtich语句更加简便、直观。其语法格式如下:

★switch(表达式)

{case常量表达式1:[语句块1;][break;]case常量表达式2:[语句块2;][break;]……case常量表达式n:[语句块n;][break;][default:语句块n+1;]}

◇switch后面圆括号中的“表达式”只能是整型、字符型、枚举型或布尔型等离散类型,而不能是实型(float、double型)等连续类型。

◇“case”起到标号的作用,其后“常量表达式”的类型必须与“表达式”的类型匹配,并且所有case后常量表达式的值不能重复。

◇当“表达式”的值与某一个case后“常量表达式”的值相等时,就执行这个case后的语句;如果所有case后“常量表达式”的值都不与“表达式”的值匹配时,就执行“default”后的语句。

◇符号“[]”表示其中的内容是可选的;语句块、break和default都是可选的,语句块由一条语句或者一个复合语句组成。

switch语句的执行过程为:(1)计算switch后表达式的值。(2)将表达式的值依次与case后常量表达式的值相比较,如果表达式的值与某一常量表达式的值相等,则执行该case后的语句块,直到遇到break或switch语句的右花括号。(3)如果表达式的值与case后所有常量表达式的值都不相等,则执行default后的语句;如果switch语句不包含default,则不执行任何操作。【例3-4】用switch语句将百分制成绩按等级输出。1 /**************************************************2 程序文件名:Ex3_4.cpp3 switch语句的使用4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int n;10 cout<<"Please enter the score:"<<endl;11 cin>>n;12 switch(n/10)13 {14 case 10:15 cout<<"The grade is A"<<endl;16 break;17 case 9:18 cout<<"The grade is A"<<endl;19 break;20 case 8:21 cout<<"The grade is B"<<endl;22 break;23 case 7:24 cout<<"The grade is C"<<endl;25 break;26 case 6:27 cout<<"The grade is C"<<endl;28 break;29 default:30 cout<<"The grade is D"<<endl;31 }32 }

程序运行结果如图3-8所示。图3-8 例3-4运行结果

上例中,当n/10的值为10或9时,case后的语句相同,输出等级都是"A",同样,当n/10的值为7或6时,case后的语句也相同,输出等级都是"C"。C++规定,当多个case共用相同语句的时候,可以对相同语句进行简化处理,如例3-4中的第14~19行代码可替换为:

case 10:

case 9:

cout<<"The grade is A"<<endl;

同理,第23~28行代码也可替换为:

case 7:

case 6:

cout<<"The grade is C"<<endl;

但是下面写法是错误的:

case 10,9:

cout<<"The grade is A"<<endl;

如果switch语句中case标号后省略了break,则程序会从第1个匹配的case开始不加判断地执行switch语句中剩下的所有语句,直到遇到第1个break为止。例如:

1 switch(a)2 {3 case'A':4 cout<<"It is A"<<endl;5 case'B':6 cout<<"It is B"<<endl;7 case'C':8 cout<<"It is C"<<endl;9 break;10 default:11 cout<<"It is D"<<endl;

当字符变量a的值为'B'时,程序的执行结果为:

It is B

It is C

如果去掉第9行的break;则程序的结果变为:

It is B

It is C

It is D3.5 for循环语句

在C++程序中,常常会出现在满足给定条件下,需要重复执行相同操作的情况。这种重复执行相同的操作就是循环,C++提供了3种实现循环的语句:for语句、while语句和do……while语句。在循环语句中,被重复执行的操作叫做循环体,循环体可以由一条语句组成,也可以由一个语句块或空语句组成。3.5.1 for语句

for语句的语法格式如下:

★for(表达式1;表达式2;表达式3)

循环体

其中,for是关键字,表达式1用来给循环变量赋初值,表达式1在循环变量中仅执行一次;表达式2是循环继续进行的条件;表达式3是对循环变量的值进行修改。

for语句的执行过程为:(1)计算表达式1的值。(2)计算表达式2的值,如果值为false或0,则结束循环,转到(6)。(3)如果表达式2的值为true或非0,则执行循环体。(4)计算表达式3的值。(5)转到(2)。(6)执行for语句的后续语句。

执行过程如图3-9所示。

for语句不仅可以用于循环次数确定的情况,还可以用于循环次数虽不确定但给出了循环继续条件的情况。图3-9 for语句流程图【例3-5】用for语句计算1+2+3+……+100。1 /**************************************************2 程序文件名:Ex3_5.cpp3 for语句计算1+2+3+……+1004 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int i, sum=0; //将累加变量sum初始化为010 for(i=1;i<=100;i++) //i为循环变量,循环次数为10011 {12 sum+=i; //实现累加13 }14 cout<<"sum="<<sum<<endl;15 }

程序运行结果如图3-10所示。图3-10 例3-5运行结果

根据程序的不同要求,可以对for语句中的表达式进行相应缺省。(1)表达式1可以缺省。由于表达式1的作用是给循环变量赋初值,所以若缺省表达式1,则应在for语句之前有给循环变量赋初值的语句。虽然缺省表达式1,但是其后的分号不能缺省。

例如:

int a=0;

for(;a<100;a++) //缺省表达式1,但分号必须保留

sum+=a;(2)表达式2可以缺省。表达式2的作用是判断循环继续进行的条件,若缺省表达式2,则循环会无条件永远进行下去。虽然缺省表达式2,但是其后的分号同样不能缺省。

例如:

for(a=0;a++) //缺省表达式2,但分号必须保留

sum+=a;

等价于:

for(a=0;1;a++) //缺省表达式2相当于循环条件永远为真

sum+=a;(3)表达式3可以缺省。表达式3的作用是对循环变量进行修改,因此,若缺省表达式3,则应该在每次循环结束后有对循环变量进行修改的语句,否则,循环一旦开始运行,则永远无法正常结束。

例如:

for(a=0;a<100;) //缺省表达式3,循环变量的值永远为0,程

sum+=a; //序将永远进行下去

若缺省表达式3并让程序能够正常结束,则需要添加如下修改循环变量的语句。for(a=0;a<100;)

{

sum+=a;

a++; //使循环变量a自增1

}

for语句不但可以缺省3个表达式中的一个,而且可以同时缺省3个表达式中的任意两个,C++认为最极端的同时缺省3个表达式的写法也是合法的。不管表达式缺省与否,for语句圆括号中的两个分号缺一不可。3.5.2 for语句的循环嵌套

在一个循环中又包含其他循环的结构叫做循环嵌套,for语句可以实现循环嵌套。【例3-6】用for语句实现在屏幕上输出以下图案。

*

***

*****

*******1 /**************************************************2 程序文件名:Ex3_6.cpp3 for语句实现循环嵌套4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int i, j,k;10 for(i=0;i<=3;i++) //外重循环,循环4次,显示4行11 {12 for(j=0;j<=2-i;j++) //内重循环13 {14 cout<<"";15 }16 for(k=0;k<=2 *i;k++) //内重循环17 {18 cout<<"*";19 }20 cout<<"\n"; //外重循环中的语句21 }22 }【程序解释】

◇在例3-6中,共有两重循环嵌套,第10行的for语句对应的是外重循环,主要控制对应图案显示的行数。

◇第12行和第16行的for语句对应的都是内重循环,分别显示每一行的空格符和“*”。

◇第20行是外重循环的语句,用来实现每行输出结束后换行,其中"\n"的作用和endl的作用完全相同。3.6 whiIe循环语句3.6.1 while语句

while循环语句也称为当型循环,当满足判断条件,则执行循环;语句的语法格式如下:

★while(条件表达式)

循环体

其中,while是关键字,条件表达式是C++中的一个合法表达式,表示是否执行循环体的判断条件,循环体可以是一条语句,也可以是复合语句。如果循环体是一条语句,则其两边有无花括号皆可;如果循环体是复合语句,则复合语句必须被包含在一组花括号中。

while语句的执行过程为:(1)计算条件表达式的值,如果值为false或0,则结束循环,转到(4)。(2)如果条件表达式的值为true或非0,则执行循环体。(3)转到(1)。(4)执行while语句的后续语句。

执行过程如图3-11所示。图3-11 whiIe语句流程图【例3-7】用while语句计算1+2+3+……+100。1 /**************************************************2 程序文件名:Ex3_7.cpp3 while语句计算1+2+3+……+1004 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int i=1,sum=0;10 while(i<=100) //判断i<100是否成立11 {12 sum+=i; //实现累加13 i++;14 }15 cout<<"sum="<<sum<<endl;16 }

程序运行结果如图3-12所示。图3-12 例3-7运行结果

◇对比例3-5和例3-7可以看出,while前“i=1”的作用相当于for语句的表达式1,用来给循环变量赋初值。

◇while语句中条件表达式“i<=100”的作用相当于for语句的表达式2。

◇while语句中“i++”的作用相当于for语句的表达式3,用来修改循环变量。

◇while语句和for语句可以互相替换使用。3.6.2 do……while语句

do……while循环语句也称为直到型循环,语句的语法格式如下:

◇do

循环体

while(条件表达式);

其中,do和while都是关键字,循环体可以是一条语句,也可以是复合语句;条件表达式是C++中的一个合法表达式,给出是否继续执行循环体的判断条件。

do……while语句的执行过程为:(1)执行循环体。(2)计算条件表达式的值,如果值为false或0,则结束循环,转到(4)。(3)如果条件表达式的值为true或非0,则转到(1)。(4)执行do……while语句的后续语句。

执行过程如图3-13所示。图3-13 do……whiIe语句流程图【例3-8】用do……while语句计算1+2+3+……+100。1 /**************************************************2 程序文件名:Ex3_8.cpp3 do……while语句计算1+2+3+……+1004 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int i=1,sum=0;10 do //不判断条件,先执行一次循环体11 {12 sum+=i; //实现累加13 i++;14 }while(i<=100); //判断i<=100是否成立,分号不能漏掉15 cout<<"sum="<<sum<<endl;16 }

程序运行结果如图3-14所示。图3-14 例3-8运行结果

◇对比do……while语句和for语句、while语句可以看出,不管条件表达式是否成立,do……while语句中循环体最少执行次数为1,而for语句和while语句的最少执行次数为0。

◇do……while语句条件表达式后的分号一定不能漏掉。3.7 转移语句

C++语言提供了一些用以改变程序中语句执行顺序的转移语句,转移语句能够使程序从某一条语句有目的地转移到另条一语句执行。常用的转移语句有break语句、continue语句和goto语句。其中,break语句和continue语句是半结构化的控制语句,goto语句是非结构化的控制语句。3.7.1 break语句

break语句用在switch语句和所有循环语句中。

break语句在switch语句中,用来跳出switch语句,继续执行switch后的语句;在循环语句中,用来跳出包含它的最内层循环体。例如,下面的代码在执行了break语句后,继续执行“sum=100”处的语句,而不是跳出所有循环:for(i=0;i<100;i++){for(j=0;j<i;j++){if(j>100){break;}sum+=j;}sum=100;}【例3-9】输出10~100之间的全部素数(素数n是指除1和n之外,不能被2~(n-1)之间的任何整数整除的数)。1 /**************************************************2 程序文件名:Ex3_9.cpp3 输出10~100间的全部素数4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int i=11,j, counter=0;10 for(;i<=100;i+=2) //外循环:为内循环提供一个整数i11 {12 for(j=2;j<=i-1;j++) //内循环:判断整数i是否是素数13 {14 if(i%j==0) //i不是素数:因为能被2~(i-1)之间的某个数整除15 break; //强行结束内循环,执行第17行语句16 }17 if(counter%10==0) //输出10个数后换行18 cout<<"\n"; //换行19 if(j>=i) //整数i是素数:输出后计数器加120 {21 cout<<""<<i;22 counter++;23 }24 }25 cout<<"\n";26 }

程序运行结果如图3-15所示。【程序解释】

◇在例3-9中,共有两重循环嵌套,第10行的for语句对应外重循环,第12行的for语句对应内重循环。

◇当执行到第15行的break时,跳出内重循环,继续执行第17行。图3-15 例3-9运行结果3.7.2 continue语句

continue语句仅用在循环语句中。

continue语句的功能是从包含continue的最内层循环体的当前位置,跳过循环体中本次循环尚未执行的语句,接着进行下一次是否执行循环的判定。也可以这样理解:在循环体中如果遇到continue语句,则立即结束循环体的本次循环,接着开始下一次循环的判定。

例如:1 for(int i=100;i<=200;i++)2 {3 if(i%3==0) //判断i能否被3整除4 continue;5 cout<<i<<endl;6 }

这段代码用来输出100~200间所有不能被3整除的整数。

其执行过程为:(1)判断表达式2(即i<=200)是否成立。(2)如果值为false或0,则结束循环,转到(8)。(3)如果值为true或非0,则转到(4)。(4)判断“i%3==0”是否成立,如果不成立,转到(5),否则转到(6)。(5)执行第5行,转到(7)。(6)执行第4行“continue”,转到(7)。(7)计算表达式3(即i++)的值,转到(1)。(8)执行for的后续语句。

continue语句与break语句的区别是:遇到continue语句,立即结束本次循环,但不终止包含它的整个循环;遇到break语句则立即结束循环,并且终止包含它的整个循环。3.7.3 goto语句

goto语句使程序跳转到语句标号所指的位置开始执行,其格式如下:

★goto语句标号;

语句标号属于标识符,其形式如下:

★语句标号:语句

这里的语句可以是任何语句,如while语句、for语句等。

在C++中,goto语句只能用在函数体,在同一函数体中,语句标号应该是唯一的。例如:

i=1,sum=0;wen:sum+=i;if(i<=100)goto wen;cout<<"sum is"<<sum<endl;

编译器遇到goto语句会无条件地转向语句标号后的语句。但是在循环语句中,只能利用goto语句从循环体内跳出,而不能从循环体外跳到循环体内。3.8 实例应用与剖析【例3-10】输出100~200之间所有不能被3整除也不能被7整除的整数。1 /**************************************************2 程序文件名:Ex3_10.cpp3 for语句求素数4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {cout<<"100-200不能被3和7整除的数为\n";10 for(int i=100;i<=200;i++)9 11{12 if(i%3!=0&&i%7!=0)13 cout<<i<<"";14 }15 cout<<"\n";16 }

程序运行结果如图3-16所示。图3-16 例3-10运行结果【程序解释】

◇第10行使用了for语句,表达式1“int i=100”在定义循环变量i的同时给其赋初值100。

◇for语句从第10行开始,到第14行结束,循环体循环201次。【例3-11】中国古代数学史上著名的百钱买百鸡问题:“今有鸡翁一,值钱五;鸡婆一,值钱三;鸡雏三,值钱一。凡百钱买鸡百只,问鸡翁、鸡婆、鸡雏各几何?”1 /**************************************************2 程序文件名:Ex3_11.cpp3 嵌套for语句计算百钱买百鸡问题4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int cock, hen, chick;10 cout<<"鸡翁鸡婆鸡雏"<<endl;11 for(cock=0;cock<=20;cock++)12 for(hen=0;hen<=33;hen++)13 {14 chick=100-cock-hen;15 if(cock*5+hen*3+chick/3==100&&chick%3==0)16 cout<<""<<cock<<""<<hen<<""<<chick<<endl;17 }18 }

程序运行结果如图3-17所示。图3-17 例3-11运行结果【程序解释】

◇这是双重for循环嵌套问题。

◇由cock+hen+chick=100和5 *cock+3 *hen+chick/3=100可知,百钱最多能买鸡翁20只或鸡婆33只,这与第11行和第12行的两个循环继续条件相对应。

◇第15行的判断条件为鸡翁、鸡婆和鸡雏的总和为100,并且鸡雏的数目要被3整除。【例3-12】编写程序,从键盘输入一个整数,逆向输出其各位数字,同时求出其位数以及各位数字之和。1 /**************************************************2 程序文件名:Ex3_12.cpp3 while语句求逆序问题4 **************************************************/5 #include<iostream>6 using namespace std;7 main()8 {9 int num, sum=0,k, i=0;10 cout<<"请输入一个整数:";11 cin>>num;12 cout<<"逆序为:";13 while(num>0)14 {15 k=num%10;16 cout<<k;17 sum+=k;18 i++;19 num/=10;20 }21 cout<<"\n各个位数数字之和为:"<<sum<<endl;22 cout<<"你输入的是:"<<i<<<"位数"<<endl;23 }

程序运行结果如图3-18所示。图3-18 例3-12运行结果【程序解释】

◇根据提示输入整数,如:“635214”,编译器会自动生成逆序“412536”以及各位数字之和“21”。

◇num存储被输入的整数,第15行“k=num%10”,表示每次循环时,k变量的值是num的个位数字。

◇每次循环结束后,num=num/10,使编译器能够依次获取每位数字;编译器将每次获取的数字输出,其输出的组合便是原数字的逆序。

◇变量sum的作用是将每次循环计算得到的k值相加,所得结果便是各位数字之和,循环的次数便是输入数字的位数。3.9 小结(1)语句是由在表达式后添加分号构成的;语句包括单语句和块语句,块语句是用{}括起来的若干语句的组合,其作用相当于一条单语句。(2)if语句是最简单的判断控制语句,包括if和if……else两种基本形式,简单的if……else语句可以和条件运算符互相替换使用,并且else的匹配原则是与前面最近的尚未被匹配过的if匹配。(3)switch……case语句是解决多重选择分支问题的语句,能够使程序的结构更加清晰;case语句可以有不同的简要表示形式,并且往往与break语句结合使用。(4)C++的循环语句包括for语句、while语句和do……while语句,for语句的使用最为广泛也最为灵活,在大多数情况下,for语句可以与while语句互相替换。一般当不知道循环次数时,考虑使用while语句;当不知道循环次数并且循环至少执行一次时,考虑使用do……while语句;当已知确切循环次数时,考虑使用for语句。for语句的3个表达式有各自不同的作用,在某些情况下可以缺省,但是表达式间的两个分号必须保留。(5)C++提供3种转移语句,break语句可用在多重;选择switch……case语句和循环语句中,在循环语句中起到结束包含它的循环体的作用;continue语句仅在循环语句中使用,起到结束本次循环继续进行下一次循环的作用;建议尽量少使用goto语句。3.10 习题3

1.选择题(1)下列正确的if语句是()。

A. if(a<b)a++

B. if(a==b)a++

C. if(x<y){x++y++;}

D. if(x!=y)cout<<x;else cout<<y;(2)下列程序的输出结果是()。

main()

{

int a(20),b(30),c(40);

if(a>b)

a=b, b=c, c=a;

cout<<”a=”<<a<<”,b=”<<b<<”,c=”<<c;

}

A. a=20,b=30,c=40

B. a=20,b=40,c=20

C. a=30,b=40,c=20

D. a=30,b=40,c=30(3)下列程序段的输出结果是()。

int i(2),j(3),k(9),m(8),n(7);

if(i<j)

if(k<m)

n=1;

else if(i<k)

if(j<m)

n=6;

else n=3;

else n=7;

else n=11;

cout<<n;

A. 3

B. 6

C. 7

D. 11(4)下列程序的输出结果是()。

main()

{

int i(0),j(0),k(1);

switch(k)

{

case 0:j++;

case 1:i++;

case 2:i++;j++;

}

cout<<”i=”<<i<<”j=”<<j;

}

A. i=2,j=2

B. i=1,j=1

C. i=1,j=0

D. i=2,j=1(5)j=(i>0?1:i<0?-1:0);的功能等同于下列哪个if语句()。

A. j=-1;

if(i)

if(i>0)j=1;

else if(i==0)j=0;

else j=-1;

B. if(i)

if(i>0)j=1;

else if(i<0)j=-1;

C. if(i>0)j=1;

else if(i<0)j=-1;

else j=0;

D. j=0;

if(i>=0)

if(i>0)j=1;

else j=-1;(6)下列程序段的结果是()。

int i(1),j(10);

do

{

j-=i;i++;

}while(j——<0);

cout<<i<<“,”<<j;

A. a=3,b=11

B. a=2,b=8

C. a=1,b=-1

D. a=4,b=9(7)下列的程序段循环了多少次()。

int k=10;

while(k=3)

k=k-1;

A. 0次

B. 3次

C. 7次

D.死循环(8)下列与for(式1;式2;式3;)功能相同的语句是()。

A.式1;while(式2){循环体;式3;}

B.式1;while(式2){式3;循环体;}

C.式1;do{循环体;式3;}while(式2)

D. do{式1;循环体;式3;}while(式2)(9)下列循环执行的次数是()。

for(int x=0,y=0;(y=1)&&(x<3);x++)

A. 1次

B. 2次

C. 3次

D. 4次(10)下列程序段的运行结果是()。

int i(2),j(2);

for(;i<=100;i++)

{

if(j>=10)break;

if(j%3==1)

{

j+=3;

continue;

}

}

cout<<i;

A. 101

B. 6

C. 5

D. 4

2.编程题(1)求sn=a+aa+aaa+……+aa……aa(n个a)之值,其中a是一个数字。(例如:3+33+333+3333,此时n=4,n由键盘输入)(2)从键盘输入一个整数,判断该数是否为回文数。所谓回文数,就是从左向右读和从右向左读一样的数,如7887、23432是回文数。(3)编写程序,分别正向、逆向输出26个大写英文字母。

3.读程序写结果(1) #include<iostream>

using namespace std;

int sum, k;

main

{

for(sum=0,k=1;k<10;k++)

if(k%2==0)

continue;

sum+=k;

cout<<"sum="<<sum<<endl;

}(2) #include<iostream>

using namespace std;

main()

{

int a(18),b(21),m(0);

switch(a%3)

{

case 0:m++;break;

case 1:m++;

switch(b%2)

{

default:m++;

case 0:m++;break;

}

}

cout<<m;

}第4章 函数

引言

随着程序代码量和复杂度的增加,需要将一些功能重复的程序段抽象出来形成独立的功能模块,继而将程序分成规模小并且容易管理的模块,这样的模块叫做函数。函数是C++程序的基础,要编写好的程序,就要合理地规划程序中的函数。学习本章后,应该能够领会函数调用的内部实现机制,区分函数声明与定义。

学习目标(1)熟练掌握函数的声明与定义。(2)掌握函数的调用、嵌套调用及形参和实参的传递过程。(3)掌握递归函数的使用,并区分递归函数与循环结构。(4)掌握函数重载的机制。(5)掌握带有默认形参值的函数及其与重载函数的区别。(6)灵活使用各种函数及调用关系。4.1 函数的定义与调用

在C++实际编程中,很多程序的代码动辄几万行甚至上百万行。对这样规模的程序代码进行修改和维护需要高效的策略,一种策略是将大的程序分解成若干相对独立、易于管理的程序单元,也就是模块化,这不仅可以方便程序员对代码进行修改和维护,还可以方便用户阅读。

C++由函数实现模块化的功能,函数是把相关语句组织在一起,并注明相应的名称。C++中的函数分为标准库函数和用户自定义函数。标准库函数是由系统提供的公共函数,可以在包含了相应头文件后被程序直接使用;用户自定义函数是由用户根据需要自己编写的函数。任何C++程序都是从main()函数开始的,main()函数也叫主函数,它是程序执行的入口,程序中其他的函数是子函数。主函数只能被系统自动调用,而不能被其他子函数或自身调用;子函数可以被主函数调用,也可以被其他子函数或自身调用。4.1.1 函数的定义

函数只有在定义后才能被使用。函数定义是指编写实现某种功能的程序块。函数定义的语法格式为:

函数首部是定义格式中的第一行,包括函数返回类型、函数名和形式参数列表(简称形参列表)。其中:

◇函数返回类型是函数返回值的类型,可以是任意数据类型,它是函数执行过程中通过return语句返回值的类型;如果函数没有返回值,则需要使用“void”作为函数返回类型。

◇函数名是函数的标识,应该符合C++标识符的命名规则,用户通过使用该函数名和实际参数可以调用该函数。

◇形参列表可以包含任意多个参数,也可以没有。当形参列表多于一个参数时,前后两项之间必须用逗号隔开。每个参数项由一种已经定义的数据类型和一个合法的变量标识符组成,该变量标识符称为函数的形式参数(简称形参),其前面的数据类型是该形参的类型。

◇如果函数没有形参,则形参列表位置为空白,但是其两边的圆括号不能省略。这种没有形参的函数称为无参函数,如int func();但是这种形式在C语言中表示“一个可带任意参数(任意数目、任意类型)的函数”。

◇花括号中的函数体由若干语句序列组成,用于完成具体的操作,函数体中没有任何语句的函数称为空函数。

◇每个函数完成独立的功能,在一个函数体中定义另外一个函数的情况称为函数的嵌套定义,C++不允许出现嵌套定义。

例如下面程序段中max()函数定义在function()函数中,是非法的嵌套定义:

int function1(float a, int b)

{

……

double max(double m, double n) //错误!嵌套定义

{

……

}

……

}

◇main()函数是程序的入口,称为主函数,C++规定在一个程序中有且仅有一个main()函数。【例4-1】定义一个求两个整数中最大值的函数。1 int max(int x, int y) //int max(int x, y)是错误的2 {3return x>y?x:y;4 }

上面程序段第1行是函数首部,函数返回类型是int,函数名是max;函数有两个形参,第1个是x,第2个是y,两个形参的类型都为int(注意:每个形参的类型都必须单独说明,即使类型相同也不能省略);第2行到第4行是函数体,return语句后的表达式x>y?x:y的类型必须与函数返回类型完全一致,也为int。

函数一经定义,就可以在程序中被多次使用,C++通过函数调用来实现函数的使用。4.1.2 函数的调用

在C++中,除了main()函数被系统自动调用外,其他的函数都被main()函数直接或间接调用。调用函数就是执行该函数的函数体,调用函数的语法格式如下:

◇函数的调用语句由三部分组成:函数名、实际参数列表(简称实参列表)和分号。

函数的实参用来给形参赋值,因此实参的个数、顺序以及数据类型应该与形参的完全一致。

◇函数被调用时,首先计算实参的值,然后跳转到该函数的定义部分,将实参的值赋给相应的形参(第1个实参的值赋给第1个形参,第2个实参的值赋给第2个形参,……)。

◇如果函数没有返回值,即返回值类型为void,则该函数没有返回值,调用函数一般可作为单独的语句使用;如果函数有返回值,调用函数一般以表达式的形式出现,其返回值直接参与运算。

函数调用的执行过程分3个步骤:①函数调用;②函数体的执行;③返回函数值,即将函数运算结果返回到函数调用语句。【例4-2】输入两个整数,求其中较大的数。1 /**************************************************2 程序文件名:Ex4_2.cpp3 函数调用应用举例4 **************************************************/5 #include<iostream>6 using namespace std;7 int max(int x, int y)8 {return x>y?x:y;10 }9 11 main()12 {13 int m, n;14 cout<<"请输入两个整数:";15 cin>>m>>n;16 cout<<"较大的数是:"<<max(m, n)<<endl;17 }

程序运行结果如图4-1所示。图4-1 例4-2运行结果

例4-2中,第7行到第10行是函数的定义,定义了名为max,返回值类型为int的函数。第16行的“max(m, n)”是函数的调用,其值可直接参与运算。程序的执行过程为:

①进入main()函数,按照提示输入两个整数,分别存储在变量m和n中。

②在第16行遇到了函数的调用“max(m, n)”,跳转到第7行,也就是函数的定义体,并且将实参m、n的值分别赋给形参x、y。

③将函数体运算后的结果(即两个数中较大者)通过return语句返回到函数的调用位置,此例中“max(m, n)”获得返回值98。

注意:

函数参数的传递是单向的,只能由实参传给形参,而不能由形参传给实参。4.1.3 函数的嵌套调用

C++中函数的定义是平行的,除了main()函数被系统自动调用外,其他函数都可以互相调用。假设存在3个函数:f1()、f2()和f3(),函数f1()既可以直接调用函数f3(),也可以先调用函数f2(),再由函数f2()调用函数f3(),达到由函数f1()间接调用函数f3()的目的,调用过程如图4-2所示。这种函数间的间接调用称为嵌套调用。图4-2 函数的嵌套调用kkkk【例4-3】计算sum=1+2+3+……+N。1 /**************************************************2 程序文件名:Ex4_3.cpp3 函数的嵌套调用4 **************************************************/5 #include<iostream>6 using namespace std;7#define K 4 //符号常量8#define N 59 long f1(int n, int k) //计算n的k次方10 {11 long power=n;12 int i;13 for(i=1;i<k;i++)14 {15 power*=n;16 }17 return power;18 }19 long f2(int n, int k) //计算1到n的k次方累加和20 {21 long sum=0;22 int i;23 for(i=1;i<=n;i++)24 {25 sum+=f1(i, k); //调用函数f126 }27 return sum;28 }29 main()30 {31 cout<<"Sum of"<<K<<"powers of integers from 1 to"<<N<<"=";32 cout<<f2(N, K)<<endl; //调用函数f2,N、K是实参33 }程序运行结果如图4-3所示。图4-3 例4-3运行结果【程序解释】

◇函数f1()和f2()的作用分别为计算n的k次方和计算1到n的k次方累加和。

◇例4-3中第32行,由主函数调用函数f2(),此时执行将跳转到函数f2()定义处,即第19行,并且将实参N和K传给函数定义的形参n和k。

◇第25行,函数f2()的for循环语句调用了函数f1(),此时执行跳转到函数f1()处,即第9行。

◇函数f1()的计算结果通过第17行的return语句返回到第25行的调用点;函数f2()的计算结果通过第27行的return语句返回到第32行的调用点。

◇main()函数通过函数f2()间接调用了函数f1()实现了函数的嵌套调用。4.1.4 递归调用

在有的C++程序中会出现一个函数直接或间接调用自身的情况,这种函数调用自身称为递归调用。在递归调用中,调用函数又是被调函数,执行递归函数将反复调用其自身,每调用一次就进入新的一层。为了防止递归调用无终止地进行,必须在函数内设定终止递归调用的条件,并返回一个已知值。

使用递归的方法可以定义许多学科的概念。例如计算数学中的阶乘:

上式中n为不小于0的整数,“n=0时,n!=1”就是终止递归调用的条件。求阶乘问题是一个经典的递归调用问题,从定义公式可以知道,阶乘采用了自己定义自己的方法。【例4-4】利用递归求n!。1 /**************************************************2 程序文件名:Ex4_4.cpp3 函数的递归调用4 **************************************************/5 #include<iostream>6 using namespace std;7 long power(int n) //递归调用函数定义8 {9 long f;10 if(n>0)11 f=power(n-1)*n;12 else13 f=1;14 return f;15 }16 main()17 {18 int n;19 long y;20 cout<<"Please input an integar:";21 cin>>n;22 y=power(n);23 cout<<n<<"!="<<y<<endl;24 }

程序运行结果如图4-4所示。图4-4 例4-4运行结果【程序解释】

◇第7行的函数power()是递归调用函数,因为在其函数体内,即第11行调用了函数自身,所不同的是参数有了变化,正是由于递归函数参数的变化才能在不同层次进行计算。

◇在函数内第13行设定终止递归调用的条件,其含义解释为“if(n<=0)f=1”。

◇当程序执行到n==0的层次时,给f赋指1,即power(0)=f=1;由power(1)=power(0)*1,便可计算出power(1)的值,依此类推,便可回归到power(n)=power(n-1)*n的层次,最终得到power(n)的值。

◇由例4-4可以知道,递归调用实际包含了“递推”和“回归”两个过程:当程序执行到递归调用函数时开始向新层次进行递推,一直递推到递归函数的终止条件时返回一个已知值,然后在已知值的基础上进行回归计算,最终得到函数值。4.2 函数的声明

在C++程序中,如果某函数在被调用前已经被定义过,编译器就可获得该函数的名称、类型和形参列表信息;如果某函数定义在其函数调用后,由于C++程序执行前总是先由上向下顺序进行编译,编译到该函数调用时,会认为该函数是未经定义的非法函数。因此,如果函数调用在函数定义之前,必须在函数调用语句前先对该函数原型进行声明,以通知编译器函数的名称、类型和形参列表信息,这样编译器才会认为该函数是合法的。

函数原型声明的语法格式如下:

◇函数原型声明和函数定义时的首部在形式上基本相同,不同的是函数原型的声明是一条语句,结尾必须有分号,而函数定义没有分号。

◇函数原型中的返回类型、函数名和形参列表必须与函数定义完全一致。

例如下面对例4-2进行部分修改,使用了函数原型的声明:int max(int x, int y); //函数原型声明main(){int m, n;cout<<"请输入两个整数:";cin>>m>>n;cout<<"较大的数是:"<<max(m, n)<<endl; //函数的调用}int max(int x, int y) //函数定义{return x>y?x:y;}

◇函数原型声明中的形参名可以与函数定义的形参名不一致,并且C++允许函数原型声明可以缺省形参名。

例如下面两个函数原型的声明:

int max(int x, int y);

int max(int, int);

都是合法的,并且意义完全相同,函数原型声明中的形参名仅起到使程序清晰、增强可读性的作用。4.3 内联函数

函数的使用不仅有利于代码重用,提高程序开发效率,还能够增强程序的可维护性。但是,频繁地进行函数调用也可能降低系统的效率,因为无论函数简单与否,其调用都包括参数传递、执行函数体和返回3个过程:程序遇到函数调用语句后,立即将执行转移到函数定义所在内存的某个地址,执行完函数体后再返回到函数的调用点继续执行后续语句。这要求程序在转移执行函数体之前,系统要保存现场并记忆调用语句的地址,当执行完函数体后再恢复现场,并按记忆的地址继续执行。因此,函数调用需要一定时间和空间方面的开销,尤其是某些规模不大、语句简单的函数体,如果被频繁地调用,其开销就要相对增加。

为了解决这一矛盾,C++提供了一种称为“内联函数”的机制。内联函数也称为内嵌函数。当在一个函数的定义或声明前加上关键字“inline”,则该函数被定义为内联函数。若一个函数被定义为内联函数,在程序编译阶段,编译器会把每次调用该函数的地方都直接替换为该函数体中的代码,这样节省了函数调用的保存现场、参数传递和返回等开销,从而提高程序的执行效率。

需要注意的是,关键字inline必须与函数体放在一起才能使函数成为内联函数,仅将inline放在函数声明前面不起任何作用。如下面程序段的函数isNumber不能成为内联函数:inline int isNumber(char); //inline仅与函数声明放在一起main(){……}int isNumber(char ch){return(ch>='0'&&ch<='9')?1:0;}

而下面风格的函数isNumber则是内联函数:

int isNumber(char);main(){……}inline int isNumber(char ch){return(ch>='0'&&ch<='9')?1:0;}

因此,inline是一种“用于实现的关键字”,而不是“用于声明的关键字”。虽然内联函数的声明、定义前都加inline关键字在语法上没有错误,但从高质量C++/C程序设计风格的角度讲,inline不应该出现在函数声明中,函数声明与定义不可混为一谈。【例4-5】判断输入项是否为数字。1 /**************************************************2 程序文件名:Ex4_5.cpp3 内联函数的使用4 **************************************************/5 #include<iostream>6 using namespace std;7 int isNumber(char);8 main()9 {10 char c;11 while((c=getc(stdin))!='\n')12 {13 if(isNumber(c))14 cout<<"you entered a digit"<<endl;15 else16 cout<<"you entered a non_digit"<<endl;17 }18 }19 inline int isNumber(char ch)20 {21 return(ch>='0'&&ch<='9')?1:0;22 }

程序运行结果如图4-5所示。图4-5 例4-5运行结果

例4-5中第19行将“isNumber”定义为内联函数。当程序执行到第13行遇到了“if(isNumber(c))”时,由于“isNumber”是内联函数,相当于把函数体“return(ch>='0'&&ch<='9')?1:0;”直接复制到if()的圆括号中,而无须进行调用。

虽然内联函数在某种程度提高了程序的执行效率,但是由于内联函数是以代码膨胀(复制)为代价,仅节省了函数调用的开销,如果执行函数体内代码的时间开销远大于函数调用的开销,那么内联函数提高的执行效率就会很少。因为内联函数的使用要复制代码,这也将增大程序的总代码量,消耗更多的内存空间,所以以下情况不宜使用内联函数:(1)函数体内的代码较长,使用内联将导致内存消耗代价较高。(2)函数体内出现循环、switch和goto语句,那么执行函数体内代码的时间要比函数调用的开销大。(3)函数体内出现数组的说明和递归函数。(4)内联函数只适合于包含1~5行简单语句的小函数。4.4 函数重载

C或PASCAL等很多编程语言规定程序中的函数名不能重复,需要为每个函数名设定唯一的标识符。例如,求整型、字符型和浮点型数据的和,不得不设定3个不同的函数名:addint()、addchar()和adddouble(),这不仅增加了编程人员的工作量,还增加了程序阅读的困难。

为解决这一矛盾,C++借鉴客观世界的方法,提供了“函数重载”的机制。函数重载是指程序中定义两个或两个以上函数名完全相同的函数,但是函数的参数列表(参数类型或参数个数)不完全相同。例如,在客观世界中人们可以说“打饭”、“打毛衣”或“打篮球”,虽然“打”字相同,但是由于所施动的宾语不同,这3个“打”便有了不同的含义。如果把“打(饭)、打(毛衣)、打(篮球)”近似看做3个函数原型的话,那么“打”就是函数名,“饭”、“毛衣”和“篮球”就是对应函数的参数列表的值,按照函数重载的概念,这三者之间就是互为重载的关系。所谓重载,就是函数名的重复加载(使用)。在函数调用时,编译器根据实参和形参的类型和个数进行匹配,调用合适的重载函数。例如:

int add(int, int);

long add(long, long, long);

double add(double, double);

都是合法的函数重载。【例4-6】定义4个名为add的重载函数,分别实现两个整数相加、两个浮点数相加、一个整数和一个浮点数相加(要求返回值为整型)以及一个浮点数和一个整数相加的功能。1 /**************************************************2 程序文件名:Ex4_6.cpp3 函数重载应用举例4 **************************************************/5 #include<iostream>6 using namespace std;7 int add(int x, int y);8 double add(double x, double y);9 int add(int x, double y);10 double add(double x, int y);11 main()12 {13 cout<<add(9,16)<<endl;14 cout<<add(9. 0,16.0)<<endl;15 cout<<add(9,16. 0)<<endl;16 cout<<add(9. 0,16)<<endl;17 }18 int add(int x, int y)19 {20 cout<<"两个整数相加";21 return x+y;22 }23 double add(double x, double y)24 {25 cout<<"两个浮点数相加";26 return x+y;27 }28 int add(int x, double y)29 {30 cout<<"整数和浮点数相加,返回值为整数";31 return int(x+y); //强制类型转换为int型32 }33 double add(double x, int y)34 {35 cout<<"浮点数和整数相加";36 return x+y;37 }

程序运行结果如图4-6所示。图4-6 例4-6运行结果【程序解释】

◇第7~10行分别声明了函数名都为add的4个互为重载函数原型。

◇当程序执行到第13行时,根据表达式add(9,16),调用函数名为add,包含两个参数,并且参数类型都为int型的函数定义体,即第18~22行代码段。

◇当程序执行到第14行时,根据表达式add(9.0,16.0),调用函数名为add,包含两个参数,并且参数类型都为double型的函数定义体,即第23~27行代码段。

◇当程序执行到第15行时,根据表达式add(9,16.0),调用函数名为add,包含两个参数,并且第一个参数类型为int型,第二个参数类型为double型的函数定义体,即第28~32行代码段。

◇当程序执行到第16行时,根据表达式add(9.0,16),调用函数名为add,包含两个参数,并且第一个参数类型为double型,第二个参数类型为int型的函数定义体,即第33~37行代码段。

使用重载函数应该注意以下方面:(1)重载函数的类型(即函数返回值的类型)可以相同,也可以不同。但如果仅仅是重载函数返回类型不同而函数名相同、形参列表也相同,则是不合法的,编译器会报“语法错误”。如:

int add(int a, int b);

double add(int a, int b);

编译器不认为是重载函数,只认为是对同一个函数原型的重复声明。(2)让重载函数执行不同的功能虽然在语法上是合法的,但这将导致程序的可读性降低。(3)确定对重载函数的哪个函数进行调用的过程称为绑定(binding),绑定的优先次序为精确匹配、对实参的类型向高类型转换后的匹配、实参类型向低类型转换后的匹配。例如,add(float(9),float(16));的实参向(double, double)转换,然后与add(double, double)函数体绑定。4.5 带默认形参值的函数

C++可以在函数声明或函数定义中为形参赋默认的参数值。在调用函数时,要为函数的形参给定相应的实参,但有的情况下,程序没有提供实参值,就需要使用默认的形参值。例如:int func1(int a=4,int b=5) //给形参a、b分别赋默认值4、5{return a+b;}main(){int x;x=func1(6,7); //给形参a、b传值分别为6、7,x=6+7=13x=func1(6); //给形参a传值为6,b使用默认值5,x=6+5=11x=func1(); //形参a、b分别使用默认值4、5,x=4+5=9}(1)当一个函数既有定义又有声明时,形参的默认值必须在声明中指定,而不能在定义中指定。只有当函数没有声明时,才可以在函数定义中指定形参的默认值。(2)形参默认值的定义必须遵守从右到左的顺序,如果某个形参没有默认值,则它左边的形参也不能有默认值。如:

void func1(int a, double b=4. 5,int c=3); //合法

void func1(int a=1,double b, int c=3); //不合法(3)在进行函数调用时,实参与形参的匹配按从左到右的顺序进行。当实参的数目少于形参时,如果对应位置形参没有设定默认值,就会产生编译错误;如果设定了默认值,编译器将为那些没有对应实参的形参取默认值。(4)形参默认值可以是确定的常量值,也可以是函数调用。如:

int func1(int a, int b=3,int c=add(4,5)); //add()为合法函数(5)函数原型声明中给形参赋默认值时,形参名可以省略。如:

int func1(int a, int=3,int c=add(4,5)); //第二个参数省略形参名(6)如果某函数的声明或定义中有n个形参设置了默认值,则对应的函数调用语句中最多可以省略n个实参。【例4-7】带默认形参值的函数举例。1 /**************************************************2 程序文件名:Ex4_7.cpp3 带默认形参值函数举例4 **************************************************/5 #include<iostream>6 using namespace std;7 int add(int a, int b=2,int c=3); //函数原型声明中设置默认值8 main()9 {10 int x(5),y(6),z(7); //相当于x=5,y=6,z=711 int sum;12 sum=add(x, y,z);13 cout<<"sum="<<sum<<endl;14 sum=add(x, y);15 cout<<"sum="<<sum<<endl;16 sum=add(x);17 cout<<"sum="<<sum<<endl;18 }19 int add(int a, int b, int c)20 {21 cout<<"a="<<a<<"b="<<b<<"c="<<c<<endl;22 return(a+b+c);23 }

程序运行结果如图4-7所示。图4-7 例4-7运行结果【程序解释】

◇第7行在函数原型声明中给后两个形参分别赋值“b=2,c=3”。

◇第10行给变量x、y、z分别赋值为5、6、7。

◇第12行调用add()函数时,3个实参都没有省略,因此x传给形参a, y传给形参b, z传给形参c,即a=5,b=6,z=7。

◇第14行调用add()函数时,省略了一个实参,因此x传给形参a, y传给形参b,形参c取默认值3,即a=5,b=6,c=3。

◇第16行调用add()函数时,省略了两个实参,因此x传给形参a,形参b取默认值2,形参c取默认值3,即a=5,b=2,c=3。4.6 实例应用与剖析2【例4-8】输出10~1000之内的所有回文数x,并且同时满足x和3x也是回文数。

回文数是一种左右对称的整数,无论从左到右看还是从右到左看都是同一个数字,即从中间的数字左右对称,例如737、59395、12321等,回文数的判断主要根据该数逆置构成的数是否与原数相等。1 /**************************************************2 程序文件名:Ex4_8.cpp3 判断回文数4 **************************************************/5 #include<iostream>6 #include<iomanip>7 using namespace std;8 bool palindrome(int n);9 main()10 {11 int x;12 cout<<"xx*xx*x*x"<<endl;13 for(x=10;x<=1000;x++)14 if(palindrome(x)&&palindrome(x*x)&&palindrome(x*x*x))15 cout<<x<<setw(13)<<x*x<<setw(16)<<x*x*x<<endl;16 }17 bool palindrome(int n)18 {19 int m=0,t=n;20 while(n!=0)21 {22 m=m*10+n%10;23 n/=10;24 }25 return m==t;26 }

程序运行结果如图4-8所示。图4-8 例4-8运行结果【程序解释】

◇第6行包含格式化输出头文件iomanip.h,因为程序第15行使用了函数setw(),setw(n)的作用是设域宽为n个字符。

◇第8行对函数原型palindrome()进行了声明,对应于第17行开始的函数定义体。

◇第19行通过变量t先保存n的值,第20~24行得到原数n的逆序m,循环结束时赋值为0。

◇第25行判断m是否与原来的n值(变量t)相等。

◇函数的返回值返回到调用点的过程为:当函数执行到return语句时,系统将在内存中创建一个临时变量来保存函数的返回值,然后结束函数的运行;调用函数从该临时变量中获取值后释放该临时变量。【例4-9】求圆的周长,使用内联函数表示周长函数。1 /**************************************************2 程序文件名:Ex4_9.cpp3 使用内联函数求圆的周长4 **************************************************/5 #include<iostream>6 using namespace std;7 double Circumference(double radius);8 main()9 {10 double r1(3. 0),r2(5);11 cout<<"半径为r1的圆周长为"<<Circumference(r1)<<endl;12 cout<<"半径为r1+r2的圆周长为"<<Circumference(r1+r2)<<endl;13 }14 inline double Circumference(double radius)15 {16 return 2 *3. 14 *radius;17 }

程序运行结果如图4-9所示。图4-9 例4-9运行结果【程序解释】

◇第14行定义了内联函数Circumference(),只有在函数定义体使用inline定义内联函数才是有效的,仅在声明中使用inline的函数,系统不认为是内联函数。

◇内联函数的函数体是被“复制”到函数调用点,而不是被调用。【例4-10】递归调用求解汉诺塔问题。

汉诺塔问题的描述是:有3根柱子分别为A、B、C,如图4-10所示。A柱上有从小到大依次排列的若干个盘子,要求借助于B柱把这些盘子全部搬到C柱上,每次只能搬动一个盘子,并且在搬动时,每个柱子上必须保持盘子从小到大的排放次序。图4-10 汉诺塔示意图

问题分析:将A柱上的盘子移到C柱可按如下步骤进行。(1)将A柱上的n-1个盘子借助于C柱按照要求的顺序移到B柱上。(2)将A柱剩下的第n个盘子也就是最大的盘子移到C柱上。(3)将B柱上的n-1个盘子借助于A柱按照要求的顺序移到C柱上。图4-11 汉诺塔问题解决步骤示意图1 /**************************************************2 程序文件名:Ex4_10.cpp3 使用递归调用求解汉诺塔问题4 **************************************************/5 #include<iostream>6 using namespace std;7 void move(int n, char a, char c) //n表示第n个盘子8 {9 cout<<"(第"<<n<<"个盘子从"<<a<<"移动到"<<c<<")"<<endl;10 }11 void hanoi(int n, char A, char B, char C)12 {13 if(n==1)14 move(1,A, C);15 else16 {17 hanoi(n-1,A, C,B);18 move(n, A,C);19 hanoi(n-1,B, A,C);20 }21 }22 main()23 {24 int number;25 cout<<"请输入汉诺塔盘子的数量";26 cin>>number;27 hanoi(number,'A','B','C');28 }

程序运行结果如图4-12所示。图4-12 例4-10运行结果【程序解释】

◇第11行的hanoi()是递归调用函数,递归终止的条件是“n==1”。

◇汉诺塔的递归调用算法可用数学归纳法证明其正确性。

◇对于既能使用循环又能使用递归解决的问题,使用循环的效率要高于递归,因为递归过程的本质是函数调用过程,需要在栈中为局部变量(第5章介绍)分配空间、保存返回地址等,这些需要大量的时间和空间开销。

◇在递归执行过程中,通过递推和回归实现循环的功能。【例4-11】编写重载函数max,分别求取2个整数、3个整数、2个双精度数、3个双精度数的最大值。1 /**************************************************2 程序文件名:Ex4_11.cpp3 使用重载函数求不同数据的最大值4 **************************************************/5 #include<iostream>6 using namespace std;7 int max(int a1,int a2) //求取2个整数的最大值8 {return a1>a2?a1:a2;10 }9 11 int max(int a1,int a2,int a3) //求取3个整数的最大值12 {13 return max(a1,a2)>a3?max(a1,a2):a3;14 }15 double max(double d1,double d2) //求取2个双精度数的最大值16 {17 return d1>d2?d1:d2;18 }19 double max(double d1,double d2,double d3) //求取3个双精度数的最大值20 {21 return max(d1,d2)>d3?max(d1,d2):d3;22 }23 main()24 {25 int a1=1,a2=2,a3=3;26 cout<<max(a1,a2)<<'\n';27 cout<<max(a1,a2,a3)<<'\n';28 double d1=2. 5,d2=3.5,d3=4.5;29 cout<<max(d1,d2)<<'\n';30 cout<<max(d1,d2,d3)<<'\n';31 }【例4-12】编写递归函数,求从1开始的n个奇数和,如fun(3)=1+3+5。1 /**************************************************2 程序文件名:Ex4_12.cpp3 递归函数应用4 **************************************************/5 #include<iostream>6 using namespace std;7 int fun(int n)8 {9 int a;10 if(n==1)11 a=1;12 else13 {13 a=2 *n-1+fun(n-1);15 }16 return a;17 }18 main()19 {20 int m;21 cout<<"请输入奇数的个数:";22 cin>>m;23 cout<<"The sum is"<<fun(m)<<endl;24 }【程序解释】

◇第7行递归函数的参数n表示奇数个数。4.7 小结

函数是具有独立功能的代码段,在程序中,通过函数名与传递参数值实现对函数的调用,一个函数只有被声明或定义后才能被使用。C++必须知道函数的返回类型、接收的参数个数和类型,如果函数定义出现在函数调用后,则必须在程序开始部分用函数原型进行声明。

函数调用是通过栈操作实现的,函数可以进行递归,并且不能出现函数的嵌套定义;带有inline关键字的函数叫内联函数,在编译时,将函数体复制到函数调用点;函数重载允许用同一个函数名定义多个函数;在函数定义中,通过赋值运算,可以指定默认参数值。

使用函数的好处是:可读性好;易于查错和修改;便于分工编写,分阶段调试;各个函数之间接口清晰,便于相互交换信息和使用;节省程序代码和存储空间;减少用户总的工作量;成为实现结构程序设计思想的重要工具;扩充语言和计算机的原设计能力;便于验证程序正确性。4.8 习题4

1.选择题(1)下列函数定义形式中的正确项是()。

A. double add()

B. double add(inr x, int y)

C. int add();

D. double add(int x, y)(2)C++语言中规定函数的返回值的类型是由()决定的。

A.调用该函数时的主调函数类型决定

B.定义该函数时所指定的数据类型所决定

C.调用该函数时系统临时决定

D. return语句中的表达式类型决定(3)若有函数调用语句:fun(a+b,(x, y),(x, y,z));则此调用语句中的实参个数为()。

A. 3

B. 4

C. 5

D. 6(4)下面关于默认形式参数值的描述,正确的是()。

A.设置默认行参值时,形参名可以省略

B.只能在函数定义时设置默认形参值

C.应该先从左边的形参开始向右边依次设置

D.应该全部设置(5)下面关于重载函数的描述,正确的是()。

A.重载函数参数的个数必须不同

B.重载函数参数中至少有一个类型不同

C.重载函数参数个数相同时,类型必须不同

D.重载函数的返回类型必须不同(6)若同时定义了如下的函数,add(3,4.5)调用的是下列哪个函数()。

A. fun(float, int)

B. fun(double, int)

C. fun(char, float)

D. fun(double, double)(7)下列哪个选项是带有默认形参值函数的正确表示()。

A. int add(int a, int b=5,int c)

B. int add(int, int=5,int=8)

C. int add(int a;int b=5;int c=6)

D. int add(int a=3,int b=4,int c)(8)下面关于内联函数的描述,不正确的是()。

A.内联函数的关键字inline可以只放函数定义中

B.内联函数的关键字inline可以只放在函数原型声明中

C.内联函数的关键字inline可以在函数声明和函数定义中同时存在

D.内联函数的执行本质上是函数体的复制

2.程序补充

设计函数GetPower(int x, int y),其功能是计算x的y次幂。

#include<iostream>

using namespace std;

long GetPower(int x, int y);

main()

{

int number, power;

long answer;

cout<<“Enter a number:”;

cin>>number;

cout<<”To what power?”;

cin>>power;

answer=GetPower(number, power);

cout<<number<<”to the”<<power<<”th power is”<<answer<<endl;

}

long GetPower(int x, int y)

{

}

_________①__________

_________②_________

_________③_________

_________④_________

3.编程题(1)用递归和非递归的方法分别实现1!+2!+……+5!。(2)编写函数分别求两个数的最大公约数和最小公倍数,并在主函数中分别调用这两个函数。(3)判断2~100的数是否为素数,如果不为素数,则把它分解为素数之积,并输出。

4.读程序写结果

#include<iostream>

using namespace std;

void f(int n)

{

if(n/10)

cout<<n%10<<endl;

else

cout<<n<<endl;

}

main()

{

f(235);

}第5章 程序结构

引言

C++的程序由一个或多个函数组成,要对C++的程序结构有一个全面的了解,就必须掌握如何将多个函数组成模块,几个模块如何组成一个程序,以及如何在模块之间共享数据和调用同一工程文件中其他模块的函数的方法。掌握这些方法的基础是理解程序编译的过程以及系统内存空间的分配和释放。

学习目标(1)区分各种变量的类型及其分配的内存空间。(2)掌握全局变量、一般局部变量特点和使用。(3)掌握静态局部变量的特性。(4)能够区分并综合运用不同类型的变量。(5)理解作用域、可见度和生存期的概念。(6)掌握不同作用域的范围。(7)掌握几种预处理命令的用法。5.1 全局变量与局部变量

C++程序中的变量根据定义位置的不同,其可见度和生存期也不同。可见是指变量可以被正常使用,可见区域也称为作用域;生存期是指变量占用的内存单元。某些变量可能在生存期内某时刻占用内存单元,却不能被使用(即不可见)。根据定义位置的不同,变量可以被分为全局变量和局部变量。5.1.1 内存区域的布局

C++程序运行时所占用的内存空间分为4个区域,如图5-1所示。图5-1 程序运行时内存空间的分配(1)代码区,存储程序的可执行代码,即程序中的各函数代码块。(2)全局数据区,存储一般的全局变量和静态变量(静态全局变量、静态局部变量)。该区的变量在没有初始化的情况下被自动默认为0。(3)栈区,存储程序的局部变量,包括函数的形参和函数内定义的一般变量。分配栈区时,不处理原内存中的值,即栈区中的变量在没有初始化的情况下,其初值是不确定的。(4)堆区,存储程序的动态数据,堆区中的数据多与指针有关。分配堆区时,也不处理原内存中的值。5.1.2 全局变量

全局变量是指定义在所有函数体外的变量在整个程序中都是可见的,能够被所有函数所共享。全局变量存储在全局数据区,在主函数main()运行之前就已经存在了,如果其在定义时没有给出初始值,则自动初始化为0。如:

int i=10; //全局变量

void sub()

{

i=i+10;

cout<<i;

}【例5-1】全局变量应用举例。1 /**************************************************2 程序文件名:Ex5_1.cpp3 全局变量应用举例4 **************************************************/5 #include<iostream>6 using namespace std;7 int i=10; //全局变量8 void add()9 {10 i=i+10;11 cout<<"i="<<i<<endl;12 }13 main()14 {15 i=i+5;16 cout<<"i="<<i<<endl;17 add();18 i=i+6;19 cout<<"i="<<i<<endl;20 }

程序运行结果如图5-2所示。

图5-2例5-1运行结果【程序解释】

◇第7行定义了全局变量i,定义在所有函数之外。

◇程序从入口第13行main()函数开始,第15行执行了“i=i+5”后,全局变量i的值变为15。

◇程序执行到第17行调用add()函数后,跳转到add()函数定义体执行“i=i+10”,此时全局变量i的值变为“15+10=25”;因为全局变量对程序中所有函数是可见的,所以如果程序中的某个函数修改了全局变量,则其他函数仅能“可见”并共享修改后的结果。

◇执行完add()函数后返回到函数调用点执行其后续语句,即“i=i+6”,这时,全局变量i的值变为“25+6=31”。

全局变量通常定义在程序顶部,其一旦被定义后,就在程序中的任何位置可见,C++允许在程序中的任何位置定义全局变量,但要在所有函数之外。全局变量对其定义之前所有函数定义是不可见的。例如,若例5-1第7行全局变量的定义语句修改为add()与main()之间,即void add(){i=i+10;cout<<"i="<<i<<endl;}int i=10; //定义位置修改为此处main()……

这时,程序就会提示add()函数中“i未定义”的编译错误。5.1.3 局部变量

局部变量是指在一个函数或复合语句中定义的变量。局部变量在栈中分配空间,其类型修饰是auto,但习惯上通常省略auto。局部变量仅在定义它的函数内,从定义它的语句开始到该函数结束的区域是可见的。

由于函数中的局部变量存放在栈区,在函数开始运行时,局部变量在栈区被分配空间,函数结束时,局部变量随之消失,其生命期也结束。在一个函数内可以为局部变量定义合法的任意名字,而无须担心与其他函数中的变量或者全局变量同名。当某函数中的局部变量与全局变量同名时,在该函数内部局部变量的可见区域中,局部变量的优先级要高于同名的全局变量,即局部变量可见,而同名的全局变量不可见。

如果局部变量没有被显式初始化,其值是不可预知的。【例5-2】局部变量应用举例。1 /**************************************************2 程序文件名:Ex5_2.cpp3 局部变量应用举例4 **************************************************/5 #include<iostream>6 using namespace std;7 int i=10; //全局变量i8 void add()9 {10 i=i+7; //全局变量i11 cout<<"i="<<i<<endl; //全局变量i12 int i=3; //局部变量i13 i=i+10; //局部变量i14 cout<<"i="<<i<<endl; //局部变量i15 }16 main()17 {18 i=i+5; //全局变量i19 cout<<"i="<<i<<endl; //全局变量i20 add();21 i=i+6; //全局变量i22 cout<<"i="<<i<<endl; //全局变量i23 }程序运行结果如图5-3所示。图5-3 例5-2运行结果【程序解释】

◇第7行定义了全局变量i,定义在所有函数之外。

◇在add()函数中,第10行定义了同名的局部变量i,并初始化为3。局部变量i仅在add()函数中是可见的,其生命期从第10行开始,到第13行结束。根据同名变量的特点可以知道,在此区域内同名的全局变量i不可见。

◇在add()函数中,局部变量i定义之前的变量i系统都认为是全局变量,如第10行和第11行的语句是对全局变量i的操作。

◇局部变量与其同名的全局变量是相互独立的变量,局部变量存储在栈区,而全局变量存储在全局数据区。

◇第18行全局变量i的值为“10+5=15”;之后调用add()函数,第10行全局变量i的值为“15+7=22”,第13行局部变量i的值为“3+10=13”;返回到函数调用点执行第21行全局变量i的值为“22+6=28”。

当定义局部变量的函数又调用了其他函数,如:

……1 int i;2 void sub()3 {4 i=i-5;5 }6 void add()7 {8 int i=3;9 i=i+6;10 sub();11 }12 main()13 {14……15 add();……

上面程序段中定义了全局变量i,在add()函数中定义了局部变量i;main()函数调用add()函数,add()函数又调用sub()函数;局部变量i的可见区域仅为第8~11行,而被调用的sub()函数中的代码不是局部变量i的可见区域,所以第4行是对全局变量i的操作。

局部变量包括函数的形参、函数内定义的变量和复合语句内定义的变量;由于局部变量具有一定的范围局限,所以不同的函数可以定义同名的变量,这些变量之间没有任何逻辑关系,不会相互影响。5.1.4 静态局部变量

用static修饰的变量称为静态变量。定义的语法格式为:

◇static数据类型变量名=初值;

根据变量声明位置的不同,可分为静态全局变量和静态局部变量。静态变量存储在全局数据区,像全局变量一样,如果没有显式地给静态变量初始化,那么该变量将自动默认为0,静态变量的初始化仅进行一次。

静态全局变量是定义在所有函数体外的静态变量,只能供本模块使用,不能被其他模块重新声明为extern变量(外部变量,5.2节介绍);静态局部变量是定义在函数或复合语句块中的静态变量。静态局部变量既具有局部变量的性质又具有全局变量的性质,即具有局部作用域和全局生命期。静态局部变量实质上是一个可供函数局部存取的全局变量。【例5-3】静态局部变量应用举例。1 /**************************************************2 程序文件名:Ex5_3.cpp3 静态局部变量应用举例4 **************************************************/5 #include<iostream>6 using namespace std;7 void add()8 {9 static int i=0;10 int j=1;11 i++;12 j++;13 cout<<"i="<<i<<","<<"j="<<j<<endl;14 }15 main()16 {17 for(int i=0;i<5;i++)18 add();19 }

程序运行结果如图5-4所示。图5-4 例5-3运行结果【程序解释】

◇例中第9行,在add()函数中定义了静态局部变量i,并初始化为0。

◇程序的执行过程为:(1)进入main()函数,执行for语句的第一次循环,即调用add()函数。(2)进入add()函数,执行第9行语句“static int i=0”,定义静态局部变量i并赋初值。(3)执行第10行语句“int j=1”,定义局部变量并赋初值,依次执行第11行、12行和13行的语句。(4)结束add()函数,返回到调用点,并判断for循环语句是否结束,若结束,跳转到第5步,否则,跳转到第3步。(5)程序结束。

◇从程序的执行过程可以知道,静态局部变量的定义语句“static int i=0”在程序中仅执行一次。

◇因为静态局部变量的生命期与全局变量一样是全局生命期,所以每次add()函数结束时,静态局部变量i的内存空间和值被保留下来,由结果可以看出,后续有关i的运算都是在上一次计算结果的基础上进行的;而局部变量j在每次add()函数结束时都被释放掉,再一次调用add()函数时,局部变量j被重新分配空间和初始化。5.2 外部存储类型

一个源文件(.cpp)仅能够完整表达规模较小的程序,本节之前章节中的程序都是由单个源文件组成的。但是,在实际应用中的程序大多由若干个源文件组成,几个源文件分别编译成目标文件(.obj),最后连接成一个完整的可执行文件(.exe)。这些源文件一般添加在同一个工程文件(.prj)中,C++规定,同一工程的多个文件中,有且只有一个源文件包含主函数main(),否则会出现程序入口的二义性。

如果存在同一工程的多个文件共同使用的全局变量,就在其中的一个文件中定义全局变量或函数,在其他文件中使用“extern”关键字进行声明,其作用既通知了编译器该变量或函数已经被定义过,又避免了重复定义的错误发生。被使用“extern”说明的变量或函数是外部文件。语法格式如下:

★extern数据类型变量名/函数原型;

工程文件的创建方法是:(1)选中Projects标签的“Win32 Console Application”(Win32控制台应用)项。(2)选择存储工程文件的磁盘位置(Location)并给工程命名(Project name),工程名和其包含的文件名可以同名,如图5-5所示。图5-5 工程文件的创建【例5-4】外部存储结构应用举例。1 /**************************************************2 工程文件名:E5_4.prj3 源文件名:Ex5_4_1.cpp4 Ex5_4_2. cpp5 外部存储结构应用举例6 **************************************************/ //Ex5_4_1. cpp7 #include<iostream>8 using namespace std;9 void fun1();10 extern void fun2();11 int i;12 main()13 {14 i=3;15 fun1();16 cout<<i<<endl;17 }18 void fun1()19 {20 fun2(); //fun2()的定义体不在本文件中21 } //Ex5_4_2. cpp1 extern int i; //i定义在本工程的其他源文件中2 void fun2()3 {4 i=18;5 }【程序解释】

◇在Ex5_4_1.cpp中定义了函数fun1()和全局变量i,而函数fun2()在本工程的Ex5_4_2.cpp中定义,因此在Ex5_4_1.cpp的第10行声明函数fun2()前添加了extern,说明其为外部文件。

◇由于C++默认所有函数的声明和定义都是extern的,所以文件Ex5_4_1.cpp中“extern void fun2()”前的extern说明符可以省略,函数fun2()的声明等价于:

void fun2();

◇主函数main()仅在Ex5_4_1.cpp中进行了定义,由于全局变量i定义在Ex5_4_1.cpp文件中,所以当Ex5_4_2.cpp文件要使用全局变量i时,就需要在其开头声明“extern int i”,表示该变量i不在本文件中分配空间,而在程序其他文件中进行了定义。

◇在工程的各个源文件中,使用extern只是将其他源文件中已经定义的变量或函数声明为外部文件,可以在本文件中使用,不是对变量或函数的定义,因此不能进行赋值操作。如下面写法是错误的:

extern int i=8;

◇假设某个工程由两个或两个以上源文件组成,其中一个源文件中出现了extern修饰的变量或函数,那么在其他某个源文件中必须有对该变量或函数的定义。5.3 作用域

作用域是标识符在程序代码中的有效范围,其范围可分为:函数原型作用域、局部作用域(块作用域)、函数作用域和文件作用域。除标号外的标识符的作用域都开始于标识符的声明处。5.3.1 函数原型作用域

函数原型作用域是C++中范围最小的作用域。函数原型声明中的形式参数就在该作用域内,其范围起始于函数原型声明的左圆括号,结束于函数原型声明的右圆括号。例如下面的函数原型声明:

void fun1(int i, int j=4,int m=6);

标识符i、j和m的作用域为函数原型声明内部,因此函数原型声明中形式参数的标识符可以省略,保留标识符的作用在于增强程序的可读性。习惯上将函数原型声明的形参标识符与其对应的函数定义中形参的标识符保持一致。例如:

void fun1(int i, int j=4,int m=6); //函数原型声明

……

void fun1(int i, int j, int m) //函数定义

{

……

}5.3.2 局部作用域

局部作用域也称为块作用域。所谓块,是指用一对花括号“{}”括起来的部分。当标识符位于某块内时,其作用语从声明处开始,到块结束处为止。

块可以进行嵌套,即块中内嵌新的块,在出现嵌套块的情况下,标识符的作用域从属于包含它的最近的块。

函数定义的形参和函数体同属于一个块;语句是一个程序单位,如果语句中出现标识符的声明,则标识符的作用域在语句中,如if……else语句、switch语句和循环语句等。例如,下面的程序段将出现变量重复定义的错误:

void func(int i)

{

int i; //错误,变量重复定义

}

如果将上面程序段中的“int i”包含到新的内嵌块中,则代码是正确的,即void func(int i)

{

{

int i;

}

}

形参中i的作用域从声明处开始到外层花括号结束时止,函数体i的作用域从声明处开始到内层花括号结束时止。

而语句中的如果出现标识符新的定义,则标识符的作用域从新的声明处开始而不会有重复定义的错误提示。以while循环语句为例1 while(int i=3)2 {3 int i=6;4 cout<<i<<endl;5 }

第1行定义的i=3的作用域从声明处开始到第5行结束;第3行定义的i=6的作用域从声明处开始到第5行结束;当相互嵌套块中有相同名字的标识符时,则块重叠部分的标识符的作用域从属于内层块,因此第4行的i是对第3行中的i进行的,其值为6;while()后{}中的块相当于while的内嵌块。5.3.3 函数作用域

C++中,goto语句的标号是唯一具有函数作用域的标识符。标号的声明使其在声明的函数内的任何位置都可以被使用。例如:

……

void fun1()

{

goto M;

int i=5;

if(i>5)

{

M:

cout<<"i="<<i<<endl;

}

}

……5.3.4 文件作用域

既不在函数原型中,又不在块中的标识符具有文件作用域。文件作用域是在所有函数定义之外说明的,从声明处开始,到文件结束时为止。全局变量和常量具有文件作用域。

如果头文件(下节介绍)被其他源文件导入“include”,则在头文件的文件作用域中声明的标识符的作用域也扩展到该源文件,直到源文件结束。

◇C++中函数的定义是平行的,除了main()函数被系统自动调用外,其他函数都可以互相调用。假设存在3个函数:f1()、f2()和f3(),函数f1()既可以直接调用函数f3(),也可以间接调用它,即先调用函数f2(),再由f2()调用f3()。5.4 文件结构5.4.1 头文件

头文件是指以.h作为扩展名的文件,头文件里包含被同一工程中多个源文件引用的具有外部存储类型的声明。头文件的引入可以将工程中共享的变量或函数的声明被集合化,每个需要使用这些共享信息的源文件只需在自身文件顶端导入头文件即可。头文件的导入使用编译预处理“#include”方法。头文件一般可以包含如下声明:

函数声明,如extern int func1();

常量定义,如const int a=3;

数据声明,如extern int I;

内联函数定义,如inline int add(int i)

{return++i}

包含质量,如#include<math.h>

宏定义,如#define PI 3.14159;

头文件中不能包含一般函数的定义、数据的定义等。【例5-5】设计一个包含3个源文件的工程,源文件的功能分别为计算球体的体积、计算球体的表面积和输入球体半径并调用另外两个源文件,共享的声明存储在头文件。/**************************************************工程文件名:Ex5_5.prj头文件名:myball.h头文件应用举例**************************************************/double volume(double i);double area(double i);const float PI=3. 14;/**************************************************工程文件名:Ex5_5.prj源文件名:Ex5_5_1.cpp计算球体的体积**************************************************/#include"myball. h"double volume(double i){return 4 *PI*i*i*i/3;}/**************************************************工程文件名:Ex5_5.prj源文件名:Ex5_5_2.cpp计算球体的表面积**************************************************/#include"myball. h"double area(double i){return 4 *PI*i*i;}/**************************************************工程文件名:Ex5_5.prj源文件名:Call.cpp调用函数**************************************************/#include<iostream>#include"myball. h"using namespace std;main(){double radius;cout<<"请输入球体的半径:";cin>>radius;cout<<"球体的体积为"<<volume(radius)<<endl;cout<<"球体的表面积为"<<area(radius)<<endl;}

程序执行前需要分别对3个源文件进行编译,然后与头文件进行链接,程序运行结果如图5-6所示。图5-6 例5-5运行结果5.4.2 编译预处理

C++中以#开头,以换行符结尾的命令行成为预处理命令。一个程序运行前,首先进行去掉注释行、变换格式等预处理操作,然后再将通过编译器将高级语言编写的程序翻译成计算机能识别的机器语言。预处理命令不以分号结尾。

1.#include命令

#include命令也成为文件包含命令,是指在一个C++文件中将另一个文件的内容全部包含进来。文件包含命令的语法格式为:

#include<包含文件名>

#include“包含文件名”

第一种形式<>表示包含的文件在编译器指定的文件包含目录中;第二种形式“”表示系统首先到当前目录下查找包含文件,如果没找到,再到系统指定的文件包含目录中查找。通常情况下,<>包含的是系统提供的头文件,而“”包含的是程序员自己定义的头文件。一条include命令只能指定一个被包含文件,如果要包含m个文件,就要使用m条include命令。

2.条件编译

在有些情况下,不需要程序中的所有语句都参加编译,而是根据一定的条件去编译程序中的不同部分,这称为条件编译。这种机制使同一程序在不同的编译条件下生成不同的目标文件。常用的条件编译指令有:(1)表达式作为编译条件

其语法格式为:

#if表达式

程序段1

[#else

程序段2]

#endif

其中,#if后只能是常量表达式,表示如果表达式的值不为零,则执行程序段1,否则执行程序段2。(2)宏名已经定义作为编译条件

其语法格式为:

#ifdef宏

程序段1

[#else

程序段2]

#endif

表示如果宏已经被定义,则编译程序段1,否则编译程序段2。(3)宏名未被定义作为编译条件

其语法格式为:

#ifndef宏

程序段1

[#else

程序段2]

#endif

表示如果宏未被定义,则编译程序段1,否则编译程序段2。

利用条件编译还可以在调试程序的过程中增加调试语句,借以达到程序跟踪的目的。5.5 实例应用与剖析【例5-6】全局变量、局部变量和静态局部变量的综合应用。1 /**************************************************2 工程文件名:Ex5_6.cpp3 全局变量、局部变量和静态局部变量综合应用4 **************************************************/5 #include<iostream>6 using namespace std;7 void func();8 int n=1; //全局变量9 main()10 {11 static int a; //静态局部变量12 int b=-10; //局部变量13 cout<<"a:"<<a<<"b:"<<b<<"n:"<<n<<endl;14 b+=4;15 func();16 cout<<"a:"<<a<<"b:"<<b<<"n:"<<n<<endl;17 n+=10;18 func();19 }20 void func()21 {22 static int a=2; //静态局部变量23 int b=5; //局部变量24 a+=2;25 n+=12;26 b+=5;27 cout<<"a:"<<a<<"b:"<<b<<"n:"<<n<<endl;28 }程序运行结果如图5-7所示。图5-7 例5-6运行结果【程序解释】

◇运行结果显示本例共有4行输出。

◇本例定义了1个全局变量、2个一般局部变量和2个静态局部变量。局部变量互相重名,但本质上是相互独立的变量。

◇程序的执行过程为:进入main()函数,在第11行定义了main()中的静态局部变量a,系统自动赋初始值为0;第12行定义了一般局部变量b,其初值为-10,因此第1行分别输出main()中的a、b以及全局变量n分别为0、-10和1。

◇第14行语句对main()的一般局部变量b进行加4操作,此时main()中b的值为-6,第15行调用func()函数,跳转到对应的函数定义体。

◇在func()函数中,定义了名为a的静态局部变量并赋初始值为2,此函数中的a与main()函数中的a分配的内存空间不同,它们之间没有任何逻辑关系。第29行分别输出func()中的a、b以及全局变量n分别为4、10和13。

◇执行完func()函数后,跳回函数调用点执行第16行语句,此处的变量分别为main()中的a、b和全局变量n,结果分别为0、-6和13。

◇第17行对全局变量n进行加10操作,即n的值变为23;第18行第二次调用func()函数,根据静态局部变量的特点,在执行函数体的过程中,不再执行第22行中变量定义语句,而直接从第23行开始执行,第24行对a的操作是在上次计算结果a=4的基础上加2,因此a=6,即第4行输出结果分别为a=4、b=10、n=35。【例5-7】作用域应用举例。1 /**************************************************2 工程文件名:Ex5_7.cpp3 作用域应用举例4 **************************************************/5 #include<iostream>6 using namespace std;7 int i=36; //全局变量8 void func(int i=12);9 main()10 {11 cout<<"作用域示例:"<<endl;12 func();13 }14 void func(int i)15 {16 cout<<"局部作用域:i="<<i<<endl;17 {18 int i=13;19 cout<<"内嵌局部作用域:i="<<i<<endl;20 {21 for(int i=14;i<15;cout<<"for局部作用域: i="<<i<<endl, i++)22 {23 cout<<"for局部作用域:i="<<i<<endl;24 int i=15;25 i++;26 cout<<"for的内嵌局部作用域:i="<<i<<endl;27 }28 }29 }30 cout<<"局部作用域:i="<<i<<endl;31 }

程序运行结果如图5-8所示。图5-8 例5-7运行结果【程序解释】

◇第8行为函数声明,形参的定义i=12是范围最小的函数原型作用域。

◇第16行显示是func()函数的局部作用域,从第14行开始,到第31行结束;第19行显示是func()内的内嵌局部作用域,第18行定义的i不同于第14行定义的i,作用域到第29行结束。

◇第21定义的i=14的作用域从定义点开始,到for语句结束为止;但是在for语句内第24行,又定义了i=15,此处的i不同于第21行定义的i,属于不同的块。

◇只有在不同的块中才能定义同名的变量,否则系统会提示重复定义的错误。

◇第30、16行的i对应的是第14行的定义,第25行的i对应的是第24行的定义,第21行的i对应的是第21行的定义,第19行的i对应的是第18行的定义。5.6 小结(1)全局变量是定义在所有函数体外的变量,能够被所有函数所使用;局部变量是定义在某函数或复合语句内部的变量,只在函数或复合语句内部有效。(2)静态局部变量有全局变量和一般局部变量双重性质:定义在全局数据区,函数被多次调用,变量的值只被初始化一次,并且每次都是在上一次的计算结果上执行新的操作;只能在定义的函数体内操作。(3)全局变量、静态变量、字符串常量存储在全局数据区,函数和代码存储在代码区,函数参数、局部变量、返回地址存放在栈区,动态内存分配在堆区。(4)如果存在同一工程的多个文件共同使用的全局变量,就在其中的一个文件中定义全局变量或函数,在其他文件中使用“extern”关键字进行声明,extern说明的变量或函数为外部文件。(5)C++程序的作用域按范围由小到大可分为:函数原型作用域、局部作用域(块作用域)、函数作用域和文件作用域。除标号外的标识符的作用域都开始于标识符的声明处。(6)以#开头、以换行符结尾的行称为预处理命令。预处理命令在程序编译前由预处理器执行。5.7 习题5

1.找出下列程序的错误(每个题中的源文件都属于相同的工程文件)(1)工程中包含func1.cpp和func2.cpp两个源文件。 //func1. cpp

int a=6;

int b=7;

extern double c; //func2. cpp

int a;

extern double b;

extern int c;(2)工程中包含file1.cpp、file2.cpp和file3.cpp三个源文件。 //file1. cpp

int a=1;

double func()

{

……

} //file2. cpp

extern int a;

double func();

void add()

{

a=int(func());

} //file3. cpp

extern int a=2;

int add();

main()

{

a=add();

}

2.选择题(1)以下叙述正确的是()。

A.在相同的函数中不能定义相同的名字变量

B.函数中的形式参数是静态局部变量

C.在一个函数体内定义的变量只在本函数范围内有效

D.在一个函数内的复合语句中定义的变量在本函数范围内有效(2)以下叙述不正确的是()。

A.预处理命令必须以#号开头

B.凡是以#开头的语句都是预处理命令行

C.在程序执行前执行预处理命令

D.#definePI=3. 14是一条正确的预处理命令(3)下列程序的运行结果为()。

main()

{

int i=100;

{

i=1000;

for(int i=0;i<1;i++)

{

int i=-1;

}

cout<<i;

}

cout<<”,”<<i;

}

A.-1,-1

B. 1,1000

C. 1000,100

D.死循环(4)下列程序的运行结果为()。

int i=100;

int fun

{

static int i=10;

return++i;

}

main()

{

fun();

cout<<fun()<<”,”<<i;

}

A. 10,100

B. 12,100

C. 12,12

D. 11,100(5)下列程序的运行结果为()。

#include<iostream>

using namespace std;

void func()

{

static int i=1;

cout<<“i=”<<++i<<endl;

}

main()

{

for(int x=0;x<3;x++)

func();

}

A. i=1

B. i=2

C. i=1

D. i=2

i=1

i=2

i=2

i=3

i=1

i=2

i=3

i=4

3.读程序写结果

int a=1;

int fun()

{static int a=1;

return++a;

}

main()

{

fun();

cout<<fun()<<”,”<<a;

}第6章 数组

引言

C++程序除了支持使用基本数据类型描述的数据外,还支持使用用户构造的复杂的数据类型以描述复杂的问题。构造数据类型也成为自定义数据类型或导出类型,主要包括:数组、指针、引用、字符串和结构体。

本章主要介绍数组类型。数组是由单一类型的数据元素组成的有序集合,可以是一维的,也可以是多维的,许多实际的应用都是基于数组数据类型的。

学习目标(1)了解数组的存储结构及与一般变量的联系。(2)掌握一维数组的定义与初始化。(3)理解多维数组与一维数组的联系。(4)掌握二维数组的定义与初始化。(5)熟练使用一维数组和二维数组来解决实际问题。(6)掌握数组名作为形参的方法和传值过程。(7)掌握字符数组的定义和初始化。(8)熟悉字符数组操作的特点,并能够区分字符数组输入输出方式与其他数组的不同。6.1 一维数组

数组是由单一类型的数据元素组成的有序数据集合,每个数据元素使用数组名和下标来表示。数组中的数据元素不但类型相同,而且存放在连续的内存单元中,这方便了程序对数据的快速查找和存取。因此,数组这种数据类型适合于处理批量相同类型的数据,如某班学生的学号或姓名。6.1.1 一维数组的定义

1.数组的定义

一维数组定义的语法格式为:

★数据类型数组名[常量表达式];

其中:(1)数据类型可以是除void型外的任何一种基本数据类型(整型、字符型、浮点型等),也可以是用户自己已定义的构造数据类型。(2)数组名的命名应该遵循标识符的命名规则。(3)数组名除了表示数组的名称外,还代表数组元素在内存中的起始地址,是一个地址常量。(4)数组名后是用方括号[]括起来的常量表达式,[]也称为数组下标运算符。(5)数组定义中的常量表达式是unsigned int型的正整数或const常量,表示数组中元素的个数(即数组长度)。(6)数组中的元素是连续存储的,在内存中是从低地址开始顺序排列的,所有数组元素的存储单元大小相同。

例如,下面分别定义了整型数组和字符型数组:

int butter[5]; //定义了名字为butter的整型数组,数组长度为5

char fly[6]; //定义了名字为fly的字符型数组,数组长度为6

C++允许在同一行语句中定义类型相同的多个数组,也允许在同一行语句中定义类型相同的变量和数组,如:

int butter[5],xin[6]; //定义了两个整型数组

int a, butter[5]; //定义了整型变量a和整型数组butter

数组元素的命名和内存地址的分配是有序的,其使用与一般变量完全相同,数组元素也称为有序的变量。

2.数组元素的访问

数组元素的访问包括存取数组元素和访问数据元素的地址。

数组元素通过数组名和下标作为标志进行访问的,这种带下标的数组元素称为下标变量。同一数组的数组元素的存储单元是连续的,数组的起始地址对应于第一个数组元素的地址,数组的起始地址可以由数组名表示。

访问一维数组元素的语法格式为:

★数组名[下标表达式];

其中,下标表达式可以是整型常量,也可以是整型变量,表示当前数组元素距离第一个数组元素的偏移量,即第一个数组元素的下标表达式是0。例如,长度是n的数组,其下标表示范围是0~(n-1),对应的数组元素分别表示为:a[0]、a[1]、……、a[n-1]。

数组元素的操作和普通变量的操作相同,例如下面的语句:

a=100; //给变量a赋值100

a[3]=100; //给数组a的第4个数组元素赋值100

3.数组的初始化

一维数组的初始化指在定义数组的同时给数组中的全部或部分数组元素赋值。一维数组初始化的语法格式为:

★数据类型数组名[常量表达式]={初始值1,初始值2,……,初始值n};

其中:(1)初始值的个数n要大于0,并且不能大于数组元素的实际个数,即常量表达式的值。如:

int array[3]={23,34,45,56}; //错误,初始值的个数多于数组元素个数

int array[3]={}; //错误,初始值的个数不能为0(2){初始值1,初始值2,……,初始值n}称为初始值表,初始值之间由逗号分隔。(3)初始值i与数组的第i个数组元素相对应。当初始值的个数少于数组元素的个数时,初始值按照自左向右的顺序逐个顺次给数组元素赋值,不允许初始值通过跳过逗号进行省略,即如果某个数组元素没有初始值,则它后面的数组元素也不能有初始值。如:

int array[5]={16,36,68}; //合法

int array[5]={16,36,68}; //不合法

int array[5]={16,36,68,}; //不合法(4)如果初始值的个数少于数组元素的个数时,没有被赋初始值的数组元素的默认值为0(全局或静态数组元素)或不确定值(局部数组元素)。【例6-1】一维数组的初始化。1 /**************************************************2 程序文件名:Ex6_1.cpp3 一维数组初始化应用举例4 **************************************************/5 #include<iostream>6 using namespace std;7 int array[5]={16,36,68}; //第4、5个数组元素取默认值8 main()9 {10 int i;11 int array2[5]={22,33,44}; //第4、5个数组元素取默认值12 static int array3[5]={1,2,3}; //第4、5个数组元素取默认值13 cout<<"全局变量:"<<endl;14 for(i=0;i<5;i++)15 {16 cout<<""<<array1[i]; //第4、5个数组元素默认值为017 }18 cout<<"\n局部变量:"<<endl;19 for(i=0;i<5;i++)20 {21 cout<<""<<array2[i]; //第4、5个数组元素默认值不确定22 }23 cout<<"\n静态局部变量:"<<endl;24 for(i=0;i<5;i++)25 {26 cout<<""<<array3[i]; //第4、5个数组元素默认值为027 }28 cout<<endl;29 }(5)如果对全部数组元素初始化,则可以省略“常量表达式”,数组长度就是初始值的个数。如:

int array[]={12,23,34,45};

则数组长度为4。但是,不允许在没有初始值的情况下省略数组长度。如:

int array[];//错误,没有确定数组大小(6)数组初始值表内可以用最多一个逗号结尾,此处逗号没有任何意义。例如:

int array[5]={12,23,34,}等价于int array[5]={12,23,34}6.1.2 一维数组的地址表示

数组的地址包括数组名表示的数组地址常量和数组元素的地址。

数组名表示数组的首地址,它是一个地址常量,与第一个数组元素的地址相同。数组名是地址常量,因此不能作为左值。例如:

int array1[6],array2[6];

array1=array2; //错误,数组名array1时地址常量,不能被赋值

数组元素的地址通过数组名表示,其语法格式为:

★数组名+整形表达式;

数组元素地址的这种表达方式叫相对地址,相对于第一个数组元素的位置。例如:

设array是一个int型数组名,则array+6表示的是第(6+1)=7个元素array[6]的相对地址。该数组元素的实际地址为:

array+6 *sizeof(int)6.1.3 一维数组的使用

数组是C++用来存储和表示数据的重要手段和方法,可以使用数组结合循环等控制语句对批量数据进行快速查找和管理,如排序、查找等运算。

1.数据排序

数据排序是数组的重用计算应用之一。根据算法的不同,数组可以进行不同效率的排序,本节介绍最简单的冒泡排序(bubble sort)。【例6-2】冒泡排序(按从小到大的顺序排序,即升序)。冒泡排序的算法思路是使数组中较小的值像气泡一样浮到数组顶部,较大的值则下沉到数组的底部。

分析:假设数组元素个数为n,冒泡排序需要数组元素进行n-1轮排序,每一轮排序都可以找到当前参与比较的最大的数组元素,最大的数组元素不参加下一轮的排序。每一轮对相邻的数组元素两两进行比较,每次比较都是将较小的数调换到前面。这样能够保证每轮比较结束的时候,参加比较的最大的数组元素沉到数组底部。

以数组n[5]={78,58,12,6,36}为例,图6-1给出冒泡排序过程,竖线右侧数组元素不再参与比较。图6-1 冒泡排序过程图/**************************************************程序文件名:Ex6_2.cpp冒泡排序法**************************************************/1 #include<iostream>2 using namespace std;3 void bubblesort(int a[],int m); //冒泡排序函数4 void showbubble(int a[],int m); //输出数组5 main()6 {7 int n[5]={78,58,12,6,36};8 int length=sizeof(n)/sizeof(int); //计算数组长度9 cout<<"未排序前:";10 for(int i=0;i<length;i++)11 cout<<n[i]<<"";12 cout<<endl<<endl;13 bubblesort(n, length);14 showbubble(n, length);15 }16 void bubblesort(int a[],int m)17 {18 int i, j,t;19 for(i=0;i<m-1;i++)20 {21 cout<<"第"<<i+1<<"轮:";22 for(j=0;j<m-1-i;j++)23 if(a[j]>a[j+1])24 {25 t=a[j];26 a[j]=a[j+1];27 a[j+1]=t;28 }29 for(int number=0;number<m;number++)30 cout<<a[number]<<"";31 cout<<endl;32 }33 cout<<endl;34 }35 void showbubble(int a[],int m)36 {37 cout<<"排序后:";38 for(int i=0;i<m;i++)39 cout<<a[i]<<"";40 cout<<endl;41 }

程序运行结果如图6-2所示。图6-2 例6-2运行结果【程序解释】

◇程序第3行声明了冒泡排序函数bubblesort(),a[]表示带排序的数组,m表示数组长度。

◇第8行通过计算数组所占用内存空间得到数组长度。

◇在bubblesort()函数中,第19行的for循环控制比较的轮数,如果数组长度为n,则比较轮数为n-1轮,例中共比较5-1=4(轮)。

◇第22行的for循环控制每轮两两比较的次数j, j与轮数i有关,即在第i轮进行j=n-i次比较。

◇第35行函数showbubble()输出排序后的数组。

2.数据查找

查找算法是在一个数据集合中对待查找数据进行定位。比较常用的查找算法是线性查找和二分查找。下面以线性查找为例进一步加深对数组操作的理解。【例6-3】在长度为n的一维数组中查找某确定值x。线性查找使用的方法是从数组的第一个元素开始,逐个与待查找值x比较。如果找到,则返回数组元素的下标,否则返回-1(有效的数组下标从0开始)。

以数组n[5]={78,58,12,6,36}为例,查找值为6和35的数组元素。/**************************************************程序文件名:Ex6_3.cpp线性查找数组元素**************************************************/1 #include<iostream>2 using namespace std;3 const int size=5;4 int seek(int list[],int arraySize, int value);5 main()6 {7 int array[size]={78,58,12,6,36};8 int result, x;9 cout<<"请输入待查找元素值:";10 cin>>x;12 result=seek(array, size, x);12 if(result==-1)13 cout<<"查找失败,没有找到:"<<x<<endl;14 else15 cout<<"值"<<x<<"是该数组的第"<<(result+1)<<"个元素"<<endl;16 }17 int seek(int list[],int arraySize, int value)18 {19 for(int i=0;i<arraySize;i++)20 if(value==list[i])21 return i;22 return-1;23 }

程序运行结果如图6-3所示。图6-3 例6-3运行结果6.2 二维数组

C++中数组的下标可以有两个或两个以上,以方便表示复杂数据结构中的数据。例如,二维表中的数据仅用一维数组无法表示,一维数组无法同时存放行和列的信息,要识别和表示表中的某个元素必须指定两个数组下标。6.2.1 二维数组的定义

1.数组定义

二维数组是指具有两个下标的数组,习惯上第一个下标表示该元素所在行,第二个下标表示该元素所在列。二维数组的语法格式为:

★数据类型数组名[常量表达式1][常量表达式2];

其中常量表达式1和常量表达式2分别表示数组的行数和列数。数组元素的行标和列标也都是从0开始。例如:

int array[2][3];

表示名为array的2行×3列的整型二维数组,其元素分别是:

array[0][0],array[0][1],array[0][2]//可看做名为array[0]的一维数组

array[1][0],array[1][1],array[1][2]//可看做名为array[1]的一维数组

数组array在内存中的排列表示如图6-4所示。图6-4 2行×3列数组的内存排列表示

2.数组元素的访问

二维数组元素的访问也包括存取数组元素和访问数据元素的地址。

访问二维数组元素的语法格式为:

★数组名[行下标表达式][列下标表达式];

其中:(1)行下标表达式和列下标表达式既可以是整型常量,也可以是整型变量,并且都是从0开始。要访问二维数组中的某个元素,必须同时确定其行数和列数。例如,array[i][j]表示该数组元素的行数是(i+1)、列数是(j+1),位于第i+1行和第j+1列交叉的位置。(2)数组array[m][n]的行下标表示范围是0~(m-1)、列下标表示范围是0~(n-1)。二维数组元素的操作也和普通变量的操作相同。

3.数组的初始化

二维数组的初始化和一维数组类似,也是在定义数组的同时给数组中的全部或部分数组元素赋值。根据初值组成的不同,二维数组的初始化可分为初值集合表示和初值线性表示两种形式。(1)集合表示

以3行×4列的二维整型数组array[3][4]为例,其初始化的集合表示格式为:

int array[3][4]={{3,4,5,2},{1,6,78,43},{65,87,24,9}};

其中:

◇内层花括号{}组成了3个集合:{3,4,5,2},{1,6,78,43},{65,87,24,9},每个集合对应数组中相应行的元素初始值,即{3,4,5,2}是第1行数组元素的初始值,{1,6,78,43}是第2行数组元素的初始值,{65,87,24,9}是第3行数组元素的初始值,表示如下:

◇C++允许仅对二维数组内层花括号内的部分数组元素赋值。例如:

int array[3][4]={{5,2},{1,6,43},{65,87,24,9}};

赋值后各数组元素表示为:

如果初始化全部的数组元素,则可以省略“行下标表达式”,如:

int array[][4]={{5,2},{1,6,43},{65,87,24,9}};

但是不能省略“列下标表达式”,如下表示是错误的:

int array[3][]={{5,2},{1,6,43},{65,87,24,9}}; //错误(2)线性表示

二维数组初始化线性表示类似于一维数组的初始化形式,即所有数据写在一个花括号内,按数组元素排列的顺序赋初值,如:

int array[3][4]={3,4,5,2,1,6,78,43,65,87,24,9};

其中:

◇初始值的个数不能多于数组行数和列数的乘积,即上例中初始值的个数要小于或等于3×4=12。

◇线性表示的二维数组初始化形式的初始值也可以省略,并且只有“行下标表达式”可以省略,不能省略“列下标表达式”。

如果只是对二维数组的部分元素初始化,无论使用集合表示还是线性表示,没有被赋初始值的数组元素的默认值为0(全局或静态数组元素)或不确定值(局部数组元素)。6.2.2 二维数组的地址表示

二维数组元素的地址与其排列的顺序有关,例如数组array[m][n]中的数组元素array[i][j]的位置偏移量和在内存中的地址计算公式分别为:

偏移量:n*i+j+1

地址:array的起始地址+(n*i+j)*sizeof(数据类型)

例如,下列形式的二维数组元素表示:

array[i]//数组的第i+1行的起始地址,即array[i][0]的地址

array+i//数组的第i+1行的起始地址,即array[i][0]的地址

array[i]+j//数组的第i+1行的第j+1个元素的地址,即array[i][j]的地址6.2.3 二维数组的使用

使用二维数组多用于解决用复杂数据结构抽象表示的实际问题。【例6-4】用二维数组解决猴子分桃子问题。

问题描述:甲、乙、丙3个猴子带着21个篮子去摘桃子。回来以后,发现有7个篮子装满了桃子,还有7个篮子装了半篮桃子,另外7个篮子是空的。假设7个满篮中桃子的重量都相同为a千克,7个半篮中桃子的重量也相同为b千克。在不将桃子倒出的前提下,如何将桃子平均分成3份放入3个篮子。

分析:

根据问题描述,可以知道每个猴子应该分到7个篮子,而桃子的数量是3.5篮。可以使用3×3的数组a来表示3个猴子分到的桃子。其中每个猴子对应数组a的一行,数组的第1列存放分到桃子的整篮数,数组的第2列存放分到的半篮数,数组的第3列存放分到的空篮数。由题意可以推出:(1)数组的每行或每列的元素之和都为7。(2)对数组的行来说,满篮数加半篮数等于3.5。(3)每个猴子所得的满篮数不能超过3篮。(4)每个猴子都必须至少有1个半篮,且半篮数一定要为奇数。/**************************************************程序文件名:Ex6_4.cpp二维数组解决猴子分桃子问题**************************************************/1 #include<iostream>2 using namespace std;3 int monkey[3][3],count;4 main()5 {6 int i, j,k, m,n, flag;7 cout<<"可能的分配方案\n";8 for(i=0;i<=3;i++)//试探第一个猴子满篮a[0][0]的值,满篮数不能大于39 {10 monkey[0][0]=i;11 for(j=i;j<=7-i&&j<=3;j++) //试探第二个猴子满篮a[1][0]的值,满篮数不能大于312 {13 monkey[1][0]=j;14 if((monkey[2][0]=7-j-monkey[0][0])>3)15 continue; //第三个猴子满篮数不能大于3 */16 if(monkey[2][0]<monkey[1][0])17 break;//要求后一个猴子分的满篮数>=前一个猴子,以排除重复情况18 for(k=1;k<=5;k+=2) //试探半篮a[0][1]的值,半篮数为奇数19 {20 monkey[0][1]=k;21 for(m=1;m<7-k;m+=2) //试探半篮a[1][1]的值,半篮数为奇数22 {23 monkey[1][1]=m;24 monkey[2][1]=7-k-m;25 for(flag=1,n=0;flag&&n<3;n++)//判断每个猴子分到的桃子是3.5篮,flag为标记变量26 if(monkey[n][0]+monkey[n][1]<7&&monkey[n][0]*2+monkey[n][1]==7)27 monkey[n][2]=7-monkey[n][0]-monkey[n][1]; //应得的空篮数28 else29 flag=0; //不符合题意则置标记为030 if(flag)31 {32 cout<<"No."<<++count<<"满篮子桃:半篮子桃:空篮子\n";33 for(n=0;n<3;n++)34 cout<<"猴子"<<'A'+n<<"分到的篮子是:"<<monkey[n][0]<<":"<<monkey[n][1]<<:"<<monkey[n][2]<<endl;35 }36 }37 }38 }39 }40 }

程序运行结果如图6-5所示。图6-5 例6-4运行结果6.3 数组作为函数参数

函数参数可以由一般变量充当,也可以由数组充当。数组作为函数参数包括数组元素,作为函数参数和数组名作为函数参数。6.3.1 数组元素作为函数参数

因为数组元素是命名和内存地址有序的变量,所以无论是一维数组元素还是多维数组元素,它们作为函数参数与普通变量的操作一样,都是单向传值调用,即只能由实参向形参传值。【例6-5】设计一维数组元素作为函数参数,并利用函数输出数组中全部元素的值。/**************************************************程序文件名:Ex6_5.cpp数组元素作为函数参数**************************************************/1 #include<iostream>2 using namespace std;3 void showed(int i);4 main()5 {6 int array[]={43,56,2,98,18,22};7 for(int i=0;i<6;i++)8 showed(array[i]);9 cout<<endl;10 }11 void showed(int i)12 {13 cout<<""<<i;14 }

程序运行结果如图6-6所示。图6-6 例6-5运行结果

第8行的语句“showed(array[i]”位于for循环中,每次调用showed()函数时,都将数组元素array[i]作为实参传值给第11行的形参变量i。这是单向传值,而形参值i的变化对实参没有任何影响。本例中输出的是形参i的值,而没有直接输出数组元素。6.3.2 数组名作为函数参数

数组名是数组的首地址,可以作为函数的形参使用,用以接受实参传的地址值。当作为形参的数组名接受了实参地址值后,形参便与实参共享内存中的相同内存空间。与一般变量和数组元素作为函数参数不同的是,数组名作为函数形参时,函数体可以通过形参对数组内容的改变影响到相应实参,即这种改变会直接作用于实参。【例6-6】在例6-5的基础上,使array数组元素的值扩大3倍,并利用函数输出数组中全部元素的值。/**************************************************程序文件名:Ex6_6.cpp数组名作为函数参数**************************************************/1 #include<iostream>2 using namespace std;3 void showed(int a[],int i);4 void triplevalue(int a[],int i);5 main()6 {7 int array[]={43,56,2,98,18,22};8 triplevalue(array,6); //数组元素内容乘以3showed(array,6);10 cout<<endl;9 11}12 void showed(int a[],int i)13 {14 for(int index=0;index<i;index++)15 cout<<""<<a[index];16 }17 void triplevalue(int a[],int i)18 {19 for(int index=0;index<i;index++)20 a[index]*=3;21 }

程序运行结果如图6-7所示。

第8行调用了triplevalue()函数,在第17行该函数体内,通过for语句实现对形参数组a[]的所有元素扩大3倍的目的。返回到调用点执行第9行调用showed()函数,输出的数组array[]元素的值都被扩大了3倍。本例通过使用数组名作为函数形参,达到了形参的改变直接修改相应实参的目的。图6-7 例6-6运行结果

使用数组名作为函数参数需要注意:(1)使用数组名传递地址时,虽然传递的是地址,但是形参与实参的地址类型必须保持一致。(2)形参中数组元素个数没有给定,因此,在函数体中,对数组存取的下标可以为任意值而不会出现编译错误。但是,当这个下标超过了实参数组的个数范围时,存取的就不是实参数组中的内容了。例如:

void func(int array[]){array[8]=5;}int array1[3],array2[12]func(array1); //错误,函数体中array[8]的下标超出array1[3]的范围func(array2); //正确,因为array2的下标范围是0~116.4 字符数组与字符串

C++中的字符串变量不能直接定义和使用,必须通过定义字符型数组或字符型指针来间接完成。字符数组是指用来存放字符型数据的数组。字符数组也可分为一维数组和多维数组。6.4.1 字符数组的定义

字符数组的定义格式与其他数组的相同。例如:

char array[10]; //定义了一维字符数组

char array[1][2]; //定义了二维字符数组

如果整数在0~255之内,那么C++可以将字符型数据与整型数据等同处理,即可以通用,但是两者在内存空间上有一定区别。例如:

char array1[10];

int array2[10];

array1的存储空间是10个字节,而array2的存储空间是40个字节,第2章已经介绍每个int类型的变量占4个字节。6.4.2 字符数组的初始化

1.字符数组的初始化

对字符数组的初始化与其他数组的初始化一样,如果初始值的个数少于数组长度,则没有被赋值的数组元素将被系统自动赋值为空字符(即‘\0’,ASCII码值为0)。

例如:

char array[10]={‘h','e','l',’y’};

array[0]~array[3]的值依次为‘h’、‘e’、‘l’和‘y’,其余的6个数组元素为‘\0’。

2.字符串的初始化

对字符串的处理既可以通过字符数组实现,又可以通过字符串进行初始化。其语法格式为:

char数组名[常量表达式]={“字符串常量”};

例如:

char array1[]=“HELLO”;

char array2[][]={“I”,“LOVE”,“XIAO”,“XIN”};

字符串是以字符‘\0’结尾的依次排列的多个字符序列,因此用字符串初始化字符数组时,‘\0’与前面的字符一起作为字符数组的元素。用字符串初始化数组后,数组名就是字符串的首地址,数组就是一个字符串。而在使用字符串初始化数组时,除非将‘\0’作为一个元素放在初值表中,否则‘\0’不会自动附在初值表中的字符后。因此,一个字符数组不一定是字符串。

用字符串初始化字符数组时,系统会在字符数组的末尾自动加上一个字符‘\0’,因此数组的大小比字符串中实际字符的个数多1。用字符串初始化一维字符数组时,可以省略花括号{}。

3.字符数组的使用

字符数组的使用主要包括按数组元素单个处理使用和按字符串整体处理使用。(1)按单个字符输入输出,例如:

char array[10];

for(int i=0;i<10;i++)

cin>>array[i]; //使用循环语句每次输入一个字符

for(int i=0;i<10;i++)

cout<<array[i]; //使用循环语句每次输出一个字符

但是,当cin遇到空格、跳格、换行符时,将略过不读取。如果必须从输入流中读取这些特殊符号,则可使用cin的成员函数get(),例如:

for(int i=0;i<10;i++)

cin. get(array[i]);(2)将字符串作为一个整体输入输出,例如:

char array[10];

cin>>array; //从键盘输入字符串保存到array

使用cout可以一次性地输出一个字符串。例如:

char array[10]={‘I'‘L'‘O'‘V'‘E'‘\0’‘I’‘\0’};

cout<<name;

输出结果为ILO,系统将第一个‘\0’前面的字符作为一个串整体输出。这种输出必须要求字符数组以‘\0’作为结束标记,否则将出错。

在所有类型的数组中,只有字符串才能进行整体输入和输出操作,其他数组元素的输入和输出只能采用循环的方法逐个操作。【例6-7】从键盘任意输入任意个字符后,对字符排序。/**************************************************程序文件名:Ex6_7.cpp字符数组应用举例**************************************************/1 #include<iostream>2 using namespace std;3 char DATA[100][80];4 void SelectionSort(char DATA[][80],int size);5 void swap(char DATA[][80],int i, int j);6 main()7 {8 int i;9 int n; //n个字符10 cout<<"请输入字符的个数";11 cin>>n;12 fflush(stdin); //清空标准输入的buffer13 for(i=1;i<=n;i++) //输入字符14 {15 cout<<"请输入第"<<i<<"个字符:";16 cin>>DATA[i];17 }18 cout<<"排序前:"<<endl; //排序前的字符19 for(i=1;i<=n;i++)20 {21 cout<<"第"<<i<<"个字符是:"<<DATA[i]<<endl;22 }23 SelectionSort(DATA, n); //排序24 cout<<endl;25 cout<<"排序后:"<<endl; //排序后的字符26 for(i=1;i<=n;i++)27 {28 cout<<"第"<<i<<"个字符是:"<<DATA[i]<<endl;29 }30 }31 void SelectionSort(char DATA[][80],int size)32 {33 int i, j;34 int min; //最小字串的位置35 for(i=1;i<=size-1;i++)36 {37 min=i;38 for(j=i+1;j<=size;j++)39 if(strcmp(DATA[min],DATA[j])>0)40 min=j;41 if(min!=i)42 swap(DATA, i,min);43 }44 }45 void swap(char DATA[][80],int i, int j)46 {47 char tmp[80]; //暂存字串48 strcpy(tmp, DATA[i]);49 strcpy(DATA[i],DATA[j]);50 strcpy(DATA[j],tmp);51 }

程序运行结果如图6-8所示。

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

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载