實驗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++多態意味着調用成員函數時,會根據調用函數的對象的類型來執行不同的函數;形成多態必須具備三個條件: