第5章 对象和类.ppt

上传人:夺命阿水 文档编号:747137 上传时间:2023-11-06 格式:PPT 页数:177 大小:669.50KB
返回 下载 相关 举报
第5章 对象和类.ppt_第1页
第1页 / 共177页
第5章 对象和类.ppt_第2页
第2页 / 共177页
第5章 对象和类.ppt_第3页
第3页 / 共177页
第5章 对象和类.ppt_第4页
第4页 / 共177页
第5章 对象和类.ppt_第5页
第5页 / 共177页
点击查看更多>>
资源描述

《第5章 对象和类.ppt》由会员分享,可在线阅读,更多相关《第5章 对象和类.ppt(177页珍藏版)》请在课桌文档上搜索。

1、第5章 对象和类,5.1 类及其实例化5.2 类和对象的性质5.3 结构和联合5.4 构造函数5.5 析构函数5.6 综合例题,5.7 重载对象的赋值运算符5.8 对象成员的初始化5.9 类模板与标准模板库5.10 面向对象编程的文件规范实验习题,本章重点介绍在C+中定义类、建立和使用对象的方法。虽然同类对象在其数据成员的取值方面是不相同的,但可以共用相同的代码。类是对同类对象的描述,它不但描述了对象之间的公有接口,同时也给出了对象的内部实现(数据成员和成员函数)。,像构造枚举和结构一样,类也是一种用户自己构造的数据类型并遵循C+的规定。例如,类也要先声明后使用;不管声明的内容是否相同,声明同

2、一个名字的两个类是错误的,类是具有惟一标识符的实体;在类中声明的任何成员不能使用extern、auto和register关键字进行修饰;类中声明的变量属于该类,在某些情况下,变量可以被该类的不同实例所共享。,5.1 类及其实例化 5.1.1 定义类,类和其他数据类型不同的是,组成这种类型的不仅可以有数据,而且可以有对数据进行操作的函数,它们分别叫做类的数据成员和类的成员函数,而且不能在类声明中对数据成员使用表达式进行初始化。,1.声明类类是对一组性质相同对象的程序描述。在C+中声明类的一般形式为 class 类名 private:私有数据和函数 public:公有数据和函数 protected

3、:保护数据和函数;,类声明以关键字class开始,其后跟类名。类所声明的内容用花括号括起来,右花括号后的分号作为类声明语句的结束标志。这一对花括号“”之间的内容称为类体。类中定义的数据和函数称为这个类的成员(数据成员和成员函数)。类成员均具有一个属性,叫做访问权限,通过它前面的关键字来定义。顾名思义,关键字private、public和protected 以后的成员的访问权限分别是私有、公有和保护的,把这些成员分别叫做私有成员、公有成员和保护成员。访问权限用于控制对象的某个成员在程序中的可访问性,如果没有使用关键字,则所有成员默认声明为private权限。这些关键字的使用顺序和次数也都是任意的

4、。,【例5.1】描述点的Point类。class Point/类名Pointprivate:/声明为私有访问权限 int x,y;/私有数据成员public:/声明为公有访问权限 void Setxy(int a,int b);/无返回值的公有成员函数 void Move(int a,int b);/无返回值的公有成员函数 void Display();/无返回值的公有成员函数 int Getx();/返回值为int的公有成员函数 int Gety();/返回值为int的公有成员函数;/类声明以分号结束x和y是私有成员,Setxy、Display、Move、Getx和Gety是公有成员。因为只

5、是声明函数,所以可只给出函数原型。【例5.2】是其等效的声明方式。,【例5.2】使用默认关键字及改变关键字顺序和次数的Point类。class Point/类名Pointint x;/默认私有数据成员 public:/声明为公有访问权限/无返回值的公有成员函数Setxy的函数原型 void Setxy(int,int);/无返回值的公有成员函数Move的函数原型void Move(int,int);void Display();/无返回值的公有成员函数的函数原型int Getx();/返回值为int的公有成员函数的函数原型int Gety();/返回值为int的公有成员函数的函数原型,priv

6、ate:/声明为私有访问权限int y;/私有数据成员;/类定义以分号结束由此可见,成员函数声明的规则与第4章所述的函数声明规则相同。2.定义成员函数类中说明的成员函数用来对数据成员进行操作。例如,Point类的Setxy函数用来为该类的对象设置初始值,而当调用成员函数Getx时,则返回一个对象的数据成员x的值。在类中只对这些成员函数进行了函数声明,还必须在程序中实现这些成员函数。定义成员函数的一般形式为,返回类型 类名成员函数名(参数列表)成员函数的函数体/内部实现 其中“”是作用域运算符,“类名”是成员函数所属类的名字,“”用于表明其后的成员函数是属于这个特定的类。换言之,“类名成员函数名

7、”的意思就是对属于“类名”的成员函数进行定义,而“返回类型”则是这个成员函数返回值的类型。余下的工作就是定义成员函数的函数体。例如Setxy是类Point的成员函数,它没有返回值,则定义如下:void Point Setxy(int a,int b)x=a;y=b;,将“void PointSetxy(int a,int b)”理解为定义Point的函数成员Setxy(int a,int b),该成员带有两个整型参数,函数没有返回值(void)。按此方法,可写出其他几个成员函数的定义:void Point Move(int a,int b)x=x+a;y=y+b;void Point Disp

8、lay()coutx,yendl;int Point Getx()return x;int Point Gety()return y;,也可以使用关键字inline将成员函数定义为内联函数,例如:inline int Point Getx()return x;如果在声明类的同时,在类体内给出成员函数的定义,则默认为内联函数。例如在类中将声明Getx的语句“int Getx();”改为“int Getx()return x;”,则Getx为内联函数。一般直接在类体内给出简单成员函数的定义。有些成员函数的实现方式不止一种,例如void Point Display()coutGetx(),Gety(

9、)endl;,是调用成员函数Getx()和Gety()实现的,它们使用了cout流,应在定义之前包含如下语句:#include using namespace std;3.数据成员的赋值不能在类体内给数据成员赋值,即下面的方法是错误的:Class Point int x=25,y=56;,当然,在类体外面就更不允许了。数据成员的具体值是用来描述对象的属性的。只有产生了一个具体的对象,这些数据值才有意义,所以又称对象的初值或对象初始化。这跟简单数据类型的道理一样,int是整数类型,但需要声明整数对象之后才有意义,“int x=5;”使得整数对象x的值为5,只是类具有成员函数而已。假设已经有了一个

10、对象A,则可使用“.”运算符调用成员函数Setxy赋初值。例如:A.Setxy(25,56);,将对象A的数据成员x和y分别赋给25和56,即A.x=25,A.y=56。其实,真正的初始化是使用与Point同名的构造函数Point(int,int)实现的。在1.10.1节介绍使用string的对象str时,使用“string str(real is);”语句。按此推理,可以写出产生Point的对象的语句:Point A(25,56);这就是使用构造函数产生类的实例。不过,现在还没有定义构造函数Point(int,int),所以还不能使用这种方法。但可以看到数据封装的迹象了!,Point类是用户

11、定义的一种类型,Point类所说明的数据成员描述了对象的内部数据结构,对数据成员的访问通过类的成员函数来进行。使用Point在程序中声明变量,具有Point类的类型的变量被称为Point的对象。只有产生类的对象,才能使用这些数据和成员函数。类Point不仅可以声明对象,还可以声明对象的引用和对象的指针,语法与基本数据类型一样。Point A,B;/定义Point类型的对象A和BPoint*p=&A;/定义指向对象A的Point类型的指针Point&R=B;/定义R为Point类型对象B的引用,5.1.2 使用类的对象,对象和引用都使用运算符“.”访问对象的成员,指针则使用“-”运算符。例如:A

12、.Setxy(25,88);/为对象A设置初值R.Display();/显示对象B的数据成员/B.x和B.y之值p-Display();/显示指针p所指对象A的数据/成员A.x和A.y之值,【例5.3】根据上面对Point类的定义,演示使用Point类的对象。void print(Point a)/使用Point的对象a作为函数参数a.Display();/显示对象a的数据成员的值void main()Point A,B;/声明对象A.Setxy(25,55);/为对象A赋初值B=A;/B的数据成员取A的数据成员之值A.Display();/显示A的数据成员A.Move(-10,20);/移动

13、Aprint(A);/等价于A.Display();print(B);/等价于B.Display(),coutA.Getx()endl;/只能使用A.Getx(),不能使用A.x本例中的print函数使用Point的对象作为参数。C+推荐使用下面的引用的形式:void print(Point&a)/使用对象的引用作为函数参数a.Display();/显示引用对象a的数据成员之值对象A移动之后,对象B仍为原来的值,所以输出如下:25,55/原来的A和B 15,75/新的A 25,55/原来的B 15/A.Getx()返回对象A的数据成员x的值如果在print函数或主函数里使用如下语句,则产生错误

14、。coutA.x,A.yendl;/错误!,暂不涉及还没有介绍的保护成员,可以归纳出如下规律:类的成员函数可以直接使用自己类的私有成员(数据成员和成员函数);类外面的函数不能直接访问类的私有成员(数据成员和成员函数);类外面的函数只能通过类的对象使用该类的公有成员函数,例如print和main函数;对象A和B的成员函数的代码一样,不同对象的区别是属性的取值。,在程序运行时,通过为对象分配内存来创建对象。在创建对象时,使用类作为样板,故称对象为类的实例。从表5.1中可以看出,A和B两个对象占据内存中的不同区域。A的数据是A.x和A.y,而B的数据是B.x和B.y,它们各有表现自己的属性数据,但用

15、来操作数据的代码均是一样的,例如A.Getx()和B.Getx()的代码一样。为节省内存,在建立对象时,只分配用于保存数据的内存,代码为每个对象共享。类中定义的代码放在计算机内存的一个公用区中,供该类的所有对象共享。这只是C+编译器实现对象的一种方法,作为程序员仍要将对象理解为是由数据和代码组成的。正是由于类拥有这两类成员,才使得数据封装等思想得以实现。,表5.1 类Point的两个实例A和B,【例5.4】演示使用内联函数定义Point类及使用Point类指针和引用的完整例子。#include/包含头文件using namespace std;/声明命名空间class Point/使用内联函数

16、定义类Pointprivate:/声明为私有访问权限 int x,y;/私有数据成员public:/声明为公有访问权限 void Setxy(int a,int b)/无返回值的内联公有成员函数 x=a;y=b;void Move(int a,int b)/无返回值的内联公有成员函数 x=x+a;y=y+b;void Display()/无返回值的内联公有成员函数 coutx,yendl;,int Getx()return x;/返回值为int的内联公有成员函数 int Gety()return y;/返回值为int的内联公有成员函数;/类定义以分号结束void print(Point*a)/

17、类指针作为print函数的参数定义重载函数a-Display();void print(Point&a)/类引用作为print函数的参数定义重载函数a.Display();void main()/主函数 Point A,B,*p;/声明对象和指针 Point&RA=A;/声明对象RA引用对象A A.Setxy(25,55);/使用成员函数为对象A赋值,B=A;/使用赋值运算符为对象B赋值 p=&B;/类Point的指针指向对象B p-Setxy(112,115);/使用指针调用函数Setxy重新设置B的值 print(p);/传递指针显示对象B的属性 p-Display();/再次显示对象B的

18、属性 RA.Move(-80,23);/引用对象RA调用Move函数 print(A);/验证RA和A同步变化 print(&A);/直接传递A的地址作为指针参数程序运行结果如下:112,115112,115-55,78-55,78,面向对象的程序设计,是通过为数据和代码建立分块的内存区域,以便提供对程序进行模块化的一种程序设计方法,这些模块可以被用做样板,在需要时再建立其副本。根据这个定义,对象是计算机内存中的一块区域,通过将内存分块,每个模块(即对象)在功能上保持相对独立。另外,定义也表明如下问题。,5.1.3 数据封装,这些内存块中不但存储数据,而且也存储代码,这对保证对象受保护很重要。

