C++繼承的定義與注意事項
一、什么是繼承?
1.1、概念
**繼承(inheritance)機制是面向對象程序設計使代碼可以復用的最重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能,這樣產(chǎn)生新的類,稱派生類。
1.2、定義
下面我們看到Person是父類,也稱作基類。Student是子類,也稱作派生類

1.2.1、繼承關系和訪問限定符

1.2.2、繼承基類成員訪問方式的變化

// 實例演示三種繼承關系下基類成員的各類型成員訪問關系的變化
class Person
{
public :
void Print ()
{
cout<<_name <<endl;
}
protected :
string _name ; // 姓名
private :
int _age ; // 年齡
};
//三種繼承方式,對應上面圖表
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected :
int _stunum ; // 學號
};
注意:
1.基類private成員在派生類中無論以什么方式繼承都是不可見的。不可見是指基類的私有成員還是被繼承到了派生類對象中,但是語法上派生類對像無法訪問;
2.使用關鍵字class時默認的繼承方式是private,使用struct時默認的繼承方式是public,不過最好顯示的寫出繼承方式;
3. 在實際運用中一般使用都是public繼承,不提倡使protetced/private繼承,因為protetced/private繼承下來的成員都只能在派生類的類里面使用,實際中擴展維護性不強;
二、基類和派生類對象賦值轉換
派生類(子類)對象可以賦值給基類(父類)對象/指針/引用;
即將父類中那部分賦值給基類;
這里有個形象的說法叫切片或者切割。寓意把派生類中父類那部分切來賦值過去。
基類對象不能賦值給派生類對象;
基類的指針可以通過強制類型轉換賦值給派生類的指針。但是必須是基類的指針是指向派生類對象時才是安全的。這里基類如果是多態(tài)類型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 來進行識別后進行安全轉換;

Student sobj ; // 1.子類對象可以賦值給父類對象/指針/引用 Person pobj = sobj ; Person* pp = &sobj; Person& rp = sobj;
//2.基類對象不能賦值給派生類對象 sobj = pobj;//錯誤
// 3.基類的指針可以通過強制類型轉換賦值給派生類的指針 pp = &sobj Student* ps1 = (Student*)pp; // 這種情況轉換時可以的。 ps1->_No = 10; pp = &pobj; Student* ps2 = (Student*)pp; // 這種情況轉換時雖然可以,但是會存在越界訪問的問題 ps2->_No = 10;
三、繼承中的作用域
- 在繼承體系中基類和派生類都有獨立的作用域。
- 子類和父類中有同名成員,子類成員將屏蔽父類對同名成員的直接訪問,這種情況叫隱藏,也叫重定
- 義。(在子類成員函數(shù)中,可以使用 基類::基類成員 顯示訪問)
- 需要注意的是如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構成隱藏。
- 注意在實際中在繼承體系里面最好不要定義同名的成員。
// Student的_num和Person的_num構成隱藏關系,可以看出這樣代碼雖然能跑,但是非常容易混淆
class Person
{
protected :
string _name = "小李子"; // 姓名
int _num = 111; // 身份證號
};
class Student : public Person
{
public:
void Print()
{
cout<<" 姓名:"<<_name<< endl;
cout<<" 身份證號:"<<Person::_num<< endl;
cout<<" 學號:"<<_num<<endl;
}
protected:
int _num = 999; // 學號
};
void Test()
{
Student s1;
s1.Print();
};
// B中的fun和A中的fun不是構成重載,因為不是在同一作用域
// B中的fun和A中的fun構成隱藏,成員函數(shù)滿足函數(shù)名相同就構成隱藏。
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "func(int i)->" <<i<<endl;
}
};
void Test()
{
B b;
b.fun(10);
};
四、派生類的默認成員函數(shù)
6個默認成員函數(shù),“默認”的意思就是指我們不寫,編譯器會變我們自動生成一個,那么在派生類中,這幾個
成員函數(shù)是如何生成的呢?
- 派生類的構造函數(shù)必須調用基類的構造函數(shù)初始化基類的那一部分成員。如果基類沒有默認的構造函
- 數(shù),則必須在派生類構造函數(shù)的初始化列表階段顯示調用。
- 派生類的拷貝構造函數(shù)必須調用基類的拷貝構造完成基類的拷貝初始化。
- 派生類的operator=必須要調用基類的operator=完成基類的復制。
- 派生類的析構函數(shù)會在被調用完成后自動調用基類的析構函數(shù)清理基類成員。因為這樣才能保證派生類
- 對象先清理派生類成員再清理基類成員的順序。
- 派生類對象初始化先調用基類構造再調派生類構造。
- 派生類對象析構清理先調用派生類析構再調基類的析構。


