C++繼承的賦值轉(zhuǎn)換與菱形虛擬繼承深入詳解
一、繼承的概念及定義
繼承是面向?qū)ο笕筇匦灾弧?/p>
1.1、繼承的概念
繼承(inheritance)機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能,這樣產(chǎn)生新的類,稱派生類。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡單到復(fù)雜的認(rèn)知過程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用。繼承是類設(shè)計(jì)層次的復(fù)用。
1.2、繼承的定義
繼承的語法:class 子類 : 繼承方式 父類
繼承方式:
- 共有繼承
- 私有繼承
- 保護(hù)繼承

基類private成員在派生類中無論以什么方式繼承都是不可見的。這里的不可見是指基類的私有成員還是被繼承到了派生類對(duì)象中,但是語法上限制派生類對(duì)象不管在類里面還是類外面都不能去訪問它。
二、基類和派生類對(duì)象賦值轉(zhuǎn)換
- 派生類對(duì)象可以賦值給基類的對(duì)象/基類的指針/基類的引用。這里有個(gè)形象的說法叫切片或者切割。寓意把派生類中父類那部分切來賦值過去。
- 基類對(duì)象不能賦值給派生類對(duì)象。
- 基類的指針或者引用可以通過強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針或者引用。但是必須是基類的指針是指向派生類對(duì)象時(shí)才是安全的。

class person{
protected:
string _name;
int _age;
};
class student :public person
{
public:
int _No;
};
void test01()
{
student sobj;
//1.子類對(duì)象可以賦值給父類對(duì)象/指針/引用
person pobj = sobj;
person* pp = &sobj;
person& rp = sobj;
//2.基類對(duì)象不可以賦值給派生類對(duì)象
//sobj = pobj;
//3.基類的指針可以通過強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針
pp = &sobj;
student* ps1 = (student*)pp;//這種情況是可以的
ps1->_No = 10;
pp = &pobj;
student* ps2 = (student*)pp;//這種情況轉(zhuǎn)換時(shí)雖然可以,但存在越界訪問的問題
ps2->_No = 10;
}
三、繼承中的作用域
3.1、繼承同名成員處理方式
??問題:當(dāng)子類與父類出現(xiàn)同名的成員,如何通過子類對(duì)象,訪問到子類或父類中同名的數(shù)據(jù)呢?
class Base {
public:
Base(){
m_A = 100;
}
void func(){
cout << "Base - func()調(diào)用" << endl;
}
void func(int a){
cout << "Base - func(int a)調(diào)用" << endl;
}
public:
int m_A;
};
class Son : public Base {
public:
Son(){
m_A = 200;
}
//當(dāng)子類與父類擁有同名的成員函數(shù),子類會(huì)隱藏父類中所有版本的同名成員函數(shù)
//如果想訪問父類中被隱藏的同名成員函數(shù),需要加父類的作用域
void func()
{
cout << "Son - func()調(diào)用" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func();
s.Base::func();
s.Base::func(10);
}??????總結(jié):
- 子類對(duì)象可以直接訪問到子類中同名成員
- 子類對(duì)象加作用域可以訪問到父類同名成員
- 當(dāng)子類與父類擁有同名的成員函數(shù),子類會(huì)隱藏父類中同名成員函數(shù),加作用域可以訪問到父類中同名函數(shù)。
- 當(dāng)然父類對(duì)象隨便調(diào)用父類成員。
注:子類和父類中有同名成員時(shí)構(gòu)成隱藏關(guān)系,也叫重定義。需要注意的是,如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構(gòu)成隱藏。
3.2、繼承同名靜態(tài)成員處理方式
??:問題:繼承中同名的靜態(tài)成員在子類對(duì)象上如何進(jìn)行訪問?
靜態(tài)成員和非靜態(tài)成員出現(xiàn)同名,處理方式一致:
- 子類對(duì)象訪問子類同名成員 直接訪問即可
- 子類對(duì)象訪問父類同名成員 需要加作用域
class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}
static int m_A;
};
int Base::m_A = 100;
class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
//同名成員屬性
void test01()
{
//通過對(duì)象訪問
cout << "通過對(duì)象訪問: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//通過類名訪問
cout << "通過類名訪問: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名成員函數(shù)
void test02()
{
//通過對(duì)象訪問
cout << "通過對(duì)象訪問: " << endl;
Son s;
s.func();
s.Base::func();
cout << "通過類名訪問: " << endl;
Son::func();
Son::Base::func();
//出現(xiàn)同名,子類會(huì)隱藏掉父類中所有同名成員函數(shù),需要加作作用域訪問
Son::Base::func(100);
}總結(jié):同名靜態(tài)成員處理方式和非靜態(tài)處理方式一樣,只不過有兩種訪問的方式(通過對(duì)象 和 通過類名)
3.3、繼承與友元
友元關(guān)系不可以繼承,也就是說基類的友元不要可以訪問子類的私有成員和保護(hù)成員。
(就好比說爸爸的朋友不一定是我的朋友)
3.4、繼承與靜態(tài)成員
基類定義了static靜態(tài)成員,則整個(gè)繼承體系只有這一個(gè)成員(我們知道靜態(tài)成員是整個(gè)類共享的),無論派生出多少個(gè)子類,都只有這么一個(gè)static成員。
class person
{
public:
person()
{
_count++;
}
protected:
string _name;
public:
static int _count;//統(tǒng)計(jì)人數(shù)
};
int person::_count = 0;
class student:public person
{
protected:
int _stuNum;
};
class graduate :public student
{
protected:
string course;
};
void test()
{
student s1;
student s2;
student s3;
graduate s4;
cout << "人數(shù)" << person::_count << endl;
student::_count = 0;
cout << "人數(shù)" << person::_count << endl;
}
人數(shù)4
人數(shù)0
請(qǐng)按任意鍵繼續(xù). .
代碼解釋:因?yàn)樽宇悓?duì)象構(gòu)造是會(huì)調(diào)用基類的構(gòu)造函數(shù),所以每實(shí)例化一個(gè)子類對(duì)象都會(huì)調(diào)用一次基類構(gòu)造,從而_count++,并且靜態(tài)成員是整個(gè)類共享的,所以無論哪個(gè)子類都可修改?。。?/p>
四、派生類的默認(rèn)成員函數(shù)
6個(gè)默認(rèn)成員函數(shù),“默認(rèn)"的意思就是指我們不寫,編譯器會(huì)變我們自動(dòng)生成一個(gè),那么在派生類中,這幾個(gè)成員函數(shù)是如何生成的呢?
- 派生類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員。如果基類沒有默認(rèn)的構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。
- 派生類的拷貝構(gòu)造函數(shù)必須調(diào)用基類的拷貝構(gòu)造完成基類的拷貝初始化。
- 派生類的operator=必須要調(diào)用基類的operator=完成基類的復(fù)制。
- 派生類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)清理基類成員。因?yàn)檫@樣才能保證派生類對(duì)象先清理派生類成員再清理基類成員的順序。
- 派生類對(duì)象初始化先調(diào)用基類構(gòu)造再調(diào)派生類構(gòu)造。
- 派生類對(duì)象析構(gòu)清理先調(diào)用派生類析構(gòu)再調(diào)基類的析構(gòu)。
class person
{
public:
person(const char* name = "pxl")
:_name(name)
{}
person(const person& p)
:_name(p._name)
{}
person& operator=(const person& p)
{
if (this != *p){
_name = p._name;
}
return *this;
}
~person()
{}
protected:
string _name;
};
class student :public person
{
public:
student(const char* name, int num)
:person(name)//顯示調(diào)用基類的構(gòu)造函數(shù)初始化基類成員
, _num(num)
{}
student(const student& s)
:person(s)//注意這里有個(gè)隱式的切片操作 person& p = s;
, _num(s._num)
{}
student& operator=(const student& s)
{
if (this != &s){
person::operator=(s);//調(diào)用基類的operator=完成基類的賦值
_num = s._num;
}
return *this;
}
~student()
{
cout << "~student()" << endl;
//注意這里會(huì)自動(dòng)調(diào)用父類析構(gòu)
}
protected:
int _num;
};
void test()
{
student s1("ppp", 20);
student s2(s1);
student s3("xxx", 30);
s1 = s3;
}??留意代碼中注釋部分!
五、復(fù)雜菱形繼承及菱形虛擬繼承
5.1、繼承分類
單繼承:一個(gè)子類只有一個(gè)直接父類時(shí)稱為單繼承

