繼承作者:@小萌新
成都創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:做網(wǎng)站、成都做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的太平網(wǎng)站設(shè)計(jì)、移動媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
專欄:@C++進(jìn)階
作者簡介:大二學(xué)生 希望能和大家一起進(jìn)步!
本篇博客簡介:簡單介紹C++中繼承的概念
繼承是一種面向?qū)ο缶幊痰母拍?,它指的是一個類(稱為子類)可以從另一個類(稱為父類)中繼承屬性和方法。這意味著子類可以獲得父類中定義的所有屬性和方法,并且可以在不改變父類代碼的情況下擴(kuò)展或修改這些屬性和方法。
那么這么做的優(yōu)點(diǎn)是什么呢?
很顯然的一點(diǎn) 可以增強(qiáng)代碼的復(fù)用性 減少冗余代碼
用代碼來舉個例子
class person
{public:
void Print()
{cout<< "name : "<< _name<< endl;
cout<< "age : "<< _age<< endl;
}
protected:
string _name = "zhangsan";
int _age = 18;
};
// 學(xué)生類
class student :public person
{public:
private:
int _stuid;
};
// 教師類
class teacher :public person
{public:
private:
int _jobid;
};
從而達(dá)到一個這樣子的效果
繼承之后父類的所有成員 包括成員變量和方法 都會成為子類的一部分
繼承的定義方式如下
我們都知道 訪問限定符有三種
繼承的方式也有三種
基類當(dāng)中被不同訪問限定符修飾的成員,以不同的繼承方式繼承到派生類當(dāng)中后,該成員最終在派生類當(dāng)中的訪問方式將會發(fā)生變化。
如下圖
實(shí)際上稍作觀察之后我們就能發(fā)現(xiàn)
在子類中的訪問訪問方式遵循以下規(guī)則
什么意思呢? 比如說父類的訪問方式是public 子類使用protected繼承 那么它在子類中的訪問方式就變成protected了
如果父類的訪問方式是protected 子類使用public繼承 那么它在子類中的訪問方式還是protected
那么不可見又是什么意思呢?
我們寫出下面的一段代碼
class person
{public:
void Print()
{cout<< "name : "<< _name<< endl;
cout<< "age : "<< _age<< endl;
}
protected:
string _name = "zhangsan";
int _age = 18;
private:
string _add = "chenghuadadao";
};
// 學(xué)生類
class student :public person
{public:
void testerr()
{cout<< this->_add<< endl;
}
我們可以發(fā)現(xiàn) 在學(xué)生類中 我們是無法訪問父類中的_add的
事實(shí)上這里的編譯器也直接給了我們紅線報錯
這里其實(shí)也從側(cè)面說明了protected訪問限定符為什么會出現(xiàn)
它的作用就是為了不想讓類外部訪問 而想讓子類訪問
但是 我們在實(shí)際寫代碼的過程中一般都是用public繼承
這也是C++被人詬病的語法缺點(diǎn)之一 后續(xù)的python語言甚
至都沒有繼承方式這一說了
默認(rèn)繼承方式這里我們不推薦使用默認(rèn)繼承方式 所以也就不多講了
我們只需要知道兩點(diǎn)
class的默認(rèn)繼承方式是 private
struct的默認(rèn)繼承方式是 public
基類和派生類的賦值轉(zhuǎn)換派生類對象可以賦值給基類的對象 基類的指針 基類的引用
在這個過程當(dāng)中會發(fā)生基類和派生類對象之間的賦值轉(zhuǎn)換
我們來看代碼
class person
{public:
string _name;
string _sex;
int age;
};
class student : public person
{private:
int _stuid;
};
像上面的代碼 我們寫下下面這些操作全部是合法的
student s;
person p = s;
person* ptr = &s;
person& ref = s;
對于我們上面的操作 C++中給了一個比較專業(yè)的名詞叫做切片
意思就是將子類中繼承基類的那部分切出來 賦值給基類
對象賦值
指針賦值
引用賦值
那么這個時候我們再想一下 基類對象能否賦值給子類呢
我們寫出上面的代碼 結(jié)果發(fā)現(xiàn)報錯了
其實(shí)想想也能明白 基類相對于子類來說會少一些東西
所以肯定是不能切片賦值的
但是子類的指針和引用可以通過強(qiáng)制類型轉(zhuǎn)換的方式來賦值
代碼和顯示效果如下
student* ptrs = (student *)&p;
student& refs = (student&)p;
同樣的 我們知道有這個方式存在就好 不建議使用!
接下來我們要學(xué)習(xí)的是C++繼承中的又一大缺陷(bushi) 特性之一
還是一樣 我們先來看代碼
//父類
class Person
{protected:
int _num = 111;
};
//子類
class Student : public Person
{public:
void fun()
{cout<< _num<< endl;
}
protected:
int _num = 999;
};
int main()
{Student s;
s.fun();
return 0;
}
我們?nèi)绻蒙仙厦娴囊欢未a 并且調(diào)用fun函數(shù)的話 由于函數(shù)的局部性原理 我們會得到子類中的num值 如下圖
這個時候如果我們想要訪問父類中的num就需要使用域操作符
void fun()
{cout<< Person::_num<< endl;
}
在繼承體系中的基類和派生類都有獨(dú)立的作用域。若子類和父類中有同名成員,子類成員將屏蔽父類對同名成員的直接訪問,這種情況叫隱藏,也叫重定義。
需要注意的是,如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構(gòu)成隱藏。
還是拿上面的代碼距離 我們給父類中加上一個fun函數(shù)
class Person
{public:
void fun()
{cout<< _num<< endl;
}
像這樣 如果我們要調(diào)用父類里面的person函數(shù)只能加上一個域操作符 如下圖
特別的 這兩個函數(shù)不構(gòu)成函數(shù)重載 因?yàn)闃?gòu)成函數(shù)重載的兩個函數(shù)一定要在同一作用域
我們在真正寫代碼的時候應(yīng)該避免重名的問題
派生類的默認(rèn)成員函數(shù)我們都知道 類有六大默認(rèn)成員函數(shù)
下面我們看看派生類當(dāng)中的默認(rèn)成員函數(shù),與普通類的默認(rèn)成員函數(shù)的不同之處。
其實(shí)這里只要記住一點(diǎn)就好
凡是與基類相關(guān)的部分 都要調(diào)用基類的相關(guān)函數(shù)
接下來我們來看代碼
這是基類person的代碼
//基類
class Person
{public:
//構(gòu)造函數(shù)
Person(const string& name = "peter")
:_name(name)
{cout<< "Person()"<< endl;
}
//拷貝構(gòu)造函數(shù)
Person(const Person& p)
:_name(p._name)
{cout<< "Person(const Person& p)"<< endl;
}
//賦值運(yùn)算符重載函數(shù)
Person& operator=(const Person& p)
{cout<< "Person& operator=(const Person& p)"<< endl;
if (this != &p)
{ _name = p._name;
}
return *this;
}
//析構(gòu)函數(shù)
~Person()
{cout<< "~Person()"<< endl;
}
private:
string _name; //姓名
};
這是子類student的代碼
//派生類
class Student : public Person
{public:
//構(gòu)造函數(shù)
Student(const string& name, int id)
:Person(name) //調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員
, _id(id) //初始化派生類的成員
{cout<< "Student()"<< endl;
}
//拷貝構(gòu)造函數(shù)
Student(const Student& s)
:Person(s) //調(diào)用基類的拷貝構(gòu)造函數(shù)完成基類成員的拷貝構(gòu)造
, _id(s._id) //拷貝構(gòu)造派生類的成員
{cout<< "Student(const Student& s)"<< endl;
}
//賦值運(yùn)算符重載函數(shù)
Student& operator=(const Student& s)
{cout<< "Student& operator=(const Student& s)"<< endl;
if (this != &s)
{ Person::operator=(s); //調(diào)用基類的operator=完成基類成員的賦值
_id = s._id; //完成派生類成員的賦值
}
return *this;
}
//析構(gòu)函數(shù)
~Student()
{cout<< "~Student()"<< endl;
//派生類的析構(gòu)函數(shù)會在被調(diào)用完成后自動調(diào)用基類的析構(gòu)函數(shù)
}
private:
int _id; //學(xué)號
};
是不是發(fā)現(xiàn)完全符合我們上面的推論
這里有兩個特殊結(jié)論
派生類對象初始化時,會先調(diào)用基類的構(gòu)造函數(shù)再調(diào)用派生類的構(gòu)造函數(shù)。
派生類對象在析構(gòu)時,會先調(diào)用派生類的析構(gòu)函數(shù)再調(diào)用基類的析構(gòu)函數(shù)。
所以說我們在派生類的析構(gòu)函數(shù)中不需要在顯示調(diào)用基類的析構(gòu)函數(shù)了
我們可以發(fā)現(xiàn) 假設(shè)我們顯示的調(diào)用基類的析構(gòu)函數(shù)的話 就會析構(gòu)兩次
這在一些情況(開辟釋放內(nèi)存)下會發(fā)生錯誤
此外還有以下三個注意點(diǎn)
這里還是記住一點(diǎn)就好 友元關(guān)系不可以繼承
比如說
class Student;
class Person
{public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{protected:
int _stuNum; // 學(xué)號
};
void Display(const Person& p, const Student& s)
{cout<< p._name<< endl;
cout<< s._stuNum<< endl;
}
void main()
{Person p;
Student s;
Display(p, s);
}
我們這里Display函數(shù)不能訪問student
要想訪問的話必須要在student中也聲明友元
class Student : public Person
{public:
//聲明Display是Student的友元
friend void Display(const Person& p, const Student& s);
protected:
int _id; //學(xué)號
};
這個時候我們就可以使用dis函數(shù)訪問它們的內(nèi)容了
繼承與靜態(tài)成員我們都知道 靜態(tài)變量是儲存在靜態(tài)區(qū)里面的 那么不管我們派生出多少個子類 實(shí)際上它們的靜態(tài)變量儲存地址都是一樣的
我們可以使用下面的代碼來證明
class person
{public:
static int _count ;
};
int person::_count = 0;
class student :public person
{public:
void test()
{_count++;
}
};
我們試著調(diào)用子類中的函數(shù)讓_count++
之后打印子類和父類中的_count看看是不是都是1
繼承方式 單繼承單繼承:一個子類只有一個直接父類時稱這個繼承關(guān)系為單繼承。
比如下面這種方式
多繼承多繼承:一個子類有兩個或兩個以上直接父類時稱這個繼承關(guān)系為多繼承。
比如說下面這樣子
菱形繼承菱形繼承:菱形繼承是多繼承的一種特殊情況。
從上面的圖我們不難看出 菱形繼承一定存在著數(shù)據(jù)冗余和二義性的問題
比如說我們來看下面的代碼
class Person
{public:
string _name; //姓名
};
class Student : public Person
{protected:
int _num; //學(xué)號
};
class Teacher : public Person
{protected:
int _id; //職工編號
};
class Assistant : public Student, public Teacher
{protected:
string _majorCourse; //主修課程
};
int main()
{Assistant a;
a._name = "peter"; //二義性:無法明確知道要訪問哪一個_name
return 0;
}
我們運(yùn)行上面的代碼之后就會發(fā)生一個這樣子的問題
對于二義性問題 我們可以通過指定作用域來解決
Assistant a;
a.Student::_name = "peter";
a.Teacher::_name = "peter2";
但是數(shù)據(jù)冗余的問題缺一定解決不了 如下圖
這個時候就輪到我們的虛繼承出場了
這種繼承方式就是為了專門解決菱形繼承的二義性還有數(shù)據(jù)冗余問題被發(fā)明出來的
我們只需要在上面的繼承方式前加上這段代碼
class Person
{public:
string _name; //姓名
};
class Student : virtual public Person //虛擬繼承
{protected:
int _num; //學(xué)號
};
class Teacher : virtual public Person //虛擬繼承
{protected:
int _id; //職工編號
};
class Assistant : public Student, public Teacher
{protected:
string _majorCourse; //主修課程
};
int main()
{Assistant a;
a._name = "peter"; //無二義性
return 0;
}
這個時候我們便發(fā)現(xiàn)不會報錯了
此時 不管你是訪問teacher還是student中的name 都是訪問的一個地址
菱形虛擬繼承的原理我們首先看看 如果我們不適用菱形虛擬繼承 那么這幾個類在內(nèi)存中會是怎么樣分布的
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;
return 0;
}
接下來我們打開內(nèi)存窗口
我們可以發(fā)現(xiàn) 它在內(nèi)存中的分布是這樣子的
(重點(diǎn)觀察_a 兩個a的地址不一樣)
接下來我們看看虛繼承會是什么樣子的
#includeusing namespace std;
class A
{public:
int _a;
};
class B : virtual public A
{public:
int _b;
};
class C : virtual 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;
return 0;
}
還是一樣 我們來打開內(nèi)存窗口
這個時候我們可以看到 _a被存放到了最后 而一開始存放_a的兩個數(shù)據(jù)則被兩個奇怪的數(shù)據(jù)代替了
實(shí)際上呢 這兩個奇怪的數(shù)據(jù)就是指針
它們被叫做虛基表指針
分別指向一個虛基表 而我們通過虛基表則能夠找到_a的地址
如下圖
繼承與組合很多人都說C++語法復(fù)雜,其實(shí)多繼承就是一個體現(xiàn)。有了多繼承,就可能存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實(shí)現(xiàn)就很復(fù)雜。所以一般不建議設(shè)計(jì)出菱形繼承,否則代碼在復(fù)雜度及性能上都容易出現(xiàn)問題,當(dāng)菱形繼承出問題時難以分析,并且會有一定的效率影響。
那么這個時候我們的組合就出現(xiàn)了
繼承和組合的區(qū)別有什么呢?
繼承是一種is-a的關(guān)系,也就是說每個派生類對象都是一個基類對象;而組合是一種has-a的關(guān)系,若是B組合了A,那么每個B對象中都有一個A對象。
比如說我們看下面的代碼
車和保時捷就是一種is a的關(guān)系 我們可以說 保時捷是一輛車
所以說這種情況使用繼承好一點(diǎn)
class Car
{protected:
string _colour; //顏色
string _num; //車牌號
};
class Porsche : public Car
{;
};
但是呢 像車輪胎和保時捷 就是一種has a的關(guān)系了
我們只能說保時捷有車輪胎 不能說保時捷是車輪胎
class Tire
{protected:
string _brand; //品牌
size_t _size; //尺寸
};
class Car
{protected:
string _colour; //顏色
string _num; //車牌號
Tire _t; // 這里就使用了一個包含關(guān)系
};
若是兩個類之間既可以看作is-a的關(guān)系,又可以看作has-a的關(guān)系,則優(yōu)先使用組合。
菱形繼承是多繼承的一種特殊情況,兩個子類繼承同一個父類,而又有子類同時繼承這兩個子類,我們稱這種繼承為菱形繼承。
菱形繼承因?yàn)樽宇悓ο螽?dāng)中會有兩份父類的成員,因此會導(dǎo)致數(shù)據(jù)冗余和二義性的問題。
菱形虛擬繼承是指在菱形繼承的腰部使用虛擬繼承(virtual)的繼承方式,菱形虛擬繼承對于D類對象當(dāng)中重復(fù)的A類成員只存儲一份,然后采用虛基表指針和虛基表使得D類對象當(dāng)中繼承的B類和C類可以找到自己繼承的A類成員,從而解決了數(shù)據(jù)冗余和二義性的問題。
1 繼承是一種is-a的關(guān)系,而組合是一種has-a的關(guān)系。如果兩個類之間是is-a的關(guān)系,使用繼承;
2 如果兩個類之間是has-a的關(guān)系,則使用組合;
3 如果兩個類之間的關(guān)系既可以看作is-a的關(guān)系,又可以看作has-a的關(guān)系,則優(yōu)先使用組合。
本篇博客主要介紹了C++繼承的一些相關(guān)知識
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
文章名稱:C++進(jìn)階繼承-創(chuàng)新互聯(lián)
瀏覽路徑:http://muchs.cn/article14/csphge.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、網(wǎng)站營銷、用戶體驗(yàn)、自適應(yīng)網(wǎng)站、外貿(mào)建站、移動網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容