第五章类与对象.ppt

上传人:本田雅阁 文档编号:3120688 上传时间:2019-07-12 格式:PPT 页数:85 大小:560.03KB
返回 下载 相关 举报
第五章类与对象.ppt_第1页
第1页 / 共85页
第五章类与对象.ppt_第2页
第2页 / 共85页
第五章类与对象.ppt_第3页
第3页 / 共85页
第五章类与对象.ppt_第4页
第4页 / 共85页
第五章类与对象.ppt_第5页
第5页 / 共85页
点击查看更多>>
资源描述

《第五章类与对象.ppt》由会员分享,可在线阅读,更多相关《第五章类与对象.ppt(85页珍藏版)》请在三一文库上搜索。

1、第五章 类与对象,5.1 类与对象,5.5 运算符的重载,5.4 构造函数和析构函数,5.3 引用,5.2 从面向过程到面向对象,5.10 全局对象及其它,5.9面向对象的程序设计和Windows编程,5.8 结构和联合,5.7 静态成员,5.6 友元,5.1 类与对象,5.1.3对象的创建与使用,5.1.4名字空间域和类域,5.1.1 C+类的定义,5.1.2 成员函数的定义,5.1.1 C+类的定义,在C+中,类是一种数据类型。 定义一个类的一般格式为: class 类名 private: 成员表1; public: 成员表2; protected: 成员表3; ;,5.1.1 C+类的定

2、义,例如描述一种商品,在C+中可以这样表述: class CGoods private : char Name21 ; int Amount ; float Price ; float Total_value ; public : void RegisterGoods(char*,int,float) ; void CountTotal(void) ; void GetName(char*) ; int GetAmount(void) ; float GetPrice(void) ; float GetTotal_value(void) ;,5.1.1 C+类的定义,类把数据(事物的属性)和函

3、数(事物的行为操作)封装为一个整体。还应注意到:四个数据成员被说明成私有的,而六个函数成员被说明成公有的;这就是说如果从外部对四个数据成员进行操作的话,只能通过六个公有函数来完成,数据受到了良好的保护,不易受副作用的影响。公有函数集定义了类的接口(interface)。,5.1.2 成员函数的定义,在前面的小结中,只对成员函数作了一个声明,或者讲只给出了函数的原型,并没有对函数进行定义。函数通常在类的说明之后进行,其格式如下: 返回值类型 类名:函数名(参数表) /函数体 其中运算符“:”称为作用域解析运算符(scope resolution operator),它指出该函数是属于哪一个类的成

4、员函数。当然也可以在类的定义中直接定义函数。,5.1.3 对象的创建与使用,对象是类的实例(instance),正如在前几章称变量是数据类型的实例一样。声明一种数据类型只是告诉编译系统该数据类型的结构形式,并没有预定内存,或者讲并没有创建了可用来存放数据的变量。类只是一个样板,以此样板可以在内存中开辟出一个个同样结构的实例对象。 创建类的对象可以有两种常用方法。第一种是直接定义类的实例对象: CGoods Car; 这个定义创建了CGoods类的一个对象Car,同时为它分配了属于它自己的存储块,用来存放数据和对这些数据实施操作的成员函数(代码)。与变量定义一样,一个对象只在定义它的域中有效。

5、第二种是采用动态创建类的对象的方法,将在第七章中学习,当然变量也同样可动态创建。所谓动态指在程序运行时建立对象。而前一种是在编译时(程序运行前)建立。 一个样板可以创造出无数相同的物品来,同样,一个类可以创建出无数同样组成的对象来。,5.1.3 对象的创建与使用,有两种方法可存储对象。,5.1.3 对象的创建与使用,【例5.1】商品类对象应用实例: #include #include #include /省略了类定义 void main( ) CGoods car ; char string21 ; int number ; float pr ;,5.1.3对象的创建与使用,coutnumbe