19、只有对象中的代码才可以访问存储于这个对象中的数据,这清楚地限定了对象所具有的功能(即一个对象在一个软件系统中所能起到的作用),并使对象保护它自己不受未知的外部事件的影响,从而使自己的数据和功能不会因此遭到破坏。这些内存块的结构可被用做样板产生对象的更多副本。例如,一旦定义了一个窗口对象,只要内存允许,就可以建立许多这样的对象。在面向对象的程序中,对象的行为只有向对象发送消息才能引用,例如通过Display发出显示消息,所以说面向对象是消息处理机制。,对象之间只能通过成员函数调用实现相互通信。这样,对象之间的相互作用方式是仔细控制的,一个对象外部的代码就没有机会通过直接修改对象的内存区域妨碍对象

20、发挥其功能。当对象的一个函数被调用时,对象执行其内部的代码来响应这个调用,这就使对象呈现出一定的行为,即表现出该对象的功能。对象被视为能做出动作的实体,动作在对象相互作用时被激励,换句话说,对象就像在宿主计算机上拥有数据和代码,并能相互通信的具有特定功能的一台较小的计算机。,对象的这一特点导致了模拟现实世界的一种新型方法:面向对象就是将世界看成是由一组彼此相关并能相互通信的实体即对象组成的。程序中的对象映射现实世界中的对象。C+对其对象的数据成员和成员函数的访问是通过访问控制权限来限制的。可以试验一下,将【例5.1】中对数据成员x和y的声明改为public,就可以在主函数中直接使用语句“cou

21、tA.x;”输出A的数据成员x之值。这就取消了它的封装性。由此可见,private是限制类之外的函数的访问权。只要将数据成员或成员函数使用private限定,就设定了一道防线。不过,如果都在防线之内,这个类也就没有用处了。所以它还必须留下与外面打交道的接口,这通过具有public权限的成员函数实现(目前暂不涉及protected)。,由此可见,在C+中,数据封装是通过类来实现的。在类中指定了各成员的访问权限。一般情况下将数据成员说明为私有的,以便隐藏数据;而将部分成员函数说明为公有的,用于提供外界和这个类的对象相互作用的接口(界面),从而使得其他函数(例如main函数)也可以访问和处理该类的对

22、象。对于那些仅是为支持公有函数的实现而不作为对象界面的成员函数,也将它们说明为私有的。公有的成员函数是外界所能观察到(访问到)的对象界面,它们所表达的功能构成对象的功能,使同一个对象的功能能够在不同的软件系统中保持不变。,这样,当数据结构发生变化时,只需要修改少量的代码(类的成员函数的实现代码)就可以保证对象的功能不变。只要对象的功能保持不变,则公有的成员函数所形成的接口就不会发生变化。这样,对象内部实现所做的修改就不会影响使用该对象的软件系统。这就是面向对象程序设计使用数据封装为程序员的程序开发活动所带来的益处。在传统的 C 程序设计风格中,数据保存在数据结构中,然后生成函数来操作这些数据。

23、最后将此结构和函数放进源文件,单独编译,作为模块。这个方法的缺点是,即使该结构和函数是放在一起使用的,仍然可以不通过使用函数就能直接存取数据。C+使用封装的方法较好地解决了这个问题。,可以像第4章那样,对成员函数重载或使用默认参数。下面的例子演示了使用私有成员函数封装函数,并使用成员函数重载和默认参数。【例5.5】构造一个求4个正整数中最大者的类Max,并用主程序验证它的功能。class Max/声明类 private:/封装数据成员和成员函数int a,b,c,d;/数据成员int Maxi(int,int);/只允许类内部的成员函数调用,5.1.4 成员函数重载及默认参数,public:/

24、对外界的接口void Set(int,int,int,int);/设置对象初值int Maxi();/求最大值A3;/声明类的对象数组,定义结束/类中成员函数的实现int MaxMaxi(int x,int y)/求两个数的最大值return(xy)?x:y;void MaxSet(int x1,int x2,int x3=0,int x4=0)/使用两个默认参数a=x1;b=x2;c=x3;d=x4;int MaxMaxi()/求自己类中4个数的最大值 int x=Maxi(a,b);/x和y为Maxi()函数的局部整数对象 int y=Maxi(c,d);return Maxi(x,y);

25、,/主程序#includeusing namespace std;void main()A0.Set(12,45,76,89);/为数组对象A0置初值A1.Set(12,45,76);/为数组对象A1置初值A2.Set(12,45);/为数组对象A2置初值for(int i=0;i3;i+)/输出对象求值结果 coutAi.Maxi();,程序演示了可在声明类的同时也声明类的对象,这里是声明对象数组A,作用与在主程序里使用“Max A3;”语句相同。为了提高可读性,一般不在声明类时声明对象,这里只是为了演示它的性质。程序输出结果为:897645类中重载了函数Maxi,一个原型为Maxi(int

26、,int),用来求两数中的大者。因为它只被自己的成员函数使用,所以定义为private。另一个原型为Maxi(),它调用两次Maxi(int,int),然后再用这两次的结果作为Maxi(int,int)的参数,求出4个数中的最大值。赋值成员函数Set使用2个默认参数是为了书写方便。,在定义Point类的两个对象A和B之后,当执行语句“A.Setxy(25,55);”时,A.x和A.y就被赋值。但是,函数Setxy(int,int)作为代码,在计算机里是和具体的对象分开存储的,它是怎样知道,是要对A进行操作而不是对B进行操作呢?当执行A.Setxy(25,55)时,成员函数 Setxy(int,

27、int)有一个隐藏参数,名为this指针。也就是说,源程序被编译器编译后,Setxy(int a,int b)实际上是如下形式:,5.1.5 this 指针,void PointSetxy(int a,int b,(Point*)this)this-x=a;this-y=b;这时,成员函数的this指针指向对象A。成员中对x和y的引用表示是引用对象A的成员x和y。对于任何访问该成员函数的类的对象,C+编译器都认为是访问this指针所指向的对象中的成员。由于不同的对象调用成员函数Setxy(int,int)时,this指针指向不同的对象,因此成员函数Setxy(int,int)可以为不同对象的x

28、和y置初值。,使用this指针,保证了每个对象可以拥有自己的数据成员,但处理这些数据成员的代码可以被所有的对象共享。由此可见,C+规定,当一个成员函数被调用时,系统自动向它传递一个隐含的参数,该参数是一个指向接受该函数调用的对象的指针,从而使成员函数知道该对哪个对象进行操作。在程序中,可以使用关键字this来引用该指针。this指针是C+实现封装的一种机制,它将对象和该对象调用的成员函数连接在一起,在外部来,每一个对象都拥有自己的成员函数。如果在定义Setxy函数时使用this指针,也不要给出隐含参数,而写成如下形式:,void PointSetxy(int a,int b)this-x=a;

29、this-y=b;除非特殊需要,一般情况下都省略符号“this-”,而让系统进行默认设置。,因为类本身就是一种新的数据类型,所以一个类可以作为另一个类的成员。假设有A和B两个类,可以通过在B类里定义A的对象作为B的数据成员,或者定义一个返回类型为A的函数作为B的成员函数。假设定义了坐标点的类Point,矩形类Rectangle的属性需要一个坐标点及长和宽。一个Point的对象恰好可以作为矩形的顶点坐标,即Rectangle可以使用Point的一个对象作为数据成员。在Rectangle类中,用Point类定义一个返回Point类型指针的函数作为Rectangle的成员函数。,5.1.6 一个类的

30、对象作为另一个类的成员,【例5.6】使用对象成员的例子。#includeusing namespace std;class Point/定义点类int x,y;public:void Set(int a,int b)x=a;y=b;int Getx()return x;int Gety()return y;class Rectangle/在矩形类里使用Point类的成员 Point Loc;/定义一个Point类的对象作为顶点 int H,W;/H为高,W为宽,public:void Set(int x,int y,int h,int w);Point*GetLoc();/用Point类定义返

31、回指针的函数 int GetHeight()return H;int GetWidth()return W;void Rectangle Set(int x,int y,int h,int w)Loc.Set(x,y);/初始化坐标顶点 H=h;W=w;Point*Rectangle GetLoc()/返回类型Point*,作/为Rectangle的成员函数 return&Loc;/返回对象Loc的地址,void main()Rectangle rect;rect.Set(10,2,25,20);coutGetx()Gety()endl;新类Rectangle具有一个顶点,所以也具有Point

32、类的属性。这就是第1章提到的类的结构关系(聚合关系,有时又称包含)。,构成的新类不能直接操作另一个类的数据,必须通过原构成类的对象使用它们的成员函数来实现,本例使用Loc.Set(x,y)方式实现。程序输出结果为:25,20,10,2,结合前面的例子,可以归纳对象的一些基本特性如下:对象之间可以相互赋值。例如,如下语句使A和B的数据成员有相同的值:Point A,B;A.Setxy(25,55);B=A;可使用对象数组。例如,“Max A3;”定义数组A可以存储3个Point类的对象。,5.2 类和对象的性质 5.2.1 类对象的性质,可使用指向对象的指针,使用取地址运算符&将一个对象的地址置

33、于该指针中,如:Point*p=&A;p-Display();注意:不能取私有数据成员的地址,也不能取成员函数的地址。有关指向成员的指针在后面的章节中讨论。指向对象指针的算术运算规则与第4章介绍的一样,不再赘述。,对象可以用做函数参数,这时参数传递采用传值方式,即在被调用函数中对形参所作的改变不影响调用函数中作为实参的对象。如果要传址,可以采用对象的引用或指针作为参数。但是,如果参数对象被修改,相应的实参对象也将被修改。C+推荐使用引用作为参数传递。为了避免被调用函数修改原来对象的数据成员,可以使用第4章介绍的const修饰符。对象作为函数参数时,可以使用对象值、对象引用和对象地址(指针)。以

34、介绍过的print函数为例,它们的参数传递形式为:void print(Point a)a.Display;void print(Point&a)a.Display;void print(Point*p)p-Display;,它们的原型分别为print(Point),print(Point&)和print(Point*)。对于对象A而言,print(&A)调用的是原型为print(Point*)的函数形式。注意:参数为对象和引用时,编译系统无法区别,重载print只能选择其中的一种。一个对象可以用做另一个对象的成员。例如在【例5.6】中,定义Point类的对象Loc。,1.使用类的权限假设声明

35、了Point类,为了简单具体,讨论数据成员为私有,成员函数为公有的情况。这时会有函数(主函数和一般函数)、对象和类本身的成员函数需要使用Point类的成员。类本身的成员函数可以使用类的所有成员(私有和公有成员)。类的对象只能访问公有成员函数,例如输出x只能使用A.Getx(),不能使用A.x。,5.2.2 类的性质,其他函数不能使用类的私有成员,也不能使用公有成员函数,它们只能通过类的对象使用公有成员函数。一个类可以使用另外一个类的对象,这个类也只能通过那个类的对象使用该类的成员函数,通过成员函数使用数据成员,例如Loc.Set(x,y)。2.不完全的类声明类不是内存中的物理实体,只有当使用类

36、产生对象时,才进行内存分配,这种对象建立的过程称为实例化。实例化一词在面向对象程序设计范围内使用广泛,用以表示产生类的实例或物理实体的动作。在某些语言中,对象就被称作实例。虽然在C+中并不是这样,但实例化仍然被广泛使用。,应当注意的是:类必须在其成员被使用之前进行声明。有时也需要将类作为一个整体来使用,而不存取其成员。声明指针就是这种情况,例如:class MembersOnly;/不完全的类声明 MembersOnly*club;/定义一个全局变量类指针 void main()./函数体/主函数 class MembersOnly./类体;/完全定义该类第一条语句称为不完全类声明,它用于在类

37、没有完全定义之前就引用该类的情况,例如引用另一文件中定义的类。由于类标识符MembersOnly通过不完全类声明进入了作用域,所以就可以声明全局变量指针club。编译器执行到该指针的声明处时,只了解指针所指类型是一个叫MembersOnly的类,而不了解其他任何情况。,不完全声明的类不能实例化,企图实例化会产生编译出错信息;不完全声明仅用于类和结构,企图存取没有完全声明的类成员,也会引起编译出错信息。另外,将数据成员的声明放在最后,有利于先理解类的界面。如果类体内有内联函数,这就像是在声明数据成员之前已经用到它们,确实是“使用在前,说明在后”。这在类中是允许的,因为C+编译器先扫描类的成员说明

