第3章面向对象程序设计语言基础_第1页
第3章面向对象程序设计语言基础_第2页
第3章面向对象程序设计语言基础_第3页
第3章面向对象程序设计语言基础_第4页
第3章面向对象程序设计语言基础_第5页
已阅读5页,还剩382页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第3章面向对象程序设计语言基础3.1面向对象程序设计语言3.2C++语言基础3.3Java的基础知识3.1面向对象程序设计语言掌握面向对象程序设计,首先应该学习面向对象程序设计语言。当今有很多种编程语言能体现基本的面向对象思想,如C++、Java、Smalltalk、CLOS等,但普遍被推崇的、流行的有两种,即C++和Java。下面将介绍C++和Java的一些基础知识。3.1.1程序程序是有目的的动作序列,它是一种软件。对于程序员来说,程序是以某种语言为工具编制出来的动作序列,它表达了人解决现实世界问题的思想。计算机程序是用计算机程序设计语言所要求的规范书写出来的一系列动作,它表达了程序员要求计算机执行的操作。对于计算机来说,一组机器指令就是程序。我们所说的机器代码或者机器指令,都是指程序,它是按计算机硬件设计规范的要求编制出来的动作序列。在计算机中,程序通常以文件的形式保存。3.1.2语言及其分类语言按其等级可分为高级语言和低级语言。机器语言和汇编语言都是低级语言。程序语言越低级,程序的描写越复杂,越难懂,程序描述的问题越接近机器。最早,程序员使用最原始的计算机指令,即机器语言程序。只有机器语言才能被机器识别和运行,这些指令以一串二进制数表示。不久,发明了汇编语言,它可以将机器指令映射为一些能被人读懂的助记符。以后,各种高级语言迅速发展起来。发展过程中,经历了严酷的优胜劣汰过程,最后剩下一些比较优秀的高级语言。C++作为一种多范型语言,就是一种优秀的高级语言。例如,我们用C++和汇编语言分别来实现表达式a=2b+3c?1,用C++实现为a=2*b+3*c?1;用汇编来实现为

moveax,DWORDPTRb_$[ebp]movecx,DWORDPTRc_$[ebp]muleax,2mulecx,3addeax,ecxdeceaxmovDWORDPTRb_$[ebp],eax语言越低级,就表明越靠近机器;越高级,就表明越靠近人的表达与理解、越自然。程序语言的发展,是从低级到高级,直到可用人的自然语言来描述。程序语言的发展也是从具体到抽象的发展过程。从上面的例子中可以看出,作为高级语言的C++在实现表达式时要比汇编语言简洁得多。3.1.3C++是面向对象程序设计语言正如第1、2章所述,程序设计方法正在从结构化程序设计走向面向对象程序设计。C语言能够很好地支持结构化范型程序设计,而C++能够很好地支持面向对象范型程序设计。在第4、5章中将介绍用C++来实现面向对象范型的程序设计。3.2C++语言基础3.2.1C++的发展史

C++仅有二十几年的历史,它是C语言的扩展和完善。C语言是贝尔实验室的DennisRitchie在B语言的基础上开发出来的。1972年在一台DECPDP-11计算机上实现了最初的C语言。到20世纪70年代末,C已经演化为现在所说的“传统的C语言”。Kernighan和Ritchie在1978年出版的《TheCProgrammingLanguage》一书中全面地介绍了传统的C语言,1989年美国国家标准协会制定了C语言的标准(ANSIC)。Kernighan和Ritchie编著的第二版《TheCProgrammingLanguage》(1988年版)介绍了ANSIC的全部内容。至此,C语言以其如下独有的特点风靡了全世界:(1)语言简洁、紧凑,使用方便、灵活。C语言只有32个关键字,程序书写形式自由。(2)丰富的运算符和数据类型。(3)C语言可以直接访问内存地址,能进行位操作,能够胜任开发操作系统的工作。(4)生成的目标代码质量高,程序运行效率高。(5)可移植性好。

C语言盛行的同时,也暴露出了其局限性:(1)类型检查机制相对较弱,这使得程序中的一些错误不能在编译时发现;(2)C本身几乎没有支持代码重用的语言结构,因此一个程序员精心设计的程序很难为其他程序所用;(3)当程序的规模达到一定的程度时,程序员很难控制程序的复杂性。为满足管理程序复杂性的需要和克服C语言的以上缺点,就必须对C语言进行完善或以一种新的语言来代替C语言,C++就是在这种环境下应运而生的。1980年,贝尔实验室的BjarneStroustrnp开始对C进行改进和扩充。最初的成果称为“带类的C”,1983年正式取名为C++。在对C++进行了三次修订后,于1994年制定了ANSIC++标准的草案,以后又经过不断完善成为目前的C++。C++仍在不断发展中。

C++包含了整个C,C是建立C++的基础;添加了对面向对象程序设计(OOP)的完全支持。下面介绍在用C++来实现面向对象程序设计中,必须掌握的C++的关于支持面向对象方面等的语法基础。对C缺乏了解的读者,建议在本书之外,尽快浏览一下C语言的有关内容。3.2.2C++基础语法1.关键字关键字是预先定义好的标识符,这些标识符在C++编写程序的时候,有特殊的含义。在C++中共有61个关键字。在程序设计的过程中应注意,程序中用到的其他名字不能与这些关键字有相同的拼法(包括大小写都不能相同)。表3-1给出了C++中定义的关键字。表3-1C++中定义的关键字staticautobreakunsignedprotectedconstuniondefaultdoubleelseexplictcontinuefloatforgotointlongregistervolatileshortsignedsizeofstructswitchtypedefoperatorcasevoidreturnboolcatchclassconst_castdeleteenumfalseinlinemutablenamespacedynamic_castnewexternprivatepublicthistemplatereinterpret_castthrowturetrytypenameusingvirtualstatic_castwchar_tdoifwhilefriendchartypeid

