资源描述
单击此处编辑母版标题样式,*,电子科技大学计算机学院 软件学院,*,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,面向对象程序设计语言,C+,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,第七章 类与对象,丘志杰,电子科技大学,计算机学院 软件学院,C+,与,C,最大的不同在于,C+,增加了,面向对象,的概念。,C+,允许用户定义新的抽象数据类型,类类型,,它将一组数据和与这些数据相关的操作封装在一起,实现了面向对象技术中的核心概念,数据封装,。,2024/8/11,2,抽象是对具体对象(问题)进行概括,抽出这一类对象的,公共性质,并加以描述的过程。,先注意问题的本质及描述,其次是实现过程或细节。,数据抽象:描述某类对象共有的属性或状态。,行为抽象:描述某类对象共有的行为特征或具有的功能。,抽象,2024/8/11,3,抽象实例,:时钟,数据抽象:,具有表明当前时间的,时、分、秒,行为抽象:,具有,设置时间,和,显示时间,两个最基本的功能,2024/8/11,4,抽象实例,:,人,数据抽象:,姓名、年龄、性别等,行为抽象:,生物属性:吃饭、穿衣、睡觉、行走等行为,社会属性:工作、学习等行为,2024/8/11,5,在研究问题时,侧重点不同,可能会产生不同的抽象结果;解决同一问题时要求不同,也会产生不同的抽象结果。例如:,如果开发一个人事管理软件,那么关心的是员工的姓名、性别、工龄、工资、工作部门等相关信息。,如果开发学籍管理软件,那么关心的是学生的姓名、性别、年龄、籍贯、所在学院等相关信息。,因此,抽象是相对,而非绝对的。,2024/8/11,6,如何实现抽象?,抽象定义了一个事物的本质特征,那么从软件设计的角度又如何对抽象出来的结果进行描述呢?,封装可以实现抽象。,封装就是将,数据和操作这些数据的函数,进行有机地结合。,C+,的解决方案:,用类来实现封装机制,2024/8/11,7,定义和使用类的基本过程,第一步:进行抽象,第二步:定义类,第三步:实现类,第四步:使用类,2024/8/11,8,类的定义:类的成员构成,class,ClassName,数据成员,函数成员,;,类是一种抽象数据类型,声明形式如下:,关键字,class,用于声明一个类类型,用于表达数据抽象,用于表达行为抽象,2024/8/11,9,类定义举例:时钟类,class Clock,int,Hour,Minute,Second;,void,SetTime,(,int,h,int,m,int,s);void ShowTime();,;,2024/8/11,10,类成员的访问控制,什么是“类内”和“类外”,在类定义之内称为类内,在类定义之外称为类外,数据封装的目的就是,信息隐蔽,。为了达到信息隐蔽,在,C+,类中,并非所有的成员都是对外可见的(或者说,是类外可以访问的)。,通过设置成员的,访问控制属性,来实现对类成员的访问控制。这些控制属性有:,public,、,protected,、,private,2024/8/11,11,类的定义:对成员的访问控制,class,ClassName,public:,公有成员,protected:,保护成员,private:,私有成员,;,2024/8/11,12,公有成员,在关键字,public,后面声明,它们是类与外部的接口,任何,类内、类外,函数,都可以访问公有数据和函数。,class Clock,public:,void,SetTime,(,int,h,int,m,int,s);,/,公有成员函数,void ShowTime();,/,公有成员函数,;,2024/8/11,13,私有成员,在关键字,private,后面声明,,只允许本类中的函数(,即类内函数,)访问,而类外的任何函数都不能访问。,class Clock,private:,int,Hour,Minute,Second;/,私有数据成员,;,2024/8/11,14,保护成员,在关键字,protected,后面声明,的数据成员或成员函数。与,private,类似,其差别表现在继承与派生时对派生类的影响不同,在第八章中再描述。,2024/8/11,15,类定义举例:时钟类,class Clock,public:,void,SetTime,(,int,h,int,m,int,s);void ShowTime();,private:,int,Hour,Minute,Second;,;,2024/8/11,16,类的实现,实现一个类,就是按照所设定的功能语义去实现类中的每一个成员函数。,2024/8/11,17,class Clock,private:,int,Hour,Minute,Second;,public:,void,SetTime,(,int,h,int,m,int,s),Hour=h;Minute=m;,Second=s;,void ShowTime(),cout,“Current Time:”,cout,Hour“:”Minute“:”Second,endl,;,;,在类内实现成员函数,编译器按内联函数处理,在类内实现成员函数,编译器按内联函数处理,2024/8/11,18,class Clock,private:,int,Hour,Minute,Second;,public:,void,SetTime,(,int,h,int,m,int,s);,/,给出函数原型,void ShowTime();,/,给出函数原型,;,void,Clock:,SetTime,(,int,h,int,m,int,s),Hour=h;Minute=m;,Second=s;,void,Clock:,ShowTime,(),cout,“Current Time:”,cout,Hour“:”Minute“:”Second,公有成员函数名,(,形参列表,);,对象的指针,-,公有数据成员,2024/8/11,21,举例:一个比较完整的,Clock,类,/,定义,Clock,类,class Clock,private:,int,Hour,Minute,Second;,public:,void,SetTime,(,int,h,int,m,int,s);,void,addHour(int,h);,void,addMinute(int,m);,void,addSecond(int,s);,void ShowTime();,;,2024/8/11,22,/,实现,Clock,类中的成员函数,void,Clock:SetTime,(,int,h,int,m,int,s),Hour=h;Minute=m;,Second=s;,void,Clock:addHour,(,int,h)Hour+=h;,void,Clock:addMinute,(,int,m)Minute+=m;,void,Clock:addSecond,(,int,s)Second+=s;,void,Clock:ShowTime,(),cout,“Current Time:”;,cout,Hour“:”Minute“:”Second,endl,;,2024/8/11,23,/,使用,Clock,类,void main(),Clock clock_1,clock_2;,clock_1.,SetTime,(9,5,25);,clock_2.,SetTime,(15,16,45);,clock_1.,addHour,(3);,clock_2.,addMinute,(8);,clock_1.ShowTime();,clock_2.ShowTime();,程序输出:,Current Time:12:5:25,Current Time:15:24:45,2024/8/11,24,总结:定义和使用类类型的过程,抽象:,对事物进行抽象,定义类:,根据抽象的结果定义类的特性,实现类:,实现类中成员函数的逻辑,使用类:,在程序中定义类的实例,使用类的公有成员。,2024/8/11,25,在定义类时,把可以被外部访问的成员说明为,public,属性,作为外部访问类中成员的接口。把不能被外部访问的成员说明为,private,或,protected,属性。,私有成员,公有成员,类对外的接口,保护成员,2024/8/11,26,请思考以下情况,能调用下面语句吗?,clock_1.Hour=21;,如果将,ShowTime,说明为,private,属性,还能调用如下语句吗?,clock_2.ShowTime();,2024/8/11,27,练习一,设计一个三角形类,CTriangle,,该类满足下述要求:,a,)有一个成员函数,SetEdge,,用于设置三角形的边。,b,)有一个成员函数,GetArea,,用于获得三角形的面积。,c,)有一个成员函数,GetCircumference,,用于获得三角形的周长。,在,main,函数中,实例化一个,Ctriangle,类的对象,将三条边分别为,3,、,4,、,5,,然后将其面积和周长显示在屏幕上。,2024/8/11,28,备注:,1,三角形面积的计算方式如下:,a,、,b,、,c,分别为三条边,,S,代表面积,,p=(a+b+c)/2,,那么,S2=p(p-a)(p-b)(p-c),。,2,求开平方的库函数是,sqrt,,需要包含,math.h,头文件,使用方式的举例如下:,int a,b;b=36;a=sqrt(b);,那么,a,的值为,6,2024/8/11,29,练习二,Need To Do,2024/8/11,30,几点说明,在声明类时,具有不同访问属性的成员可以按任意顺序出现。,class Clock,private:,int,Hour,Minute,Second;,public:,void,SetTime,(,int,h,int,m,int,s);void ShowTime();,protected:,;,2024/8/11,31,修饰访问属性关键字可以多次出现。,class Clock,public:,void,SetTime,(,int,h,int,m,int,s);,private:,int,Hour,Minute,Second;,public:,void ShowTime();,;,2024/8/11,32,一个成员只能具有一种访问属性,否则会出现歧异,。,class Clock,public:,void,SetTime,(,int,h,int,m,int,s);,void ShowTime();,private:,void ShowTime();,int,Hour,Minute,Second;,;,2024/8/11,33,例子:简单,int,数组类,/,数组类的定义,const,int,ARRAY_SIZE=4;,class,MyArray,int,dataARRAY_SIZE,;,public:,int,getArraySize,();,int,setVal(int,pos,int,val,);,int,getMaxVal,();,int,getMinVal,();,;,2024/8/11,34,/,数组类的实现,int,MyArray:getArraySize,(),return ARRAY_SIZE;,int,MyArray:setVal(int,pos,int,val,),if(pos,ARRAY_SIZE),return-1;,datapos,=,val,;,return 0;,2024/8/11,35,/,数组类的实现,(,续,),int,MyArray:,getMaxVal,(),int,temp=data0;,for(int,i=1;itemp),temp=,datai,;,return temp;,int,MyArray:,getMinVal,(),int,temp=data0;,for(int,i=1;i,ARRAY_SIZE;i,+),if(datai,temp),temp=,datai,;,return temp;,2024/8/11,36,/,数组类的使用,int,main(),MyArray,array;,int,i,size,v,max,min,;,size=,array.getArraySize,();,cout,please input size numbers:,endl,;,for(i,=0;iv;,array.setVal(i,v,);,max=,array.getMaxVal,();,min=,array.getMinVal,();,cout,max value is:max,endl,;,cout,min value is:min,endl,;,return 0;,2024/8/11,37,改造:为简单,int,数组类增加公用的私有函数,/,数组类的定义:增加公用的私有函数,const,int,ARRAY_SIZE=4;,class,MyArray,int,dataARRAY_SIZE,;,int,getVal(int,condition);,public:,int,getArraySize,();,int,setVal(int,pos,int,val,);,int,getMaxVal,();,int,getMinVal,();,;,2024/8/11,38,/,数组类的实现:增加和调用公用的私有函数,int,MyArray:getVal(int,condition),int,temp=data0;,for(int,i=1;itemp),temp=,datai,;,else,if(datai,SetTime,(9,5,25),;,p-,ShowTime();,p-,SetTime,(10,3,28),;,p-,ShowTime();,2024/8/11,41,关键字,this,C+,为每个,非静态成员函数,提供一个,this,指针。,this,指针是一个,隐含的指针,,它指向了正在被成员函数操作的那个对象。,this,指针不能显式声明,它只是非静态成员函数的一个形参。,2024/8/11,42,this,指针举例,class Counter,int,value;,public:,void,setValue,(,int,value),this-value=,value,;,;,class Counter,int,value;,public:,void,setValue,(,int,v),value=v;,;,void main(),Counter obj1,obj2;,obj1.setValue(1);,obj2.setValue(2);,2024/8/11,43,class Counter,int,value;,public:,void,setValue(Counter,*this,int,v),this-value=v;,;,class Counter,int,value;,public:,void,setValue,(,int,v),value=v;,;,void main(),Counter obj1,obj2;,obj1.setValue(1);,obj2.setValue(2);,编译器做了特殊处理,void main(),Counter obj1,obj2;,obj1.setValue(,obj2.setValue(,2024/8/11,44,构造函数和析构函数,类的数据成员是不能在定义时直接初始化的,以,Clock,类为例:,class Clock,private:,int,Hour=0,Minute=0,Second=0;/,错误,;,2024/8/11,45,当,Clock,类被实例化时,如何初始化对象的状态,即如何初始化,Hour,、,Minute,、,Second,三个成员呢?一种可行的方式就是显式调用初始化函数。,Clock clock1;,clock1.SetTime(21,34,43);,但是这种方式存在一些不好的情况:,程序员可能会忘记调用,SetTime,(),函数,导致对象没有被初始化。,2024/8/11,46,构造函数,实际上,我们更希望这个初始化工作能够自动进行。类的,构造函数,提供了这种,自动化功能,。,构造函数是类的一种特殊成员,,函数名和类名相同,没有,返回类型,,可以,有参数。当创建类的一个新对象时,构造函数被,自动调用,,完成对象的初始化工作。,2024/8/11,47,例:为,Clock,类添加构造函数,class Clock,private:,int,Hour,Minute,Second;,public:,Clock(int,h,int,m,int,s);,;,2024/8/11,48,构造函数初始化数据成员的两种方式,赋值语句的方式:,Clock(int,h,int,m,int,s),Hour=h;,Minute=m;,Second=s;,表达式表的方式:,Clock(int,h,int,m,int,s),:,Hour(h,),Minute(m,),Second(s,),2024/8/11,49,如果构造函数带有参数,那么在创建对象时必须给出对应的实参。,如果构造函数仅有一个参数:,className,obj,=parameter;,如果构造函数有一个或多个参数:,className,obj(parameter,list);,如果构造函数有参数,但是在创建对象时又不给出对应的实参,此时编译出错。,传给构造函数实参的两种方式,2024/8/11,50,void main(),Counter counter1(8);,Counter counter2=9;,Clock clock1(3,5,1);,Clock clock2();/,错误,class Counter,private:,int,value;,public:,Counter(int,v):value(v,),;,举例,class Clock,private:,int,Hour,Minute,Second;,public:,Clock(int,h,int,m,int,s);,;,2024/8/11,51,重载构造函数,一个类可以提供多个构造函数,但它们的参数表必须不同。重载的目的是为了,满足不同的初始化需要,。,class Clock,private:,int,Hour,Minute,Second;,public:,Clock(int,h,int,m,int,s);,Clock();,Clock(char,*,timestr,);,;,void main(),Clock clock1(23,12,0);,Clock clock2();,Clock clock3(“14:45:32”);,2024/8/11,52,具有缺省参数的构造函数,构造函数也可以有缺省参数。如果在类外实现该函数时,就不能再说明缺省值了。,class Clock,private:,int,Hour,Minute,Second;,public:,Clock(int,h=0,int,m=0,int,s=0);,Clock(const,char*,timestr,);,;,请思考一下,这个类还能再有一个没有任何参数的构造函数吗?即:,Clock();,2024/8/11,53,缺省的构造函数,对于没有构造函数的类,,编译器将会自动为它生成一个没有参数的构造函数,该函数,不做任何工作,。,class Clock,private:,int,Hour,Minute,Second;,;,2024/8/11,54,析构函数,与构造函数相对的是析构函数。,C+,通过析构函数来处理对象被销毁时的清理工作。,析构函数没有返回类型,没有参数,函数名是在类名前加“,”,。,析构函数将会在,对象的生存期结束后,被,自动调用,。,如果没有进行显式说明,系统将会生成一个不做任何事的缺省的析构函数。,2024/8/11,55,举例:为,Clock,类添加析构函数,class Clock,private:,int,Hour,Minute,Second;,public:,Clock(int,h=12,int,m=0,int,s=0);,Clock();,;,Clock:Clock,(),cout,“Clock,obj,destroyed!”,endl,;,2024/8/11,56,void,func,(),Clock obj1,obj2;,void main(),func,();,/,输出:,Clock,obj,destroyed,!,Clock,obj,destroyed,!,2024/8/11,57,析构函数的典型用法,class,MyString,private:,int,len,;,char*,buf,;,public:,MyString(int,n);,void,copy(char,*,src,);,;,MyString:MyString,(,int,n),len,=n;,buf,=new,charn,;,void,MyString:copy(char,*,src,),strcpy(buf,src,);,void,func,(),MyString,obj,(64);,obj.copy(“helloworld,”);,void main(),func,();,!此时,,obj,的,buf,所指向的内存空间没有释放,2024/8/11,58,class,MyString,private:,int,len,;,char*,buf,;,public:,MyString(int,n);,MyString,();,void,copy(char,*,src,);,;,MyString:MyString,(,int,n),len,=n;,buf,=new,charn,;,void,MyString:copy(char,*,src,),strcpy(buf,src,);,MyString,:,MyString,(),delete ,buf,;,void,func,(),MyString,obj,(64);,obj.copy(“helloworld,”);,void main(),func,();,!当,obj,对象被释放时,析构函数被自动调用,,buf,所指向的内存空间被释放,2024/8/11,59,拷贝构造函数,构造函数的参数可以是任意类型,如果将与,自己同类的对象的引用,作为参数时,该构造函数就称为,拷贝构造函数,。,拷贝构造函数的特点:,首先,它是一个构造函数,当创建对象时系统会自动调用它。,其次,它将一个已经创建好的对象作为拷贝构造函数的参数,并根据需要将该对象中的数据成员逐一对应地赋值给新对象。,2024/8/11,60,示例,class Point,private:,float x,y;,public:,Point(float,a,float b),x=a;y=b;,Point(Point,&,obj,),x=,obj.x,;,y=,obj.y,;,;,void main(),Point obj1(5,15);,/,调用,Point(float,float),Point obj2(obj1);,/,调用,Point(A,&),Point obj3=obj2;,/,调用,Point(A,&),2024/8/11,61,缺省的拷贝构造函数的应用,如果一个类没有定义拷贝构造函数,那么,C+,编译器会为该类产生一个,缺省的拷贝构造函数,。缺省的拷贝构造函数使用,位拷贝的方法,来完成对象到对象的复制。也就是说,将第一个对象中的数据成员的值,原封不动,拷贝到第二个对象的数据成员中。,a_obj,b_obj,Bit Copy,2024/8/11,62,例:缺省的拷贝构造函数的应用,class Point,private:,float x,y;,public:,Point(float,a,float b),x=a;y=b;,;,void main(),Point obj1(5,15);,/,调用,A(float,float),Point obj2(obj1);,/,调用缺省的拷贝构造函数,Point obj3=obj2;,/,调用缺省的拷贝构造函数,obj2,是,obj1,的精确拷贝,,obj3,是,obj2,的精确拷贝,2024/8/11,63,缺省的拷贝构造函数的缺点,在大多数情况下,缺省拷贝构造函数工作得很好,但在一些特殊的场合,它的表现将不尽人意。,2024/8/11,64,class,MyString,private:,int,len,;,char*,buf,;,public:,MyString(int,n);,MyString,();,void,copy(char,*,src,);,;,MyString:MyString,(,int,n),len,=n;,buf,=new,charn,;,void,MyString:copy(char,*,src,),strcpy(buf,src,);,MyString:MyString,(),delete ,buf,;,void,func,(),MyString,obj1(64);,obj1.copy(“helloworld”);,MyString,obj2=obj1;,void main(),func,();,!,由于调用的是默认的拷贝构造函数,因此,obj1,和,obj2,中的,buf,指向同一内存空间,当它们被释放时,该内存空间将被释放两次。,2024/8/11,65,改进办法:增加拷贝构造函数,class,MyString,public:,MyString(int,n);,MyString(MyString,&,obj,);,;,MyString:MyString(MyString,&,obj,),len,=,obj.len,;,buf,=new char,len,;,strcpy,(,buf,obj.buf,);,通过自定义拷贝构造函数,可以准确地复制数据,以免发生错误。,2024/8/11,66,拷贝构造函数起作用的地方,拷贝构造函数除了在创建新对象时被调用外,还在以下情况被调用:,对象作为函数参数,函数返回对象,2024/8/11,67,举例,class Counter,private:,int,value;,public:,Counter(int,v),value=v;,void,add(int,v),value+=v;,void show(),cout,value,endl,;,;,Counter,func(Counter,obj,),obj.add(6);,return,obj,;,void main(),Counter b1=5;,Counter b2=func(b1);,b1.show();,b2.show();,输出:,5,11,2024/8/11,68,调用,func(b1),时调用默认的拷贝构造函数,调用,return,obj,时调用默认的拷贝构造函数,将,func,(),返回值赋给,b2,时调用默认的拷贝构造函数,5,b1,11,temp,11,b2,5(11),obj,func(b1),return,obj,b2=func(b1),2024/8/11,69,对象数组,与任何其它数据类型一样,可以创建一个类的对象数组。例如:,Clock clocks10;,通过下标访问数组中的对象,进而访问该对象的公有成员,例如:,clocks3.ShowTime();,2024/8/11,70,举例,class Point,public:,float,x,y,;,Point()x=0;y=0;,Point(float,a)x,=,a;y,=0;,Point(float,a,float b),x=,a;y,=b;,void show(),cout,x“:”y,endl,;,;,void main(),Point array3;,array1.x=5;,array1.y=6;,array1.show();,输出:,5:6,2024/8/11,71,对象数组的初始化,对象数组的初始化过程,实际上就是,调用构造函数,对每一个数组元素进行初始化的过程。,如果在声明数组时给出每一个数组元素的初始值,在初始化过程中就会调用,最匹配,的构造函数。,2024/8/11,72,class Point,public:,Point()x=0;y=0;,Point(float,a)x,=,a;y,=0;,Point(float,a,float,b)x,=,a;y,=b;,;,void main(),Point array3=Point(3,4),5;,举例:对象数组初始化,初始化,array0,时调用的是,Point(float,float),构造函数,初始化,array1,时调用的是,Point(float,),构造函数,初始化,array2,时调用的是,Point(),构造函数,2024/8/11,73,对象指针的加减操作,class Counter,private:,int,value;,public:,Counter(int,v),value=v;,void show(),cout,valueshow();,p+;,p-show();,p+;,p-show();,输出:,5,6,7,2024/8/11,74,类类型作参数类型的三种方式,对象本身作为参数,由于,C+,采用传值的方式传递参数,因此使用对象本身参数时,形参是实参的一个拷贝。在这种情况下,最好显式地为类定义一个拷贝构造函数,以免出现不容易发现的错误。,2024/8/11,75,举例,:,对象本身做函数参数,class Counter,private:,int,value;,public:,Counter(int,v),value=v;,void,add(int,v),value+=v;,void show(),cout,valueadd(6);,输出:,5,11,2024/8/11,78,静态成员,当用关键字,static,说明一个类成员时,该成员称为静态成员。因此可以分为:,静态数据成员,静态成员函数,2024/8/11,79,静态数据成员,class ABCD,/,声明静态数据成员,static,int,s_value,;,int,value;,;,ABCD A,B,C,D;,Object A,Value,Object D,value,Object B,value,s_value,Object C,value,类的所有对象共享静态数据成员,因此无论建立多少个该类的对象,静态数据成员只有一份拷贝。,静态数据成员属于类,而不属于具体的对象。,2024/8/11,80,静态数据成员的定义和初始化,在类内只是静态数据成员的声明,但是静态数据成员必须在类外定义,格式如下:,类型 类名,:,静态数据成员,int,ABCD:s_value,;,在定义静态数据成员的时候可以对其进行初始化,方式如下:,类型 类名,:,静态数据成员,int,ABCD:s_value,=6;,2024/8/11,81,静态数据成员的使用,静态数据成员也有,public,和,private,之分,由于静态数据成员是属于类的,因此:,在类外,按如下形式访问,public,静态数据成员:,类名,:,静态数据成员,ABCD:s_value,+;,在类内,可以直接访问所有属性的静态数据成员,特别地,当类对象不存在时,也可以访问类的静态数据成员。,2024/8/11,82,例,1,:静态数据成员的基本用法,class counter,static,int,count;/,声明,public:,void,setcount(int,num),count=num;,void,showcount,(),cout,count“;,;,/,定义,int,counter:count,=0;,void main(),counter a,b;,a.showcount,();,b.showcount,();,a.setcount(34);,a.showcount,();,b.showcount,();,/,输出:,0 0 34,34,2024/8/11,83,静态成员函数,静态成员函数的定义,class ABCD,public:,static void,show_staticvalue,(),;,静态成员函数只属于一个类,它没有,this,指针。,静态成员函数也可以声明为,public,或,private,属性。,2024/8/11,84,请思考,能将构造函数声明为非,public,属性吗?,如果能,将会出现什么情况?,class Clock,private:,int,Hour,Minute,Second;,Clock(int,h,int,m,int,s);,;,void main(),Clock aclock(1,2,3);,编译出错,因为构造函数是,private,属性,2024/8/11,85,举例:静态成员的应用,如果有一个类,MyClass,,如何设计才能保证在程序运行中该类只能有一个实例?,利用静态成员可以解决该问题。,class,MyClass,private:,static,MyClass,*instance;,MyClass,(),public:,static,MyClass,*,getInstance,(),if(instance,=NULL),instance=new,MyClass,();,return instance;,;,MyClass,*,MyClass:instance,=NULL;,void main(),MyClass,*,obj,;,obj,=,MyClass:getInstance,();,将构造函数声明为非,public,属性,可以控制对象的生成!,2024/8/11,86,友元关系,封装的目的就是为了实现信息隐蔽。,一个对象的私有成员只能被自己的成员访问到。当类外的对象或函数要访问这个类的私有成员时,只能通过该类提供的,公有成员,间接地进行。,出于效率(而非技术上必须)的考虑,,C+,提供了,友元机制,来打破私有化的界限,即一个类的友元可以访问到该类的私有成员。,2024/8/11,87,友元函数,问题的提出,Point,类代表“点”,考虑如下需求:需要一个函数来计算任意两点间的距离,那么这个函数该如何设计呢?,class Point,float x,y;,public:,Point(float xx=0,float,yy,=0)x=,xx;y,=,yy,;,float,GetX,()return x;,float,GetY,()return y;,;,2024/8/11,88,解决方案,1,将计算两点距离的函数设计为类外的普通函数,float,Distance(Point,a,Point b),float x1,x2,y1,y2,dx,dy;,x1=,a.GetX,();y1=,a.GetY,();,x2=,b.GetX,();y2=,b.GetY,();,dx,=x1-x2;,dy,=y1-y2;,return,sqrt(dx,*,dx+dy,*,dy,);,void main(),Point p1(3.0,5.0),Point p2(4.0,6.0);,float d=Distance(p1,p2);,cout,The distance is ;,cout,d,endl,;,/,输出:,The distance is 1.1421,2024/8/11,89,这种方案的缺点:,因为类外函数是不能直接访问类中的私有成员的,因此该函数必须调用,GetX,和,GetY,公有成员函数先获取“点”的坐标,然后再计算两点距离,这种方式是不太方便的。,另一种方案就是将计算两点距离的函数设计成为,Point,类的成员函数。虽然从语法的角度来看这不难实现,但是理解起来却有问题:,距离反映的是两点之间的关系,它既不属于每一个单独的点,也不属于整个,Point,类。,2024/8/11,90,解决方案,2,:设计为友元函数,将一个函数声明为一个类的友元函数时,该函数就具有以下特点:,该类函数可以直接访问私有成员。,友元函数不属于任何类,因此友元函数没有,this,指针。,友元函数访问非静态成员的方式与静态成员函数是一样的。,友元函数的声明可以放在类内的任何位置。,和普通函数一样,可直接调用友元函数。,2024/8/11,91,float,Distance(Point,a,Point b),float,dx,dy,;,dx,=,a.x-b.x,;,dy,=,a.y-b.y,;,return,sqrt(dx,*,dx+dy,*,dy,);,void main(),Point p1(3.0,5.0);,Point p2(4.0,6.0);,float d=Distance(p1,p2);,cout,The distance is ;,cout,d,endl,;,/,输出:,The distance is 1.1421,class Point,friend float,Distance(Point,a,Point b);,;,2024/8/11,92,友元类,除了将一个普通函数声明为一个类的友元函数外,也可以将一个类,Y,声明为另一个类,X,的友元类,那么,类,Y,中的所有成员函数都成为类,X,的友元函数,都能直接访问类,X,中所有的成员。,2024/8/11,93,举例,class Y;/,向前说明,class X,int,x;,friend class Y;,public:,void,show()cout,x;,;,class Y,public:,void,SetX,(X&,obj,int,v),obj.x,=v;,;,void main(),X,xobj,;,Y,yobj,;,yobj.SetX(xobj,5);,xobj.show,();,/,输出:,5,2024/8/11,94,将成员函数说明为另一个类的友元函数,class X;/,向前说明,class Y,public:,void,SetX,(X&,obj,int,v);,void,func(X,&,obj,);,;,class X,friend void,Y:SetX,(X&,obj,int,v);,;,void,Y:SetX,(X&,obj,int,v)obj.x,=v;,2024/8/11,95,对友元关系的总结,友元具有如下的性质:,类的友元可以直接访问它的所有成员。,友元的声明必须放在类的内部,但放在哪个段没有区别。,友元关系不具备对称性,即,X,是,Y,的友元,但,Y,不一定是,X,的友元。,友元关系不具备传递性,即,X,是,Y,的友元,,Y,是,Z,的友元,但,X,不一定是,Z,的友元。,2024/8/11,96,类的组合,一个类的对象作为另一个类的成员,这体现的是整体和部分的关系,即对象的包含关系,这个作为成员的对象被称为子对象。,2024/8/11,97,举例:,Circle,类的实现,class Point,float x,y;,public:,Point(float xx,float,yy,),x=,xx;y,=,yy,;,float,GetX,()return x;,float,GetY,()return y;,void,moveto(float,xx,float,yy,),x=xx;y=,yy,;,;,2024/8/11,98,class Circle,Point center;,float radius;,public:,Circle(float,x,float,y,float,r):,center(x,y,),radius=r;,void,moveto(float,xx,float,yy,),center.moveto(xx,yy,);,void main(),Circle acircle(0,0,5);,acircle.moveto(5,8);,若子对象对应的类的构造函数有参数,那么包含该子对象的类必须使用表达式的方式先初始化子对象。,2024/8/11,99,
展开阅读全文