繼承 與 派生
繼承(Inheritance)可以理解為一個類從另一個類獲取成員變量和成員函數(shù)的過程。
派生(Derive)
繼承是兒子接收父親的產(chǎn)業(yè),派生是父親把產(chǎn)業(yè)傳承給兒子。
被繼承的類稱為父類或基類,繼承的類稱為子類或派生類。
class 派生類名:[繼承方式] 基類名{
// 成員
};
例:
class Student : public People { };
繼承方式
繼承方式包括 public(公有的)
、private(私有的)
和 protected(受保護的)
,此項是可選的,如果不寫,那么默認為 private
。
- public繼承方式
- 基類中所有 public 成員在派生類中為 public 屬性;
- 基類中所有 protected 成員在派生類中為 protected 屬性;
- 基類中所有 private 成員在派生類中不能使用。
- protected繼承方式
- 基類中的所有 public 成員在派生類中為 protected 屬性;
- 基類中的所有 protected 成員在派生類中為 protected 屬性;
- 基類中的所有 private 成員在派生類中不能使用。
- private繼承方式
- 基類中的所有 public 成員在派生類中均為 private 屬性;
- 基類中的所有 protected 成員在派生類中均為 private 屬性;
- 基類中的所有 private 成員在派生類中不能使用。
繼承方式中的 public
、protected
、private
是用來指明基類成員在派生類中的 最高訪問權(quán)限 的
基類中的 protected 成員
可以在 派生類
中使用,而基類中的 private 成員
不能在 派生類
中使用
基類的 private 成員是能夠被繼承的,并且(成員變量)會占用派生類對象的內(nèi)存,只是在派生類中不可見,導致無法使用。private 成員的這種特性,能夠很好的對派生類隱藏基類的實現(xiàn),以體現(xiàn)面向?qū)ο蟮姆庋b性。
#include <iostream>
#include <string>
using namespace std;
// 模板類
class People
{
private:
// 私有,無論是直接還是繼承 都無法訪問
string m_name;
public:
// 共有,私有變量接口,訪問并設置私有變量
string get_name() const { return m_name; };
void set_name(string name) { m_name = name; };
protected:
// 保護,無法直接訪問,可以通過 繼承 被 子類 調(diào)用
string Identity;
string get_Identity() const { return Identity; };
void set_Identity(string Identity) { this->Identity = Identity; };
};
class Student : public People
{
// 不能訪問 People 類的 private 變量,但可以使用 get函數(shù) 和 set函數(shù)
private:
// 私有變量
string school_num;
public:
// 共有,私有變量接口,訪問并設置私有變量
string get_school_num() const { return school_num; };
void set_school_num(string school_num) { this->school_num = school_num; };
// 成員函數(shù)
void message()
{
this->set_Identity("xxx xxx xxx xxx xxx xxx"); // 訪問 保護 的成員函數(shù)
cout
<< "姓名:" << this->get_name() << endl
<< "身份證:" << this->Identity << endl
<< "學號:" << this->school_num << endl;
}
};
int main()
{
Student Sir;
Sir.set_name("馬保國");
Sir.set_school_num("2020111401");
// Sir 不能訪問 protected 的成員函數(shù)
Sir.message();
return 0;
}
結(jié)果:
姓名:馬保國
身份證:xxx xxx xxx xxx xxx xxx
學號:2020111401
在派生類中訪問基類 private 成員的唯一方法就是借助基類的非 private 成員函數(shù),如果基類沒有非 private 成員函數(shù),那么該成員在派生類中將無法訪問。
改變訪問權(quán)限
使用 using 關鍵字可以改變基類成員在派生類中的訪問權(quán)限
修飾符:
using 基類 : : 成員名;
注意:using 只能改變基類中 public
和 protected
成員的訪問權(quán)限,不能改變 private 成員的訪問權(quán)限
#include <iostream>
#include <string>
using namespace std;
// 基類
class People
{
private:
// 私有,無論是直接還是繼承 都無法訪問
string m_name;
public:
// 共有,私有變量接口,訪問并設置私有變量
string get_name() const { return m_name; };
void set_name(string name) { m_name = name; };
protected:
// 保護,無法直接訪問,可以通過 繼承 被 子類 調(diào)用
string Identity;
string get_Identity() const { return Identity; };
void set_Identity(string Identity) { this->Identity = Identity; };
};
// 子類
class Student : public People
{
// 修改權(quán)限,不能修改 基類定義的 private 成員
private:
// 私有變量
string school_num;
// 修改權(quán)限
using People::Identity;
public:
// 修改權(quán)限,基類中為protected,繼承過來依舊是 protected,外部無法訪問,修改為 public ,可直接通過 類的實例對象訪問
using People::set_Identity;
using People::get_Identity;
// 共有,私有變量接口,訪問并設置私有變量
string get_school_num() const { return school_num; };
void set_school_num(string school_num) { this->school_num = school_num; };
// 成員函數(shù)
void message()
{
cout
<< "姓名:" << this->get_name() << endl
<< "身份證:" << this->Identity << endl
<< "學號:" << this->school_num << endl;
}
};
int main()
{
Student Sir;
Sir.set_name("馬保國");
Sir.set_Identity("xxx xxx 1951xxxx xxxx"); // 修改權(quán)限,訪問受保護的函數(shù)
Sir.set_school_num("2020111401");
Sir.message();
return 0;
}
結(jié)果:
姓名:馬保國
身份證:xxx xxx 1951xxxx xxxx
學號:2020111401
繼承時的名字遮蔽問題
如果派生類中的成員(包括成員變量和成員函數(shù))和基類中的成員重名,那么就會遮蔽從基類繼承過來的成員。
#include <iostream>
#include <string>
using namespace std;
// 基類
class People
{
public:
int sum(int a, int b)
{
return a + b;
}
};
// 派生類
class Student : public People
{
public:
string sum(string a, string b)
{
return a + b;
}
};
void Study()
{
Student Sir;
//Sir.sum(6,9); 直接 error,強制類型轉(zhuǎn)換
cout << Sir.sum("渾圓形意太極門掌門人", "馬保國");
}
如果成員被遮蔽,但仍要要訪問, 則就要加上類名和域解析符 來訪問
Sir.People::sum(6,9);
基類成員函數(shù)和派生類成員函數(shù)不構(gòu)成重載
成員函數(shù),不管函數(shù)的參數(shù)如何,只要名字一樣就會造成遮蔽
基類和派生類的構(gòu)造函數(shù)
類的構(gòu)造函數(shù)不能被繼承
在派生類的構(gòu)造函數(shù)中調(diào)用基類的構(gòu)造函數(shù),對基類的 private變量 進行 初始化
實現(xiàn)方式:
- 在 派生類構(gòu)造函數(shù)定義 時,對 派生類成員變量 初始化,以及 基類 構(gòu)造函數(shù) 初始化
#include <iostream>
#include <string>
using namespace std;
// 基類
class People
{
protected:
string m_name;
public:
// 構(gòu)造函數(shù)
People(string name) : m_name(name) { cout << this->m_name << endl; };
// 析構(gòu)函數(shù)
~People() { };
};
// 子類
class Student : public People
{
private:
string m_work;
public:
// 構(gòu)造函數(shù)
Student(string name, string work) : m_work(work), People(name) { cout << this->m_work << endl; };
// 析構(gòu)函數(shù)
~Student() { };
};
class Pupil : public Student
{
private:
int m_age;
public:
// 構(gòu)造函數(shù)
Pupil(string name, string work,int age) : m_age(age), Student(name,work) { cout << m_age << endl; };
// 析構(gòu)函數(shù)
~Pupil() { };
};
int main()
{
Pupil Sir("馬保國", "渾圓形意太極門掌門人",69);
return 0;
}
結(jié)果:
馬保國
渾圓形意太極門掌門人
69
注意:基類構(gòu)造函數(shù)的調(diào)用放在函數(shù)頭部,不能放在函數(shù)體中。 因為基類構(gòu)造函數(shù)不會被繼承,不能當做普通的成員函數(shù)來調(diào)用。
基類構(gòu)造函數(shù)總是被優(yōu)先調(diào)用,這說明創(chuàng)建派生類對象時,會先調(diào)用基類構(gòu)造函數(shù),再調(diào)用派生類構(gòu)造函數(shù)
C++ 當存在多級繼承,A -> B -> C , 其中禁止在 C 中顯式地調(diào)用 A 的構(gòu)造函數(shù)。
注意:構(gòu)造參數(shù)顯式的定義,系統(tǒng)不會再生成默認的構(gòu)造函數(shù),就必須傳參,若不傳參,就需要手動定義一個空的構(gòu)造函數(shù)
Student Sir; // 錯誤,創(chuàng)建對象,系統(tǒng)不會創(chuàng)建默認的構(gòu)造函數(shù)
基類和派生類的析構(gòu)函數(shù)
和構(gòu)造函數(shù)類似,析構(gòu)函數(shù)也不能被繼承
派生類的析構(gòu)函數(shù)中不用顯式地調(diào)用基類的析構(gòu)函數(shù)
析構(gòu)函數(shù)的執(zhí)行順序:
- 創(chuàng)建派生類對象時,構(gòu)造函數(shù)的執(zhí)行順序和繼承順序相同,即先執(zhí)行基類構(gòu)造函數(shù),再執(zhí)行派生類構(gòu)造函數(shù)。
- 而銷毀派生類對象時,析構(gòu)函數(shù)的執(zhí)行順序和繼承順序相反,即先執(zhí)行派生類析構(gòu)函數(shù),再執(zhí)行基類析構(gòu)函數(shù)。
#include <iostream>
#include <string>
using namespace std;
// 基類
class People
{
protected:
string m_name;
public:
// 構(gòu)造函數(shù)
People(string name) : m_name(name) { };
// 析構(gòu)函數(shù)
~People() { cout << this->m_name << endl; };
};
// 子類
class Student : public People
{
private:
string m_work;
public:
// 構(gòu)造函數(shù)
Student(string name, string work) : m_work(work), People(name) { };
// 析構(gòu)函數(shù)
~Student() { cout << this->m_work << endl; };
};
// 子孫類
class Pupil : public Student
{
private:
int m_age;
public:
// 構(gòu)造函數(shù)
Pupil(string name, string work,int age) : m_age(age), Student(name,work) { };
// 析構(gòu)函數(shù)
~Pupil() { cout << m_age << endl; };
};
int main()
{
Pupil Sir("馬保國", "渾圓形意太極門掌門人",69);
return 0;
}
結(jié)果:
69
渾圓形意太極門掌門人
馬保國
多繼承(多重繼承)
派生類都只有一個基類,稱為單繼承(Single Inheritance)。
一個派生類可以有兩個或多個基類, 稱為多繼承(Multiple Inheritance)。
多繼承容易讓代碼邏輯復雜、思路混亂,一直備受爭議,中小型項目中較少使用,后來的 Java、C#、PHP 等干脆取消了多繼承。
格式:
class 子類 : 修飾符 基類1, 修飾符 基類2 ·····{
// 成員
}
例如:
class Student : public School , protected Family, private Person{
// 成員
}
構(gòu)造函數(shù):
子類(形參列表): 基類1(實參列表), 基類2(實參列表) ···{
// 其他
}
例如:
Student (int num, string grade, int height , int weight) : School(grade) , Family(num), Person(height, weight){
// 其他
}
基類構(gòu)造函數(shù)的調(diào)用順序 和它們在派生類構(gòu)造函數(shù)中出現(xiàn)的順序無關,而是 和聲明派生類時基類出現(xiàn)的順序相同。
#include <iostream>
#include <string>
using namespace std;
// 基類 1
class School
{
private:
string m_grade;
public:
// 構(gòu)造函數(shù)
School(string grade) : m_grade(grade) { cout << "班級:" << this->m_grade << endl; };
};
// 基類 2
class Family
{
private:
int m_num;
public:
// 構(gòu)造函數(shù)
Family(int num) : m_num(num) { cout << "家庭人口數(shù):" << this->m_num << endl; };
};
// 基類 3
class Person
{
private:
int m_height;
int m_weight;
public:
// 構(gòu)造函數(shù)
Person(int height,int weight) : m_height(height),m_weight(weight) {
cout
<< "身高:" << this->m_height << endl
<< "體重:" << this->m_height << endl;
};
};
class Student : public School, protected Family, private Person {
private:
string m_name;
public:
// 構(gòu)造函數(shù)
Student(string name,int num, string grade, int height, int weight) : m_name(name) ,Family(num), School(grade), Person(height, weight) {
// 其他
cout << "姓名:" <<this->m_name << endl;
}
};
int main()
{
Student Sir("小明",5,"軟件工程",168,120);
return 0;
}
結(jié)果:
班級:軟件工程
家庭人口數(shù):5
身高:168
體重:168
姓名:小明
例子中,繼承的基類順序 school
family
person
初始化順序 成員變量
、family
、school
、person
輸出結(jié)果可以清晰的看到:初始化 基類優(yōu)先,基類中先繼承的優(yōu)先
命名沖突
當兩個或多個基類中有同名的成員時,如果直接訪問該成員,就會產(chǎn)生命名沖突,編譯器不知道使用哪個基類的成員。
因此需要在成員名字前面加上 類名 和 域解析符 ::
,以顯式地指明到底使用哪個類的成員,消除二義性。
#include <iostream>
#include <string>
using namespace std;
// 基類 1
class Family
{
protected:
string m_name;
public:
Family(string name) : m_name(name) {};
};
// 基類 2
class Person
{
protected:
string m_name;
public:
Person(string name) : m_name(name) {};
};
// 派生類
class Student : protected Family, private Person {
public:
Student(string n1,string n2) : Family(n1), Person(n2)
{
// cout << m_name << endl; // error,存在二義性,不確定是哪一個
cout << Family::m_name << endl;
}
};
int main()
{
Student Sir("小強","小亮");
return 0;
}
結(jié)果:
小強
虛繼承和虛基類詳解
多繼承(Multiple Inheritance)是指從多個直接基類中產(chǎn)生派生類的能力,多繼承的派生類繼承了所有父類的成員。
graph LR
A-->B
A-->C
B-->D
C-->D
類 A 派生出類 B 和類 C,類 D 繼承自類 B 和類 C,此時類 A 中的成員變量和成員函數(shù)繼承到類 D 中變成了兩份,一份來自 A-->B-->D ,另一份來自 A-->C-->D 。
在一個派生類中保留間接基類的多份同名成員,雖然可以在不同的成員變量中分別存放不同的數(shù)據(jù),但大多數(shù)情況下這是多余的:因為保留多份成員變量不僅占用較多的存儲空間,還會產(chǎn)生命名沖突。
#include <iostream>
#include <string>
using namespace std;
// 基類
class Family
{
public:
void say() { cout << "A類" << endl; }
};
// 子類 1
class Person : public Family{};
// 子類 2
class Student : public Family{};
// 派生類,多繼承
class Man : public Person, public Student{};
int main()
{
Man Sir;
// Sir.say(); error,錯誤。不明確,不知道來自哪
Sir.Student::say();
Sir.Person::say();
return 0;
}
虛繼承
虛繼承(Virtual Inheritance)使得在派生類中只保留一份間接基類的成員。
虛派生只影響從指定了虛基類的派生類中進一步派生出來的類,它不會影響派生類本身。
virtual
關鍵字 代表 虛繼承
#include <iostream>
#include <string>
using namespace std;
// 基類
class Family
{
public:
void say() { cout << "A類" << endl; }
};
// 子類 1
class Person : virtual public Family{};
// 子類 1
class Student : virtual public Family{};
// 派生類,多繼承
class Man : public Person, public Student{};
int main()
{
Man Sir;
// 均可以訪問
Sir.say();
Sir.Student::say();
Sir.Person::say();
return 0;
}
C++標準庫中的 iostream 類就是一個虛繼承的實際應用案例。iostream 從 istream 和 ostream 直接繼承而來,而 istream 和 ostream 又都繼承自一個共同的名為 base_ios 的類,是典型的菱形繼承。此時 istream 和 ostream 必須采用虛繼承,否則將導致 iostream 類中保留兩份 base_ios 類的成員。
在虛繼承的最終派生類中只保留了一份虛基類的成員,所以該成員可以被直接訪問,不會產(chǎn)生二義性
假設 A 定義了一個名為 x 的成員變量,當我們在 D 中直接訪問 x 時,會有三種可能性:
- 如果 B 和 C 中都沒有 x 的定義,那么 x 將被解析為 A 的成員,此時不存在二義性。
- 如果 B 或 C 其中的一個類定義了 x,也不會有二義性,派生類的 x 比虛基類的 x 優(yōu)先級更高。
#include <iostream>
#include <string>
using namespace std;
// 基類
class Family
{
public:
void say() { cout << "A類" << endl; }
};
// 子類 1
class Person : virtual public Family{
public:
void say() { cout << "Person類" << endl; }
};
// 子類 1
class Student : virtual public Family{};
// 派生類,多繼承
class Man : public Person, public Student{};
int main()
{
Man Sir;
Sir.say();
Sir.Student::say();
Sir.Person::say();
return 0;
}
- 如果 B 和 C 中都定義了 x,那么直接訪問 x 將產(chǎn)生二義性問題。
#include <iostream>
#include <string>
using namespace std;
// 基類
class Family
{
public:
void say() { cout << "A類" << endl; }
};
// 子類 1
class Person : virtual public Family{
public:
void say() { cout << "Person類" << endl; }
};
// 子類 1
class Student : virtual public Family{
public:
void say() { cout << "Student類" << endl; }
};
// 派生類,多繼承
class Man : public Person, public Student{};
int main()
{
Man Sir;
//Sir.say(); error,錯誤,存在二義性
Sir.Student::say();
Sir.Person::say();
return 0;
}
虛繼承時的構(gòu)造函數(shù)
最終派生類的構(gòu)造函數(shù)必須要調(diào)用虛基類的構(gòu)造函數(shù)
虛基類是間接基類(間接繼承),而不是直接基類(繼承)。
#include <iostream>
#include <string>
using namespace std;
// 基類
class Family
{
private:
int m_a;
public:
// 構(gòu)造函數(shù)
Family(int a) : m_a(a){
cout << "m_a=" << m_a << ";";
};
};
// 子類 1
class Person : virtual public Family{
private:
int m_b;
public:
// 構(gòu)造函數(shù)
Person(int a, int b): m_b(b) ,Family(a) {
cout << "m_b=" << m_b << ";";
};
};
// 子類 2
class Student : virtual public Family{
private:
int m_c;
public:
// 構(gòu)造函數(shù)
Student(int a,int c): m_c(c), Family(a) {
cout << "m_c=" << m_c << ";";
};
};
// 派生類,多繼承
class Man : public Person, public Student{
private:
int m_d;
public:
// 構(gòu)造函數(shù)
Man(int a, int b ,int c ,int d): m_d(d),Person(a,b),Family(a),Student(a,c) {
cout << "m_d=" << m_d << ";";
};
};
int main()
{
Man Sir(1,2,3,4);
cout << endl;
Person p(1, 2);
cout << endl;
Student s(1, 2);
cout << endl;
return 0;
}
結(jié)果:
m_a=1;m_b=2;m_c=3;m_d=4;
m_a=1;m_b=2;
m_a=1;m_c=2;
編譯器總是先調(diào)用 虛基類的構(gòu)造函數(shù) ,再按照 出現(xiàn)的順序 調(diào)用 其他的構(gòu)造函數(shù)
派生類賦值給基類
類 是一種 數(shù)據(jù)類型 ,可以發(fā)生 數(shù)據(jù)類型轉(zhuǎn)換 ,這種轉(zhuǎn)換只有在 基類 和 派生類 之間才有意義,并且只能將派生類賦值給基類 ,包括將派生類對象賦值給基類對象、將派生類指針賦值給基類指針、將派生類引用賦值給基類引用,稱為向上轉(zhuǎn)型(Upcasting)。
將基類賦值給派生類稱為 向下轉(zhuǎn)型(Downcasting)
將派生類對象賦值給基類對象
賦值的本質(zhì)是將現(xiàn)有的數(shù)據(jù)寫入已分配好的內(nèi)存中,對象的內(nèi)存只包含了成員變量,所以對象之間的賦值是成員變量的賦值,成員函數(shù)不存在賦值問題。
#include <iostream>
#include <string>
using namespace std;
// 基類
class People
{
public:
int m_num;
public:
// 構(gòu)造函數(shù)
People(int num) : m_num(num) {};
// 成員變量
void show()
{
cout << "num = " << this->m_num << endl;
}
};
// 子類
class Student : public People {
public:
int m_code;
public:
// 構(gòu)造函數(shù)
Student(int code, int num) :m_code(code), People(num) {};
// 成員變量
void show()
{
cout << "code = " << this->m_code << ";"
<< "num = " << this->m_num << endl;
}
};
int main()
{
People sir_1(10);
sir_1.show();
Student sir_2(996, 777);
sir_2.show();
// 將 子類 賦值 基類
sir_1 = sir_2;
sir_1.show();
sir_2.show();
return 0;
}
結(jié)果:
num = 10
code = 996;num = 777
num = 777
code = 996;num = 777
子類 是由 基類 派生而來,因此將 子類對象賦值給基類對象,便可以修改基類相關的參數(shù)就會發(fā)生改變
只能用派生類對象給基類對象賦值,而不能用基類對象給派生類對象賦值 ,因為 基類不包含派生類的成員變量,無法對派生類的成員變量賦值。同理,同一基類的不同派生類對象之間也不能賦值。
將派生類指針賦值給基類指針
編譯器通過指針來訪問成員變量,指針指向哪個對象就使用哪個對象的數(shù)據(jù);編譯器通過指針的類型來訪問成員函數(shù),指針屬于哪個類的類型就使用哪個類的函數(shù)。
int main()
{
People *sir_1 = new People(10);
sir_1->show();
Student* sir_2 = new Student(996, 777);
sir_2->show();
// 將 子類 賦值 基類
sir_1 = sir_2;
sir_1->show();
sir_2->show();
return 0;
}
結(jié)果:
num = 10
code = 996;num = 777
num = 777
code = 996;num = 777
將派生類引用賦值給基類引用
引用和指針的類似,是因為引用和指針本質(zhì)上區(qū)別不大,引用僅僅是對指針進行了簡單封裝
int main()
{
Student sir_2(996, 777);
sir_2.show();
People &sir_1 = sir_2;
sir_1.show();
sir_2.show();
return 0;
}
結(jié)果:
code = 996;num = 777
num = 777
code = 996;num = 777
向上轉(zhuǎn)型后通過基類的對象、指針、引用只能訪問從基類繼承過去的成員(包括成員變量和成員函數(shù)),不能訪問派生類新增的成員