2.基本数据类型在程序中要用到数据,数据常以变量或常数的形式来描述。每个变量或常数都有数据类型。变量是存储信息的存储单元,它对应于某个内存空间,用变量名代表其存储空间,程序能在变量中存储和取出值。数据类型用来告诉编译器应为变量分配多少内存空间、怎样组织已分配的内存空间,以及变量中要存储值的类型。数据类型分为基本数据类型和非基本数据类型。非基本数据类型包括数组、指针和结构等类型。基本数据类型有char、int、float、double、wchar_t和bool等。图3-1描述了C++的数据类型的分类。图3-1中的type表示非空的数据类型,是程序员可以自己定义的数据类型。在图3-1中只是给出了常用数据类型和部分数据类型修饰符,它们用来改变基本类型的意义,用于适应各种情况的需要。修饰符有long(长型符)、short(短型符)、signed(有符号)和unsigned(无符号)等。数据类型修饰符确定了该数据所占内存空间的大小和可表示的数值范围。表3-2是16位计算机中各数据类型所能表示的数值范围。图3-1C++的数据类型表3-2常用基本数据类型描述类型说明长度(字节)表示范围备注char字符型1-128~127-27~(27-1)unsignedchar无符号字符型10~2550~(28-1)signedchar有符号字符型1-128~127-27~(27-1)int整型2-32 768~32 767-215~(215-1)unsignedint无符号整型20~65 5350~(216-1)signedint有符号整型2-32 768~32 767-215~(215-1)shortint短整型2-32 768~32 767-215~(215-1)longint长整型4-2 147 183 648~2 147 483 647-231~(231-1)float浮点型(单精度)4-3.4×1038~3.4×10387位有效位double双精度8-1.7×1038~1.7×103815位有效位longdouble长双精度16-3.4×104932~3.4×10493219位有效位3.变量定义1)变量名的命名C++中变量的命名应该遵守以下规则:(1)不能是C++关键字;(2)第一个字符必须是字母或下划线;(3)不要太长,一般不超过31个字符;(4)不能以数字开头;(5)中间不能有空格;(6)变量名中不能包含“.;,"'+?'之类的特殊符号。(7)变量名不要与C++中库函数名、类名、和对象名相同。2)变量定义方式在给变量命名时,通常用带有描述性的字符串,例如用area命名一个面积变量,用time命名时间变量。这些变量在应用时,程序员和读者对其意义一目了然。例如:

intnumber;doublesum;floatlength;

3)变量赋值与初始化用赋值运算符“=”给变量赋值。变量初始化是指在定义变量时直接给变量赋值,也可以先定义变量,然后用赋值语句给其赋值。并不是所有的变量都需要初始化,未初始化的变量在编译器编译时并不会出错。例如:

unsignedshortwidth;doublearea,radius=23;width=4;

其中,变量width先定义后赋值;而变量radius在定义时初始化;变量area未初始化。这些变量在编译时均不会出现错误。4)typedef

用typedef可以为一个已有的类型名提供一个同义词,该同义词可以代替该类型在程序中使用。用法是以typedef开始,随后是要表示的类型,最后是同义词和分号。typedef实际上没有定义一个新的数据类型,在建立一个typedef类型时没有分配内存。例如:

typedefdoubleprofit;//定义double的同义类型

typedefintINT,integer;//定义int的两个同义类型

INTa; //同inta;integera; //同inta;profitd; //同doubled;3.2.3常数1.实数常数实数常数的表示方法有两种:(1)十进制小数。它由数字和小数点组成,如0.123,0.234,0.0等都是十进制数。(2)指数形式。如:425e4或425E4表示425×104。要注意E或e的前面必须是数字,且E或e后面的指数必须为整数。实数型常数常分为单精度(float)、双精度(double)和长双精度(longdouble)3类。在16位计算机中,float型数据在内存中占4个字节,double型数据在内存中占8个字节,longdouble数据类型在内存中占16个字节。float型提供7位有效数字,double型提供15位有效数字,longdouble提供19位有效数字。在C++中,一个实数如果没有说明,则其类型为默认类型double型。要表示float型数,则必须在实数后面加上F或f;要表示longdouble型数,则必须在实数后面加上L或l。例如:3.2f //float型实数3.2L

//longdouble型实数3.2 //没有说明,故为默认类型double型2.字符常数字符常数是用单引号括起来的一个字符,它分为一般字符常数和特殊字符常数。一般字符常数有a、x等字符,特殊字符常数是以“\”开头的字符。表3-3列出了常用的特殊字符。表3-3常用的特殊字符字符形式值功能\a0x07响铃\n0x0A换行\t0x09制表符(横向跳格)\v0x0B竖向跳格\b0x08退格\r0x0D回车\\0x5C反斜杠字符“\”\"0x22双引号\'0x27单引号\ddd

1~3位八进制\xhh