五、繼承與友元
友元關系不能繼承,也就是說基類友元不能訪問子類私有和保護成員;
六、繼承與靜態(tài)成員
基類定義了static靜態(tài)成員,則整個繼承體系里面只有一個這樣的成員;
七、菱形繼承
單繼承:一個子類只有一個直接父類

多繼承:一個子類有兩個或以上直接父類時稱這個繼承關系為多繼承

菱形繼承:菱形繼承是多繼承的一種特殊情況。

菱形繼承的問題:從下面的對象成員模型構造,可以看出菱形繼承有數(shù)據(jù)冗余和二義性的問題。在Assistant的對象中Person成員會有兩份。

虛擬繼承可以解決菱形繼承的二義性和數(shù)據(jù)冗余的問題,在Student和Teacher的繼承;
加關鍵字:virtual
菱形繼承的內存對象成員模型:這里可以看到數(shù)據(jù)冗余

菱形虛擬繼承的內存對象成員模型:這里可以分析出D對象中將A放到的了對象組成的最下面,這個A同時屬于B和C,那么B和C如何去找到公共的A呢?
這里是通過了B和C的兩個指針,指向的一張表。這兩個指針叫虛基表指針,這兩個表叫虛基表。虛基表中存的偏移量。通過偏移量可以找到下面的A。
虛基表:存放偏移量


八、繼承和組合
public繼承是一種is-a的關系。也就是說每個派生類對象都是一個基類對象,高耦合;
組合是一種has-a的關系。假設B組合了A,每個B對象中都有一個A對象,低耦合;
優(yōu)先使用對象組合,而不是類繼承
繼承和耦合的使用:
// Car和BMW Car和Benz構成is-a的關系
class Car{
protected:
string _colour = "白色"; // 顏色
string _num = "陜ABIT00"; // 車牌號
};
class BMW : public Car{
public:
void Drive() {cout << "好開-操控" << endl;}
};
class Benz : public Car{
public:
void Drive() {cout << "好坐-舒適" << endl;}
};
// Tire和Car構成has-a的關系
class Tire{
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17; // 尺寸
};
class Car{
protected:
string _colour = "白色"; // 顏色
string _num = "陜ABIT00"; // 車牌號
Tire _t; // 輪胎
};
總結
到此這篇關于C++繼承的定義與注意事項的文章就介紹到這了,更多相關C++繼承內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
c++將引用或者是指針作為函數(shù)參數(shù)實現(xiàn)實參的運算
這篇文章主要介紹了c++將引用或者是指針作為函數(shù)參數(shù)實現(xiàn)實參的運算,需要的朋友可以參考下2014-05-05
Linux?C/C++實現(xiàn)顯示NIC流量統(tǒng)計信息
NIC流量統(tǒng)計信息是由操作系統(tǒng)維護的,當數(shù)據(jù)包通過NIC傳輸時,操作系統(tǒng)會更新相關的計數(shù)器,通過讀取這些計數(shù)器,我們可以獲得關于網(wǎng)絡流量的信息,下面我們就來學習一下如何通過C/C++實現(xiàn)顯示NIC流量統(tǒng)計信息吧2024-01-01

