面向对象C 第四章_第1页
面向对象C 第四章_第2页
面向对象C 第四章_第3页
面向对象C 第四章_第4页
面向对象C 第四章_第5页
已阅读5页,还剩103页未读 继续免费阅读

下载本文档

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

文档简介

面向对象C第四章第1页,共108页,2023年,2月20日,星期四4.1.1定义类像枚举和结构一样,类也是一种用户自己构造的数据类型并遵循C++的规定。类也要先声明后使用;不管声明的内容是否相同,声明同一个名字的两个类是错误的,类是具有惟一标识符的实体;在类中声明的任何成员不能使用extern、auto和register关键字进行修饰;类中声明的变量属于该类,在某些情况下,变量可以被该类的不同实例所共享。类和其他数据类型不同的是,组成这种类型的不仅可以有数据,而且可以有对数据进行操作的函数,它们分别叫做类的数据成员和类的成员函数,而且不能在类声明中对数据成员使用表达式进行初始化。第2页,共108页,2023年,2月20日,星期四1.声明类类是对一组性质相同对象的程序描述。在C++中声明类的一般形式为:class类名{private:私有数据和函数public:公有数据和函数protected:保护数据和函数};类声明以关键字class开始,其后跟类名。类所声明的内容用花括号括起来,右花括号后的分号作为类声明语句的结束标志。这一对花括号“{}”之间的内容称为类体。第3页,共108页,2023年,2月20日,星期四类中定义的数据和函数称为这个类的成员(数据成员和成员函数)。类成员均具有一个属性,叫做访问权限,通过它前面的关键字来定义。关键字private、public和protected以后的成员的访问权限分别是私有、公有和保护的,把这些成员分别叫做私有成员、公有成员和保护成员。访问权限用于控制对象的某个成员在程序中的可访问性,如果没有使用关键字,则所有成员默认声明为private权限。这些关键字的使用顺序和次数也都是任意的。第4页,共108页,2023年,2月20日,星期四【例4.1】描述点的Point类。classPoint{//类名Pointprivate://声明为私有访问权限intx,y;//私有数据成员public://声明为公有访问权限voidSetxy(inta,intb);//无返回值的公有成员函数voidMove(inta,intb);//无返回值的公有成员函数voidDisplay();//无返回值的公有成员函数intGetx();//返回值为int的公有成员函数intGety();//返回值为int的公有成员函数};//类声明以分号结束x和y是私有成员Setxy、Display、Move、Getx和Gety是公有成员因为只是声明函数,所以可只给出函数原型。【例4.2】是其等效的声明方式。第5页,共108页,2023年,2月20日,星期四【例4.2】使用默认关键字及改变关键字顺序和次数的Point类。#include<iostream>usingnamespacestd;classPoint{//类名Pointintx;//默认私有数据成员public://声明为公有访问权限voidSetxy(int,int);//无返回值的公有成员函数的函数原型voidMove(int,int);//无返回值的公有成员函数的函数原型voidDisplay();//无返回值的公有成员函数的函数原型intGetx();//返回值为int的公有成员函数的函数原型intGety();//返回值为int的公有成员函数的函数原型private://声明为私有访问权限inty;//私有数据成员};//类定义以分号结束由此可见,成员函数声明的规则与第3章所述的函数声明规则相同。

第6页,共108页,2023年,2月20日,星期四2.定义成员函数类中说明的成员函数用来对数据成员进行操作。在类中只对这些成员函数进行了函数声明,还必须在程序中实现这些成员函数。定义成员函数的一般形式为:

返回类型类名::成员函数名(参数列表){成员函数的函数体//内部实现}其中“::”是作用域运算符,“类名”是成员函数所属类的名字,“::”用于表明其后的成员函数是属于这个特定的类。“类名::成员函数名”的意思就是对属于“类名”的成员函数进行定义,而“返回类型”则是这个成员函数返回值的类型。第7页,共108页,2023年,2月20日,星期四定义成员函数的函数体。例如Setxy定义如下:voidPoint::Setxy(inta,intb){x=a;y=b;}定义Point的函数成员Setxy(inta,intb),该成员带有两个整型参数,函数没有返回值(void)。voidPoint::Move(inta,intb){x=x+a;y=y+b;}voidPoint::Display(){cout<<x<<","<<y<<endl;}intPoint::Getx(){returnx;}intPoint::Gety(){returny;}第8页,共108页,2023年,2月20日,星期四

可以使用关键字inline将成员函数定义为内联函数,例如:inlineintPoint::Getx(){returnx;}如果在声明类的同时,在类体内给出成员函数的定义,则默认为内联函数。例如在类中将声明Getx的语句“intGetx();”改为“intGetx(){returnx;}”,则Getx为内联函数。一般直接在类体内给出简单成员函数的定义。有些成员函数的实现方式不止一种,例如voidPoint::Display(){cout<<Getx()<<","<<Gety()<<endl;}是调用成员函数Getx()和Gety()实现的,它们使用了cout流,应在定义之前包含如下语句:#include<iostream>usingnamespacestd;第9页,共108页,2023年,2月20日,星期四3.数据成员的赋值不能在类体内给数据成员赋值,即下面的方法是错误的:ClassPoint{intx=25,y=56;……};当然,在类体外面就更不允许了。数据成员的具体值是用来描述对象的属性的。只有产生了一个具体的对象,这些数据值才有意义,所以又称对象的初值或对象初始化。假设已经有了一个对象A,则可使用“.”运算符调用成员函数Setxy赋初值。例如:A.Setxy(25,56);将对象A的数据成员x和y分别赋给25和56,即A.x=25,A.y=56。

真正的初始化是使用与Point同名的构造函数Point(int,int)实现的。产生Point对象的语句:PointA(25,56);第10页,共108页,2023年,2月20日,星期四4.1.2使用类的对象及指针Point类是用户定义的一种类型,使用Point在程序中声明变量,具有Point类的类型的变量被称为Point的对象。只有产生类的对象,才能使用这些数据和成员函数。类Point不仅可以声明对象,还可以声明对象的引用和对象的指针,语法与基本数据类型一样。PointA,B;//定义Point类型的对象A和BPoint*p=&A;//定义指向对象A的Point类型的指针Point&R=B;//定义R为Point类型对象B的引用

第11页,共108页,2023年,2月20日,星期四对象和引用都使用运算符“.”访问对象的成员,指针则使用“->”运算符。例如:A.Setxy(25,88);//为对象A设置初值p->Display();//显示指针p所指对象A的数据成员//A.x和A.y之值R.Display();//显示对象B的数据成员B.x和B.y之值【例4.3】根据上面对Point类的定义,演示使用Point类的对象。voidprint(Pointa)//使用Point的对象a作为函数参数{a.Display();//显示对象a的数据成员的值}voidmain()第12页,共108页,2023年,2月20日,星期四{ PointA,B;//声明对象 A.Setxy(25,55);//为对象A赋初值 B=A;//B的数据成员取A的数据成员之值 A.Display();//显示A的数据成员 A.Move(-10,20);//移动A print(A);//等价于A.Display(); print(B);//等价于B.Display() cout<<A.Getx()<<endl;//只能使用A.Getx(),//不能使用A.x}本例中的print函数使用Point的对象作为参数。C++推荐使用下面的引用的形式:voidprint(Point&a)//使用对象的引用做为函数参数{a.Display();}//显示引用对象a的数据成员之值第13页,共108页,2023年,2月20日,星期四对象A移动之后,对象B仍为原来的值,所以输出如下:25,55//原来的A和B15,75//新的A25,55//原来的B15//A.Getx()返回对象A的数据成员x的之值如果在print函数或主函数里使用如下语句,则产生错误:cout<<A.x<<","<<A.y<<endl;//错误!第14页,共108页,2023年,2月20日,星期四暂不涉及还没有介绍的保护成员,可以归纳出如下规律:(1)类的成员函数可以直接使用自己类的私有成员(数据成员和成员函数)。(2)类外面的函数不能直接访问类的私有成员(数据成员和成员函数)。(3)类外面的函数只能通过类的对象使用该类的公有成员函数,例如print和main函数。(4)类对象A和B的成员函数的代码一样,不同对象的区别是属性的取值。第15页,共108页,2023年,2月20日,星期四在程序运行时,通过为对象分配内存来创建对象。在创建对象时,使用类作为样板,故称对象为类的实例。A和B两个对象占据内存中的不同区域。A的数据是A.x和A.y,而B的数据是B.x和B.y,它们各有表现自己的属性数据,但用来操作数据的代码均是一样的,例如A.Getx()和B.Getx()的代码一样。为节省内存,在建立对象时,只分配用于保存数据的内存,代码为每个对象共享。类中定义的代码被放在计算机内存的一个公用区中供该类的所有对象共享。

第16页,共108页,2023年,2月20日,星期四表4.1类Point的两个实例A和B对象A对象B对象成员对象成员分配内存内容对象成员对象成员分配内存内容A.xA.x的值B.xB.x的值A.yA.y的值B.yB.y的值A.Setxy(int,int)Setxy函数代码B.Setxy(int,int);Setxy函数代码A.Move(int,int)Move函数代码B.Move(int,int);Move函数代码A.Display()Display函数代码B.Display()Display函数代码A.Getx()Getx函数代码B.Getx()Getx函数代码A.Gety()Gety函数代码B.Gety()Gety函数代码第17页,共108页,2023年,2月20日,星期四可以通过类对象的指针访问对象的成员。定义类对象指针的语法如下:

类名*对象指针名;对象指针名=对象的地址;也可以直接进行初始化。例如:类名*对象指针名=对象的地址;类对象的指针可以通过“->”运算符访问对象的成员,即:

对象指针名->对象成员名第18页,共108页,2023年,2月20日,星期四【例4.4】演示使用内联函数定义Point类及使用Point类指针和引用的完整例子。#include<iostream>//包含头文件usingnamespacestd;//声明命名空间classPoint{//使用内联函数定义类Pointprivate://声明为私有访问权限 intx,y;//私有数据成员public://声明为公有访问权限voidSetxy(inta,intb)//无返回值的内联公有成员函数{x=a;y=b;}voidMove(inta,intb)//无返回值的内联公有成员函数{x=x+a;y=y+b;}voidDisplay()//无返回值的内联公有成员函数{cout<<x<<","<<y<<endl;}intGetx(){returnx;}//返回值为int的内联公有成员函数intGety(){returny;}//返回值为int的内联公有成员函数};//类定义以分号结束voidprint(Point*a)//类指针作为print函数的参数定义重载函数{a->Display();}voidprint(Point&a)//类引用作为print函数的参数定义重载函数{a.Display();}第19页,共108页,2023年,2月20日,星期四voidmain()//主函数{PointA,B,*p;//声明对象和指针Point&RA=A;//声明对象RA引用对象AA.Setxy(25,55);//使用成员函数为对象A赋值B=A;//使用赋值运算符为对象B赋值p=&B;//类Point的指针指向对象Bp->Setxy(112,115);//使用指针调用函数Setxy//重新设置B的值print(p);//传递指针显示对象B的属性p->Display();//再次显示对象B的属性RA.Move(-80,23);//引用对象RA调用Move函数print(A);//验证RA和A同步变化print(&A);//直接传递A的地址作为指针参数}第20页,共108页,2023年,2月20日,星期四程序运行结果如下:112,115112,115-55,78-55,78由此可见,使用对象和对象指针的效果一样。第21页,共108页,2023年,2月20日,星期四4.1.4数据封装面向对象的程序设计,是通过为数据和代码建立分块的内存区域,以便提供对程序进行模块化的一种程序设计方法,这些模块可以被用做样板,在需要时再建立其副本。根据这个定义,对象是计算机内存中的一块区域,通过将内存分块,每个模块(即对象)在功能上保持相对独立。另外,定义也表明如下问题:①这些内存块中不但存储数据,而且也存储代码,这对保证对象是受保护的这一点很重要。对象保护它自己不受未知的外部事件的影响,从而使自己的数据和功能不会因此遭到破坏。第22页,共108页,2023年,2月20日,星期四②这些内存块的结构可被用做样板产生对象的更多副本。例如,一旦定义了一个窗口对象,只要内存允许,就可以建立许多这样的对象。在面向对象的程序中,对象的行为只有向对象发送消息才能引用,例如通过Display发出显示消息,所以说面向对象是消息处理机制。面向对象就是将世界看成是由一组彼此相关并能相互通信的实体即对象组成的。程序中的对象映射现实世界中的对象。

第23页,共108页,2023年,2月20日,星期四C++对其对象的数据成员和成员函数的访问是通过访问控制权限来限制的。private是限制类之外的函数的访问权。只要将数据成员或成员函数使用private限定,就设定了一道防线。它还必须留下与外面打交道的接口,这通过具有public权限的成员函数实现。在C++中,数据封装是通过类来实现的。在类中指定了各成员的访问权限。一般情况下:将数据成员说明为私有的,以便隐藏数据;将部分成员函数说明为公有的,用于提供外界和这个类的对象相互作用的接口(界面),从而使得其他函数(例如main函数)也可以访问和处理该类的对象。对于那些仅是为支持公有函数的实现而不作为对象界面的成员函数,也将它们说明为私有的。第24页,共108页,2023年,2月20日,星期四4.2构造函数建立一个对象时,对象的状态(数据成员的取值)是不确定的。为了使对象的状态确定,必须对其进行正确的初始化。C++有一个称为构造函数的特殊成员函数,它可自动进行对象的初始化。第25页,共108页,2023年,2月20日,星期四4.2.1默认构造函数当没有为一个类定义任何构造函数的情况下,C++编译器总要自动建立一个不带参数的构造函数。例如,如果在上面的例子中没有为Point类定义任何构造函数,则C++编译器要为它产生一个默认构造函数,这个默认构造函数具有下面这种形式:Point::Point(){}即它的函数体是空的。因此,当建立Point类的一个对象时,对象的状态是不确定的,即没有被初始化。

第26页,共108页,2023年,2月20日,星期四

一旦程序定义了自己的构造函数,系统就不再提供这个默认构造函数。如果程序员没有定义一个无参数的构造函数,但又声明了一个没有初始化的对象(如“PointA;”),则因系统已经不再提供默认构造函数,编译就会出错。“默认”也称为“缺省”,本书统一使用“默认”。不带参数的构造函数又称作默认构造函数。如果只声明对象数组(即不提供初值),因为每个元素对象均需要调用一次默认构造函数来为自己初始化,所以必须要求有一个默认构造函数。

第27页,共108页,2023年,2月20日,星期四4.2.2定义构造函数1.构造函数的定义和使用方法构造函数的作用是在对象被创建时使用特定的值构造对象,或者说将对象初始化为一个特定的状态。在对象创建时由系统自动调用。如果程序中未声明,则系统自动产生出一个默认形式的构造函数允许为内联函数、重载函数、带默认形参值的函数。第28页,共108页,2023年,2月20日,星期四构造函数的特点构造函数名与类名相同;没有返回值;定义在public下;构造函数由系统自动调用;若不显式定义,系统自动生成一个不带参数的构造函数;显式定义了构造函数后,默认的构造函数不起作用;构造函数可以重载;第29页,共108页,2023年,2月20日,星期四【例4.5】构造函数的定义和执行过程实例程序。#include<iostream>usingnamespacestd;classPoint{private:intx,y;public:Point();//声明不带参数的构造函数 Point(int,int);//声明带2个参数的构造函数};Point::Point():x(0),y(0)//定义不带参数的构造函数{cout<<"Initializingdefault"<<endl;}//定义带2个参数的构造函数Point::Point(inta,intb):x(a),y(b){cout<<"Initializing"<<a<<","<<b<<endl;}第30页,共108页,2023年,2月20日,星期四voidmain(){PointA;//使用不带参数的构造函数产生对象APointB(15,25);//使用带参数的构造函数产生对象BPointc[2];//使用不带参数的构造函数产生对象数组CPointD[2]={Point(5,7),Point(8,12)};//使用带参数//的构造函数产生对象数组D}第31页,共108页,2023年,2月20日,星期四程序输出结果如下:InitializingdefaultInitializing15,25InitializingdefaultInitializingdefaultInitializing5,7Initializing8,12程序中和Point类同名的两个成员函数是构造函数:一个不带参数,另一个带有2个参数。第32页,共108页,2023年,2月20日,星期四构造函数可以通过构造函数的初始化列表给其数据成员赋值Point::Point(inta,intb):x(a),y(b)完成“x=a,y=b”的功能,它与下面的方法等价:Point::Point(int,intb){x=a;y=b;cout<<"Initializing"<<a<<","<<b<<endl;}第33页,共108页,2023年,2月20日,星期四构造函数在类体里的声明形式如下:

类名(形参1,形参2,…形参n);//可以没有形参构造函数在类体外定义。可以使用初始化列表或者在构造函数的函数体内定义数据成员的初值。假设数据成员为x1,x2,…,xn,则有如下两种形式:

类名::类名(形参1,形参2,…形参n):x1(形参1),x2(形参2),…,xn(形参n){}类名::类名(形参1,形参2,…形参n){x1=形参1;x2=形参2;……xn=形参n;}构造函数的参数是无顺序排列,只要保证相互的对应顺序即可。可使用默认参数或重载。第34页,共108页,2023年,2月20日,星期四当声明一个外部对象时,外部对象只是引用在其他地方声明的对象,程序并不为外部对象说明调用构造函数。如果是全局对象或静态对象,在main函数执行之前要调用它们的构造函数,使用下面的主程序演示全局对象的情况:【例4.6】使用前面定义的Point类演示全局对象的例子。Pointglobal(5,7);voidmain(){cout<<"Enteringmainandexitingmain“<<endl;}程序在进入main函数之前先构造全局对象,输出“Initializing5,7”。进入主函数之后输出为:Enteringmainandexitingmain。第35页,共108页,2023年,2月20日,星期四2.自动调用构造类成员函数程序员不能在程序中显式地调用构造类成员函数,这类函数是自动调用的。Pointa.Point(x,y);错误Pointa(x,y);。正确编译系统会自动调用Point(x,y)产生对象a并使用x和y将其正确地初始化。也不能在产生对象a之后,使用构造函数改变对象的属性值。a.Point(x,y);错误如果要改变对象的属性值,需要设计专门的成员函数,如使用a.Setxy(x,y)改变对象的属性值。第36页,共108页,2023年,2月20日,星期四4.2.3构造函数和运算符new运算符new用于建立生存期可控的对象并返回这个对象的指针。使用new建立动态对象的语法和建立动态变量的情况类似,其不同点是new和构造函数一同起作用。【例4.7】使用前面的Point类演示new运算符和构造函数的关系的例子。voidmain(){Point*ptr1=newPoint;Point*ptr2=newPoint(5,7);deleteptr1;deleteptr2;}运行这个程序,程序的输出结果是:InitializingdefaultInitializing5,7第37页,共108页,2023年,2月20日,星期四当使用new建立一个动态对象时,new首先分配足以保存Point类的一个对象所需要的内存,然后自动调用构造函数来初始化这块内存,再返回这个动态对象的地址。使用new建立的动态对象在不用时必须用delete删除,以便释放所占空间。第38页,共108页,2023年,2月20日,星期四4.2.4构造函数的默认参数如果程序定义自己的有参数构造函数,又想使用无参数形式的构造函数,解决的方法是将相应的构造函数全部使用默认参数设计。

第39页,共108页,2023年,2月20日,星期四【例4.8】设计构造函数的默认参数。#include<iostream>usingnamespacestd;classPoint{private:intx,y;public:Point(int=0,int=0);//声明两个参数均为默认参数};Point::Point(inta,intb):x(a),y(b)//定义构造函数{cout<<"Initializing"<<a<<","<<b<<endl;}第40页,共108页,2023年,2月20日,星期四voidmain(){PointA;//构造函数产生对象APointB(15,25);//构造函数产生对象BPointc[2];//构造函数产生对象数组C}声明构造函数原型时不需要给出参数名,即使用“int=0,int=0”。程序输出如下:Initializing0,0Initializing15,25Initializing0,0Initializing0,0第41页,共108页,2023年,2月20日,星期四4.2.5复制构造函数引用在类中一个很重要的用途是用在复制构造函数中。这是一类特殊而且重要的函数,通常用于使用已有的对象来建立一个新对象。浅拷贝就是普通的数值复制。在通常情况下,编译器建立一个默认复制构造函数,这个复制构造函数采用浅拷贝来使用已有的对象来建立新对象,所以又直译为拷贝构造函数。第42页,共108页,2023年,2月20日,星期四对类A而言,复制(拷贝)构造函数的原型为:

A::A(A&)首先它是一个构造函数。其次,它的参数有些特别,是引用类自己的对象,即用一个已有的对象来创建一个新对象。为了不改变原有对象,更普通的形式是使用const限定: A::A(constA&)但如果存在指针时,是简单地将两个指针指向同一地址还是指向不同的存储区域?简单的赋值可能使两个指针指向了同一块存储区域。当一个指针销毁后,另外一个指针指向了无效区域,访问这个指针可能会引起严重的错误。

第43页,共108页,2023年,2月20日,星期四为了克服这样的缺点,需要自定义复制构造函数。复制构造函数的原型为:

Point(Point&);//复制构造函数它的定义如下:

Point::Point(Point&t){x=t.x;y=t.y;}这个成员函数具有特殊的作用:在使用该类的一个对象初始化该类的另一个对象时,调用这个函数(有时又称为复制初始化构造函数)。例如:

Pointobj1(25,52);

Pointobj2(obj1);//使用obj1的数据成员初始化obj2,//即数据成员为25和52为了安全起见,推荐使用的原型为:

Point(constPoint&)第44页,共108页,2023年,2月20日,星期四4.3析构函数在对象消失时,使用析构函数释放由构造函数分配的内存。第45页,共108页,2023年,2月20日,星期四4.3.1定义析构函数它也是一种特殊的成员函数,执行与构造函数相反的操作,通常用于撤消对象时的一些清理任务,如释放分配给对象的内存空间等。具有一些特殊的性质:与类名相同,但前面必须加一个波浪号(~);没有参数,也没有返回值,而且不能重载。因此在一个类中只能有一个析构函数;当撤消对象时,编译系统会自动地调用析构函数。如果不显式定义,系统自动生成一个空的析构函数

第46页,共108页,2023年,2月20日,星期四例如:Point::~Point(){cout<<"Destructorisactive"<<endl;}使用下面的测试程序说明析构函数的作用:voidmain(){PointA(5,68);cout<<"Exitingmain"<<endl;}执行此程序,其输出是:Initializing5,68//调用构造函数Exitingmain//主程序输出Destructorisactive//自动调用析构函数当对象的生存期结束时,程序为这个对象调用析构函数,然后回收这个对象占用的内存。全局对象和静态对象的析构函数在程序运行结束之前调用。全局对象数组和静态对象数组的析构函数在程序结束之前被调用。数组的每个元素调用一次析构函数。第47页,共108页,2023年,2月20日,星期四4.3.2析构函数和运算符delete运算符delete与析构函数一起工作。当使用运算符delete删除一个动态对象时,它首先为这个动态对象调用析构函数,然后再释放这个动态对象占用的内存,这和使用new建立动态对象的过程正好相反。【例4.9】使用Point类演示建立和释放一个动态对象数组的例子。#include<iostream>usingnamespacestd;classPoint{private:intx,y;

第48页,共108页,2023年,2月20日,星期四public:Point(int=0,int=0);//声明两个参数均为默认参数~Point();//声明析构函数};Point::Point(inta,intb):x(a),y(b)//定义构造函数{cout<<"Initializing"<<a<<","<<b<<endl;}Point::~Point()//定义析构函数{cout<<"Destructorisactive"<<endl;}voidmain(){ Point*ptr=newPoint[2]; delete[]ptr;}第49页,共108页,2023年,2月20日,星期四程序的输出如下:Initializing0,0//调用构造函数Initializing0,0//调用构造函数Destructorisactive//调用析构函数Destructorisactive//调用析构函数C++使用“[]”号来说明这是动态对象数组即语句delete[]ptr;//注意不要错为ptr[],而且[]号内为空delete将为动态数组的每个对象调用一次析构函数,然后释放ptr所指向的内存。当程序先后创建几个对象时,系统按后建先析构的原则析构对象。当使用delete调用析构函数时,则按delete的顺序析构。第50页,共108页,2023年,2月20日,星期四4.3.3默认析构函数如果在定义类时没有定义析构函数,C++编译器要为这个类产生一个默认的析构函数,正如没有给类定义构造函数,C++编译系统会产生一个默认构造函数一样,编译器也为它产生一个函数体为空的默认析构函数:Test::~Test(){}第51页,共108页,2023年,2月20日,星期四4.4调用复制构造函数的综合实例

调用复制构造函数的情况当用类的一个对象去初始化该类的另一个对象时,系统自动调用复制构造函数实现拷贝赋值。Pointp2(p1);//用对象p1初始化对象p2,复制构造函数被调用(代入法)Pointp3=p1;//用对象p1初始化对象p3,复制构造函数被调用(赋值法)第52页,共108页,2023年,2月20日,星期四当函数的形参是类的对象,调用函数,进行形参和实参结合时。voidfun1(Pointp){cout<<p.GetX()<<endl;}voidmain(){PointA(1,2);fun1(A);//调用复制构造函数}当函数的返回值是对象,函数执行完成,返回调用者时。Pointfun2(){PointA(1,2);returnA;//调用复制构造函数}voidmain(){PointB;B=fun2();}第53页,共108页,2023年,2月20日,星期四【例4.10】演示调用构造函数及析构函数的综合例子。#include<iostream>usingnamespacestd;classPoint{private: intX,Y;public: Point(inta=0,intb=0)//构造函数{X=a;Y=b;cout<<"Initializing"<<endl;}Point(constPoint&p);//复制构造函数第54页,共108页,2023年,2月20日,星期四intGetX(){returnX;}intGetY(){returnY;}voidShow(){cout<<"X="<<X<<",Y="<<Y<<endl;}~Point(){cout<<"delete..."<<X<<","<<Y<<endl;}};Point::Point(constPoint&p)//定义复制构造函数{ X=p.X; Y=p.Y; cout<<"CopyInitializing"<<endl;}voiddisplay(Pointp)//Point类的对象作为函数的形参{p.Show();}voiddisp(Point&p)//Point类对象的引用作为函数的形参{p.Show();}第55页,共108页,2023年,2月20日,星期四Pointfun()//函数的返回值为Point类的对象{PointA(101,202);returnA;}voidmain(){PointA(42,35);//对象A//第1次调用复制构造函数PointB(A); //①用A初始化BPointC(58,94);//对象Ccout<<"calleddisplay(B)"<<endl;//第2次调用复制构造函数display(B); //②对象B作为display的实参

第56页,共108页,2023年,2月20日,星期四cout<<"Next..."<<endl;cout<<"calleddisp(B)"<<endl;disp(B);cout<<"callC=fun()"<<endl;//第3次调用复制构造函数C=fun(); //③fun的返回值赋给对象Ccout<<"calleddisp(C)"<<endl;disp(C);cout<<"out..."<<endl;}第57页,共108页,2023年,2月20日,星期四输出结果及注释如下:Initializing//创建对象ACopyInitializing//调用复制构造函数,使用对象A初始化BInitializing//创建对象Ccalleddisplay(B)//用对象B作为参数CopyInitializing//需要调用复制构造函数X=42,Y=35//进入函数体执行语句delete...42,35//退出函数时,需要调用析构函数清除对象Next...calleddisp(B)//使用引用做为参数,则不需要//调用复制构造函数X=42,Y=35//进入函数体执行语句callC=fun()//C使用函数fun()的返回值Initializing//在函数fun()内创建对象ACopyInitializing//调用复制构造函数返回值第58页,共108页,2023年,2月20日,星期四delete...101,202//返回值给对象C,调用析构函数//清除临时对象delete...101,202//退出函数体,调用析构函数清除//对象Acalleddisp(C)//使用引用做为参数X=101,Y=202//进入函数体执行语句out...delete...101,202//调用析构函数清除对象Cdelete...42,35//调用析构函数清除对象Bdelete...42,35//调用析构函数清除对象Adelete...101,202//将值传给对象C,调用析构函数//清除临时对象delete...101,202//退出函数体,调用析构函数//清除对象A第59页,共108页,2023年,2月20日,星期四第1句输出比较简单,调用函数fun,在其内调用构造函数Point(int,int)创建对象A。对象A的生命期与函数fun同在,结束调用时则调用析构函数析构对象A。在这个构造与析构之间,还有一个过程发生,即执行“returnA;”时,目的是使得C=A,使C的数据成员与A的相同,但这要调用复制构造函数实现。复制构造函数是创建对象的,它产生一个临时对象,用这个临时对象完成C=A,然后再析构这个临时对象,这就是中间两条输出信息。这样,语句“C=func();”就输出如上4条信息。第60页,共108页,2023年,2月20日,星期四4.5成员函数重载及默认参数【例4.11】构造一个求4个正整数中最大者的类Max,并用主程序验证它的功能。classMax{//声明类private://封装数据成员和成员函数inta,b,c,d;//数据成员intMaxi(int,int);//只允许类内部的成员函数调用public://对外界的接口voidSet(int,int,int,int);//设置对象初值intMaxi();//求最大值}A[3];//声明类的对象数组,定义结束第61页,共108页,2023年,2月20日,星期四//类中成员函数的实现intMax::Maxi(intx,inty) //求二个数的最大值{return(x>y)?x:y;}//使用两个默认参数voidMax::Set(intx1,intx2,intx3=0,intx4=0){a=x1;b=x2;c=x3;d=x4;}intMax::Maxi()//求自己类中四个数的最大值{ intx=Maxi(a,b);//x和y为Maxi()函数的局部整数对象inty=Maxi(c,d);returnMaxi(x,y);}第62页,共108页,2023年,2月20日,星期四//主程序#include<iostream>usingnamespacestd;voidmain(){ A[0].Set(12,45,76,89);//为数组对象A[0]置初值 A[1].Set(12,45,76);//为数组对象A[1]置初值 A[2].Set(12,45);//为数组对象A[2]置初值for(inti=0;i<3;i++)//输出对象求值结果cout<<A[i].Maxi()<<"";}第63页,共108页,2023年,2月20日,星期四程序演示了可在声明类的同时也声明类的对象,这里是声明对象数组A,作用与在主程序里使用“MaxA[3];”语句相同。为了提高可读性,一般不在声明类时声明对象,这里只是为了演示它的性质。程序输出结果为:897645类中重载了函数Maxi,一个原型为Maxi(int,int),用来求两数中的大者。因为它只被自己的成员函数使用,所以定义为private。另一个原型为Maxi(),它调用两次Maxi(int,int),然后再用这两次的结果作为Maxi(int,int)的参数,求出4个数中的最大值。赋值成员函数Set使用2个默认参数,是为了书写方便。第64页,共108页,2023年,2月20日,星期四4.6this指针在定义Point类的两个对象A和B之后,当执行语句“A.Setxy(25,55);”时,A.x和A.y就被赋值。但是,函数Setxy(int,int)作为代码,在计算机里是和具体的对象分开存储的。那么,它是怎样知道,是要对A进行操作而不是对B进行操作呢?当执行A.Setxy(25,55)时,成员函数Setxy(int,int)有一个隐藏参数,名为this指针。也就是说,源程序被编译器编译后,Setxy(inta,intb)实际上是如下形式:

voidPoint::Setxy(inta,intb,(Point*)this){this->x=a;this->y=b;}成员函数的this指针指向对象A。成员中对x和y的引用表示是引用对象A的成员x和y。对于任何访问该成员函数的类的对象来说,C++编译器都认为是访问this指针所指向的对象中的成员。第65页,共108页,2023年,2月20日,星期四C++规定,当一个成员函数被调用时,系统自动向它传递一个隐含的参数,该参数是一个指向接受该函数调用的对象的指针,从而使成员函数知道该对哪个对象进行操作。在程序中,可以使用关键字this来引用该指针。使用this指针,保证了每个对象可以拥有自己的数据成员,但处理这些数据成员的代码可以被所有的对象共享。除非特殊需要,一般情况下都省略符号“this->”,而让系统进行默认设置。第66页,共108页,2023年,2月20日,星期四4.7一个类的对象作为另一个类的成员因为类本身就是一种新的数据类型,所以一个类可以作为另一个类的成员。假设有A和B两个类,可以通过在B类里定义A的对象作为B的数据成员,或者定义一个返回类型为A的函数作为B的成员函数。第67页,共108页,2023年,2月20日,星期四假设定义了坐标点的类Point,矩形类Rectangle的属性需要一个坐标点及长和宽。一个Point的对象恰好可以作为矩形的顶点坐标,即Rectangle可以使用Point的一个对象作为数据成员。在Rectangle类中,用Point类定义一个返回Point类型指针的函数作为Rectangle的成员函数。

【例4.12】使用对象成员的例子。#include<iostream>usingnamespacestd;classPoint{//定义点类 intx,y; public: voidSet(inta,intb){x=a;y=b;} intGetx(){returnx;} intGety(){returny;}};第68页,共108页,2023年,2月20日,星期四classRectangle{//在矩形类里使用Point类的成员PointLoc;//定义一个Point类的对象作为顶点intH,W;//H为高,W为宽public:voidSet(intx,inty,inth,intw);Point*GetLoc();//用Point类定义返回指针的函数intGetHeight(){returnH;}intGetWidth(){returnW;}};voidRectangle::Set(intx,inty,inth,intw){Loc.Set(x,y);//初始化坐标顶点H=h;W=w;}Point*Rectangle::GetLoc()//返回类型Point*,作//为Rectangle的成员函数{return&Loc;}//返回对象Loc的地址第69页,共108页,2023年,2月20日,星期四voidmain(){Rectanglerect;rect.Set(10,2,25,20);cout<<rect.GetHeight()<<",“<<rect.GetWidth()<<",";Point*p=rect.GetLoc();//定义Point类的//指针对象p并初始化cout<<p->Getx()<<","<<p->Gety()<<endl;}新类Rectangle具有一个顶点,所以也具有Point类的属性。这就是类的结构关系(聚合关系,有时又称包含)。构成的新类不能直接操作另一个类的数据,必须通过原构成类的对象使用它们的成员函数来实现,本例使用Loc.Set(x,y)方式实现。程序输出结果为:25,20,10,2第70页,共108页,2023年,2月20日,星期四4.8类和对象的性质4.8.1类对象的性质归纳对象的一些基本特性如下:①对象之间可以相互赋值。例如,如下语句使A和B的数据成员有相同的值:PointA,B;A.Setxy(25,55);B=A;②可使用对象数组。例如,“MaxA[3];”定义数组A可以存储3个Point类的对象。③可使用指向对象的指针,使用取地址运算符&将一个对象的地址置于该指针中,如:Point*p=&A;p->Display();注意,不能取私有数据成员的地址,也不能取成员函数的地址。第71页,共108页,2023年,2月20日,星期四④对象可以用做函数参数,这时参数传递可以采用传值方式,也可以传址,采用对象的引用或指针作为参数。但是,如参数对象被修改,相应的实参对象也将被修改。C++推荐使用引用作为参数传递。为了避免被调用函数修改原来对象的数据成员,可以使用const修饰符。⑤对象作为函数参数时,可以使用对象值、对象引用和对象地址(指针)。以介绍过的print函数为例,它们的参数传递形式为:voidprint(Pointa){a.Display;}voidprint(Point&a){a.Display;}voidprint(Point*p){p->Display;}它们的原型分别print(Point);print(Point&);print(Point*);对于对象A,print(&A)调用的是原型为print(Point*)的函数形式。注意:参数为对象和引用时,编译系统无法区别,重载print只能选择其中的一种。⑥一个对象可以用做另一个对象的成员。例如定义Point类的对象Loc为Rectangle的数据成员,GetLoc()作为返回Point类型指针的函数。第72页,共108页,2023年,2月20日,星期四4.8.2类的性质1.使用类的权限①类本身的成员函数可以使用类的所有成员(私有和公有成员)。②类的对象只能访问公有成员函数,例如输出x只能使用A.Getx()不能使用A.x。③其他函数不能使用类的私有成员,也不能使用公有成员函数,它们只能通过类的对象使用公有成员函数。④一个类可以使用另外一个类的对象,这个类也只能通过那个类的对象使用该类的成员函数,通过成员函数使用数据成员,例如Loc.Set(x,y)。第73页,共108页,2023年,2月20日,星期四2.不完全的类声明类不是内存中的物理实体,只有当使用类产生对象时,才进行内存分配,这种对象建立的过程称为实例化。应当注意的是:类必须在其成员被使用之前先进行声明。然而,有时也需要将类作为一个整体来使用,而不存取其成员。声明指针就是这种情况,例如:classMembersOnly;//不完全的类声明MembersOnly*club;//定义一个全局变量类指针voidmain(){…//函数体}//主函数classMembersOnly{…//类体};//完全定义该类第74页,共108页,2023年,2月20日,星期四第一条语句称为不完全类声明,它用于在类没有完全定义之前就引用该类的情况,可以声明全局变量指针club。编译器执行到该指针的声明处时,只了解指针所指类型是一个叫MembersOnly的类,而不了解其他任何情况。不完全声明的类不能实例化,企图实例化会产生编译出错信息;不完全声明仅用于类和结构,企图存取没有完全声明的类成员,也会引起编译出错信息。另外,将数据成员的声明放在最后,有利于先理解类的界面。第75页,共108页,2023年,2月20日,星期四3.空类尽管类的目的是封装代码和数据,它也可以不包括任何声明。例如:classEmpty{};当然,这种类没有任何行为,但可以产生空类对象。voidmain(){Emptyobject;}为什么要产生空类呢?在开发大的项目时,需要在一些类还没有完全定义或实现时进行先期测试。这常称为“插头”,用来保证代码能正确地被编译,从而允许测试其中的一部分。不过这是早期的做法,对强类型检查的编译器,会给出一个警告。可给空类增加一个无参数构造函数(见构造函数一节)以消除警告。例如:classEmpty{public:Empty(){}};第76页,共108页,2023年,2月20日,星期四4.类作用域声明类时所使用的一对花括号形成所谓的类作用域。在类作用域中声明的标识符只在类中可见。例如:classexample{intnum;};inti=num;//错,num在此不可见intnum;//正确,num与类中说明的数据//成员num具有不同的作用域第77页,共108页,2023年,2月20日,星期四即使该成员函数的实现是在类定义之外给出的,类作用域也包含了类中成员函数的作用域classMyClass{intnumber;public:voidset(int);};intnumber;//这个number不属于类MyClassvoidMyClass::set(inti){number=i;//使用类MyClass中的标识符number}类中的一个成员名可以使用类名和作用域运算符来显式指定,这称为成员名限定。例如:voidMyClass::set(inti){MyClass::number=i;//显式指定访问MyClass//类中的标识符number}第78页,共108页,2023年,2月20日,星期四在程序中使用成员选择运算符(“.”或“->”)访问一个对象的成员时,其后的名字是引用该对象所在类中声明的成员名。例如:PointA;//声明类对象,假设类//内有成员函数Getx()intGetx(){return5;};//类外面声明的函数intx=Getx();//访问函数Getx(),返回5inty=A.Getx();//访问类Point中声明的//成员函数Getx()第79页,共108页,2023年,2月20日,星期四除非编译器在处理类声明时遇到了标识其结束的右花括号,否则这个声明仍然是引用性声明。引用性声明所声明的类名不能用来建立对象,只能用来声明指针或引用,或用在函数声明中。例如:classMyClass{MyClassmember;//错MyClass*p;//正确};classYourClass{private:MyClassd;//正确};当在类MyClass中声明成员member时,类名MyClass仅作了引用性声明,因而这个语句是错误的。第80页,共108页,2023年,2月20日,星期四4.9面向对象的标记图为了设计、开发和相互交流的需要,采用图像形式将面向对象程序设计对问题的描述直观地表示出来。UML是一种可视化建模语言,主要用于面向对象分析和建模。本书主要是借助于它来说明类的有关知识。第81页,共108页,2023年,2月20日,星期四4.9.1类和对象的UML标记图

类和对象的标记符号类似,它们都只能表示静态特征。如图4.1所示,在UML语言中,类使用短式和长式两种方式表示。第82页,共108页,2023年,2月20日,星期四短式仅用1个含有类名的长方框表示。长式使用3个方框表示。最上面的框中填入类的名称,中间框中填入属性(C++中称为数据成员),最下面的框中填入成员函数(操作)。属性和操作可以根据需要进行细化。第83页,共108页,2023年,2月20日,星期四图4.2对象的标记图对象的表示有3种方式,最简单的是只填写对象名称,完整的方式是给出对象名和类名(类名在右边,两者之间用冒号连接),并用下划线将它们标注出来。当还没有决定这个对象的名称时,可以不给出对象名,但不能省去“:”号。第84页,共108页,2023年,2月20日,星期四4.9.2表示对象的结构与连接只有定义和描述了对象类之间的关系,各个对象

温馨提示

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

评论

0/150

提交评论