C++繼承機制、概念、語法、作用域與轉換規(guī)則詳解
1. 繼承的概念以及定義
1.1 繼承的概念
繼承(inheritance)機制是面向對象程序設計使代碼可以復用的重要手段,它允許我們在保持原有類特性的基礎上進行擴展,增加方法(成員函數(shù))和屬性(成員變量),這樣產(chǎn)生新的類,稱派生類。繼承呈現(xiàn)了面對象程序設計的層次結構,體現(xiàn)了由簡單到復雜的認知過程。以前我們接觸的函數(shù)層次的復用,繼承設計是類設計層次的復用。
//將公有成員都放到Person類中
class Person {
public:
//進入校園/圖書館/實驗室刷二維碼等身份認證
void identify() {
//......
}
protected:
string _name = "張三";//姓名
string _address; //地址
string _tel; //電話
int _age = 18; //年齡
};
class Student :public Person//Student類繼承自Person
{
public:
//學習
void study() {
}
protected:
int _stuid;//學號
};
class Teacher :public Person//Student類繼承自Person
{
public:
//授課
void teacher() {
}
protected:
string title;//職稱
};
int main() {
Student s;
Teacher t;
s.identify();
t.identify();
}在上述代碼中,Teacher和Student都具有Person類的特征,那么我們就可以讓Teacher和Student都繼承自Person,再在各自類中寫入各自的特性。這樣對Person中的成員進行一個復用,又保證了不同類的特點,避免了重復定義。
1.2 繼承的定義
1.2.1 定義格式
舉個例子:下面的Person是基類,也稱作父類。Student是派生類,也稱作子類。(基類/派生類,父類/子類)。

繼承方式對照??:

1.2.2繼承基類成員訪問方式的變化
- 基類
private成員在派生類中無論以什么樣的方式繼承都是不可見的。這里的不可見是指基類的私有成員還是被繼承到了派生類對象中,但是語法上限制派生類對象不管類里面還是類外面都不能去訪問它。 - 基類
private成員在派生類中是不能被訪問,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就定義為protected。(這里可以看出保護成員限定符是因為繼承才出現(xiàn)的)。
| 類成員/繼承方式 | public繼承 | protected繼承 | private繼承 |
|---|---|---|---|
| 基類的public成員 | 派生類的public成員 | 派生類的protected成員 | 派生類的private成員 |
| 基類的protected成員 | 派生類的protected成員 | 派生類的protected成員 | 派生類的private成員 |
| 基類的private成員 | 在派生類中不可見 | 在派生類中不可見 | 在派生類中不可見 |
- 我們總結一下上面的表格,基類的私有成員在派生類中都是不可見的?;惖钠渌蓡T在派生類中的訪問方式就等于:
Min(成員在基類的訪問限定符,繼承方式),其中:public>protected>private。 - 使用關鍵字class時默認的繼承方式是
private,使用struct時默認的繼承方式時public,最好顯示的寫出繼承方式。 - 在實際運用中一般使用的都是
public繼承,幾乎很少使用protected/private繼承,也不提倡使用protected/private繼承,因為protected/private繼承下來的成員都只能在派生類的類里面使用,實際中擴展維護性不強。
簡單演示一下:
//示例演示三種繼承關系下基類成員的各類型成員訪問關系的變化
class Person {
public:
void Print() {
cout << _name << endl;
}
protected:
string _name = "張三";//姓名
private:
int _age;
};
//class Student:protected Person
//class Student:private Person
class Student :private Person
{
protected:
int _stunum;//學號
};1.3 繼承類模板
基類是類模板的時候,需要指定一下類域,否則編譯會報錯,找不到"push_back"標識符。原因是因為stack<int>實例化時,也實例化了vector<int>了。但是模板是按需實例化,push_back等成員函數(shù)未實例化,所以找不到push_back這個成員。
namespace ZL
{
//例如stack繼承自vector
template<class T>
class stack:public std::vector<T>
{
public:
void push(const T& x) {
vector<T>::push_back(x);
//push_back(x);這樣寫是不對的
}
void pop() {
vector<T>::pop_back();
}
const T& top() {
return vector<T>::back();
}
bool empty() {
return vector<T>::empty();
}
};
}
int main() {
ZL::stack<int> st;
st.push(1);
st.push(2);
st.push(3);
while (!st.empty()) {
cout << st.top() << " ";
st.pop();
}
return 0;
}2. 基類和派生類間的轉換
- 通常情況下我們把一個類型的對象賦值給另一個類型的指針或者引用時,存在類型轉換,中間會產(chǎn)生臨時對象,所以需要加
const,如:int a =1; const double& d = a;public繼承中,就是一個特殊處理的例外,派生類對象可以賦值給基類的指針/基類的引用,而不需要加const,這里的指針或者引用,可能指向基類對象,也可能指向派生類對象。 - 派生類對象賦值給基類對象是通過基類的拷貝構造函數(shù)或者賦值重載函數(shù)完成的,這個過程就像派生類自己定義部分成員切掉一樣,所以也被叫做切割或者切片。

