资源描述
实验2 派生类与继承
实验课程名:面向对象程序设计(C++)
专业班级: 学号: 姓名:
实验时间: 实验地点: 指导教师:
2.1实验目的和要求
(1) 掌握派生类的声明方法和派生类构造函数的定义方法。
(2) 掌握不同继承方式下,基类成员在派生类中的访问属性。
(3) 掌握在继承方式下,构造函数与析构函数的执行顺序与构造规则。
(4) 学习虚基类在解决二义性问题中的作用。
二、实验内容
一、构造一个类Geometry及其派生类,该类主要实现关于几何图形的基本操作。对于基类“几何图形”,有求面积、求体积的函数(纯虚函数),其派生类圆和矩形主要有初始化(构造函数),求面积,求周长操作,类圆的派生类圆球和圆柱有求表面积、体积操作。 试在主函数中分别定义圆、圆球、圆柱以及矩形的对象,并调用其成员函数实现其相应操作。
Circle
radiums
Circle()
~Circle()
Ball
Ball()
~Ball()
Geometry
Geometry()
~Geometry()
GetArea()
GetPerimeter()
Getcolume()
show()
Column
Column()
~Column()
Rectangle
Rectangle()
~Rectangle()
实验代码如下:
#include<iostream>
using namespace std;
class Geometry
{
public:
Geometry(){}
~Geometry(){}
double GetArea(){}//求面积函数
double GetPerimeter(){}//求体积函数
double Getcolume(){}//求周长函数
virtual show(){}
};
class Circle:public Geometry
{
public:
Circle(double i)
{
radiums=i;
}
~Circle(){}
double GetArea();
double Getcolume();
double R()
{
return radiums;
}
show();
private:
double radiums;
};
double Circle::GetArea()
{
double S;
S=3.14*radiums*radiums;
return S;
}
double Circle::Getcolume()
{
double L;
L=2*3.14*radiums;
return L;
}
Circle::show()
{
cout<<"圆的面积为:"<<GetArea()<<' '<<"周长为:"<<Getcolume()<<endl;
}
class Rectangle:public Geometry
{
public:
Rectangle(double i,double j){length=i;wide=j;}
~Rectangle(){}
double GetArea();
double Getcolume();
double L()
{
return length;
}
show();
private:
double length;
double wide;
};
double Rectangle::GetArea()
{
double S;
S=length*wide;
return S;
}
double Rectangle::Getcolume()
{
double L;
L=2*(length+wide);
return L;
}
Rectangle::show()
{
cout<<"矩形的面积为:"<<GetArea()<<' '<<"周长为:"<<Getcolume()<<endl;
}
class Ball:public Circle
{
public:
Ball(double i):Circle(i){};
~Ball(){};
double GetArea();
double GetPerimeter();
show();
};
double Ball::GetArea()
{
double S;
S=4*3.14*R()*R();
return S;
};
double Ball::GetPerimeter()
{
double V;
V=(4/3)*3.14*R()*R()*R();
return V;
}
Ball::show()
{
cout<<"球的体积为:"<<GetPerimeter()<<' '<<"表面积为:"<<GetArea()<<endl;
}
class Column:public Circle,public Rectangle
{
public:
Column(double i,double j,double k):Circle(i),Rectangle(j,k){};
~Column(){};
double GetArea();
double GetPerimeter();
show();
};
double Column::GetArea()
{
return(2*3.14*R()*R()+2*3.14*R()*L());
}
double Column::GetPerimeter()
{
return(3.14*R()*R()*L());
}
Column::show()
{
cout<<"圆柱的体积为:"<<GetPerimeter()<<' '<<"表面积为:"<<GetArea()<<endl;
}
int main()
{
Circle circle(2.5);
circle.show();
Rectangle rectangle(3,4);
rectangle.show();
Ball ball(3.3);
ball.show();
Column column(1,2,3);
column.show();
return 0;
}
运行结果:
代码分析:
1)首先定义基类Geometry,在定义基类的派生类Circle,Rectangle
再定义以Circle,Rectangle为基类的派生类Column,以及以Circle为基类的派生类Ball;
2)在定义派生类时用构造函数初始化私有成员;
3)最后用类的对象来调用类函数;
二、设计如下类:
(1)建立一个Point类,表示平面中的一个点;建立一个Line类,表示平面中的一条线端,内含两个Point类的对象;建立Triangle类,表示一个三角形,内含三个Line类的对象构成一个三角形。
(2)设计三个类的相应的构造函数、复制构造函数,完成初始化和对象复制
(3)设计Triangle类的成员函数完成三条边是否能构成三角形的检验和三角形面积计算,面积显示。
实验代码:#include<iostream>
#include<math.h>
using namespace std;
class Point //定义一个点的类,坐标为(x,y)
{
public:
Point(double i,double j){x=i;y=j;}
double x,y;
};
class Line
{
public:
Line(double x1,double y1,double x2,double y2):p1(x1,y1),p2(x2,y2){};
double length();
private:
Point p1,p2;
};
double Line::length()
{
return(sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y)));
}
class Triangle
{
public:
Triangle(double x1,double y1,double x2,double y2,double x3,double y3,double x4,double y4,double x5,double y5,double x6,double y6):
L1(x1,y1,x2,y2),L2(x3,y3,x4,y4),L3(x5,y5,x6,y6){};
int panduan();
double Area();
void show();
private:
Line L1,L2,L3;
};
int Triangle::panduan()
{
double a=L1.length();
double b=L2.length();
double c=L3.length();
if(a+b>c&&b+c>a&&a+c>b)
return 1;
else
return 0;
}
double Triangle::Area()
{
double a=L1.length();
double b=L2.length();
double c=L3.length();
double s=(a+b+c)/2;
return(sqrt(s*(s-a)*(s-b)*(s-c)));
}
void Triangle::show()
{
if(panduan())
cout<<"三角形的面积为:"<<Area()<<endl;
else
cout<<"不能构成三角形"<<endl;
}
int main()
{
Triangle T1(0,0,0,1,0,0,1,0,0,1,1,0);
Triangle T2(1,0,0,0,0,0,0,2,0,0,0,1);
T1.show();
T2.show();
return 0;
}
实验结果:
实验分析:
1) 在主函数中定义两个三角形的类,分别由三条边六个点组成,其中第一组能组成三角形,输出面积,第二组边不能组成三角形输出不能构成三角形;
2) 定义点类时,定义两个浮点型数据,用来表示点的坐标,定义边类时定义两个顶点,表示两个端点组成线段,定义三角形类时,用三边表示三角形;
3) 三角形的面积用海伦公式求得,在三角形中定义一个判断函数,判断三边能否组成三角形,当能组成三角形时则求面积并输出,当不能组成三角形时,输出不能构成三角形;
三、 定义一个基类MyArray,基类中可以存放一组整数,基类中有构造函数、析构函数、输入数据和输出数据的函数。
class MyArray{
public:
MyArray(int leng);
~MyArray{};
void Input();
void Display(string);
protected:
int*alist;
int length;
};
(1)声明一个类SortArray继承类MyArray,在该类中定义一个函数,具有将输入的整数从小到大进行排序的功能。
(2)声明一个类ReArray继承类MyArray,在该类中定义一个函数,具有将输入的整数进行倒置的功能。
(3)声明一个类AverArray继承类MyArray,在该类中定义一个函数,具有求输入的整数平均值的功能。
(4) 声明一个NewArray类,同时继承了类SortArray,ReArray和AverArray,使得类NewArray的对象同时具有排序、倒置和求平均值的功能。在继承的过程中声明MyArray为虚基类,体会虚基类在解决二义性问题中的作用。
实验代码:
#include<iostream>
#include<string>
using namespace std;
class Myarray
{
protected:
int *alist;
int length;
public:
Myarray(int len)
{alist=new int[len];length=len;}
~Myarray(){};
void input()
{
int i;
for(i=0;i<length;i++)
{
cout<<"输入数组的第"<<i+1<<"个元素:"<<'\n';
cin>>alist[i];
}
}
int getlen(){return length;}
void display()
{
for(int i=0;i<length;i++)
cout<<alist[i]<<' ';
cout<<endl;
}
};
class SortArray:virtual public Myarray
{
public:
SortArray(int n):Myarray(n){}
sort()
{
int i,j,t;
for(j=0;j<length-1;j++)
{
for(i=0;i<length-j-1;i++)
{
if(alist[i]>alist[i+1])
{
t=alist[i];
alist[i]=alist[i+1];
alist[i+1]=t;
}
}
}
cout<<"排序后数组为:";
for(i=0;i<length;i++)
{
cout<<alist[i]<<' ';
}
}
};
class averarray:virtual public Myarray
{
public:
averarray(int n):Myarray(n){}
float getaver()
{float aver;
int sum=0,i;
for(i=0;i<length;i++)
sum+=alist[i];
aver=(float)sum/length;
return aver;
}
};
class rearray:virtual public Myarray
{
public:
rearray(int n):Myarray(n)
{}
void turn()
{
int i,t,j=length-1;
for(i=0;i<length/2;i++,j--)
{
t=alist[i];
alist[i]=alist[j];
alist[j]=t;
}
}
};
class nawarray:public rearray,public averarray,public SortArray
{
public:
nawarray(int n):Myarray(n),rearray(n),averarray(n),SortArray(n)
{}
};
int main()
{
nawarray A(10);
A.input();
A.display();
A.sort();
cout<<"输出平均值:"<<endl;
cout<<A.getaver()<<endl;
cout<<"倒置:"<<endl;
A.turn();
A.display();
return 0;
}
实验结果:
结果分析:
定义一个基类,用指针作为数组名,定义一个数组,在定义各个基类,分别实现求平均值,排序,倒置的功能。
在调用成员时由于各个派生类都有基类的成员input,output,故用虚基类,避免出现二异性。
四、队列具有先进先出的特点,所有新来的元素都放在队列尾部,出队列的元素从队列头部出去。栈具有后进先出的特点,所有入栈的元素都放在栈顶,出栈时栈顶元素先出。这两种结构具有很多相似的地方:都存放了一系列的元素,元素的操作都在两头进行,元素个数都是动态可变的。我们可以设计一个基类,完成它们共同的功能,然后分别派生出队列类和栈类。这样可以减少代码,提高效率。设计的基类也可以用于派生出其他类。本实验要求设计这个基类以及它的两个派生类。
设计基类LinkList,用链表结构实现。要求链表类具有以下功能:
• 能够在链表的头尾增加节点以及在链表尾增加节点
• 能够记录链表的个数(用静态成员)
• 能返回链表中的节点个数
• 能查看链表头节点的元素值
• 能告知链表是否为空
• 在链表类的构造函数中初始化链表
• 在链表类的析构函数中释放链表所有元素的空间
下面给出链表类的类定义,你需要根据该定义完全实现该类。
//用链表实现的列表类
class LinkList {
//定义链表节点类型
typedef struct node{
int data;
struct node *next;
} ListDataNode;
//定义链表类型
typedef ListDataNode * ListData;
protected:
int count; //列表中元素的个数
ListData dataLinkHead, dataLinkTail;//表头、表尾指针
static ListCount; //列表个数
public:
LinkList(void); //构造函数
virtual ~LinkList(void); //析构函数
void putTail (int newData); //在表尾加入一个新元素
void putHead (int newData); //在表头插入一个新元素
int getHead (void); //从表头取出一个元素
int peekHead(void) ;//查看表头元素的值,假定列表至少有一个元素
bool empty ( ); //检查列表是否空
int getElemCount() ; //取列表元素个数
static int getListNumber(); //取列表个数
};
(1)在上面实现的链表类的基础上派生队列类和栈类,要求队列类可以进行元素入队列和出队列操作以及取队列长度操作,栈类可以进行入栈和出栈操作,还可以查看栈顶元素的值。
(2)在队列类中实现一个输出队列内容的函数printQueue,输出格式为:
Queue head-->0 : 1 : 2 : 3
其中,0、1、2、3为队列中的元素,0是队头。
在栈类中实现一个输出栈中内容的函数printStack,输出格式为:
Stack member:
| 3 |
| 2 |
| 1 |
| 0 |
-----
其中,3、2、1、0是栈中元素,3为栈顶元素。
(3)用多文件结构实现程序。三个类的定义放在一个头文件中,类的实现放在另一个源文件中。主程序用于测试你所设计的三个类的正确性。测试内容包括:
• 在队列中加入几个元素,用printQueue()打印队列内容,然后再从队列中取出这些元素,看是否正确
• 在栈中加入几个元素,用printStack()打印栈的内容,然后再从栈中取出这些元素,看是否正确
• 测试取队列长度的函数getQueueLength()的正确性
• 测试判断栈是否为空的函数empty()的正确性
实验代码:
#include<iostream>
using namespace std;
struct Node
{
int data;
Node *next;
};
class LinkList
{
public:
LinkList(int a[],int n);//构造函数
LinkList(){head=new Node;tail=new Node;head->next=NULL;tail->next=NULL;}
~LinkList();//析构函数
int Length();//求链表长度的函数
int puthead();//在头部插入元素的函数
int puttail();//在尾部插入元素的函数
void emoty();//检查链表是否为空的函数
int gethead (); //从表头取出一个元素
Node *head,*tail;
int length;
};
LinkList::LinkList(int a[],int n)
{
int i;
Node *p,*q;
head=new Node;
head->data=a[0];
p=new Node;
p->data=a[1];
head->next=p;
for(i=2;i<n;i++)
{
q=new Node;
q->data=a[i];
p->next=q;
p=q;
}
tail=p;
tail->next=NULL;
}
LinkList::~LinkList()
{
Node *p,*q;
p=head;
while(p)
{
q=p;
p=p->next;
delete q;
}
}
int LinkList::Length()
{
Node *p;
int i=0;
p=head;
while(p)
{
p=p->next;
i++;
}
return i;
}
int LinkList::puthead()
{
Node *p;
p=new Node;
cout<<"请输入要插入的元素:"<<endl;
cin>>p->data;
p->next=head;
head=p;
return 0;
}
int LinkList::puttail()
{
Node *p;
p=new Node;
cout<<"请输入要插入的元素:"<<endl;
cin>>p->data;
tail->next=p;
tail=p;
tail->next=NULL;
return 0;
}
void LinkList::emoty()
{
if(!head->next)
cout<<"链表为空"<<endl;
else
cout<<"链表不为空"<<endl;
}
int LinkList::gethead ()
{
Node *p;
p=head;
head=head->next;
return p->data;
delete p;
}
class Queue:public LinkList
{
public:
Queue(int a[],int n):LinkList(a,n){};
void inqueue();//入队函数
void outqueue();//出队函数
};
void Queue::inqueue()
{
LinkList::puttail();
}
void Queue::outqueue()
{
cout<<"Queue head->";
while(head!=NULL)
{
cout<<LinkList::gethead()<<':';
}
cout<<endl;
}
class Stack:public LinkList
{
public:
Stack():LinkList()
{
int i=0;
for(i=0;i<10;i++)
{
LinkList::puthead();
}
}
void instack();//入栈函数
void outstack();//出栈函数
};
void Stack::instack()
{
LinkList::puthead();
}
void Stack::outstack()
{
cout<<"Stack tail->";
while(head->next)
{
cout<<LinkList::gethead()<<':';
}
cout<<endl;
}
int main()
{
int a[10]={0,1,2,3,4,5,6,7,8,9};
Queue queue(a,10);
queue.inqueue();
queue.outqueue();
Stack stack;
stack.outstack();
return 0;
}
运行结果:
程序分析:
定义基类链表其中包含功能
能够在链表的头尾增加节点以及在链表尾增加节点
能够记录链表的个数(用静态成员)
能返回链表中的节点个数
能查看链表头节点的元素值
能告知链表是否为空
在链表类的构造函数中初始化链表
在链表类的析构函数中释放链表所有元素的空间
定义派生类队与栈,队列初始化用链表的初始化函数,用gettail实现入队操作用gethead实现从队列的头部出对。栈中初始化用函数puthead实现从栈顶入栈,用函数gethead()实现从栈顶出栈。
三、结论
1)继承可以从基类中获得派生类中不曾定义过的成员,提高了编程效率;
2)继承与派生分为共有、私有、保护三种继承方式,其中共有使用最广泛,它使得派生类与基类中的成员具有相同的属性。
3)多重继承存在有二义性,用虚基类能有效解决这一问题。
4)除了继承还有组合关系,及在一个类中定义另一个类的对象,此时初始化时要用对象名来调用初始化函数。调用对应的函数时,要用对象名调用它的函数。
展开阅读全文