多繼承:一個(gè)子類有兩個(gè)或者兩個(gè)以上直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承

菱形繼承:兩個(gè)派生類繼承同一個(gè)基類,又有某個(gè)類同時(shí)繼承者兩個(gè)派生類。菱形繼承帶來的主要問題是子類繼承兩份相同的數(shù)據(jù),導(dǎo)致資源浪費(fèi)以及毫無意義。
利用虛繼承可以解決菱形繼承問題

????????????????????????????????????????????????????????
對(duì)于菱形繼承的二義性問題,我們可以在訪問的時(shí)候加上類域,這樣是可以解決的,但是數(shù)據(jù)冗余無法解決。所以下面引入虛擬繼承!
5.2、虛擬繼承解決菱形繼承問題原理
為了研究虛擬繼承原理,我們給出一個(gè)簡單的菱形繼承體系,再借助內(nèi)存窗口觀察對(duì)象成員模型。
class A{
public:
int _a;
};
class B:public A
{
public:
int _b;
};
class C :public A
{
public:
int _c;
};
class D :public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
system("pause");
return 0;
}如圖是菱形繼承的內(nèi)存對(duì)象成員模型,可以看出來數(shù)據(jù)冗余!??!

下面是菱形虛擬繼承的內(nèi)存對(duì)象成員模型:

這里可以分析出D對(duì)象將A放在了對(duì)象組成的最下面,這個(gè)A同時(shí)屬于B和C,那么B和C如何去找到公共的A呢?
這里通過B和C的兩個(gè)指針,指向一張表。這兩個(gè)指針叫虛基表指針,這兩個(gè)表叫虛基表,虛基表中存的是偏移量。通過偏移量可以找到下面的A。
到此這篇關(guān)于C++繼承的賦值轉(zhuǎn)換與菱形虛擬繼承深入詳解的文章就介紹到這了,更多相關(guān)C++繼承的賦值轉(zhuǎn)換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ 中"emplace_back" 與 "push_back" 的區(qū)別
這篇文章主要介紹了C++ 中"emplace_back" 與 "push_back" 的區(qū)別的相關(guān)資料,需要的朋友可以參考下2017-04-04
Linux網(wǎng)絡(luò)編程之基于UDP實(shí)現(xiàn)可靠的文件傳輸示例
這篇文章主要介紹了Linux網(wǎng)絡(luò)編程之基于UDP實(shí)現(xiàn)可靠的文件傳輸示例,是很實(shí)用的技巧,需要的朋友可以參考下2014-08-08

