实验8 继承与派生
一、实验目的
- 理解继承的含义,掌握派生类的定义和实现方法。
- 理解公有继承下基类成员对派生类成员和派生类对象的可见性,能正确地使用继承层次中的各种类成员。
- 理解保护成员在继承中的作用,能够在适当的时候使用保护成员以便派生类成员可以访问基类的部分非公开成员。
- 理解虚基类在类的继承层次中的作用,虚基类的引入对程序运行时的影响,能够对使用虚基类的简单程序写出程序结果。
二、知识要点
- 继承
继承是C++语言的一种重要机制,它允许在已定义的类的基础上产生新类。
从已定义类产生新类的过程称为派生。已存在的用来派生新类的类为基类,又称父类。从已存在的类派生出的新类称为派生类,又称为子类。如,从哺乳动物类派生出狗类,哺乳动物是父类,狗是子类;从汽车类派生出轿车类,汽车是父类,轿车是子类。
在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承,从多个基类派生的继承称为多继承。
- 派生类的定义格式
(1)单继承的定义格式
class<派生类名>:<继承方式><基类名>
{
<派生类新定义成员>
};
其中:
基类名是已经定义类的名称。派生类名是新定义的一个类的名字,它是从基类中派生的;
派生类是按指定继承方式从基类派生的,继承方式常用的有如下3种:
public 表示公有继承
private 表示私有继承
protected 表示保护继承
在单继承中,每个类可以有多个派生类,但是每个派生类只能有一个基类,从而形成树形结构。
(2)多继承的定义格式
class<派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,……
{
<派生类新定义成员>
};
其中继承方式1、继承方式2、……是3种继承方式public、private和protected之一。
多继承与单继承的主要区别从定义格式上看,主要是多继承的基类多于一个。
- 派生类的3种继承方式
由下表来理解3种继承方式的各自特点。
基类 |
基类 内部函数 |
基类对象 |
private继承方式 |
protected继承方式 |
public继承方式 |
|||
派生类 内部函数 |
派生类 对象 |
派生类 内部函数 |
派生类 对象 |
派生类 内部函数 |
派生类 对象 |
|||
private成员 |
可访问 |
不可访问 |
不可访问 |
不可访问 |
不可访问 |
不可访问 |
不可访问 |
不可访问 |
protected成员 |
可访问 |
不可访问 |
可访问,转为private |
不可访问 |
可访问,转为protected |
不可访问 |
可访问,保持protected |
不可访问 |
public成员 |
可访问 |
可访问 |
可访问,转为private |
不可访问 |
可访问,转为protected |
不可访问 |
可访问,保持public |
可访问 |
- 派生类和基类的关系
任何一个类都可以派生出很多个新类,派生类也可以再派生出新类,因此,基类和派生类是相对而言的。一个基类可以是另一个基类的派生类,这样便形成了复杂的继承结构,出现了类的层次。一个基类派生出一个派生类,它又做另一个派生类的基类,则原来基类为该派生类的间接基类。
基类和派生类之间的关系可以有以下3种描述。
(1)派生类是基类的具体化
类的层次通常反映了客观世界中某种真实的模型。基类是对若干个派生类的抽象,而派生类是基类的具体化。基类抽取了它的派生类的公共性,而派生类通过增加行为将抽象类变为某种有用的类型。
(2)派生类是基类定义的延续
先定义一个抽象基类,该基类中有些操作并未实现,然后定义非抽象的派生类,实现抽象基类中定义的操作。例如虚函数就属于此类情况。这时派生类是抽象的基类的实现,既可以看成是基类定义的延续,这也是派生类的一种常用方法。
(3)派生类是基类的组合。
在多重继承时,一个派生类有多于一个的基类,这时派生类将是所有基类行为的组合。
- 虚基类的引入和说明
引进虚基类的真正目的是为了解决二义性的问题。
声明虚基类的方法是:在定义虚基类的直接派生类时,用关键字virtual引出基类名。
- 二义性问题
一般来说,在派生类中对基类成员的访问应该是唯一的,但是由于多继承情况下,可能造成对基类中某个成员的访问出现不唯一的情况,则称为对基类成员访问的二义性问题。
由多重继承引起的二义性问题是指:当一个派生类从多个基类派生,而这些基类又有一个共同的基类,则对该基类中说明的成员进行访问时,可能会出现二义性。
- 派生类构造函数和析构函数
- 派生类的对象的数据成员是由基类中说明的数据成员和派生类中说明的数据成员共同构成。将派生类的对象中由基类说明的数据成员和操作所构成的封装体称为基类子对象,它由基类中的构造函数进行初始化。
- 构造函数不能够被继承,因此派生类的构造函数必须通过调用基类的构造函数来初始化基类子对象。所以在定义派生类的构造函数时除了对自己的数据成员进行初始化外,还必须负责调用基类构造函数使基类的数据成员得以初始化,如果派生类中还有子对象时,还应该包含对子对象初始化的构造函数。
- 派生类构造函数的一般格式如下
(派生类名)(<派生类构造函数总参数表>):<基类构造函数>(<参数表1>),<子对象名>(<参数表2>)
{
<派生类中数据成员初始化>
}
- 派生类构造函数的调用顺序如下:
基类的构造函数-->子对象类的构造函数-->派生类的构造函数
- 当对象被删除时,派生类的析构函数被执行。由于析构函数不能被继承,因此在执行派生类的析构函数时,基类的析构函数也将被调用。执行顺序是先执行派生类的析构函数,再执行基类的析构函数,其顺序与执行构造函数时的顺序正好相反。
- 派生类构造函数使用中应注意的问题:
派生类构造函数的定义中可以省略对基类构造函数的调用,其条件是在基类中必须有默认的构造函数或者根本没有定义构造函数。当然,如果基类中没有定义构造函数,那么派生类根本不必负责调用基类构造函数。
当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数的途径。在某些情况下,派生类构造函数的函数体可能为空,仅起到参数传递作用。
三、实验内容和步骤
1.定义和使用类的继承关系与定义派生类
【实例1】编写一个学生和教师数据输入和显示程序,学生数据有编号、姓名、班级和成绩,教师数据有编号、姓名、职称和部门。要求将编号、姓名的输入和显示设计成一个类Person,并作为学生数据操作类Student和教师数据操作类Teacher的基类。
题目分析:
由题目可以得出需要设计一个Person基类,Teacher类和Student类都是由Person类派生的,即Teacher类和Student类都是由Person类继承而来,并且Teacher类和Student类都有编号和姓名数据成员,可以把它们作为Person类的公有或保护数据成员。
程序示例:
#include<iostream.h>
class Person
{
protected:
char name[10];
int number;
public:
void input()
{ cin>>name>>number;
}
void show()
{ cout<<name<<"\t"<<number<<endl;
}
};
class Student :public Person
{
char sclass[10];
float score;
};
class Teacher :public Person
{
char dept[10];
char title[6];
};
void main()
{
Student s1;
Teacher t1;
cout<<"Please input the name and the number of a student:"<<endl;
s1.input();
s1.show();
cout<<"Please input the name and the number of a teacher:"<<endl;
t1.input();
t1.show();
}
注意:把input和show两个函数放在Person类中。
实验要求:
- 上机运行该程序。
- 为Teacher类编写系别和职称的输入/输出函数;为Student类编写班级和成绩的输入/输出函数。
2.熟悉不同方式下对基类成员的访问控制
【实例2】给出下面程序的执行结果。
#include<iostream.h>
class A
{
public:
A (int i,int j)
{ a=i;
b=j;
}
void move(int x,int y)
{ a+=x;
b+=y;
}
void show()
{ cout<<"("<<a<<","<<b<<")"<<endl;
}
private:
int a,b;
};
class B : public A
{
public:
B(int i,int j,int k,int l) : A(i,j)
{ x=k;
y=l;
}
void show()
{ cout<<x<<","<<y<<endl;
}
void fun()
{ move(3,5);
}
void f1()
{ A::show();
}
private:
int x,y;
};
void main()
{
A a(1,2);
a.show();
B b(3,4,5,6);
b.fun();
b.show();
b.f1();
}
运行结果:
(1,2)
(5,6)
(6,9)
注意:
(1)类A和类B中的数据成员都是私有属性,故对它们的访问只能通过成员函数。
(2)注意对象的初始化方法。
实验要求:
上机运行程序,并修改已知数据,分析结果。
【实例3】指出下面程序的错误并改正之。
#include<iostream.h>
class Point
{ int x,y;
public:
Point (int xx,int yy)
{ x=xx;
y=yy;
}
void add(int xa,int ya)
{ x+=xa;
y+=ya;
}
void show()
{ cout<<"x="<<x<<","<<"y="<<y<<endl;
}
};
class Rect:private Point
{ int len,width;
public:
Rect (int x,int y,int ll,int ww) : Point (x,y)
{
len=ll;
width=ww;
}
void showRect()
{ show();
cout<<"length="<<len<<","<<"width="<<width<<endl;
}
};
void main()
{ Rect rect(0,2,3,6);
rect.add(4,5);
rect.showRect();
}
题目分析:
一个类被私有继承之后,其成员在派生类中访问属性会变为private,因而在派生类的对象rect不能直接访问基类的成员函数add。有两种改正的方法:第一改变继承方式;第二在派生类中重新定义add函数。
实验要求:
- 上机运行该程序,分析给出的错误提示。
- 按照上面两种方法,改正程序,程序编译通过后,给出运行结果。
3.分析下列程序中的访问权限,并回答所提的问题
#include<iostream.h>
class A
{
int i1;
public:
void f1();
private:
int j1;
};
class B : public A
{
int i2;
public:
void f2();
private:
int j2;
};
class C : public B
{
int i3;
public:
void f3();
private:
int j3;
};
void main()
{
A a;
B b;
C c;
}
回答下列问题:
- 派生类B中成员函数f2能否访问基类A中的成员函数f1和数据成员i1、j1?
- 派生类B的对象b能否访问基类A中的成员函数f1和数据成员i1、j1?
- 派生类C中成员函数f3能否访问直接基类B中的成员函数f2和数据成员j2?能否访问间接基类A中的成员函数f1和数据成员i1、j1?
- 派生类C的对象c能否访问直接基类B中的成员函数f2和数据成员j2?能否访问间接基类A中的成员函数f1和数据成员i1、j1?
- 从对(1)~(4)问题的回答可以得出对公有继承有什么结论?(在公有继承时,派生类的成员函数可以访问基类中的公有成员和保护成员,派生类的对象仅可以访问基类中的公有成员。)
4.利用虚基类解决二义性问题
【实例4】 同一基类被多次继承产生的二义性
#include<iostream.h>
class X
{
protected:
int a;
public:
X()
{ a=10;
}
};
class X1 : public X
{
public:
X1()
{ cout<<"X1 "<<a<<"\n";
}
};
class X2 : public X
{
public:
X2()
{ cout<<"X2 "<<a<<"\n";
}
};
class y : public X1, public X2
{
public:
y()
{ cout<<X1::a<<"\n";
cout<<X2::a<<"\n";
}
};
void main()
{ y obj;
}
四、思考与练习
- 在什么情况下会发生二义性问题?如何解决二义性问题?
- 派生类构造函数和基类构造函数有什么关系?
- 定义一个哺乳动物类Mammal,并从中派生出一个狗类Dog,下面给出Mammal类的定义,要求:
- 添加Dog类的颜色数据成员,访问属性为私有,通过SetColor和GetColor成员函数来对颜色进行设置和获取。
- 分别为基类和派生类添加相应的构造函数(有参、无参)和析构函数,并进行测试。
class Mammal
{
protected:
int itsAge;
int itsWeight;
public:
int GetAge(){return itsAge;}
void SetAge(int age) {itsAge=age;}
int GetWeight() { return itsWeight;}
void SetWeight(int weight) {itsWeight= weight;}
};
class Dog : public Mammal
{
//定义Dog类的数据成员和成员函数
};
- 设计人员基类Person。其成员包括:
数据成员:姓名(字符数组)、性别(字符数组)和年龄(整型)
成员函数:SetPerson,设置人员数据函数;
DisplayPerson,显示人员数据函数;
设计派生类1:Teacher,派生于Person。新增成员包括:
数据成员:职称(字符数组)、教研室(字符数组)和所授课程(字符数组)
成员函数:SetTeacher,设置数据成员函数;
DisplayTeacher,显示数据成员函数;
设计派生类2:Student,派生于Person。新增成员包括:
数据成员:专业(字符数组)、班级(字符数组)和类别(int)
其中类别取值:1(本科生)、2(硕士生)、3(博士生)
成员函数:SetStudent,设置数据成员函数;
DisplayStudent,显示数据成员函数;
设计派生类3:PostDoctor(博士后),多重继承于Student与Teacher。新增成员包括:
数据成员:无
成员函数:SetPostDoctor,设置数据成员函数;
DisplayPostDoctor,显示数据成员函数;
主函数:
输入并输出一个教师、一个本科生、一个博士后数据。
实验八
1.#include<iostream.h>
class Person
{
protected:
char name[10];
int number;
public:
void input()
{ cin>>name>>number;
}
void show()
{ cout<<name<<"\t"<<number<<endl;
}
};
class Student :public Person
{
char sclass[10];
float score;
};
class Teacher :public Person
{
char dept[10];
char title[6];
};
void main()
{
Student s1;
Teacher t1;
cout<<"Please input the name and the number of a student:"<<endl;
s1.input();
s1.show();
cout<<"Please input the sclass and the scour of a student:"<<endl;
s1.input();
s1.show();
cout<<"Please input the name and the number of a teacher:"<<endl;
t1.input();
t1.show();
cout<<"Please input the dept and the title of a teacher:"<<endl;
t1.input();
t1.show();
}
2.#include<iostream.h>
class A
{
public:
A (int i,int j)
{ a=i;
b=j;
}
void move(int x,int y)
{ a+=x;
b+=y;
}
void show()
{ cout<<"("<<a<<","<<b<<")"<<endl;
}
private:
int a,b;
};
class B : public A
{
public:
B(int i,int j,int k,int l) : A(i,j)
{ x=k;
y=l;
}
void show()
{ cout<<"("<<x<<","<<y<<")"<<endl;
}
void fun()
{ move(3,5);
}
void f1()
{ A::show();
}
private:
int x,y;
};
void main()
{
A a(1,2);
a.show();
B b(3,4,5,6);
b.fun();
b.show();
b.f1();
}
3.#include<iostream.h>
class Point
{ int x,y;
public:
Point (int xx,int yy)
{ x=xx;
y=yy;
}
void add(int xa,int ya)
{ x+=xa;
y+=ya;
}
void show()
{ cout<<"x="<<x<<","<<"y="<<y<<endl;
}
};
class Rect:private Point
{ int len,width;
public:
void add(int xa,int ya)
{int x,y;
x+=xa;
y+=ya;
}
Rect (int x,int y,int ll,int ww) : Point (x,y)
{
len=ll;
width=ww;
}
void showRect()
{ show();
cout<<"length="<<len<<","<<"width="<<width<<endl;
}
};
void main()
{ Rect rect(0,2,3,6);
rect.add(4,5);
rect.showRect();
}
3.分析下列程序中的访问权限,并回答所提的问题
#include<iostream.h>
class A
{
int i1;
public:
void f1();
private:
int j1;
};
class B : public A
{
int i2;
public:
void f2();
private:
int j2;
};
class C : public B
{
int i3;
public:
void f3();
private:
int j3;
};
void main()
{
A a;
B b;
C c;
}
回答下列问题:
- 派生类B中成员函数f2能访问基类A中的成员函数f1,能访问数据成员i1、不能访问j1。
- 派生类B的对象b不能访问基类A中的成员函数f1,能访问数据成员i1、不能访问j1。
- 派生类C中成员函数f3能访问直接基类B中的成员函数f2和不能访问数据成员j2,能问间接基类A中的成员函数f1和数据成员i1、不能访问j1。
- 派生类C的对象c不能访问直接基类B中的成员函数f2,能访问数据成员j2,不能访问间接基类A中的成员函数f1和数据成员j1,能访问i1.
- 从对(1)~(4)问题的回答可以得出对公有继承有什么结论?(在公有继承时,派生类的成员函数可以访问基类中的公有成员和保护成员,派生类的对象仅可以访问基类中的公有成员。)
- 思考与练习
- 虚基类用于某类从多个类继承,这多个基类有共同基类时,这个最上层基类的成员会多次在最终派生类出现而产生二义性,为避免二义性,使得最终派生类中,最上层的基类成员只有一份,这时需要虚拟继承,该最上层类就是虚基类,需要注意的是,该类在第一层派生时就要虚拟继承才行,使用方法是在继承方式前加上一个 virtual就可以了。
(2)任何一个类都可以派生出很多个新类,派生类也可以再派生出新类,因此,基类和派生类是相对而言的。一个基类可以是另一个基类的派生类,这样便形成了复杂的继承结构,出现了类的层次。一个基类派生出一个派生类,它又做另一个派生类的基类,则原来基类为该派生类的间接基类。
(3).#include <iostream.h>
#include <string>
using namespace std;
class Mammal
{
protected:
int itsAge;
int itsWeight;
public:
Mammal();
~Mammal();
int GetAge(){return itsAge;}
void SetAge(int age) {itsAge=age;}
int GetWeight() { return itsWeight;}
void SetWeight(int weight) {itsWeight= weight;}
};
class Dog : public Mammal
{
protected:
char itscolor[20];
public:
Dog();
void Setcolor(char *color) {strcpy(itscolor,color);}
void getcolor(){cout<<"狗的颜色"<<itscolor<<endl;}
//定义Dog类的数据成员和成员函数
};
ammal::Mammal()
{
int age1,weight1;
cout<<"请输入动物的年龄:"<<endl;
cin>>age1;
SetAge(age1);
cout<<"请输入动物的体重:"<<endl;
cin>>weight1;
SetWeight(weight1);
}
Mammal::~Mammal()
{
cout<<"Destructorcalled."<<endl;
}
Dog::Dog()
{ char color[20];
cout<<"请输入狗的颜色:"<<endl;
cin>>color;Setcolor(color);
cout<<"狗的颜色"<<itscolor<<"体重"<<GetWeight()<<"年龄"<<GetAge()<<endl;
}
void main()
{
Dog dog1;
}
- 设计人员基类Person。
#include <iostream.h>
#include <string>
using namespace std;
#define n 20
////////////类的定义
class Person
{
protected:
char name[n];
char sex[n];
int age;
public:
Person();
void setperson();
void displayperson();
};
class Teacher :virtual public Person
{
protected:
char job[n];
char room[n];
char subject[n];
public :
Teacher();
void setteacher();
void displayteacher();
};
class Student:virtual public Person
{
protected:
char major[n];
char banji[n];
int leibie;
public :
Student();
void setstudent();
void displaystudent();
};
class Postdoctor:public Teacher,public Student
{
public :
Postdoctor();
void setpostdoctor();
void displaypostdoctor();
};
/////////////结构函数
Person::Person()
{
setperson();
}
Teacher::Teacher()
{
setteacher();
}
Student::Student()
{
setstudent();
}
Postdoctor::Postdoctor()
{
}
//////////////////设置数据//////////////////
void Person::setperson()
{
cout<<"*****"<<"姓名:";
cin>>name;
cout<<"*****"<<"性别:";
cin>>sex;
cout<<"*****"<<"年龄:";
cin>>age;
}
void Teacher::setteacher()
{
cout<<"*****"<<"职称:";
cin>>job;
cout<<"*****"<<"教研室:";
cin>>room;
cout<<"*****"<<"所授课程:";
cin>>subject;
}
void Student::setstudent()
{
cout<<"*****"<<"专业:";
cin>>major;
cout<<"*****"<<"班级:";
cin>>banji;
cout<<"*****"<<"类别(1本科2硕士3博士):";
cin>>leibie;
}
/////////////数据显示///////////
void Person::displayperson()
{
cout<<"姓名:"<<name<<"性别:"<<sex<<"年龄:"<<age;
}
void Teacher::displayteacher()
{
displayperson();
cout<<"职称:"<<job<<"教研室:"<<room<<"所授课程:"<<subject<<endl;
}
void Student::displaystudent()
{
displayperson();
cout<<"专业:"<<major<<"班级:"<<banji<<"类别:"<<leibie<<endl;
}
void Postdoctor::displaypostdoctor()
{
displayperson();
cout<<"职称:"<<job<<"教研室:"<<room<<"所授课程:"<<subject<<"专业:"<<major<<"班级:"<<banji<<"类别:博士后"<<endl;
}
///////////////////
void main()
{
cout<<"您正在输入一个老师的信息:"<<endl;
Teacher t1;
cout<<"***************************************************************************syy割"<<endl;
cout<<"您正在输入一个学生的信息:"<<endl;
Student s1;
cout<<"***************************************************************************syy割"<<endl;
cout<<"您正在输入一个博士后的信息:"<<endl;
Postdoctor p1;
cout<<"***************************************************************************syy割"<<endl;
cout<<endl;
t1.displayteacher();
cout<<endl;
t1.displayteacher();
cout<<endl;
s1.displaystudent();
cout<<endl;
p1.displaypostdoctor();
}
实验体会:
C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数;形成多态必须具备三个条件: