繼承與派生(二)
目錄
繼承與派生(二)
1. 派生類成員的通路屬性
1.1 公用繼承
1.2 私有繼承
1.3 保護成員和保護繼承
1.4 多級派生時的通路屬性
2. 派生類的構造函數和析構函數
2.1 簡單的派生類的構造函數
2.2 有子對象的派生類的構造函數
2.3 多層派生時的構造函數
2.4 派生類構造函數的特殊形式
2.5 派生類的析構函數
1. 派生類成員的通路屬性
1.1 公用繼承
基類的公有成員和保護成員在派生類中保持原有通路屬性;私有成員仍為基類私有,隻有基類的成員函數可以引用它,不能被派生類成員函數引用,是以基類私有成員成為派生類中的不可通路的成員。表1給出了公用基類的成員在派生類中的通路屬性:
在基類的通路屬性 | 繼承方式 | 在派生類的通路屬性 |
private | public | 不可通路 |
public | public | public |
protected | public | protected |
下面給出一個公用繼承的例1:
#include<iostream>
#include<string>
using namespace std;
//通路公有基類的成員
class Student{ //聲明基類
public: //基類公用成員
void get_value() { //輸入基類資料的成員函數
cin >> num >> name >> sex;
}
void display() { //輸出基類資料的成員函數
cout << " num:" << num << endl;
cout << " name:" << name << endl;
cout << " sex:" << sex << endl;
}
private: //基類私有成員
int num;
string name;
char sex;
};
class Student1 :public Student { //以public方式聲明派生類Student1
public:
void get_value_1() { cin >> age >> addr; } //輸入派生類資料
void display_1() {
cout << " age:" << age << endl; //引用派生類的私有成員,正确
cout << " address:" << addr << endl; //引用派生類的私有成員,正确
}
private:
int age;
string addr;
};
//通路公有基類的成員 分别調用基類的display函數和派生類中的display_1函數 先後輸出5個資料
int main11_1() {
Student1 stud; //定義派生類Student1的對象stud
stud.get_value();
stud.get_value_1();
stud.display();
stud.display_1();
return 0;
}
1.2 私有繼承
基類的公有成員和保護成員在派生類中成了私有成員,即派生類的成員函數可以通路它們,而在派生類外不能通路它們。基類的私有成員仍為基類私有,在派生類中成為不可通路的成員,隻有基類的成員函數可以引用它們。表2給出了私有基類的成員在派生類中的通路屬性:
在基類的通路屬性 | 繼承方式 | 在派生類的通路屬性 |
private | private | 不可通路 |
public | private | private |
protected | private | private |
下面給出一個私有繼承的例2:
//私有繼承方式
class Student{ //聲明基類
public: //基類公用成員
void get_value() { //輸入基類資料的成員函數
cin >> num >> name >> sex;
}
void display() { //輸出基類資料的成員函數
cout << " num:" << num << endl;
cout << " name:" << name << endl;
cout << " sex:" << sex << endl;
}
private: //基類私有成員
int num;
string name;
char sex;
};
class Student2 :private Student { //以private方式聲明派生類Student2
public:
void get_value_1()
{
get_value(); //調用基類的公用函數輸入基類的3個資料
cin >> age >> addr; } //輸入派生類資料
void display_1() {
display(); //調用基類的公用成員函數輸出3個資料成員的值
cout << " age:" << age << endl; //引用派生類的私有成員,正确
cout << " address:" << addr << endl; //引用派生類的私有成員,正确
}
private:
int age;
string addr;
};
int main() {
Student2 stud; //定義派生類Student1的對象stud
stud.get_value_1();
stud.display_1();
return 0;
}
1.3 保護成員和保護繼承
基類的公有成員和保護成員在派生類中成了保護成員,基類的私有成員仍為基類私有。保護成員的意思是,不能被外界引用,但可以被派生類的成員引用。這樣基類原有的公有成員被保護起來,類外不能任意通路。表3給出了保護基類的成員在派生類中的通路屬性,表4給出了派生類中的成員的通路屬性
在基類的通路屬性 | 繼承方式 | 在派生類的通路屬性 |
private | protected | 不可通路 |
public | protected | protected |
protected | protected | protected |
派生類中通路屬性 | 在派生類中 | 在派生外部中 | 在下一層公用派生類中 |
公用 | 可以 | 可以 | 可以 |
保護 | 可以 | 不可以 | 可以 |
私有 | 可以 | 不可以 | 不可以 |
不可通路 | 不可以 | 不可以 | 不可以 |
下面給出一個保護繼承的例3:
//在派生類中引用保護成員
class Student3 { //聲明基類
public: //基類無公用成員
protected: //基類保護成員
int num;
string name;
char sex;
};
class Student31 :protected Student3 { //用protected方式聲明派生類Student31
public:
void get_value1(); //派生類公用成員函數
void display1(); //派生類公用成員函數
private:
int age; //派生類私有資料成員
string addr; //派生類私有資料成員
};
void Student31::get_value1() { //定義派生類公用成員函數
cin >> num >> name >> sex; //輸入保護基類資料成員
cin >> age >> addr; //輸入派生類資料成員
}
void Student31::display1() { //定義派生類公用成員函數
cout << " num:" << num << endl; //引用基類的保護成員
cout << " name:" << name << endl; //引用基類的保護成員
cout << " sex:" << sex << endl; //引用基類的保護成員
cout << " age:" << age << endl; //引用派生類的私有成員
cout << " address:" << addr << endl; //引用派生類的私有成員
}
//在派生類中引用保護成員
int main() {
Student31 stud1; //stud1是派生類Student31類的對象
stud1.get_value1(); //get_value1是派生類中的公用成員函數,輸入資料
stud1.display1(); //display1是派生類中的公用成員函數,輸出資料
return 0;
}
1.4 多級派生時的通路屬性
如果派生關系為C->B->A,類A為基類,類B是類A的派生類,類C是類B的派生類,那麼類C也是類A的派生類。
類B是類A的直接派生類,類C是類A的間接派生類。類A是類B的直接基類,類A是類C的間接基類。
如果在多級派生時都采用公有繼承方式,那麼直到最後一級派生類都能通路基類的公用成員和保護成員。如果采用私有繼承方式,經過若幹次派生後,基類的所有的成員已經變成不可通路的了。如果采用保護繼承方式,在派生類外是無法通路派生類中的(來自基類的)任何成員的。
下面給出一個多級派生的例子:
class A { //基類
public:
int i;
protected:
void f1();
int j;
private:
int k;
};
class B:public A { //public 派生類
public:
void f2();
protected:
void f3();
private:
int m;
};
class C :protected B{ //protected 派生類
public:
void f4();
private:
int n;
};
2. 派生類的構造函數和析構函數
構造函數的主要作用是對資料成員初始化,在設計派生類的構造函數時,不僅要考慮派生類所增加的資料成員的初始化,還應當考慮基類的資料成員初始化。是以,希望在執行派生類的構造函數時,調用基類的構造函數。
2.1 簡單的派生類的構造函數
任何派生類都包含基類的成員,簡單的派生類隻有一個基類,而且隻有一級派生(隻有直接派生類,沒有間接派生類),在派生類的資料成員中不包含基類的對象(即子對象)。派生類構造函數一般形式為:
派生類構造函數名(總參數表):基類構造函數名(參數表)
{派生類中新增資料成員初始化語句}
Student1(int n, string nam, char s, int a, string ad) :Student(n, nam, s)
//定義派生類構造函數
{
age = a; //在函數體中隻對派生類新增的資料成員初始化
addr = ad;
}
冒号“:”前面部分是派生類構造函數的主幹,它的總參數表中包括基類構造函數所需的參數和對派生類新增的資料成員初始化所需的參數。冒号“:”後面部分是要調用的基類構造函數及其參數。從上面列出的派生類Student1構造函數首行中可以看到,派生類構造函數名(Student1)後面括号内的參數表中包括參數的類型和參數名(如int n),而基類構造函數名後面括号内的參數表列隻有參數名而不包括參數類型(如n, nam, s),因為這裡不是定義基類構造函數,而是調用基類構造函數,是以這些參數是實參而不是形參。它們可以是常量、全局變量和派生類構造函數總參數表中的參數。
在main函數中定義對象stud1時指定了5個實參,它們按順序傳遞給派生類構造函數Student1的形參(n, nam, s, a, ad)。然後派生類構造函數将前面3個(n,nam,s)傳遞給基類構造函數的實參。通過Student(n, nam, s)把3個值再傳給基類構造函數的形參。
下面給出一個例子,定義簡單的派生類構造函數:
class Student5 { //聲明基類Student5
public:
Student5(int n, string nam, char s) { //定義基類的構造函數
num = n;
name = nam;
sex = s;
}
~Student5(){} //基類析構函數
protected: //保護部分
int num;
string name;
char sex;
};
class Student51 :public Student5 {
//聲明公用派生類Student51
public: //派生類的公用部分
Student51(int n, string nam, char s, int a, string ad) :Student5(n, nam, s) {
//定義派生類構造函數
age = a; //在函數體中隻對派生類新增的資料成員初始化
addr = ad;
}
void show() {
cout << "num:" << num << endl;
cout << " name:" << name << endl;
cout << " sex:" << sex << endl;
cout << " age:" << age << endl;
cout << " address:" << addr << endl;
}
~Student51(){} //派生類析構函數
private: //派生類的私有部分
int age;
string addr;
};
int main() {
Student51 stud1(10010, "Wang-li", 'f', 19, "115 Beijing Road,Shanghai");
Student51 stud2(10011, "Zhang-fang", 'm', 21, "213 Shanghai Road,Beijing");
stud1.show(); //輸出第一個學生的資料
stud2.show(); //輸出第二個學生的資料
return 0;
}
也可以将派生類構造函數在類外面定義,而在類體中隻寫該函數的聲明。
Student1(int n, string nam, char s, int a, string ad); //聲明派生類構造函數
//在類的外面定義派生類構造函數:
Student1::Student1(int n, string nam, char s, int a, string ad):Student(n,nam,s)
{
age = a; //在函數體中隻對派生類新增的資料成員初始化
addr = ad;
}
初始化表不僅可以對構造函數的資料成員初始化,還可以調用派生類的基類構造函數,實作對基類資料成員的初始化。也可以在同一構造函數的定義中同時實作這兩種功能。
Student1(int n, string nam, char s, int a, string ad):Student(n,nam,s),age(a),addr(ad){}
在建立一個對象時,執行構造函數的順序是:a.派生類構造函數先調用基類構造函數 b.再執行派生類構造函數本身(即派生類構造函數的函數體)。派生類對象釋放時,先執行派生類析構函數~Student1(),再執行其基類析構函數~Student()。
2.2 有子對象的派生類的構造函數
類的資料成員可以是标準類型或系統提供的類型,還可以是類對象。如s1就是類對象中的内嵌對象,稱為子對象,即對象中的對象。子對象的初始化是在建立派生類時通過調用派生類構造函數來實作的。
Student monitor; //Student是已經聲明的類名,monitor是Student類的對象,定義子對象monitor
派生類構造函數一般形式為:
派生類構造函數名(總參數表):基類構造函數名(參數表),子對象名(參數表)
{派生類中新增資料成員初始化語句}
執行派生類構造函數的順序是:
- 調用基類構造函數,對基類資料成員初始化;
- 調用子對象構造函數,對子對象資料成員初始化;
- 再執行派生類構造函數本身,對派生類資料成員初始化;
例:派生類構造函數的首部如下,構造函數中有6個形參,前兩個是作為基類構造函數的參數,第3、4個是作為子對象構造函數的參數,第5、6個是作為派生類資料成員初始化的。由于子對象monitor也是Student類,在建立對象時也是調用基類構造函數。派生類構造函數的總參數表中的參數,應當包括基類構造函數和子對象的參數表中的參數。基類構造函數和子對象的次序可以是任意的,編譯系統是根據相同的參數名(而不是參數順序)來确定它們的傳遞關系的。
2.3 多層派生時的構造函數
Student是基類,Student1是Student的直接派生類,Student2是Student1的直接派生類,Student2是Student的間接派生類。即Student<-Student1<-Student2。下面給出基類和派生類的構造函數首部的寫法:
//基類Student的構造函數首部
Student(int n, string nam)
//派生類Student1的構造函數首部
Student1(int n, string nam, int a):Student(n,nam)
//派生類Student2的構造函數首部
Student2(int n, string nam, int a, int s):Student1(n,nam,a)
在聲明Student2類對象時,調用Student2構造函數;在執行Student2構造函數時,先調用Student1構造函數;在執行Student1構造函數時,先調用基類Student構造函數。初始化的順序是:a.先初始化基類的資料成員 b.再初始化Student1的資料成員 c.最後再初始化Student2的資料成員。
2.4 派生類構造函數的特殊形式
- 當不需要對派生類新增的成員進行任何初始操作時,派生類構造函數的函數體可以為空,即構造函數是空函數。
- 如果在基類中沒有定義構造函數,或定義了沒有參數的構造函數,那麼,在定義派生類構造函數時可以不寫基類構造函數。因為此時派生類構造函數沒有向基類構造函數傳遞參數的任務。在調用派生類構造函數時,系統會自動首先調用基類的預設構造函數。子對象構造函數和基類構造函數都沒有參數,且無需對派生類資料成員初始化時,可以不必顯式定義派生類構造函數,系統會自動調用預設構造函數。
2.5 派生類的析構函數
析構函數沒有類型也沒有參數,派生類是不能繼承基類的析構函數的,也需要通過派生類的析構函數去調用基類的析構函數。在執行派生類的析構函數時,系統會自動調用基類的析構函數和子對象的析構函數,對基類和子對象進行清理。
調用的順序與構造函數正好相反:先執行派生類自己的析構函數,對派生類新增加的成員進行清理,然後調用子對象的析構函數,對子對象進行清理,最後調用基類的析構函數,對基類進行清理。