- 指針與引用轉換(圖1、圖2):
Person*或Person&指向/引用Student對象時,僅僅是視角的改變。- 特點: 底層的內(nèi)存結構沒有變化,
Student對象依然完整存在(包括_No),只是通過基類指針或引用只能“看”到和訪問基類部分(_name,_sex,_age),無法訪問派生類特有的成員。這就像給Student戴上了一個Person的面具。
- 值拷貝轉換(圖3):
- 將
Student對象賦值給Person對象時,發(fā)生了切片。 - 特點: 這是一個物理上的拷貝過程。系統(tǒng)只復制了
Student中屬于Person的那部分數(shù)據(jù)(_name,_sex,_age)到一個全新的、獨立的Person對象中。Student特有的部分(_No)被直接丟棄(切掉),且兩個對象在內(nèi)存中是完全獨立的個體。
- 將
- 基類對象不能賦值給派生類對象,派生類對象可以通過拷貝構造賦值給基類對象。
//將公有成員都放到Person類中
class Person {
public:
//進入校園/圖書館/實驗室刷二維碼等身份認證
void identify() {
//......
}
protected:
string _name = "張三";//姓名
string _address; //地址
string _tel; //電話
int _age = 18; //年齡
};
class Student :public Person//Student類繼承自Person
{
public:
//學習
void study() {
}
protected:
int _stuid;//學號
};
int main() {
Student sobj;
//派生類對象可以直接賦值給基類的指針/引用
Person* pp = &sobj;
Person& rp = sobj;
//派生類對象可以直接賦值給基類對象是通過調(diào)用拷貝構造完成的
Person pobj = sobj;
//基類對象不能直接賦值給派生類對象,否則編譯會報錯
//sobj = pobj;
return 0;
}- 基類的指針或者引用可以通過強制類型轉換賦值給派生類的指針或者引用。但必須是基類的指針是指向派生類對象時才是安全的。這里的基類如果是多態(tài)類型,可以使用
RTTI(Run-Time Type Information)的dynamic_cast來進行識別后進行安全轉換。
3.繼承中的作用域
3.1 隱藏規(guī)則
- 在繼承體系中,基類和派生類都有獨立的作用域。
- 派生類和基類中有同名成員,派生類成員將屏蔽基類對同名成員的直接訪問,這種情況叫隱藏。(在派生類成員函數(shù)中,可以使用 基類::基類成員顯示訪問)
class Person {
public:
//進入校園/圖書館/實驗室刷二維碼等身份認證
void identify() {
//......
}
protected:
string _name = "張三";//姓名
string _num="222222222222"; //身份證號
};
class Student :public Person
{
public:
//學習
void Print() {
cout << "姓名:" << _name << endl;
//這里出現(xiàn)了同名的成員,默認都會調(diào)用派生類的成員
// 如果不加類限定符號則基類的_num就會被隱藏
cout << "身份證號碼:" <<Person:: _num << endl;
cout << "學號:" << _num << endl;
}
protected:
string _num = "11111"; //學號
};
int main() {
Student s1;
s1.Print();
return 0;
}上述代碼的Student的_num和Person的_num構成隱藏關系,這樣的代碼很危險能運行但不正確。
3. 需要注意的是如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構成隱藏。
4. 注意在實際中繼承體系里面最好不要定義同名的成員。
3.2 考察繼承作用域相關的選擇題
3.2.1 A和B中兩個func構成什么關系?
A.重載 B.隱藏 C.重寫 D.無關
只需要函數(shù)名相同就構成隱藏,哪怕參數(shù)不同。選擇B。
3.2.2 下面程序的編譯運行結果是什么?
A. 編譯報錯 B.運行報錯 C.正常運行b.fun()調(diào)用的是派生類的fun()沒有參數(shù)編譯就不通過,基類的fun()被隱藏了,選擇A。
正確調(diào)用基類fun():
b.A::fun();
題目:
class A {
public:
void fun() {
cout << "func()" << endl;
}
};
class B :public A
{
public:
void fun(int i) {
cout << "func(int i)" << endl;
}
};
int main() {
B b;
b.fun(10);
b.fun();
return 0;
}到此這篇關于C++繼承機制詳解上:概念、語法、作用域與轉換規(guī)則的文章就介紹到這了,更多相關C++繼承的概念內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C語言中分支和循環(huán)的6種實現(xiàn)形式總結
C語言時一門結構化的程序設計語言,這篇文章主要介紹了C語言中的分支和循環(huán)的6種實現(xiàn)形式,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2023-04-04
static_cast,dynamic_cast,reinterpret_cast,const_cast的區(qū)別及用法詳解
以下是對static_cast,dynamic_cast,reinterpret_cast,const_cast的區(qū)別及用法進行了詳細的分析介紹,需要的朋友可以過來參考下2013-09-09
C語言實現(xiàn) 數(shù)據(jù)類型占多少字節(jié)指針占多少字節(jié)
這篇文章主要介紹了 C語言 數(shù)據(jù)類型占多少字節(jié)指針占多少字節(jié)的實例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09
C++實現(xiàn)LeetCode(111.二叉樹的最小深度)
這篇文章主要介紹了C++實現(xiàn)LeetCode(111.二叉樹的最小深度),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07

