第11章 友元与运算符重载
·310·
}
node->Pre=node->Next=0; return(node); }
Node * List::LookUp(Object &obj)
{ Node *pn=Head; while (pn)
{ if (pn->Info->IsEqual(obj)) return pn; pn=pn->Next; }
return 0; }
void List::ShowList() { Node *p=Head; while (p)
{ p->Info->Show(); p=p->Next; } }
void List::DelList()
{ Node *p, *q; p=Head; while(p)
{ delete p->Info; q=p;
p=p->Next; delete q; } }
class IntObj: public Object { private: int data; public:
IntObj(int x=0) { data=x;}
void SetData(int x) { data=x;}
int IsEqual(Object &); void Show()
//从链表中查找一个结点
//输出链表上各结点的数据值
//删除整条链表
//由抽象类派生出描述结点数据的类
第11章 友元与运算符重载
{ cout<<\重新定义虚函数 };
int IntObj::IsEqual(Object &obj) { IntObj & temp=(IntObj &) obj; return (data==temp.data); }
void main(void) { IntObj *p;
Node *pn , *pt, node; List list;
for (int i=0;i<5;i++) { p=new IntObj(i+100); pn=new Node; pn->FillInfo(p); list.AddNode(pn); }
list.ShowList();
·311·
//重新定义比较两个结点是否相等的虚函数
//建立包括五个结点的双向链表 //动态建立一个IntOb类的对象 //建立一个新结点 //填写结点的数据域 //将新结点加到链表尾 //输出链表上各结点
cout< da.SetData(102); pn=list.LookUp(da); if (pn) pt=list.DelNode(pn); list.ShowList(); cout< if (pn) list.AddNode(pt); list.ShowList(); cout< 执行程序后输出 Data=101 Date=101 Data=101 Date=102 Data=103 Date=103 //给要查找的结点置数据值 //从链表上查找指定的结点 //若找到,则从链表上删除该结点 //输出已删除结点后的链表 //将结点加入链表尾 //输出已增加一个结点的链表 Data=103 Date=104 Data=104 Date=104 Date=102 该例子只提供了双向链表的基本操作:连续五次将新建的结点加到链表尾,显示整个链表上各结点的数据值,从链表上查找一个指定的结点,再删除之,并显示删除这个结点后的链表结点数据,再将删除的这个结点加到链表尾并显示新的链表。 例中的类IntObj是由抽象类Object派生而来的,可以根据实际数据结构的需要来定义从基类中继承来的虚函数IsEqual()和Show()的具体实现。在上例中,链表上的结点只有一个整数,所以只要判断两个结点上整数是否相同。由抽象类Object派生出来的不同的派生类均可重新定义这两个纯虚函数,这样就可以实现对不同类的对象使用相同的接口实现不 第11章 友元与运算符重载 ·312· 同的操作。在程序中加入注解,说明了每个函数的功能及主要语句的作用。为此不对每一个函数作进一步的说明。 11.4 类与对象的特性 在第9章到第11章中,我们学习了类与对象的许多特性,如:封装性、派生与继承、多态性等。本节再对这些特性作如下小结。 1.封装性 面向对象的程序设计方法(OOP),是将描述某一类事物的数据与所有处理这些数据的函数封装成一个类。这样做的好处是,可以将描述一个事物的数据隐藏起来,可以做到只有通过类中的函数才能修改类中的数据。数据结构的变化,仅影响封装在一起的函数。同样地,修改函数时仅影响封装在一起的数据。真正实现了封装在一起的函数和数据不受外界的影响。 用类可以定义对象,对象数据与数据处理函数组成一个完全独立的模块,对象中的私有数据只能由对象中函数进行处理,其它任何函数都不能对其进行处理,这种特性称封装性。封装性使对象中的私有数据在对象的外部是不可见的,外部只能通过公共的接口(对象中公有函数)与对象中的私有数据发生联系。从而可以显著地提高程序模块的独立性和可维护性。 2.派生与继承性 一个类可以派生出子类。子类可以从它的父类中部分或全部地继承各种函数(形为)与数据(属性),并增加新的形为或属性。类的封装性为类的继承提供了基础。 例如,VC++中的按钮基类可派生出普通按钮、图标按钮、快捷按钮3个子类按钮。这3种按钮都继承了按钮类的全部数据与程序代码。利用派生与继承性可从基类派生出子类,减少程序设计的编程量。 3.多态性 对象之间传送的信息称为消息,同一个消息为不同对象所接收时,可以导致完全不同的行为,这种特性称为多态性。例如,在类中定义两个取绝对值同名函数:abs (int x), abs (double x),然后由对象调用取绝对值函数abs(-3)。此时,对象会将消息(即实参-3)传送给函数的形参,而对象会根据消息的数据类型(整型或实型)调用对应函数完成对整数或实数取绝对值的任务。这就是用重载函数完成多态性的例子。 多态性的重要性在于允许一个类体系的不同对象,各自以不同的方式响应同一个消息,这样就可以实现“同一接口,多种方法”。 类的多态性是通过多态性技术实现的,多态性技术是指调用同名函数完成不同的函数功能,或使用同名运算符完成不同的运算功能。它常用重载函数与虚函数来实现。函数重载或运算符重载均属于编译时的多态性,而虚函数由属于运行时的多态性。 4.对象的消息机制 对象是类的一个实例,类在程序运行时被用作样板来建立对象。对象是动态产生和动态消亡的。对象之间的通信是通过系统提供的消息机制来实现的。系统或对象可把一个消息发送给一个指定的对象或某一类对象。接收到消息的对象必须处理所接收到的消息,对 第11章 友元与运算符重载 ·313· 象对消息的处理是通过激活本对象内相应的函数来实现,并根据所处理的情况返回适当的结果。 面向对象的程序设计VC++就是用类来描述客观世界中的事物。VC++类库(MFC)为用户提供大量的类(如:窗口类、输出类、图形类、文档模板类),供用户编程开发使用。用户只要知道这些类的接口功能、作用及用法,并使用这些类去定义对象,便可实现Windows中窗口、图形、文档模板等设计,而不需要了解类中程序代码与数据结构。这无疑为用户设计复杂问题的程序提供了便利。 本章小结 1.友元的概念 由于类的封装性与安全性,类的私有数据成员只能在类中直接使用,而在类外必须要通过公有接口函数才能使用,这会给用户编程带来许多不便。为了能在类外直接使用类的私有成员或保护成员,C++提供了友元。某类的友元可使用该类的所有数据成员或成员函数。虽然友元方便了用户,但却破坏了数据的安全性,因此友元的使用要适度。 2.友元的定义 友元有三类:普通函数、成员函数、类,这三类友元的定义方式如下: (1)将普通函数定义为某类的友元函数的方法,是在该类中增加用freind修饰普通函数原型说明:friend <类型><普通函数名>(形参); 其中形参应包含用类定义的对象或对象的引用。 (2)类C的成员函数定义为类D的友元函数的方法见11.1.2节。 (3)类C定义为类D的友元的方法是在类D中增加语句:freind class C; 即可。 3.运算符重载 运算符重载是指用同一运算符完成不同的运算操作。运算符重载是通过运算符重载函数来实现的。运算符重载函数分为一元运算符重载函数和二元运算符重载函数。运算符重载函数可通过成员函数或友元函数来实现。 (1)二元运算符重载函数 ①用成员函数重载运算符 <类型><类名>:: 在执行运算符操作时,编译器将对运算符的操作解释为对运算符成员重载函数的调用,并将运算符左操作数作为调用重载函数的对象,右操作数作为重载函数的实参。因此,重载函数形参常设置为:<类名> &c。 ②用友元函数重载运算符 在类中作引用性说明:friend <类型> 重载函数作为普通友元函数一般应写在类外。在执行运算符操作时,编译器将对运算符的操作解释为对运算符友元重载函数的调用,并将运算符左、右操作数作为调用友元重载函数的实参。因此,友员重载函数形参常设置为:<类名> &c1, <类名> &c2 (2)一元运算符重载函数 ①用成员函数重载“++”运算符 前置++:<类型><类名>:: 第11章 友元与运算符重载 ·314· 后置++:<类型><类名>:: 前置++:friend <类型><类名>:: 其中形参中的int 只用于区别前置++重载函数,还是后置++重载函数,并无整型参数的含义。对于前置++成员函数,必须用this指针返回自加结果。 (3)字符串运算符重载函数 使用字符串运算符重载函数,可使字符串拷贝、拼接、比较等操作直接用字符串运算符“=”、“+”、“>”、“<”来进行。字符串常进行二元运算,其重载函数的定义格式与二元运算符重载函数相同。 4.类型转换函数 当需要将类类型的数据转换成其它类型数据时,要用到类型转换函数,其定义格式: 类名::operator <转换后数据类型>( ) {函数体} 5.多态性技术 多态性技术是指调用同名函数完成不同的函数功能,或使用同名运算符完成不同的运算功能。它常用重载函数与虚函数来实现。函数重载或运算符重载均属于编译时的多态性,而虚函数则属于运行时的多态性。 6.虚函数 在基类中用关键字 virtual修饰的成员函数称为虚函数,定义格式为: virtual <类名> <函数名>(参数){函数体} 用虚函数实现“运行时的多态性”的方法是:在派生类中定义与基类虚函数同名同参数同返回类型的虚函数,用基类定义指针变量p,将基类或派生类对象的地址赋给p(即p=&对象)后,用p->虚函数,则可实现“运行时的多态性”。 7.纯虚函数 将函数名赋0值且无函数体的虚函数称为纯虚函数,定义格式为: virtual <类名> <函数名>(参数)=0; 含有纯虚函数的类称为抽象类,不能用抽象类定义对象。因为纯虚函数无函数体,所以纯虚函数不能调用,因此必须在派生类中重新定义虚函数。 习题11 11.1 为何要使用友元?使用友元有哪些利与蔽?友元有哪三类?如何定义? 11.2 定义学生成绩类Score,其私有数据成员有学号、姓名、物理、数学、外语、平均成绩。再定义一个能计算学生平均成绩的普通函数Average(),并将该普通函数定义为Score类友元函数。在主函数中定义学生成绩对象,通过构造函数输入除平均成绩外的其它信息,然后调用Average()函数计算平均成绩,并输出学生成绩的所有信息。 11.3 定义描述圆的类Circle,其数据成员为圆心坐标(X,Y)与半径R。再定义一个描述圆柱体的类Cylinder,其私有数据成员为圆柱体的高H。定义计算圆柱体体积的成员函数Volume(),并将Volume()定义为圆类Circle的友元函数,该函数使用圆类对象的半径R来计算圆柱体的体积。在主函数中定义圆的对象ci,圆心坐标为(12,15),半径为10。再定义圆