目錄
前言
繼承
繼承是什么?
為什么會存在繼承?
語法:
一些基本的定義:
三種繼承方式:
對象模型
對于構(gòu)造和析構(gòu)的順序
同名函數(shù)的處理方式
總結(jié):
靜態(tài)成員:
定義:
性質(zhì):
共享數(shù)據(jù)?
編譯階段分配內(nèi)存
類內(nèi)聲明類外初始化
靜態(tài)成員函數(shù)
靜態(tài)成員函數(shù)與普通成員函數(shù)的區(qū)別:
靜態(tài)成員訪問:
多繼承
菱形繼承
菱形繼承會遇到的問題:
如何解決?
原理:
多態(tài)
多態(tài)分類:
靜態(tài)多態(tài)和動態(tài)多態(tài)區(qū)分:
靜態(tài)多態(tài):(靜態(tài)綁定)
動態(tài)多態(tài):(動態(tài)綁定)
動態(tài)多態(tài)實現(xiàn):
引入:
那什么是虛函數(shù)?
虛函數(shù)的重寫?
多態(tài)實現(xiàn)代碼示例:
虛函數(shù)的動態(tài)綁定機(jī)制(面試經(jīng)常問的)
?重載、重定義、重寫的區(qū)別
純虛函數(shù)
抽象類:
那什么是接口繼承呢?
那正常的繼承叫做什么?
多態(tài)原理(工具觀察):
虛析構(gòu)
語法:
原理:通過父類指針來釋放子類空間
純虛析構(gòu)
特點:
語法:
純虛析構(gòu)和虛析構(gòu)的共性與區(qū)別:
虛析構(gòu)和純虛析構(gòu)共性:
虛析構(gòu)和純虛析構(gòu)區(qū)別:
總結(jié):
總結(jié):
前言
這篇博客介紹相關(guān)c++繼承以及多態(tài)的內(nèi)容,在c++中屬于很重要的一個部分,請認(rèn)真學(xué)習(xí)(? ?_?)?
繼承
繼承是什么?
簡單從字面意思上來說就是繼承上一個事物的基本特點
準(zhǔn)確定義是:繼承是面向?qū)ο笕筇卣髦唬梢允沟米宇惥哂懈割惖膶傩院头椒?, 還可以在子類中重新定義 ,以及追加屬性和方法。
為什么會存在繼承?
假如要構(gòu)造兩個類——斑點貓和白貓,其中包含的行為有類對象的行為習(xí)慣等,而這兩個同屬于貓類,他們有很多相似的地方,如果沒有繼承,則需要重復(fù)性寫這一相似的內(nèi)容,一個兩個還好,如果多個類都有相似之處,繼承就是比較好的方式了。
語法:
class 子類名:繼承方式(public/private/protected)? 父類名
一些基本的定義:
基類:被繼承的類,又稱為“父類”
派生類:繼承其他類的類,又稱為“子類”
三種繼承方式:
公共繼承public:繼承父類的公共權(quán)限,則相應(yīng)的私有權(quán)限和保護(hù)權(quán)限也繼承過去,也是相應(yīng)的私有和保護(hù)權(quán)限
保護(hù)繼承protected:繼承父類的保護(hù)權(quán)限,則父類的公共權(quán)限到子類中變?yōu)楸Wo(hù)權(quán)限,而私有權(quán)限仍然是私有權(quán)限
私有繼承private:繼承父類的私有權(quán)限,父類中所有權(quán)限到子類中都是私有權(quán)限。
對象模型
這里創(chuàng)造兩個類,一個基類,一個派生類,B類繼承A類的公共權(quán)限
class A
class B
#include<iostream> using namespace std class A { public: int a; }; class B:public A { public: int b; };
則它們在內(nèi)存中的分布是:
也就是說,派生類會繼承一份基類的成員,然后在旁邊創(chuàng)建自己的成員
對于構(gòu)造和析構(gòu)的順序
當(dāng)創(chuàng)建一個子類對象后,如果要初始化它,則哪一個類先構(gòu)造,哪一個類后構(gòu)造,程序結(jié)束時,誰先析構(gòu)?
誰的構(gòu)造函數(shù)先調(diào)用,誰的析構(gòu)函數(shù)先調(diào)用
代碼示例:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"A的構(gòu)造函數(shù)"<<endl;
}
~A()
{
cout<<"A的析構(gòu)函數(shù)"<<endl;
}
int m_a;
};
class B:public A
{
public:
B()
{
cout<<"B的構(gòu)造函數(shù)"<<endl;
}
~B()
{
cout<<"B的析構(gòu)函數(shù)"<<endl;
}
int m_b;
};
test1(){
B b;
}
int main()
{
test1();
system("pause");
return 0;
}
?輸出結(jié)果為:
由此可見剛才問題的答案是:父類先構(gòu)造然后子類構(gòu)造,子類析構(gòu)再父類析構(gòu)
這個可以用雞生蛋來記——雞比它生的蛋要早出生,先有這只雞才有這個蛋,然后吃的時候,先吃蛋,再吃雞
同名函數(shù)的處理方式
對于基類和派生類的同名函數(shù)如何處理呢?換句話說當(dāng)調(diào)用這個同名函數(shù)時,真正會起作用的是哪一個函數(shù)呢?
我們來通過代碼看一看:
#include<iostream> using namespace std; class A { public: void speak() { cout<<"A會說話了"<<endl; } int m_a; }; class B:public A { public: void speak() { cout<<"B會說話了"<<endl; } int m_b; }; test1(){ B b; b.speak(); } int main() { test1(); system("pause"); return 0; }
通過代碼發(fā)現(xiàn)派生類調(diào)用同名函數(shù),則調(diào)用派生類的同名函數(shù)
得出結(jié)論:
如果子類中出現(xiàn)和父類同名的成員函數(shù),子類的同名成員會隱藏掉父類中所有同名成員函數(shù)
那么如何通過派生類調(diào)用基類的同名函數(shù)呢?
通過添加作用域? ? ??類名::
總結(jié):
- 當(dāng)子類與父類擁有同名的成員函數(shù),子類會隱意父類中同名成員函教,加作用域可以訪問到父類中同名函數(shù)
- 子類對象可以直接訪問到子類中同名成員
- 子類對象加作用域可以訪問到父類同名成員
作用域是??類名::
靜態(tài)成員:
定義:
簡單來說:普通的成員前加上一個static關(guān)鍵字,就被稱為靜態(tài)成員。
當(dāng)我們聲明類的成員為靜態(tài)時,這意味著無論創(chuàng)建多少個類的對象,靜態(tài)成員都只有一個副本。
性質(zhì):
共享數(shù)據(jù)?
靜態(tài)成員在類的所有對象中是共享的。如果不存在其他的初始化語句,在創(chuàng)建第一個對象時,所有的靜態(tài)數(shù)據(jù)都會被初始化為零。
編譯階段分配內(nèi)存
在編譯階段就已經(jīng)分配好內(nèi)存了
類內(nèi)聲明類外初始化
靜態(tài)成員不可以在類內(nèi)初始化,要在類外初始化,初始化時在成員名加上作用域即可
靜態(tài)成員函數(shù)
在成員函數(shù)前加上關(guān)鍵字static,這樣就把類的特定對象和該函數(shù)獨立開
靜態(tài)函數(shù)只要使用類名加范圍解析運(yùn)算符?::?就可以訪問
靜態(tài)成員函數(shù)只能訪問靜態(tài)成員數(shù)據(jù)、其他靜態(tài)成員函數(shù)和類外部的其他函數(shù)
靜態(tài)成員函數(shù)與普通成員函數(shù)的區(qū)別:
- 靜態(tài)成員函數(shù)沒有 this 指針,只能訪問靜態(tài)成員(包括靜態(tài)成員變量和靜態(tài)成員函數(shù))
- 普通成員函數(shù)有 this 指針,可以訪問類中的任意成員
靜態(tài)成員訪問:
- 通過類名訪問:類名::父類作用域::成員
- 通過對象訪問
- 同名靜態(tài)函數(shù)或者是變量,父類的所有同名函數(shù)包括函數(shù)重載會被隱藏,除非加上作用域才可以成功
多繼承
子類是否只可以繼承一個類,可以進(jìn)行多個類嗎?如果可以繼承多個類,多繼承的語法是什么?
c++允許子類可以繼承多個類
語法為:class 子類? :繼承權(quán)限 父類1 ,繼承權(quán)限 父類2,繼承權(quán)限 父類3……
但是注意:在實際開發(fā)過程中,不建議使用多繼承語法
菱形繼承
什么是菱形繼承?
如下圖:
這樣一種幾個類相互有一定的繼承關(guān)系的看起來像菱形一樣的,被稱為菱形繼承
定義:兩個派生類繼承同一個基類,又有某個類同時繼承者兩個派生類,這種繼承被稱為菱形繼承,或者鉆石繼承
菱形繼承會遇到的問題:
由于類4同時繼承類2和類3,而類2和類3都繼承了1,那么相當(dāng)于類4繼承了兩份類1.
如何解決?
使用虛繼承的方式,用關(guān)鍵字virtual,可以使派生類不重復(fù)繼承
在派生類繼承的時候在繼承權(quán)限前加上這個關(guān)鍵字virtual即可
示例:
原理:
虛繼承會產(chǎn)生虛基類指針vbptr
該指針指向虛基表,虛基表記錄的是通過指針訪問公共祖先的數(shù)據(jù)的偏移量
多態(tài)
多態(tài)是c++面向?qū)ο笕筇卣髦?/p>
多態(tài)按字面的意思就是多種形態(tài),具體解釋是指:不同對象去完成某一個行為是而產(chǎn)生的不同狀態(tài)
當(dāng)類之間存在層次結(jié)構(gòu),并且類之間是通過繼承關(guān)聯(lián)時,就會用到多態(tài)
多態(tài)性提供接口與具體實現(xiàn)之間的隔離,將what和how這兩個板塊分離開
多態(tài)好處:
- 組織結(jié)構(gòu)清晰
- 可讀性強(qiáng)
- 對于前期和后期擴(kuò)展以及維護(hù)性高
多態(tài)分類:
分為靜態(tài)多態(tài)和動態(tài)多態(tài)(其實靜態(tài)多態(tài)之前就已經(jīng)涉及了)
靜態(tài)多態(tài)和動態(tài)多態(tài)區(qū)分:
靜態(tài)多態(tài)的函數(shù)地址早綁定-編譯階段確定函數(shù)地址
動態(tài)多態(tài)的函數(shù)地址晚綁定-運(yùn)行階段確定函數(shù)地址
靜態(tài)多態(tài):(靜態(tài)綁定)
靜態(tài)多態(tài):函數(shù)重載,重定義,運(yùn)算符重載都屬于靜態(tài)多態(tài)
靜態(tài)多態(tài)的函數(shù)地址早綁定-編譯階段確定函數(shù)地址
動態(tài)多態(tài):(動態(tài)綁定)
動態(tài)多態(tài):派生類和虛函數(shù)實現(xiàn)運(yùn)行時的多態(tài)
動態(tài)多態(tài)的函數(shù)地址晚綁定-運(yùn)行階段確定函數(shù)地址
動態(tài)多態(tài)滿足條件:
1.有繼承關(guān)系
2.子類重寫父類的虛函數(shù),函數(shù)返回值類型,名稱,參數(shù)列表完全相同
3.使用->父類指針或者引用執(zhí)行于類對象
下面僅介紹重點——動態(tài)多態(tài)
動態(tài)多態(tài)實現(xiàn):
如何實現(xiàn)動態(tài)多態(tài)呢?有什么作用?使用場景又是什么?
多態(tài)是當(dāng)不同繼承關(guān)系的類對象去調(diào)用一個函數(shù)而產(chǎn)生的不同的行為
實現(xiàn)動態(tài)多態(tài)的條件
- 類對象要調(diào)用虛函數(shù),且派生類中必須要包含基類虛函數(shù)的重寫
- 通過基類的引用/指針去調(diào)用虛函數(shù)??
引入:
繼承會使子類都含有父類的數(shù)據(jù),而每一個子類都對這份數(shù)據(jù)進(jìn)行重寫,而如果想創(chuàng)建一個函數(shù),使其可以操縱父類所有派生的子類?(即使以后還有派生的子類,也可以操縱)
該怎么做呢?
其實最重要的是函數(shù)的參數(shù)
參數(shù)設(shè)定其實需要找到這幾個派生類的共性——》它們都有父類的數(shù)據(jù)
因此如果要實現(xiàn)這樣的一個函數(shù),那么就需要其參數(shù)為父類的指針或是引用,而要操縱所有子類,這就需要用父類指針來保存子類的空間地址???
而用父類指針保存子類地址會造成問題:
代碼示例:
#include<iostream>
using namespace std;
class A
{
public:
void say()
{
cout << "A會說話了" << endl;
}
};
class B : public A
{
public:
void say()
{
cout << "A類里的B會說話了" << endl;
}
};
class C :public A
{
public:
void say()
{
cout << "A類里的C會說話了" << endl;
}
};
void test1(A*a)
{
a->say();//輸出的都是 A會說話了
}
int main()
{
A* a=new B;
test1(a);
A* b = new C;
test1(b);
return 0;
}
我們發(fā)現(xiàn)本身要通過父類的指針來完成子類的重定義函數(shù),但是實際上調(diào)用的都是父類的函數(shù),為什么呢?
因為指針指向的地址是由指針指向類型決定的,A*a,這個式子就決定了它要指向父類,而子類中繼承了父類的數(shù)據(jù),因此a指向子類中的父類,實現(xiàn)的也是父類的函數(shù)
如何解決這一個問題呢??
用虛函數(shù)
那什么是虛函數(shù)?
被關(guān)鍵字virtual修飾的類成員函數(shù),而且子類中要重寫虛函數(shù)(可加virtual也可不加)
虛函數(shù)的重寫?
虛函數(shù)的重寫(又可以叫做覆蓋)??
派生類中有一個跟基類完全相同的虛函數(shù)(這里指它們的返回值類型、函數(shù)名字、參數(shù)列表完全相同),則稱子類的虛函數(shù)重寫了基類的虛函數(shù),而其中的重寫內(nèi)容可以做適當(dāng)改變,來實現(xiàn)多態(tài)
多態(tài)實現(xiàn)代碼示例:
#include<iostream>
using namespace std;
class A
{
public:
virtual void say()
{
cout << "A會說話了" << endl;
}
};
class B : public A
{
public:
void say()
{
cout << "A類里的B會說話了" << endl;
}
};
void test1(A*a)
{
a->say();//輸出的是A類里的B會說話了
}
int main()
{
A* a=new B;
test1(a);
return 0;
}
這樣父類指針就可以調(diào)用子類的函數(shù)了,解決了父類指針指向子類地址的一個問題?
那為什么變成虛函數(shù)后,父類指針可以調(diào)用子類中的子類函數(shù)而非父類呢?
虛函數(shù)的動態(tài)綁定機(jī)制(面試經(jīng)常問的)
當(dāng)一個類中的函數(shù)變?yōu)樘摵瘮?shù)之后,會產(chǎn)生虛函數(shù)指針(vfptr),虛函數(shù)指向虛函數(shù)表(vftable),而如果這個類沒有被繼承的話——》虛函數(shù)表保存的是這個虛函數(shù)的入口地址
如果被繼承了,子類會把父類中的虛函數(shù)指針給繼承過來,但是這時候這個虛函數(shù)表里的內(nèi)容就發(fā)生變化了,它里面的是子類重寫的地址
如圖本質(zhì)上A*a指針還是指向父類地址,但是由于去調(diào)用時發(fā)現(xiàn)它是一個虛函數(shù)指針,而這個指針指向的虛函數(shù)表是重寫的say函數(shù)地址,因此調(diào)用的是子類函數(shù)。
?重載、重定義、重寫的區(qū)別
重載:同一作用城,同名函教,參數(shù)的順序,個數(shù),類型不同都可以重載。函數(shù)的返回值類型不能作為重載條件(函數(shù)重載,運(yùn)算行重載)
重定義:有繼承,子類重定義父親的同名函數(shù)(非虛函數(shù)),參數(shù)順序,個數(shù),類型可以不同,子類的同名函數(shù)會屏蔽父類的所有同名函數(shù)(可以通過作用域解決)
重與(覆蓋):有繼承,子類重寫父類的虛函數(shù)。返回值類型,函數(shù)名,參數(shù)順序,個數(shù),類型都必須一致。?
而當(dāng)這樣寫后,代碼只進(jìn)行子類函數(shù)調(diào)用,此時的父類虛函數(shù)就沒有什么用了,那么我們可以不可以找到一種方式去省略掉父類虛函數(shù)的內(nèi)容——可以,用純虛函數(shù),下面就來介紹什么是純虛函數(shù)?
純虛函數(shù)
在多態(tài)中,通常父類中虛函數(shù)的實現(xiàn)是毫無意義的,主要都是調(diào)用子類重寫的內(nèi)容
因此可以將虛函數(shù)改為純虛函數(shù)純虛函數(shù)是指在虛函數(shù)上加上=0則稱為虛函數(shù)
例如:
virtual void say ()=0;
而當(dāng)一個類中由純虛函數(shù)后這個類就成了——抽象類???
抽象類:
抽象類是指類中含有純虛函數(shù)的類,抽象類又稱為接口類,抽象類不能實例化對象(因為它沒有函數(shù)體,怎么調(diào)用?)
抽象類特點:
無法實例化對象
子類必須重寫抽象類中的所有純虛函數(shù),否則也屬于抽象類?
其實抽象類主要目的是設(shè)計類的接口
也就是說只有重寫純虛函數(shù),派生類才能實例化出對象。純虛函數(shù)規(guī)范了派生類必須重寫,其實這里的純虛函數(shù)存在接口繼承
那什么是接口繼承呢?
簡單來說虛函數(shù)的繼承——接口繼承,派生類并沒有繼承基類函數(shù),而是基類虛函數(shù)的接口,這是一個實現(xiàn)多態(tài)的方式,可以達(dá)到重寫的目的,如果不要求多態(tài)實現(xiàn),函數(shù)盡量不要定義為虛函數(shù)
那正常的繼承叫做什么?
普通函數(shù)的繼承為——實現(xiàn)繼承,派生類它繼承了基類函數(shù),繼承的是函數(shù)的實現(xiàn),而非接口,派生類可以使用這個接口
多態(tài)原理(工具觀察):
前面已經(jīng)將結(jié)果多態(tài)如何通過虛函數(shù)實現(xiàn)的了,現(xiàn)在來使用工具觀察多態(tài)實現(xiàn)的虛函數(shù)表
?滿足多態(tài)以后的函數(shù)調(diào)用,并不是在編譯時確定的,而是運(yùn)行起來以后到對象中去找的。
而不滿足多態(tài)的函數(shù)調(diào)用是在編譯時確認(rèn)好的。
這里借助vs下的一個工具
輸入dir空格后出現(xiàn)目錄,然后直接輸入以下內(nèi)容,即可看代碼中的類
- cl(空格)/d1(空格)reportSingleClassLayout(類名)(空格)(文件名)(回車)? ? ?
例如在test.cpp文件中,A類的結(jié)構(gòu):
- cl /d1 reportSingleClassLayoutA test.cpp
最后就可以看見這個類的內(nèi)部情況。
?回顧:我們要達(dá)到多態(tài),有兩個條件,一個是虛函數(shù)覆蓋,一個是基類對象的指針或引用調(diào)用虛函數(shù)。
哎?有人發(fā)現(xiàn)沒,其實剛才多態(tài)實現(xiàn)里的代碼有個問題——new出來的空間一直沒有釋放,堆區(qū)空間沒釋放會發(fā)生內(nèi)存泄漏,其實這里就涉及到另一個知識了——虛析構(gòu)
這時可能會問,為什么非要用虛析構(gòu)呢?正常的析構(gòu)為什么不可以釋放掉代碼呢?這時假如你寫一個代碼,就會發(fā)現(xiàn)——父類的析構(gòu)函數(shù)正常調(diào)用,而子類的析構(gòu)函數(shù)無法調(diào)用,也就是我們沒辦法在子類析構(gòu)函數(shù)中去寫代碼,釋放掉子類的堆區(qū)數(shù)據(jù)。
做法就是在父類的析構(gòu)前加上一個很熟悉的關(guān)鍵字——virtual
這也就是下面要介紹的虛析構(gòu)
虛析構(gòu)
多態(tài)使用時,若子類中有屬性開辟到堆區(qū),那么父類指針在釋放時無法調(diào)用到子類的析構(gòu)代碼,父類在析構(gòu)的時候不會調(diào)用子類析構(gòu),如果子類有堆區(qū)屬性,則會出現(xiàn)內(nèi)存泄漏
則需要將父類中的析構(gòu)函數(shù)改為虛析構(gòu)
語法:
虛析構(gòu)語法:
virtua1 ~類名()
原理:通過父類指針來釋放子類空間
由于剛開始實現(xiàn)多態(tài)的時候,子類的空間是由父類指針儲存的,因此只能通過父類指針來釋放子類空間。
已知的是
構(gòu)造是:父類——》成員——》子類
析構(gòu)是:子類——》成員——父類
析構(gòu)函數(shù)本身也是個成員函數(shù),虛析構(gòu),產(chǎn)生虛函數(shù)指針,指向虛函數(shù)表:包含這個類的析構(gòu)函數(shù)?
純虛析構(gòu)
特點:
純虛析構(gòu)的本質(zhì):是析構(gòu)函數(shù),完成各個類的回收工作。
必須為純虛析構(gòu)函數(shù)提供一個函數(shù)體
而且純虛析構(gòu)函數(shù)必須在類外實現(xiàn)含有純虛析構(gòu)的類也是抽象類
語法:
virtual ~類名()=0;
在類外:類名::~類名()?
純虛析構(gòu)和虛析構(gòu)的共性與區(qū)別:
虛析構(gòu)和純虛析構(gòu)共性:
可以解決父類指針釋放子類對象
都需要有具體的函數(shù)實現(xiàn)
虛析構(gòu)和純虛析構(gòu)區(qū)別:如果是純虛析構(gòu),該類屬于抽象類,且無法實例化對象,需要在類外實現(xiàn)
總結(jié):
- 1.虛析構(gòu)或純虛析構(gòu)就是用來解決通過父類指針釋放子類對象
- 2.如果子類中沒有堆區(qū)數(shù)據(jù),可以不寫為虛析構(gòu)或純虛析構(gòu)
- 3.擁有純虛析構(gòu)函數(shù)的類也屬于抽象類
總結(jié):
c++的入門核心內(nèi)容基本介紹完畢,整理的有關(guān)c++的文件操作以及內(nèi)存分區(qū)放在下面了,可以按需觀看
c++文件操作-CSDN博客
c++內(nèi)存的四大分區(qū)詳解-CSDN博客文章來源:http://www.zghlxwxcb.cn/news/detail-833458.html
下一階段是對于c++的模板,歡迎點贊收藏關(guān)注主頁專欄o(* ̄▽ ̄*)ブ文章來源地址http://www.zghlxwxcb.cn/news/detail-833458.html
到了這里,關(guān)于c++入門學(xué)習(xí)⑦——繼承和多態(tài)(超級詳細(xì)版)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!