6、rpr ; car.RegisterGoods(string , number , pr) ; car.CountTotal() ; string0=0 ; /字符串string清零 car.GetName(string) ; /string赋值car.Name coutsetw(20)stringsetw(5)car.GetAmount() ; /A coutsetw(10)car.GetPrice()setw(20)car.GetTotal_value()endl ; /B ,成员名 Name21 ; Amount ; Price ; Total_value ;,RegisterGoods

7、(char*,int,float) ; CountTotal(void) ; GetName(char*) ; GetAmount(void) ; GetPrice(void) ; GetTotal_value(void) ;,minicar 5 2,10,minicar,5,2,10,5.1.4 名字空间域和类域,在C+中支持三种形式的域:局部域(local scope)、名字空间域(namespace scope)和类域(class scope)。 名字空间域是不包含在函数声明、函数定义或类定义内的程序文本部分。换言之,名字空间域可含有函数声明、函数定义或类定义等等 ,而不能相反包含。程序

8、的最外层的名字空间域被称为全局域(global scope),或全局名字空间域(global namespace scope) 程序员也可以利用名字空间定义(namespace definition)来定义用户声明的(user-declared)的名字空间,它们嵌套在全局域内。 用户声明的名字空间定义以关键字namespace开头,后面是名字空间的名字(标识符)。该名字在它被定义的域中必须唯一,不能与其它实体同名,因为用户声明的名字空间可以不连续,分为多段,但它们是同一个名字空间。,5.1.4 名字空间域和类域,名字空间域的引入,主要是为了解决全局名字空间污染(global namespace

9、 pollution)问题,即防止程序中的全局实体名与C+各种库中声明的全局实体名冲突。 using声明以关键字using开头,后面是被限定修饰的(qualified)名字空间成员名。 例如: namespace cplusplus_primer namespace Matrixlib /名字空间嵌套 class matrix /名字空间类成员matrix . ,5.1.4 名字空间域和类域,使用using指示符可以一次性地使库中所有成员都可以直接被使用,而不用限定修饰名。 using cplusplus_primer:Matrixlib:matrix ; 等同于: using cpluspl

10、us_primer:Matrixlib matrix ;,5.1.4 名字空间域和类域,使用using指示符可以一次性地使库中所有成员都可以直接被使用,而不用限定修饰名。 using指示符以关键字using开头,后面是关键字namespace,然后是名字空间名。 例如: # include “primer.h” /头文件中定义了cplusplus_primer名字空间using namespace cplusplus_primer; /using指示符使cplusplus_primer所有成员都 成为可见的 void func(matrix/ func()为cplusplus_primer名字

11、空间中的成员 /cplusplus_primer中所有成员可不加限定修饰地被使用,5.1.4 名字空间域和类域,类体也定义了一个域称为类域。在类域中说明的标识符仅在该类的类域内有效。必须加上“类名:”作限定修饰。 类的实体对象中的公有成员也可以在对象之外访问,但必须使用成员访问操作符“.”,对象名+“.”+成员名。 定义类本身的目的就是要实现一个封装性,对外是封闭的,对内是开放的,在程序中并不总是需要用成员访问符之类来引用类成员。多数程序代码本身就在类域中,这些程序可以直接访问类成员。,5.1.4 名字空间域和类域,在类域中不仅有数据和函数成员,也可以有类型说明,可以称为类成员,或成员类,其作

12、用域为类域,出了类说明之外无效。例如嵌套类(nested class): class student public : class studentID /成员类、嵌套类 int value ; public : ; private : studentID id ; /嵌套类说明的私有对象 char name20 ; student s1 , s2 ; student:studentID kk ;,5.2 从面向过程到面向对象,上世纪六十年代中后期软件危机发生之后,面向过程(procedure-oriented)的结构化程序设计(structured programming,SP)成为主流。结构