1~2位十六进制表中列出的字符又称为转义字符,即将反斜杠后面的字符转变成另外的意义。有些是控制字符,如“\n”;有些字符是在该字符前加转义字符来表示的,如“\”、“'”、“"”。在内存中,字符数据以ASCII码存储,即以整数表示,所以字符数据和整型数据在0~255(一个字节范围内)之间可以相互赋值。只要注意其表示的范围合理即可。例如:

inta='b'; //正确,将整型变量赋一个字符值(b的ASCII值为98)

charc=97; //正确,将一个字符变量赋一个整型值(97代表的ASCII字符是a)

在下列C++语句中,将自动根据变量定义时的数据类型,来确定输出的究竟是字符类型值还是整型类型值。例如:

cout<<a<<endl; //输出字符b的ASCII值98

cout<<c<<endl; //输出ASCII值97的字符b

上面两行语句自动根据a或c被定义的数据类型确定其输出结果及类型。3.字符串常数字符串常数是由一对双引号括起来的字符序列,字符串常数和字符常数是不同的,字符串常数总是以“\0”结束。如果一个字符串常数为“welcome”,那么它在内存中占用连续8个内存字节。在编程时应注意变量类型的匹配,不能将字符串常数赋给字符变量。4.枚举常数枚举常数是通过关键字enum来定义的。定义的格式为以enum开始,后跟类型名和花括号。花括号内是要定义的枚举常数(又称为助记常数,该助记常数往往代表一个整型常数值),各常数之间用逗号分隔,最后以分号结束。例如:enumCOLOR{RED=150,BULE=200,GREEN,WHITE=400};

其中,有一个枚举常数BLUE,代表200;GREEN没有被显式写出,在编译时其值自动被确定为201。如果没有显式地为枚举常量确定值,C++会为其自动确定,即花括号后的第一个枚举常数会被确定为0,其后的每个枚举常数的值为前一个枚举常数值加1。在下面程序中,企图用赋值语句给枚举变量赋值为不是定义枚举类型时规定的值,是不正确的。

COLORpaint=BULE; //正确

piant=200;//错误,因为200不是规定的枚举类型常数5.常量定义

C++中,常量是代表固定值的变量,即表示变量的值已被固定。程序中如果想让变量内容初始化后一直保持不变,则可以将其定义成一个常量。本书对常量与常数进行了区分(这与其他书有所不同),常数代表了书写时的字面含义。常量定义的格式是:在关键字const后接变量名,然后是常数值,最后以分号结束该语句。例如:

constfloatpi=3.1415926;在定义了常量pi之后,就可以在程序中用pi来代替3.1415926。常量不能放在赋值语句的左边,即常量不能被赋值。常量定义中初始化的值可以是一个由常数和操作符构成的表达式。在程序运行之前就已经知道了常量值,因此编译时就能求值。注意:该表达式中不能含有函数,例如:

constintsize=20*sizeof(int); //oksizeof是运算符

constintnumber=max(23,34); //错误6.引用引用是变量的别名,用&符号来声明。对别名的存取就是对变量的存取。别名没有存储空间,对引用声明的同时需对其进行初始化。例3.1引用实例。intx=10;int&y=x; //声明y是x的引用cout<<y<<endli; //y=10x=20;cout<<y<<endl; //y=20;y=30;cout<<x<<endl; //x=30使用引用比C语言中用指针来实现两数交换容易理解且语句简洁。例如:voidswap(int&xx,int&yy){inttemp;temp=xx;xx=yy;temp=xx;}voidmain(){inta=10,b=20;cout<<a<<"--"<<b<<endl;swap(a,b);//直接用a、b作为实参,而C语言用指针实现交换时,要用其地址

cout<<a<<"--"<<b<<endl;}3.2.4输入/输出1.输入/输出(I/O)的书写格式

I/O流是输入或输出的一系列字节。当程序需要在屏幕上显示输出时,使用操作符“<<”向cout输出流中插入字符;当程序需要执行从键盘输入时,使用操作符“>>”从cin输入流中输入字符。

例3.2I/O书写格式实例。#include<iostream.h>voidmain(){charch;cout<<"inputthecharacteryouwanttoprint:"<<endl;cin>>ch;cout<<ch<<endl;}程序运行结果为:

inputthecharacteryouwanttoprint:aa

cout代表I/O的输出对象(默认为屏幕),在例3.2中它输出一行提示语句;cin代表I/O的输入对象,在例3.2中从键盘输入了一个字符a,最后我们有一行输出语句输出所输入的字符。头文件iostream.h包含涉及cout和cin的一些必要的定义内容。在输入和输出时还可以使用控制符来控制输入/输出的格式,以满足输入/输出的需要,表3-4中列出了常用输入/输出控制符。表3-4常用输入/输出控制符控制符描述dec置基数为10hex置基数为16oct置基数为8setfill设置填充字setiosflags(ios::fixed)固定的浮点显示etw设置小数精度setiosflags(ios::left)左对齐setiosflags(ios::right)右对齐setiosflags(ios::scientific)指数表示setiosflags(ios::skipws)忽略前导空白setiosflags(ios::uppercase)十六进制数大写输出setiosflags(ios::lowercase)十六进制小写输出3.2.5表达式和语句程序是一些按次序执行的语句,而大部分的语句是由表达式构成的。表达式和语句是C++的重要内容。表达式由操作符、操作数构成,其目的是用来说明一个计算过程。在C++中操作符的优先级和结合性等规则与C语言是完全一致的,使用方法与C语言中表达式的使用方法也是相同的。