38、,最后才处理内联成员函数的代码体。,3.空类尽管类的目的是封装代码和数据,它也可以不包括任何声明。例如:class Empty;这种类没有任何行为,但可以产生空类对象。void main()Empty object;为什么要产生空类呢?在开发大的项目时,需要在一些类还没有完全定义或实现时进行先期测试。这常称为“插头”,用来保证代码能正确地被编译,从而允许测试其中的一部分。不过这是早期的做法,对强类型检查的编译器,会给出一个警告。可给空类增加一个无参数构造函数(见构造函数一节)以消除警告。例如:class Empty public:Empty();,4.类作用域声明类时所使用的一对花括号形成所谓

39、的类作用域。在类作用域中声明的标识符只在类中可见。例如:class example int num;int i=num;/错,num在此不可见 int num;/正确,num与类中说明的数据/成员num具有不同的作用域即使该成员函数的实现是在类定义之外给出的,类作用域也包含了类中成员函数的作用域。因此,当成员函数内使用一个标识符时,编译器先在包含类作用域中寻找,例如下面的例子:,class MyClass int number;public:void set(int);int number;/这个number不属于类MyClassvoid MyClass set(int i)number=i;

40、/使用类MyClass中的/标识符number 类中的一个成员名可以使用类名和作用域运算符来显式指定,这称为成员名限定。例如:void MyClass set(int i),MyClass number=i;/显式指定访问MyClass/类中的标识符number 在程序中,分析对象的生存期与过去分析变量的生存期的方法一样,由对象说明来决定。类中各数据成员的生存期由对象的生存期决定,当对象存在时,它们存在;对象消失时,它们也消失。成员函数具有外部连接属性。在程序中使用成员选择运算符(“.”或“-”)访问一个对象的成员时,其后的名字是引用该对象所在类中声明的成员名。例如:Point A;/声明类对

41、象,假设类内有成员函数Getx()int Getx()return 5;/类外面声明的函数,int x=Getx();/访问函数Getx(),返回5 int y=A.Getx();/访问类Point中声明的/成员函数Getx()在类中说明的枚举成员也使用成员选择运算符存取,枚举成员名隐藏在类作用域中。这些枚举成员不属于任何对象,为该类所有的对象共享。因此,对这些枚举成员使用成员名限定方法进行存取比较恰当。但是,在一个友元函数访问类中的枚举成员时,有时必须使用成员选择运算符。友元函数在后面的章节讨论。除非编译器在处理类声明时遇到了标识其结束的右花括号,否则这个声明仍然是引用性声明。,引用性声明所

42、声明的类名不能用来建立对象,只能用来声明指针或引用,或用在函数声明中。例如:class MyClass MyClass member;/错MyClass*p;/正确;class YourClass private:MyClass d;/正确;,在上例中,当在类MyClass中声明成员member时,类名MyClass仅作了引用性声明,因而这个语句是错误的。由此可见,C+为类中声明的标识符引入了新的类作用域概念。这些标识符只有与类对象连用时才进入作用域。在可能出现二义性的情况下,必须使用作用域限定符“”。作用域限定符“”的特点将在后继章节中讨论。,在C+中,封装也可以由struct和union等

43、关键字提供,它们也能将数据和函数组合成一个类,所以也可以使用结构和联合来定义类。结构是类的一种特例,其中成员在默认情况下是公有的。尽管C+允许在结构中定义成员函数,但它本身更适合处理数据,例如像职工记录和日期这样的数据结构。联合以关键字union来声明,成员默认为公有并且在某个给定时间,只出现一个成员。联合使若干数据成员使用同一地址。可以直接在类中使用无名联合,例如:,5.3 结构与联合,Class CU union int ivalue;float fvalue;/在关键字union后面没有给出联合名,这是一个无名联合声明,它声明ivalue和fvalue共享同一块内存,无名联合中声明的数据

44、项名字可以被直接存取。,无名联合不仅可以用于声明类中的数据成员,而且可用于程序中需要共享数据项的其他地方,例如在函数作用域中,或在文件作用域中。无名联合不能有成员函数,因为无名联合中成员的作用域在联合之外。目前介绍的类都是与其他无关的单一的类。如果能通过这些已有的类来建立新类,则新建立的类叫做“派生类”,而原来的类称为“基类”。这些将在第6章详细介绍,目前只是强调联合既不能用做任何类的基类,也不能从任何类中派生出联合,因为联合在特定的时刻只有一个数据成员处于“激活”状态。此外,在联合中也不能说明虚函数。,建立一个对象时,对象的状态(数据成员的取值)是不确定的。为了使对象的状态确定,必须对其进行