13、化程序设计的提出与发展是伴随软件日益庞大和复杂进行的,但是当软件复杂到一定的程度后,结构化程序设计也不能满足需要。当软件规模超过一定的尺度后,采用结构化程序设计,其开发和维护就越来越难控制。其根本的原因就在于面向过程的结构化程序设计的方法与现实世界(包括主观世界和客观世界)往往都不一致,结构化程序设计的思想往往很难贯彻到底。,5.2 从面向过程到面向对象,在结构化程序设计中,采用的是“自顶向下,逐步细化(divide and conquer,stepwise refinement)”的思想。它的具体操作方法是模块化,是按功能来分的,所以也称功能块。也就是从一般事物中抽象出来的操作,在C+中称为

14、一个函数,一个函数解决一个问题,即实现一个功能或一个操作。当程序规模和复杂性达到一定程度时不可避免地引入大量的全局变量,这时模块化没法坚持到底。比如一个实时管理系统,当管理的规则发生大的变化,程序的维护往往相当困难,为某一处修改通用的函数往往会影响其它部分,牵一发而动全身。可维护性差成了制约结构化程序设计应用的瓶颈。所以,必须重新进行功能抽象,必须重新建立模块间联系的规则。,5.2 从面向过程到面向对象,对象的概念是面向对象技术的核心所在。面向对象技术中的对象就是现实世界中某个具体的物理实体在计算机逻辑中的映射和体现。比如你所拥有的一部移动电话,它是现实世界中的一个实体。它由天线、发射部件、接

15、收部件、显示屏、按键、专用集成电路芯片及外壳组成;它有着其实在的功能,可以打电话,可以发短消息,可以存储、输入和编辑各种个人信息,甚至可以上网。这样一个实体可以在计算机世界中映射为一个计算机可以理解、可以操纵、具有前面所叙述的属性和操作的对象。又如你们所拥有的一辆自行车,它由车架、车轮、脚踏和传动机构、变速机构等组成,它具有代步功能,它可以进行变速骑行,特别要强调的是它有一些特征可以把你的这辆自行车与其他自行车区分开来,其中最重要的是钢印号。这些都可以在面向对象的程序中用对象及其属性和操作模拟出来。,5.2 从面向过程到面向对象,实体,抽象类别,现实世界 客观世界,抽象,抽象,实例化,映射,主

16、观世界,图5.3对象、实体与类,现实世界中的实体可以抽象出类别的概念。对应于计算机世界就有一个类(class)的概念,因为类是一个抽象的概念的对应体,所以计算机不给它分配内存,只给对象分配内存。图5.3表达了计算机世界与现实世界之间的对应关系。,5. 3 引用,在有关函数的学习中,我们知道C+函数中参数的传递方式是传值。在函数域中为参数重新分配内存,而把实参的数值传递到新分配的内存中。它的优点是有效避免函数的副作用,在函数调用中不会无意中修改了实参的值。但如果就是要求改变实参的值,怎么办呢?再者,如果参数是一些简单的数据类型,占据内存不多,重新分配内存问题不大;如果实参是一个复杂的对象,重新分

17、配内存会引起程序执行效率大大下降,怎么办呢?在C+中有一种新的导出型数据类型引用(reference)可以解决上面的难题。引用又称别名(alias)。,5. 3 引用,引用是一种非常特殊的数据类型,它不是定义一个新的变量,而是给一个已经定义的变量重新起一个别名,也就是C+系统不为引用类型变量分配内存空间。引用主要用于函数之间的数据传递。引用定义的格式为: 类型 newnum是新定义的引用类型变量,它是变量number的别名,内存分配见图5.4。,number称为引用newnum的关联变量。“&”(仍读作ampersand)在这里是引用的说明符。必须注意number和newnum都是double

18、类型。如在程序中修改了newnum也就是修改了number,两位一体。,5. 3 引用,【例5.2】使用一个函数来交换两个数据。内存分配见图5.5。 #include void swap(double ,X y,d1 d2 temp,1.414,2.718,1.414,5. 3 引用,【例5.3】采用不同返回方式的求正方形面积函数的比较 #include double temp; double fsqr1(double a) temp=a*a ; return temp; double ,图5.6 普通返回,图5.7 引用返回,5. 3 引用,【例5.4】统计学生成绩,分数在80分以上的为A类