C++中所有的操作运算都是通过表达式来实现的。由表达式组成的语句称为表达式语句,由一个表达式和一个分号组成。大多数表达式语句是赋值语句和函数调用语句。语句用来规定程序执行的控制流。复合语句又被称为块,它是一对花括号内的语句序列。C++中的语句和C语言中的语句是大致相同的,其使用方法也是一样的,在本书中不再赘述。下面对过程化语句进行介绍。1.过程化语句

语句按功能可分为两大类:第一大类用于描述计算机执行的操作运算,即操作语句;第二大类用于控制操作运算的执行顺序的控制语句,即流程控制语句。流程控制语句也被称为过程化语句。图3-2while语句的流程结构1)while语句

while语句由4个部分组成:循环变量初始化、继续条件、循环体、改变循环变量的算法。while用于判断一个条件表达式,当条件成立的时候进入循环体,不满足这个条件则不进入循环体。while语句的流程结构如图3-2所示。例3.3while语句应用实例。voidmain(){intj=1;intsum;while(j<=50){sum=sum+j;j++;}cout<<"sum="<<sum<<endl;}2)do-while语句do-while语句的格式如下:do循环体while(条件表达式);当流程到达do后,立即执行循环体语句,然后对条件表达式进行测试,如满足条件,则重复循环,否则退出,该语句结构至少使循环体执行一次。do-while语句的流程结构如图3-3所示。图3-3do-while语句的流程结构例3.4do-while语句应用实例。#include<iostream.h>voidmain(){intj,sum=0;j=1;do{sum=sum+j;j++;}while(j<=50)

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

例3.3和例3.4都是求sum=1+2+…+50的和的程序,它们使用了不同的循环控制语句,但结果是相同的。3)for语句for语句也可以用来控制循环,for语句的格式如下:for(表达式1;表达式2;表达式3)循环体for语句的执行过程如下:(1)求解表达式1;(2)求解表达式2,若为0则结束循环转到(5);(3)若表达式2为真,则执行循环体,然后求解表达式3;(4)转回(2);(5)执行for语句下面的语句。图3-4for语句的流程结构

for语句的流程结构如图3-4所示。在使用for语句时,并不是每个表达式都必须赋值,并且每个表达式都可以省略,但在省略表达式时,应该注意一些问题:在省略表达式1时,控制变量应该在使用之前的程序中被初始化;省略表达式2,即不进行判断而使循环不停的进行下去,这样在循环体中需要有跳出循环的控制语句,否则就会出现死循环;在省略表达式3时,应该注意必须另外设法使循环控制变量变化,以保证循环能正常结束。不仅每个表达式可以单独省略,而且它们可以同时省略。在省略表达式时,应注意表达式后面的分号不能省略。例3.5for语句应用实例。i=0;for(;i<=10;) //分号不能省略sum+=i++; //求值的同时改变循环变量//省略所有的表达式i=0;for(;;){sum+=i++; //求值的同时改变循环控制变量if(i>10) //控制结束条件break; //当i大于10时循环结束,跳出循环体}在本例中省略了表达式1和表达式3。4)switch语句当要实现多分支选择语句时,就要用到switch语句。switch的格式如下:switch(表达式){case常量表达式1:执行语句1case常量表达式2:执行语句2

case常量表达式n:执行语句ndefault:执行语句n+1}

switch后面括号中的表达式只能是整型、字符型或枚举类型。case后面的常量表达式必须与表达式匹配,如果不匹配,则在编译时会出现错误。当表达式的值与某一个case后面常量表达式的值相等时,就执行case后面的语句,若没有与之匹配的常量表达式,则执行default后的执行语句。例3.6switch语句应用实例。#include<iostream.h>voidmain(){chargrade;cin>>grade;switch(grade){case'A':cout<<"85-100"<<endl;break;case'B':cout<<"70-84"<<endl;break;case'C':cout<<"60-69"<<endl;break;case'D':cout<<"<60"<<endl;break;default:cout<<"error"<<endl;}}在本例中,当输入一个字符时如果它与A、B、C、D中的任何一个匹配,则输出其后对应的语句;若不能与它们之中的任何一个匹配,则输出default后的语句,即输出出错提示error。请读者自行分析break语句的作用。现在我们已经了解了C++的基本语法,下面具体分析几个例子。例3.7判明素数并输出100内中所有的素数。在判断一个数m是否是素数时,就是检验它是否能被2~中的任意一个整数整除。如果存在一个数在范围2~内且能整除m,则m不是素数;否则,m是素数。#include<iostream.h>#include<math.h>voidprime(longm){intsqrtm;sqrtm=sqrt(m);for(inti=2;i<=sqrtm;i++)if(m%i==0)break;if(sqrtm<i)cout<<m<<endl;elsecout<<m<<"itisnotaprime"<<endl;}voidmain(){longm;cout<<"inputthenumberm:"<<endl;cin>>m;

prime(m);for(intj=1;j<=100;j++)prime(j);}