45、正确的初始化。C+有称为构造函数的特殊成员函数,它可自动进行对象的初始化。初始化和赋值是不同的操作,当C+语言预先定义的初始化和赋值操作不能满足程序的要求时,程序员可以定义自己的初始化和赋值操作。,5.4 构造函数,由于初始化的重要性,调用构造函数并不是由用户来完成的;是否调用这个函数也不是可选的,而是由编译器来调用,所以编译器必须总能知道调用哪个函数。最容易也最符合逻辑的方法是指定这函数的名称与类名一样。这个函数没有返回值则是基于下面的考虑:如果它有返回值,编译器就必须知道如何处理返回值,这样就会大大增加编译器的工作,也降低了效率。但如果采取只能由用户自己来显式调用构造函数方式的话,就又容易

46、破坏安全性。,5.4.1 定义构造函数,因此,让构造函数的名字和类名同名,在定义构造函数时不能指定返回类型,即使是void 类型也不可以。【例5.7】的程序说明构造函数的定义和执行过程。【例5.7】构造函数的定义和执行过程实例程序。class Test private:int num;public:Test();/声明不带参数的构造函数Test(int);/声明带1个参数的构造函数;,#includeusing namespace std;Test Test():num(0)/定义不带参数的构造函数coutInitializing defaultendl;TestTest(int n):num

47、(n)/定义带1个参数的构造函数coutInitializing nendl;void main()Test x;/使用不带参数的构造函数产生对象x Test y(15);/使用带参数的构造函数产生对象y Test array2=5,7;/使用带参数的构造函数/产生对象数组array,程序中和Test类同名的两个成员函数是构造函数,一个不带参数,一个带有1个参数。“:num(n)”完成“num=n”的功能,这里称初始化列表。它与下面的方法等价:TestTest(int n)coutInitializing nendl;num=n;由此可见,类可以有多个构造函数,在类体里的声明形式如下:类名(形

48、参1,形参2,形参n);/可以没有形参类的构造函数可以在类体内(内联函数)声明时定义,也可以在类体外定义。可以使用初始化列表或者在构造函数的函数体内定义。,假设数据成员为x1,x2,xn,则有如下两种形式:类名类名(形参1,形参2,形参n):x1(形参1),x2(形参2),xn(形参n)类名类名(形参1,形参2,形参n)x1=形参1;x2=形参2;xn=形参n;构造函数的参数是无顺序排列,只要保证相互的对应顺序即可。可以使用默认参数或重载。,在程序中说明一个对象时,程序自动调用构造函数来初始化该对象。当执行到语句 Test x;时,程序为对象x分配内存,然后调用不带参数的构造函数来初始化这段内

49、存,将x的数据成员num 初始化为零,输出“Initailizing default”。当程序执行到语句 Test y(15);时,程序为对象y分配内存,然后调用带有参数的构造函数来初始化这段内存,将y的数据成员 num 初始化为15,输出“Initializing 15”。,当程序执行到语句 Test array2=5,7时,程序为对象数组array2分配内存,然后调用带参数的构造函数来初始化这段内存。它必须为每一个数组元素调用一次构造函数,先将array0的数据成员 num 初始化为5,输出“Initializing 5”;再将array1的数据成员 num 初始化为7,输出“Initia

50、lizing 7”。当声明一个外部对象时,外部对象只是引用在其他地方声明的对象,程序并不为外部对象说明调用构造函数。如果是全局对象或静态对象,在 main 函数执行之前要调用它们的构造函数。,使用下面的主程序演示全局对象的情况:/使用前面定义的类 Test global(5);void main()coutEntering main and exiting main endl;程序在进入main函数之前先构造全局对象,输出 Initializing 5进入主函数之后输出为:Entering main and exiting main。,运算符new用于建立生存期可控的对象,new返回这个对象的

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 在线阅读 > 生活休闲


备案号:宁ICP备20000045号-1

经营许可证:宁B2-20210002

宁公网安备 64010402000986号