19、,60分以上,80分以下的为B类,60分以下为C类。,程序:x5_4.cpp,5.4 构造函数和析构函数,定义对象时,按现在已学过的知识无法进行初始化,即无法对数据成员进行赋初值的过程。数据成员,从封装的目的出发,应该多为私有的,要对它们进行初始化,看来必须用一个公有函数来进行。同时这个函数应该在且仅在定义对象时自动执行一次,否则就不是初始化了。在C+程序设计语言中这个函数称为构造函数(constructor)。必须指出:调用构造函数也是建立对象的唯一方法(联合例题,见5.8)。,5.4 构造函数和析构函数,5. 4. 1 构造函数的定义与使用,5. 4. 3 析构函数的定义,5. 4. 2

20、拷贝构造函数,5. 4. 4 成员对象与构造函数,5.4.1 构造函数的定义与使用,对于对象的初始化,采用构造函数(constructor)是标准的方法, C+编译器也会自动产生一个缺省的构造函数,不过什么初始化也不做。所以当需要对对象进行初始化时,总是编写一个或一组构造函数。 构造函数是特殊的公有成员函数,其特征如下: 函数名与类名相同。 构造函数无函数返回类型说明。注意是没有而不是void,即什么也不写,也不 可写void!实际上构造函数有返回值,返回的就是构造函数所创建的对象,见 5.5节。 在程序运行时,当新的对象被建立,该对象所属的类的构造函数自动被调用, 在该对象生存期中也只调用这

21、一次。 构造函数可以重载。严格地讲,说明中可以有多个构造函数,它们由不同的参 数表区分,系统在自动调用时按一般函数重载的规则选一个执行。 构造函数可以在类中定义,也可以在类外定义。 6. 如果类说明中没有给出构造函数,则C+编译器自动给出一个缺省的构造函数: 类名(void) ;,5.4.1 构造函数的定义与使用,下面编写5.1节中商品类CGoods的构造函数。可以用三个参数来实现对4个数据成员的初始化: Cgoods (char* name , int amount , float price) strcpy(Name,name) ; Amount=amount ; Price=price

22、; Total_value=price*amount ; 在实际应用时,也可以只用两个参数:货名和单价,这时构造函数为: Cgoods (char* name , float price) strcpy(Name,name) ; Price=price ; Amount=0 ; Total_value=0.0 ; 这两个构造函数同时被说明。,5.4.1 构造函数的定义与使用,如果定义对象时的格式为: CGoods Car1(“夏利2000”,30,98000.0) ; 则调用了CGoods中的第一个构造函数,相当于自动调用: CGoods(“夏利2000”,30,98000.0) ; 如果定义

23、对象时的格式为: CGoods Car2(“桑塔那2000”,164000.0) ; 则调用的是第二个构造函数,参数为两个。 定义对象初始化时也可以把构造函数显式表示出来如: CGoods Car1= CGoods(“夏利2000”,30,98000.0); 如果还希望初始化时,不带任何参数,可作如下定义: CGoods( )Name0=0 ; Price=0.0 ; Amount=0 ; Total_value=0.0 ; 但是定义对象时不能加括号。例如:CGoods Car3,Car4();,5.4.1 构造函数的定义与使用,缺省的构造函数,也可以由程序员自己来编,只要构造函数是无参的或者