函数prime()用来判断一个数是否是素数。如果是,则输出该数;如果不是,则输出“itisnotaprime”。在寻找1~100内中的素数时,我们用了一个循环语言来控制输入的数为1~100,然后判断每个数是否为素数。例3.8猴子吃桃问题。猴子第一天摘下若干桃子,当即吃了一半,还不过瘾,又多吃了一个。第二天早晨又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下来的一半又多一个。这样,到第10天早晨想再吃时,发现只剩下一个桃子了,问猴子第一天摘了多少个桃子。这个问题用程序能很轻易的解决,只需要用一个循环语句构成的函数就可以实现。#include<iostream.h>intfn(intm){m=2*m;m++;returnm;}voidmain(){floatsum=1;for(inti=1;i<10;i++)sum=fn(sum);cout<<sum<<endl;}输出结果为1023,这和用手工计算得到的值是一致的。在解决这个问题时,考虑到从第10天的桃子数起,以后每天的桃子数是后一天的2倍多一个,这样就能用循环语句方便地解决这个问题。3.2.6函数

要编好程序,就要合理地划分程序中的各个程序块,即函数。C++程序是由函数组成的,一个C++程序由一个或若干个函数构成,程序中的函数在程序运行时被调用,虽然main()也是函数,但它并不是普通的函数,它表示程序的入口。程序命令按照它们在源代码中出现的顺序一行行地顺序执行,直到遇到新的函数调用,然后程序分支去执行函数调用。当函数调用完成时,程序控制立即返回调用函数下一行代码。图3-5C++程序中函数调用层次图在C++中,一个函数必须在声明后才能使用。函数声明的作用是告诉编译器,该函数是存在的。以后编译器在遇到该函数被调用时就不会出错了,同时编译器还对函数调用进行正确性检查。C++函数声明总是由函数原型构成的。

C++程序中函数调用的层次如图3-5所示。1.函数原型

C++中的函数分为两种:标准库函数和用户自定义的函数。标准库函数在使用前可以用包含指令#include包含在头文件中,而用户自己定义的函数在使用前必须先声明,即在使用前说明函数的原型。函数原型是一条程序语句,必须以分号结束。它由函数返回类型、函数名和参数构成,其形式为返回类型function(参数表);参数表包含所有参数的数据类型,各参数之间用逗号分开。函数原型与函数定义在返回类型、函数名和参数表上必须完全一致。函数的返回值也称为函数值,返回的不是函数本身而是一个值。我们经常用return语句来返回函数的值。在使用函数时会涉及到变量的使用,在C++中变量的作用域和使用规则与C语言中是完全相同的,本书中不再赘述。2.函数的调用机制

C++中函数的调用过程就是栈空间操作的过程。在函数调用时,C++会进行如下操作:(1)建立被调用函数的栈空间;(2)保护调用函数的运行状态和返回地址;(3)传递参数;(4)将控制转交给被调函数。例3.9函数调用实例。voidarea(int,int);voidsum(float,float);voidmain(){inta=4,b=5;area(a,b);sum(a,b);}在主函数调用两个函数之前,先对这两个函数的原型进行了声明。主函数中调用了两个函数,其调用过程实际上就是一个对栈的操作过程。其他函数调用如递归调用在C语言中也有详细的叙述。关于函数还应注意:任何函数之间不能嵌套定义,调用函数与被调用函数相互独立,被调用函数和调用函数之间是靠参数传递和返回值来联系的,函数被看作为一个黑盒。3.内联函数函数调用需要建立栈内存环境,进行参数传递,并产生程序执行转移。这些工作都需要一定的时间开销,若多次调用一个代码很短的函数,就会使程序的运行效率降低。为解决这个问题,应该使用内联函数。内联函数也称为内嵌函数,是能提高程序的效率的一种特殊函数。内联函数用关键字inline来声明,其具体的形式如下:

