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