24、只要各参数均有缺省值的,C+编译器都认为是缺省的构造函数,并且缺省的构造函数只能有一个 。,5.4.2 拷贝构造函数,同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或称拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员,而函数成员是共用的(只有一份拷贝)。在建立对象时可用同一类的另一个对象来初始化该对象,这时所用的构造函数称为拷贝初始化构造函数(Copy Constructor)。 对于CGoods类,可以定义拷贝构造函数为: CGoods (CGoods ,5.4.2 拷贝构造函数,通常情况下,这种按成员语义支持已经足够。但在某些情况下,它对类与对象的安全性和处理的正确性还不

25、够,这时就要求类的设计者提供特殊的拷贝构造函数(Copy Constructor)和拷贝赋值操作符(Copy Assignment Operator)的定义。 这里必须注意拷贝构造函数的参数同类(class)的对象采用的是引用的方式。如果把一个真实的类对象作为参数传递到拷贝构造函数,会引起无穷递归 。所以必须将拷贝构造函数的参数定义为一个类的对象的引用。,5.4.2 拷贝构造函数,这里成员函数的参数为同一类(class)的对象或它的引用,在函数体内使用参数对象的私有数据成员时,可用对象名加成员访问操作符点号进行。从逻辑上讲,每个对象有自己的成员函数,访问同类其他对象的私有数据成员应通过该对象的

26、公有函数,不能直接访问。但在物理上只有一个成员函数拷贝,所以直接访问是合理的。其他实例可见5.5节。对本对象的数据成员不加点号,参见6.2节。注意,仅在成员函数中可以这样做。 下面来看一个实例。有一个程序段: CGood Car1(“夏利2000”,30,98000.00);/调用三个参数的构造函数 CGood Car2= Car1;调用拷贝构造函数 CGood Car3 ( Car1);调用拷贝构造函数 这样三个对象的初始化情况完全一样。 在类定义中如果没有显式给出构造函数时,并不是不用构造函数,而是由系统自动调用缺省的构造函数或缺省的拷贝构造函数。如果有程序设计者定义的构造函数(包括拷贝构

27、造函数),则按函数重载的规律,调用合适的构造函数。,5.4.2 拷贝构造函数,拷贝构造函数并不只是在同类的一个对象去初始化该类的另一个对象时使用,它还在另二个方面使用: 1. 当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调用拷贝构造函数。 2.当函数的返回值是类对象,函数执行完成返回调用者时使用。理由也是要建立一个临时对象中,再返回调用者。为什么不直接用要返回的局部对象呢?因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名

28、临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓return 对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果返回的是变量,处理过程类似,只是不调用构造函数。,5.4.3 析构函数的定义,当一个对象定义时,C+自动调用构造函数建立该对象并进行初始化,那么当一个对象的生命周期结束时,C+也会自动调用一个函数注销该对象并进行善后工作,这个特殊的成员函数即析构函数(destructor): 1. 构函数名也与类名相同,但在前面加上字符,如 CGoods()。 2. 析构函数无函数返回类型,与构造函数在这方面是一样的。 但析构函数不带任何参数。 3. 一个类有一个也只有一个析构

29、函数,这与构造函数不同。析 构函数可以缺省。 4. 对象注销时,系统自动调用析构函数。,5.4.4 成员对象与构造函数,在定义类的对象时不仅要对对象进行初始化,还要对成员对象进行初始化。对成员对象初始化,必须调用该成员对象的构造函数来实现。 【例5.5】含有成员对象的类的构造函数: 程序:Ex5_5.cpp 这段程序希望通过构造函数中的A行进行初始化,把学生名“朱明”和学号08002132赋给对象SS。运行结果: 赋给学生的学号:0 /对象ss的对象成员id中数据成员value的初值 学生名:朱明 /对象ss的数据成员name 赋给学生的学号:08002132 /ss构造函数中建立的局部对象i

30、d中数据成员value 初值 删去学号:08002132 /局部对象id析构 删去学号:0 /对象成员id析构,5.4.4 成员对象与构造函数,最后作为对构造函数和析构函数的总结,对于不同作用域的对象类型,构造函数和析构函数的调用如下: 1. 对全局定义的对象,当程序进入入口函数main之前 对象就已经定义,这时要调用构造函数。整个程序 结束时调用析构函数。 2. 对于局部定义的对象,每当程序控制流到达该对象定 义处时,调用构造函数。当程序控制走出该局部域 时,则调用析构函数。 3. 对于静态局部定义的对象,在程序控制首次到达该对 象定义处时,调用构造函数。当整个程序结束时调 用析构函数。,5

31、.4.4 成员对象与构造函数,【例5.6】演示对象创建和撤消的对应关系。 程序Ex5_6.cpp Initializing 0 0 /全局对象首先建立,调用缺省的构造函数 Entering main /进入入口函数 Initializing 0 0 /用缺省的构造函数建立com1 Initializing 5.6 7.5 /用带参数的构造函数建立com2 Copy 0 0 /用拷贝的构造函数建立com3 0+0i /打印com3 0+0i /打印global,5.4.4 成员对象与构造函数,Copy 5.6 7.5 /调用全局函数fun(),调用拷贝构造函数建立临时对象com Entering

32、 function /进入全局函数fun() Copy 5.6 7.5 /进入global.assign(),调用拷贝构造函数建立临时对象新com Destructor /退出global.assign(),调用析构函数,清新com Exiting function /将退出fun() Destructor /退出fun(),调用析构函数,清fun()的com Exit main /将退出入口函数 Destructor /退出入口函数前,调用析构函数,清com3 Destructor /退出入口函数前,调用析构函数,清com2 Destructor /退出入口函数前,调用析构函数,清com1

33、Destructor /退出入口函数前,调用析构函数,清global,5.5 运算符的重载,C+中没有复数类型,我们可以自己来定义一个复数类(class),同样可以用+、-、*、/来进行复数的算术运算。 运算符的重载实际是一种特殊的函数重载,必须定义一个函数,并告诉C+编译器,当遇到该重载的运算符时调用此函数。这个函数叫做运算符重载函数,通常为类的成员函数。 定义运算符重载函数的一般格式: 返回值类型 类名:operator重载的运算符(参数表) operator是关键字,它与重载的运算符一起构成函数名。因函数名的特殊性,C+编译器可以将这类函数识别出来。,5.5 运算符的重载,【例5.7】定

34、义复数类,可完成复数基本运算,并应用它进行复数运算。 程序:x5_7.cpp 在本例中重载了运算符“+”、“=”、“+=”和“*”、“/”,以及求模(绝对值)的函数abs(),可以进行复数运算。首先来看“+”的重载。在做 c=c2+c3; 时,C+编译器把表达式c2+c3解释为: c2.operator+(c3) ; 这样一个函数调用过程,函数c2.operator创建一个局部的Complex对象Temp,把出现在表达式中的两个Complex类对象c2和c3的实部之和及虚部之和暂存其内,然后把这个局部对象返回,赋给Complex类对象c(注意这里调用了拷贝构造函数生成一个无名临时对象过渡)。以

35、上过程可以用图5.8形象表示。,5.5 运算符的重载,5.5 运算符的重载,使用引用类型变量作为运算符重载函数的参数,可以提高复数类型运算的效率。复数与复数相加的Operator+成员函数的最终形式: Complex complex:operator+(const complex 这里采用complex对象的引用而不是对象本身,调用时不再重新分配内存建立一个复制的对象,函数效率会更高。而在引用形式参数类型说明前加const关键字,表示被引用的实参是不可改变的,如程序员不当心在函数体中重新赋值了被引用的实参, C+编译器会认为出错。其它运算符也是类似处理。,5.5 运算符的重载,在缺省的情况下,

36、C+ 编译器为每个类生成一个缺省的赋值操作,用于同类的两个对象之间的相互赋值,缺省的语义是类成员逐个相互赋值。对复数类 complex 如果没有重载赋值运算符 =,复数的赋值语义是: Complex /参见6.2节 这种缺省的赋值操作格式对所有类是固定的,这种缺省的格式对复数是合适的,但对其他类缺省的赋值可能产生问题,那时需重载。对所有的类对象,赋值运算符“ =”即缺省的按成员拷贝赋值操作符(Copy Assignment Operator),同类对象之间可以用“=”直接拷贝。本例中重载的赋值运算符“=”取代了缺省的赋值操作,格式专用。,5.5 运算符的重载,重载的运算符“+=”标准算法是:

37、Complex /参见6.2节 ,5.5 运算符的重载,由以上例子与说明,对运算符的重载可做一小结: 1. 运算符重载函数的函数名必须为关键字Operator加一个合法的运算符。在调用 该函数时,将右操作数作为函数的实参。 2. 当用类的成员函数实现运算符的重载时,运算符重载函数的参数(当为双目运 算符时)为一个或(当为单目运算符时)没有。运算符的左操作数一定是对象, 因为重载的运算符是该对象的成员函数,而右操作数是该函数的参数,其类型 并无严格限制。C+不允许重载三目运算符。 3. 单目运算符“+”和“-”存在前置与后置问题。前置“+”格式为: 返回类型 类名:operator+() 而后置

38、“+”格式为: 返回类型 类名:operator+(int) 后置“+”中的参数int仅用作区分,并无实际意义,可以给一个变量名,也可 以不给变量名。 4. C+中只有极少数的运算符不允许重载,表5.1中列出了不允许重载的运算符。,5.5 运算符的重载,表5.1C+中不允许重载的运算符,5.5 运算符的重载,在本小节中学习了有关运算符重载的基础知识,必须指出的是这只是初步的,由于所学知识有限,重载中不少问题还不能解决。如例5.2中: c=c+d; 语句,改为 c=d+c; 因为d不是Complex的对象,C+编译器将无法找到合适的重载的“+”运算符对应的函数,最终给出出错信息。,5.6 友元,

39、在C+中采用友元(friend)函数允许在类外访问该类中的任何成员,就象成员函数一样。友元函数用关键字friend说明。下面用友元函数重载运算符“+”,以实现c=d+c。 用友元函数重载运算符“+”,实现实数与复数的加法。,5.6 友元,class Complex friend Complex operator + (double,Complex); /opration+说明为类Complex类的有元函数,friend只用于类说明中; Complex operator + (double d , Complex c) /注意友元不是成员函数,也不加friend return Complex(d

40、+c.Real , c.Image) ;/友元函数可以直接访问私有成员 void main(void) c=d+c1; c.print(); ,5.6 友元,如d和c1的初值与例5.2相同,则输出为: Real=1.5 Image=1.0 这里d+c被C+编译器解释为operator+(d,c),重载了友元函数。从这个例子由此可知友元函数在特定场合可能是必不可少的。友元函数重载运算符+,有三种形式。另两个的声明为: friend Complex operator +(Complex , Complex ) ; friend Complex operator + (Complex , doubl

41、e ) ; 则无论是复数与复数相加,还是实数与复数相加(不论实数在前还是在后)都可以用该运算符三个重载函数之一。再进一步,如果使用友元函数 friend complex operator +(complex c1 , complexc2) ; 无论是复数与复数相加,还是实数与复数相加(不论实数在前还是在后)都可以用该运算符重载函数。因为有例5.7所定义的缺省的构造函数,实数会被强制转换为虚部为零的复数。d+c1被解释为operator+(complex(d) , c1)。注意这里的两个参数是传值,在函数内是建立了两个复数对象,而把实参的值传进去,进行运算。参见图5.9。,5.6 友元,在这里友

42、元函数可以有两个参数,而对应的成员函数只有一个参数,所以友元函数的使用可以更灵活、更方便。 使用引用类型变量作为运算符重载函数的参数,以提高复数类型运算的效率和可行性。Operator+友元函数的声明可改进为: friend Complex operator+(const Complex & c1,const Complex & c2) 这里采用Complex对象的引用而不是对象本身,调用时不再重新分配内存建立一个复制的对象,函数效率会更高。加const,实参只读,可防止实参被修改。,5.6 友元,【例5.7_1】 用友元函数重载运算符,实现复数的运算。,程序Ex5.7_1.cpp,5.6 友

43、元,单目运算符前“+”的成员函数重载方式如下: Complex Complex:operator+() return (+Real , +Image) ; 采用成员函数方式重载与使用都很方便。但采用友元方式则必须使用引用,因为被施加“+”运算的是一个参数。友元函数重载后置“+”如下: friend Complex operator+(Complex 采用引用类型,后“+”是直接施加于实参。否则施加于拷贝,而实参不变。,5.6 友元,关于友元函数要注意以下几点: 1. 友元函数不是类的成员函数,在函数体中访问对象的成员,必须用对象名加运算符“.”加 对象成员名。这一点和一般函数一样。但友元函数可

44、以访问类中的所有成员(公有的、私有的、保护的),一般函数只能访问类中的共有成员。 2. 友元函数不受类中的访问权限关键字限制,可以把它放在类的公有、私有、保护部分,但结果一样。 3. 某类的友元函数的作用域并非该类作用域。如果该友元函数是另一类的成员函数,则其作用域为另一类的作用域,否则与一般函数相同。 但是友元函数破坏了面向对象程序设计类的封装性,所以友元函数如不是必须使用,则尽可能少用。或者用其他手段保证封装性。 友元还有友元类概念:整个类可以是另一个类的友元。友元类的每个成员函数都是另一个类的友元函数,都可访问另一个类中的保护或私有数据成员。定义方法如下: class A friend

45、class B;/声明B为A的友元类 ;,5.7 静态成员,由关键字static修饰说明的类成员,成为静态类成员(static class member)。虽然使用static修饰说明,但与函数中的静态变量有明显差异。类的静态成员为其所有对象共享,不管有多少对象,静态成员只有一份存于公用内存中。,5.7 静态成员,5.7.1 静态数据,5.7.2 静态函数成员,5.7.1 静态数据,在类定义中,用关键字static修饰的数据成员为静态数据成员。该类产生的所有对象共享系统为静态成员分配的一个存储空间,而这个存储空间是在编译时分配的,在定义对象时不再为静态成员分配空间。静态数据实际上是该类所有对象

46、所共有的,它更像在面向过程程序设计时的全局变量,可提供同一类的所有对象之间信息交换的捷径,正因为静态数据成员不属于类的某一特定对象,而是属于整个类的,所以使用时可用以下格式: 类名:静态数据成员名,5.7.1 静态数据,【例5.8】用静态数据成员对同一类建立的对象的数量进行计数。,程序 Ex5_8.cpp,5.7.1 静态数据,执行程序后输出: 对象数量=1 /a0构造函数产生 对象数量=2 /a1构造函数产生 对象数量=3 /a2构造函数产生 对象数量=2 /a2析构函数产生 对象数量=1 /a1析构函数产生 对象数量=0 /a0析构函数产生 上例中A行是对静态数据成员数据作定义性说明,必须

47、在文件作用域中作一次并只能做一次说明,只有在这时C+编译器为静态数据成员分配存储空间。C+静态数据成员缺省的初值为0,所以A行中“=0”是可以省去的。特别要注意不管静态变量是私有或公有,定义性说明均有效。 静态数据成员虽然具有全局变量的一些特性,但受到访问权限的约束。建议静态成员说明为私有的,从而保证面向对象程序设计的封装性。如果说明为公有的,它会带来与全局变量同样的副作用。,5.7.2 静态函数成员,函数成员说明为静态,同样将与该类的不同对象无关。严格地讲,在逻辑上该函数成员只有一个拷贝。静态函数成员的调用,在对象之外可以采用下面的方式: 类名:函数名 与静态数据成员相反,为使用方便,静态函

48、数成员多为公有的。在例5.6中的复数类中的函数成员print(),如被说明为静态的则可如下表达: Static void print()cout”Real=”Realt”Image=”Imagen 如果静态成员函数只涉及其他静态成员(包括静态数据和静态函数),因他们是独立于具体对象而存在的,似乎可以用complex:print( )来调用。但是因为数据不确定(C+系统不知应取哪一个对象的数据)而不能运行。在本例中print()函数应改为: static void print(complex 这里用complex 的对象的引用为参数,而以具体的complex对象为实参,这样就能正常运行了。,5.7.2 静态函数成员,如果静态成员函数在类定义之外定义时,则不能在定义时再加s

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

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1