inline返回类型函数名(参数表){//函数体}在编译时编译器看到关键字inline后就为该函数创建一段代码,在以后每次调用该函数时,都会用这段代码来替代相应的函数。内联函数可以在一开始仅声明一次。内联函数中不能含有复杂的结构控制语句,如switch和while,若含有这些语句,在编译时会将该函数视为普通函数一样产生函数调用代码;内联函数不能用作递归函数来使用;内联函数的返回值类型总是整型。例3.10内联函数举例。#include<iostream.h>inlineintisnumber(charch){return(ch>='0'&&ch<='9')?1:0;}voidmain(){charc;while((c=cin.getc())!='\n') //cin.getc()是用于调用输入对象的成员函数{if(isnumber(c)) //调用一个小函数cout<<"youenteredadigit"<<endl;elsecout<<"youenteredanon-digit"<<endl;}}3.2.7函数的重载在C语言中,每个函数都必须有惟一的名字。为功能近似的问题定义函数时,不得不为这些既近似又有区别的问题定义多个函数,为这些函数起不同的函数名。C++允许这些不同的函数使用相同的函数名。在函数调用时根据给定的实际参数自动匹配,究竟该调用哪一个函数。其匹配是严格按照这些函数定义时的参数类型、个数、顺序来确定的。例3.11函数重载举例。intabs(int);doubleabs(double);longabs(long);main(){abs(5); //自动匹配调用intabs(int)函数}这三个函数具有相同的名字,但它们的参数类型不相同。重载函数的匹配顺序是靠将实参类型与所有被调用的具有相同函数名的函数的形参类型一一比较来判定的。它们在匹配时按以下的顺序来寻找被匹配的函数:(1)寻找一个严格的匹配,如果找到了,就用那个函数;(2)通过内部转换寻求一个匹配,只要找到了,就用那个函数;(3)通过用户定义的转换寻求一个匹配,若能查出有惟一的一组转换,就用那个函数。在大多数情况下,函数的匹配是用寻找一个严格的匹配进行的。对于其它匹配,本书不述及。在匹配函数时,如果调用函数的实际参数与所有的重载函数在参数类型、参数个数、参数顺序上都不相同,则认为不能匹配。编译器无法分辨两个完全一样的重载函数,因此重载函数应该在参数个数、参数类型或参数顺序上有所不同。

C++是C的完善,因此C++继承了C的许多知识点。数组、指针、引用、结构体以及它们的使用范围和使用方法,在C++中与C中是完全相同的。

C++除继承C的所有知识外还有其自身的特点。正如前面章节所述及的,C++是支持多范型的语言。下面重点从实现面向对象的三个主要特征来介绍C++的其他内容。这三个主要特征为:(1)封装;(2)继承;(3)多态。3.2.8C++中的类学习了第1、2章,对类的概念已经有了一个基本的了解,现在我们详细描述C++类的语法,以及对类的一些简单使用。

1.类的建立及其成员

C++中的类与C语言中的结构体相似,类的定义方法如下:

classClassname

{//数据成员//成员函数

};其中,class为关键字,Classname为类名,在定义一个类时关键字和类名是必不可少的,一般用具有典型意义的名字来命名类。例如,定义一个学生类,可以用Student来命名。为编程的需要我们可以将类的成员定义成不同的存取属性(public、private、protected),在后续章节我们将具体介绍各类存取属性的使用范围和使用方法。下面为大家介绍类的成员定义和类的作用域等。类似于函数声明,类也有声明,其形式如下:

classStudent;2.成员的定义在类中可以定义其数据成员和成员函数,下面介绍类的成员的定义和使用。例3.12类的成员的定义和使用实例。#include<iostream.h>classStudent{

public:floatagre(floatmath,floatphy,floatEnglish,floatchinese){agv=(math+phy+English+chinese)/4;returnagv;}private:floatmath;floatphy;floatEnglish;floatchinese;floatagv;};在该例中我们定义了一个学生类,在类中又定义了一个成员函数agre()和5个成员变量。在定义成员函数和成员变量时,使用了两个关键字public和private,它们分别将成员函数声明为公有成员,将数据成员声明为私有成员。在定义类的成员时,根据我们的需要可以用关键字private、public和protected来声明成员的存取属性。被private声明后的成员,称为私有成员,由该关键字声明的成员仅被该类的成员函数调用;被protected声明后的成员,称为被保护成员,该成员除了能被该类的成员函数使用外,还有其他的用途;被public声明后的成员,称为公有成员,由它声明的成员不仅能被该类的成员函数调用,而且还能被类外的函数调用。private声明的成员和protected声明的成员在这里没有太大的区别,但在继承中有较大的差别(详见后续内容)。3.类的作用域类的作用域是指类定义时一对花括号所形成的作用域。在该范围内,类的成员对同一类的成员函数具有访问权。类的所有成员位于这个类的作用域内。类的所有成员是一个相关的整体,而类外的函数访问类的作用域的成员时受到了限制。这种思想是把一个类的数据结构和功能封装起来,从而使得在类外访问该类时,必须使用专用的接口(接口在这里指类中被public声明为公有的成员函数)。例3.13类的作用域举例。classX{public:voidfn1(){n++;};voidfn2(){fn1();};intfn(){returnn;};private:intn;};4.类的封装通过第1、2章的学习,我们了解了封装的概念,下面用一个具体的实例来介绍封装是怎样实现的。例3.14对“点”(Point)的封装。

classPoint //Point的类定义{

public: //外部接口voidset(doubleix,doubleiy);doublexoffset();doubleyoffset();doubleangle();doubleradius();private: //内部数据doublex; //横坐标doubley; //纵坐标};//Point类的成员函数定义#include<math.h>#definepi3.1415926voidPoint::set(doubleix,doubleiy){x=ix;y=iy;}doublePoint::xoffset(){returnx;}doublePoint::yoffset(){returny;}doublePoint::angle(){return(180/pi)*atan2(y,x);}doublePoint::radius(){returnsqrt(x*y+y*y);}每个成员函数前都加了类名。要使用这段代码,应包含两个头文件,一个是point.h,因为所有成员函数的声明都在类的定义中;另一个是math.h文件,因为在成员函数的定义中调用了math.h中声明的函数。在本例中我们将数据和操作结合,构成了一个不可分割的整体,即类。用这个类可生成具有不同属性值的对象。类中的一些数据成员是私有的,它们被有效的屏蔽,外界不会对其产生干扰和误操作;另一些成员(成员函数)是公共的,它们作为接口提供给外界使用。5.构造函数和析构函数在OOP中,凡是创建的对象都需要作某种形式的初始化。为此需要构造函数(ConstructorFunction),供创建类的实例对象时调用,用以自动完成对象的初始化。析构函数(DestructorFunction)是与构造函数相对应的一个概念,它用于释放对象定义时通过构造函数向系统申请的存储空间以及有关的系统资源,它是在其有效范围内自动调用的。下面通过一个例子认识构造函数和析构函数。例3.15构造函数和析构函数举例。classStack{chararray[size];int*tos;~Stack();public:Stack(); //这是一个构造函数viodpush(char);charpop(cahr);};Stack::Stack()//说明函数所属的类且与类名相同{cout<<"constructingastack"<<endl;tos=newint[10]; //分配堆空间}Stack::~Stack(){cout<<"destructingstack"<<endl;deletetos; //释放堆空间}通过该例我们对构造函数和析构函数有了一个初步的认识,下面再来对它们进行详细的讨论。1)构造函数的特性构造函数是类的一个特殊的成员函数,因此它除具有一般成员函数的受访问限制、可重载等性质外,还具有其自身的许多的特性:(1)构造函数的名称与它所属的类名相同,在类对象被创建时自动调用,它不能用常规的方法调用,不能被继承;

(2)构造函数无返回类型(任何返回类型,包括void,都是非法的),不能带有return语句;(3)一个类可以具有多个构造函数,重载时参数的个数或类型应不一样,两个具有相同参数类型和个数的构造函数是非法的;(4)如果在编程时类中没有显式的定义构造函数,在编译时系统会自动的生成一个不带任何参数的构造函数;(5)构造函数在调用时,控制成员变量的内存分配,为定义的对象向系统申请内存。

2)构造函数的设计与使用类中的私有成员一般不作初始设置,需要在定义对象时初始化。采用显式调用成员函数初始化对象容易遗漏,造成对象无意义,因此,使用构造函数对对象进行初始化是最佳选择。同时,一个类的不同对象需要有不同的初始化,因此可以采用带参数的构造函数和多构造函数来解决这个问题。下面讨论几种构造函数。(1)带参数的构造函数。例3.16带参数的构造函数举例。classCourse{floatx,y;floatGDP;public:Course(ifloatox,floatoy);intGDP(floatx,floaty);};Course::Course(intx,inty){//以参数初始化变量

x=ox;y=oy;};intCourse::GDP(intx,inty){GDP=(x+y)/2;}voidmain(){CourseS(90,85);//定义对象时初始化S.GDP(90,85);}如果在定义对象S时不带参数,如CourseS;,那么,构造函数就不能对x、y赋初值。构造函数定义了缺省值,在函数调用时,若无特别指定参数,便以缺省值作初始化,而且也可以防止遗漏初始赋值。缺省参数的构造函数的用法与一般函数的用法相同。例如将上述函数改为带缺省参数的构造函数如下:classCourse{floatx,y;floatGDP;public:Course(intox=90,oy=85){x=ox;y=oy;}

};(2)多构造函数(构造函数重载)。对于一个类的不同对象,当其需要不同类型和不同个数的初始化参数时,可以在一个类中定义几个构造函数,以适应不同的情况。例3.17多构造函数举例。

classCourse{public:Course();Course(float);Course(char,float);};voidmain(){Courseob1;Courseob2(78.5);Courseob3('M',87.5);}(3)复制构造函数(CopyConstructor)。复制构造函数是一种特殊的构造函数,它除了具有一般构造函数所具有的性质外,还具有一些特殊的性质:①具有将参数传输对象按位拷贝的功能。②编译系统在编译类时,产生一个缺省拷贝构造函数,例如,在main()中的语句

Courseob1('M',65.5);Courseob2(ob1);

对定义对象ob2,编译系统自动调用缺省拷贝构造函数,实现对象的按位拷贝。ob2与ob1完全相同,但它们是两个完全独立的对象。③程序员也可以自己定义拷贝构造函数,定义格式如下:

classname(constclassname&obj){…}

其中,obj是引用对象,可以用于初始化其他对象,若有其他参数,则在此后列出,若缺省,则这个引用对象不能省;const是习惯上的限定词(即习惯上将被拷贝对象声明为不可改写),obj对象也可以不是const型。例3.18拷贝构造函数举例。classCourse{charx;flaoty;public:Course(charox,floatoy){x=ox;y=oy;}Course(constcourse&M){x=M.x;y=M.y;}

};voidmain(){Courseob1('M',67.5);Courseob2(ob1);}注意:如果类中包含有指针成员,那么就要慎用缺省的拷贝构造函数,因为编译器提供的拷贝构造函数只是简单的按位拷贝成员数据,它会使两个对象的指针指向同一片内存区域,而不是复制指针指向的内容。若该内存区是动态分配的,编程者有义务释放它,但在释放时会遇到麻烦,因为其中一个指针被释放后,另一个指针所指的区域就失去了意义,这可能会导致严重的后果。3)析构函数的特性和用法析构函数是用于当对象离开其有效范围时,释放对象所占用的内存空间的一个特殊函数。析构函数也是成员函数,它是与构造函数相对应的,命名时在构造函数名前加“~”。析构函数只能是无返回类型,不能带任何参数,不能被重载,一个类只允许有一个析构函数。析构函数的定义一般是由一系列的delete组成的。例3.19析构函数举例。#include<iostream.h>#include<string.h>classsample{private:char*pointer_char;public:sample(){pointer_char=NULL;};sample(char*str){pointer_char=newchar[strlen(str)+1];strcpy(pointer_cahr,str);};sample(sample&ob){pointer_char=newchar[strlen(ob.pointer_char)+1];strcpy(pointer_char,ob.poiner_char);};~sample(){if(pointer_char!=NULL)deletepointer_char;};};3.2.9写C++类代码的相关基础常识写类代码(主要考虑封装性)会涉及到许多C++的语法知识细节,本节叙述写类代码时应该理解的其中一些基础C++知识。对于在C++中表现继承性和多态性的内容,在后几节将进行讨论。这些是面向对象程序设计的基础,要成为一个好的程序员,应对其有一定了解。1.const对象和const成员函数对对象的最低访问权是良好软件工程的一项基本原则。下面讲述的就是这一原则在对象上的应用。某些对象是需要修改的,而有些对象是不需要修改的。可以在程序中用关键字const指明那些不允许修改的对象,任何试图修改这种对象的尝试都会导致编译错误。例如:

