资源描述
实验4 友元函数及虚函数的应用
———————————————————————————————— 作者:
———————————————————————————————— 日期:
2
个人收集整理 勿做商业用途
实验4 友元函数及虚函数的应用
4。1实验目的
1。掌握友元函数的定义方法
2。理解并掌握利用虚函数实现动态多态性和编写通用程序的方法
3.掌握静态数据成员的特性
4.2实验内容与步骤
1。上机实验题一
利用虚函数实现的多态性来求四种几何图形的面积之和。这四种几何图形是:三角形、矩形、正方形和圆.几何图形的类型可以通过构造函数或通过成员函数来设置。
⑴分析
计算这四种几何图的面积公式分别是:
三角形的边长为W,高为H时,则三角形的面积为W* H/2;矩形的边长为W,宽为H时,则其面积为W* H;正方形的边长为S,则正方形的面积为S*S;圆的半径为R,其面积为 3。1415926 *R *R。
为设置几何图形的数据并求出几何图形的面积,需要定义一个包含两个虚函数的类:
class Shape{
public:
virtual float Area( void) =0; //求面积
virtual void Setdata(float ,float =0) =0; //设置图形数据
};
因面积的计算依赖于几何图形,故在类中只能定义一个纯虚函数Area。同理,设置几何图形数据的函数Setdata也只能定义为虚函数。
把这个基类派生出其它几何图形类。如派生出的三角形类为:
class Triangle:public Shape {
float W,H; //三角形边长为W,高为H
public:
Triangle(float w=0,float h=0){ W=w; H = h; }
float Area( void){ return W*H/2; }
void Setdata(float w,float h=0){ W=w; H = h; }
};
在派生类中定义了基类中两个虚函数的实现.为了实现求面积和设置数据的多态性,必须定义一个类,该类中定义一个指向基类Shape的指针数组,其元素分别指向由基类Shape派生出的不同的几何图形类,并完成求出所有几何图形面积之和,以及设置参数的函数.
一个完整的参考程序如下:
#include 〈iostream.h〉
#include 〈string。h>
class Shape{
public:
virtual float Area( void) =0; //虚函数
virtual void Setdata(float ,float =0) =0; //虚函数
};
class Triangle:public Shape{
float W,H; //三角形边长为W,高为H
public:
Triangle(float w=0,float h=0)
{
W=w;H = h;
}
float Area( void) //定义虚函数
{
return W*H/2;
}
void Setdata(float w,float h=0) //定义虚函数
{
W=w; H = h;
}
};
class Rectangle:public Shape{
float W,H; //矩形边长为W,高为H
public:
Rectangle(float w=0,float h=0){ W=w; H = h; }
float Area( void) //定义虚函数
{
return W*H;
}
void Setdata(float w,float h=0) //定义虚函数
{
W=w; H = h;
}
};
class Square:public Shape{
float S; //正方形边长S
public:
Square(float a=0)
{
S=a;
}
float Area( void) //定义虚函数
{
return S*S/2;
}
void Setdata(float w,float h=0) //定义虚函数
{
S=w;
}
};
class Circle:public Shape{
float R; //圆的半径为R
public:
Circle(float r=0)
{
R=r;
}
float Area( void) //定义虚函数
{
return 3.1415926*R *R ;
}
void Setdata(float w,float h=0) //定义虚函数
{
R=w;
}
};
class Compute{
Shape **s; //指向基类的指针数组
public:
Compute()
{ //给几何图形设置参数
s= new Shape *[4];
s[0] = new Triangle(3,4);
s[1] = new Rectangle(6,8);
s[2] = new Square(6。5);
s[3] = new Circle(5。5);
}
float SumArea(void ) ;
~Compute();
void Setdata(int n, float a,float b=0) //A
{
s[n]->Setdata(a,b);
} //B
};
Compute::~Compute() //释放动态分配的存储空间
{
for(int i= 0; i<4; i++) delete s[i];
delete [ ] s;
}
float Compute::SumArea(void )
{
float sum =0;
for( int i =0; i〈 4; i++)
sum += s[i]->Area(); //通过基类指针实现多态性
return sum;
}
void main(void )
{
Compute a;
cout<〈"四种几何图形的面积="〈<a。SumArea()<<’\n';
a。Setdata(2,10); //设置正方形的边长
cout<<"四种几何图形的面积=”〈〈a。SumArea()〈〈’\n’;
a。Setdata(0, 10,12); //设置三角形的边长和高
cout<<”四种几何图形的面积="〈<a。SumArea()〈<’\n';
a。Setdata(1,2,5); //设置正方形的长和宽
cout<〈”四种几何图形的面积="<<a.SumArea()<<'\n’;
a.Setdata(3,15.5);
cout<<"四种几何图形的面积="<<a.SumArea()<<’\n';
}
程序中A行的Setdata函数属于函数重载,它不是虚函数。该函数中的B行通过基类指针实现多态性。
⑵上机要求
自己设计测试数据,测试程序的正确性.
⑶写出实验报告。
2。上机实验题二
利用虚函数实现多态性,设计一个通用的双向链表操作程序。链表上每一个结点数据包括:姓名,地址和工资.要求建立一条双向有序链表,结点数据按工资从小到大的顺序排序。
⑴分析 首先定义抽象类Object,并由其派生出包含题目要求的结点数据。这两个派生类可定义为:
class Object{ //定义一个抽象类,用于派生描述结点信息的类
public:
Object(){} //缺省构造函数
virtual int IsEqual(Object &)=0; //判两个结点是否相等
virtual void Show()=0; //输出一个结点上的数据
virtual int IsGreat(Object &)=0; //判两个结点的大小
virtual ~Object(){ };
};
class MenNode:public Object{ //由抽象类派生出描述结点数据的类
char *Name; //姓名
char *Addr; //地址
int Salary; //工资
public:
MenNode(char n =0, char a=0, iny s =0)
{ //完成数据初始化
if( n==0 ) Name =0;
else {
Name = new char [strlen(n)+1]; strcpy(Name,n);
}
if( a==0 ) Addr =0;
else {
Addr = new char [strlen(a)+1]; strcpy(Addr,a);
}
Salary =s;
}
void SetData(char * ,char *, int ); //重新设置结点的数据
int IsEqual(Object &); //判二个结点是否相等
int IsGreat(Object &ob); //判ob结点是否大于当前结点
~MenNode( ) //释放动态分配的存储空间
{
if(Name) delete [ ] Name; if( Addr) delete [ ] Addr;
}
void Show() //重新定义虚函数
{ cout 〈<"姓名:”〈< Name<〈'\t’<〈 ”地址:"
〈<Addr〈〈'\t’〈〈"工资:”〈<Salary<〈'\n';
}
};
产生一个新结点时,首先要在链表上找到插入位置(按工资大小的升序),然后将新结点插入。List类的定义参见教材.完成插入的成员函数为:
void List::AddNode(Node *node)
{
if(Head ==0){ //A
Head=Tail=node; //使链表首和链表尾指针都指向这结点
node->Next=node—〉Prev=0; //指该结点的前后向指针置为空
}
else { //链表不为空,找到插入位置
Node *pn = Head;
while (pn ) { //B
Object &obj= *(node->Info);
if( pn—>Info->IsGreat( obj) <=0 ) break; //C
else pn = pn—>Next;
}
if(pn == 0 ){ //D
Tail-〉Next=node; //使原链表尾结点的后向指针指向这结点
node—>Prev=Tail; //使该结点的前向指针指向原链表尾结点
Tail=node; //使Tail指向新的链表尾结点
node—>Next=0;
}
else { //插在pn所指向结点之前
if( pn == Head ){ //E
node-〉Next = Head;
Head->Prev = node;
node—>Prev = 0;
Head = node;
}
else{ //F
pn->Prev->Next = node; //使pn指向结点的前一个结点指向node
node-〉Next = pn;
node->Prev = pn; //设置后向链
pn—>Prev = node;
}
}
}
}
A行中条件成立时,双向链表为空链,要插入结点为链表上的第上一个结点,初始化这个双向链表.B行中的循环语句实现查找插入位置,当找到插入位置或整个链表上的结点都查完后,结束这个循环语句.C行中的条件成立时,表示要把结点插在pn所指向的结点之前。D行中的条件成立时,表示要把结点插入链尾.若E行中的条件成立时,要把结点插在第一个结点之前;否则将结点插在pn所指向的结点之前。
一个完整的参考程序如下:
#include 〈iostream。h>
#include <string。h>
class Object{ //定义一个用于派生结点信息的抽象类
public:
Object(){}
virtual int IsEqual(Object &)=0; //判二个结点是否相等
virtual void Show()=0; //输出一个结点上的数据
virtual int IsGreat(Object &)=0; //判二个结点的大小
virtual ~Object(){ };
};
class Node{ //结点类
private:
Object *Info; //指向描述结点的数据域
Node *Prev,*Next; //用于构成链表的前后向指针
public:
Node (){ Info=0; Prev=0; Next=0;}
Node ( Node &node) //完成拷贝功能的构造函数
{
Info=node.Info; Prev=node。Prev; Next=node.Next;
}
void FillInfo(Object *obj){Info =obj;} //使Info指向数据域
friend class List; //定义友元类
};
class List{ //实现双向链表操作的类
Node *Head,*Tail; //链表首和链表尾指针
public:
List(){Head=Tail=0;} //置为空链表
~List(){DeleteList();} //释放链表占用的存储空间
void AddNode(Node *); //在链表尾加一个结点
Node * DeleteNode(Node *); //删除链表中的一个指定的结点
Node *LookUp(Object &); //在链表中查找一个指定的结点
void ShowList(); //输出整条链表上的数据
void DeleteList(); //删除整条链表
};
void List::AddNode(Node *node)
{
if(Head ==0){ //条件成立时,为空链表
Head=Tail=node; //使链表首和链表尾指针都指向这结点
node—〉Next=node->Prev=0; //指该结点的前后向指针置为空
}
else { //链表不为空,找到插入位置
Node *pn = Head;
while (pn ) {
Object &obj= *(node->Info);
if( pn-〉Info-〉IsGreat( obj) <=0 ) break;
else pn = pn—>Next;
}
if(pn == 0 ){ //插入链尾
Tail->Next=node; //使原链表尾结点的后向指针指向这结点
node-〉Prev=Tail; //使该结点的前向指针指向原链表尾结点
Tail=node; //使Tail指向新的链表尾结点
node->Next=0;
}
else { //插在pn所指向结点之前
if( pn == Head ){ //插在第一个结点之前
node-〉Next = Head; Head->Prev = node;
node->Prev = 0; Head = node;
}
else{ //使pn指向结点的前一个结点指向node
pn—〉Prev—〉Next = node;
node—〉Next = pn;
node—〉Prev = pn; //设置后向链
pn->Prev = node;
}
}
}
}
Node * List::DeleteNode(Node *node) //删除指定的结点
{
if( node == Head ) //二者相等,表示删除链表首结点
if(node == Tail) //二者相等,表示链表上只有一个结点
Head=Tail=0;
else { //删除链表首结点
Head=node-〉Next;
Head->Prev=0;
}
else { //删除的结点不是链表上的首结点
node->Prev—〉Next=node—〉Next; //从后向链指针上取下该结点
if(node != Tail ) node—〉Next-〉Prev=node->Prev;
else Tail = node-〉Prev ; //要删除的结点为链表尾结点
}
node->Prev=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::DeleteList() //删除整条链表
{
Node *p,*q;
p=Head;
while (p) {
delete p—>Info; //释放描述结点数据的动态空间
q=p; p=p-〉Next;
delete q; //释放Node占用的动态空间
}
}
class MenNode :public Object{ //由抽象类派生出描述结点数据的类
char *Name; //姓名
char *Addr; //地址
int Salary; //工资
public:
MenNode(char *n=0, char *a=0, int s =0)
{
if( n==0 ) Name =0;
else {
Name = new char [strlen(n)+1]; strcpy(Name,n);
}
if( a==0 ) Addr =0;
else {
Addr = new char [strlen(a)+1]; strcpy(Addr,a);
}
Salary =s;
}
void SetData(char * ,char *, int );
int IsEqual(Object &);
int IsGreat(Object &);
~MenNode( )
{
if(Name) delete [ ] Name;
if( Addr) delete [ ] Addr;
}
void Show() //重新定义虚函数
{ cout <<"姓名:"〈〈 Name〈<’\t’<〈
"地址:"<<Addr〈〈'\t' 〈〈”工资:”〈<Salary<<'\n';
}
};
void MenNode::SetData(char *n ,char *a, int s)
{
if(Name) delete [ ] Name;
if(Addr) delete [ ] Addr;
if( n==0 ) Name =0;
else {
Name = new char [strlen(n)+1]; strcpy(Name,n);
}
if( a==0 ) Addr =0;
else {
Addr = new char [strlen(a)+1]; strcpy(Addr,a);
}
Salary =s;
}
int MenNode::IsEqual(Object &obj) //定义比较结点是否相等的虚函数
{
MenNode &temp=(MenNode &) obj;
return (Salary == temp.Salary); //相等返回1,否则返回0
}
int MenNode::IsGreat(Object &obj) //定义比较结点大小的虚函数
{
MenNode &temp=(MenNode &) obj;
return (temp.Salary—Salary);
}
void main(void )
{
MenNode *p;
Node *pn,*pt, node;
List list;
for (int i=1;i<5;i++) { //建立包含五个结点的双向链表
p= new MenNode; //动态建立一个IntOb类的对象
char name[20],addr[40];
int s;
cout<〈"输入姓名,地址和工资:";
cin。getline(name,20);
cin.getline(addr,40);
cin〉〉s;cin。get();
p—〉SetData(name,addr,s);
pn= new Node; //建立一个新结点
pn->FillInfo(p); //填写结点的数据域
list。AddNode(pn); //将新结点加入链表尾
}
list。ShowList(); //输出链表上各结点的数据值
cout〈<’\n';
MenNode da;
da.SetData( "zhang”, ”NanJin”, 2000); //置要查找的结点数据值
pn=list.LookUp(da); //从链表上查找指定的结点
if (pn) pt=list.DeleteNode(pn); //若找到,则从链表上删除该结点
list.ShowList(); //输出已删除结点后的链表
cout<<’\n';
if (pn) list.AddNode(pt); //将这结点加入链表尾
list。ShowList(); //输出已加一个结点后的链表
}
⑵上机要求
自己设计测试数据,完成程序的调试工作。
⑶写出实验报告。
4。3项目选做
在以上程序的基础上,设计一个双向链表上的结点为通讯录的程序,要求按姓名的字典序排序。
展开阅读全文