constTimenoon(12,0,0);

声明了类Time的一个const对象,并把该对象初始化成时针为12点的时间。用const声明某个对象可强制实现最低访问权原则。如果程序员不小心修改了这种对象,编译器会发现这种错误,从而避免运行时的错误。尽管有的编译器允许下列错误存在,但考虑到数据封装的因素,在设计程序时应尽量避免下列错误:(1)定义一个const成员函数,但该函数又修改了对象的数据成员;(2)定义一个const成员函数,但该函数又调用了非const函数;(3)const对象调用非const成员函数;(4)试图修改const对象。

const函数的声明或定义规定必须使用关键字const,该关键字插在函数参数列表之后。例如,如下的成员函数被声明为const成员函数,它仅仅返回对象的一个数据成员的值:

intgetVuale()const{returnprivateDataMember;}如果在类的定义外部定义const成员函数,那么该成员函数的定义也必须包含关键字const。

可以把非const成员函数重载为const成员函数。编译器根据对象在声明时是否使用了const来自动确定调用哪一个重载函数。由于为了能够正确地初始化对象,构造函数必须允许修改对象,析构函数也必须要能够做一些撤销对象前的清理工作,因此,const对象的构造函数和析构函数不要用关键字const声明。例3.20把不修改对象值的成员函数都声明成const。classTime{public:Time(int=0,int=0,int=0);//默认构造函数//"set"函数voidsetTime(int,int,int); //设置时间voidsetHour(int); //设置hour的值voidsetMinute(int); //设置minute的值voidsetSecond(int); //设置second的值//"get"函数intgetHour()const;//返回hour的值intgetMinute()const; //返回minute的值intgetSecond()const;//返回second的值//输出函数(通常被声明const成员函数)voidprintMilitary()const; //输出军用格式的时间voidprintStandard()const; //输出标准格式的时间private:

inthour;//0—23intminute;//0—59intsecond;//0—59};2.复合一个类可以把其他类的对象作为自己的成员,这就叫做复合。复合是软件重用的一种形式。在建立对象的时候,其构造函数自动被调用。如果一个对象有多个成员对象,那么这些成员对象应该以它们在该对象中的定义顺序建立。但是,在编写程序的时候,不应该编写依赖于构造函数调用顺序的代码。下面的程序用类Employee和Date演示了怎样把对象变为其他对象的成员。类Employee包括私有数据成员lastName、firstName、birthDate和hireDate。成员birthDate和hireDate是类Date的对象,它们包括私有数据成员month、day和year。程序建立了类Employee的一个对象,初始化并输出了对象的数据成员。注意构造函数头部的语法形式:

Employee::Emplooyee(char*fname,char*lname,intbmonth,intbday,intbyear,inthmonthinthday,inthyear):birthDate(bmonth,bday,byear),hireDate(hmonth,hday,hyear)

这个构造函数有8个参数(fname,lname,bmonth,bday,byear,hmonth,hday和hyear)。头部的冒号用于把成员初始化值和参数列表分开。初始化值是Employee的构造函数中传递给成员构造函数的参数。因此,bmonth、bday和byear传递给了birthDate的构造函数,hmonth、hday和hyear传递给了hireDate的构造函数。多个成员初始化值之间是用逗号分隔的。1)类Date的定义classDate{public:Date(int=9,int=1,int=2003); //默认构造函数voidprint()const; //按格式"月/日/年"输出日期private:intmonth; //1-12intday; //1-31(取决于月份)

intyear;

//任意年//根据年份和月份测试天数是否正确的工具函数

intcheckDay(int);};2)成员对象初始化值的用法//构造函数:检查月份值的正确性//调用工具函数checkDay核对天数的正确性Date::Date(intmn,intdy,intyr){month=( mn>&&mn<=12)?mn:1;//检验正确性

year=yr; //也可以在这里检验正确性

day=checkDay(dy) //检验正确性

cout<<"Dateobjectconstructorfordate";print();cout<<endl;}3)检验天数正确性的工具函数intDate::checkDay(inttestDay){staticintdaysPerMonth[13]={0,31,28,31,30,31,30,31,30,31,30,31};if(month!=2){if(testDay>0&&testDay<=dayPerMonth[month])returntestDay;}else{//检查是否是闰年intdays=(year%400==0||year%4==0&&year%100!=0)?29:28);if(testDay>&&testDay<=days)returntestDay;}cout<<"Day"<<testDay<<"invalid.Settoday1.\n";retrun1; //在值有误的情况下让对象处于稳定状态}4)以“月/日/年”的形式输出对象DatevoidDate::print()const{cout<<month<<'/'<<day<<'/'<<year;}5)成员对象初始化值的用法之一classEmployee{public:Employee(char*,char*,int,int,int,int,int,int);voidprint()const;private:

charlastName[25];charfirstName[25];DatebirthDate;DatehireDate;};6)成员对象初始化值的用法之二Employee::Employee(char*fname,char*lname,intbmonth,intbday,intbyear,inthmonth,inthday,inthyear):birthday(bmonth,bday,byear),hireDate(hmonth,hday,hyear){strncpy(firstName,fname,24);firstName[24]='\

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论