為什麼使用虛函式?什麼是虛函式?虛函式是為了解決什麼問題?
物件導向的三大特徵:
封裝繼承
多型 1.普通虛函式
2.虛析構函式
3.純虛函式
4.抽象類
5.介面類
6.隱藏 vs 覆蓋
7.隱藏與覆蓋之間的關係
8.早繫結和晚繫結
9.虛函式表
靜態多型 vs 動態多型[-:>靜態多型也叫做早繫結
class rect //矩形類
;
如上面的**,他們函式名相同,引數個數不同,一看就是互為過載的兩個函式
int main()
程式在編譯階段根據引數個數確定呼叫哪個函式。這種情況叫做靜態多型(早繫結)
[-:>動態多型也叫做晚繫結
比如計算面積 當給圓形計算面積時使用圓形面積的計算公式,給矩形計算面積時使用矩形面積的計算公式。也就是說有乙個計算面積的形狀基類,圓形和矩形類派生自形狀類,圓形與矩形的類各有自己的計算面積的方法。可見動態多型是以封裝和繼承為基礎的。
class shape//形狀類
如果列印結果的話,以上程式結果會列印兩行"calcarea",因為呼叫到的都是父類的calcarea函式,並不是我們想要的那樣去分別呼叫各自的計算面積的函式。如果要想實現動態多型則必須使用虛函式
關鍵字 virtual ->虛函式
用virtual去修飾成員函式使其成為虛函式
所以以上函式的修改部分如下
class shape
//虛函式
.... //其他部分
private:
....
};....
class circle:public shape
;....
class rect:public shape
;....
這樣就可以達到預期的結果了
多型中存在的問題
[-:>記憶體洩漏,乙個很嚴重的問題
例如上面的程式中,如果在圓形的類中定義乙個圓心的座標,並且座標是在堆中申請的記憶體,則在mian函式中通過父類指標操作子類物件的成員函式的時候是沒有問題的,可是在銷毀物件記憶體的時候則只是執行了父類的析構函式,子類的析構函式卻沒有執行,這會導致記憶體洩漏。部分**如下(想去借助父類指標去銷毀子類物件的時候去不能去銷毀子類物件)
如果delete後邊跟父類的指標則只會執行父類的析構函式,如果delete後面跟的是子類的指標,那麼它即會執行子類的析構函式,也會執行父類的析構函式
class circle:public shape
;circle::circle(int x,int y,double r)
circle::~circle()
....
int main()
可見我們必須要去解決這個問題,不解決這個問題當使用的時候都會造成記憶體洩漏。面對這種情況則需要引入虛析構函式
虛析構函式
關鍵字 virtual ->析構函式
之前是使用virtual去修飾成員函式,這裡使用virtual去修飾析構函式,部分**如下
class shape
;class circle:public shape
;....
這樣父類指標指向的是哪個物件,哪個物件的建構函式就會先執行,然後執行父類的建構函式。銷毀的時候子類的析構函式也會執行。
virtual關鍵字可以修飾普通的成員函式,也可以修飾析構函式,但並不是沒有限制
virtual在函式中的使用限制
虛函式實現原理
【:-》首先:什麼是函式指標?
指標指向物件稱為物件指標,指標除了指向物件還可以指向函式,函式的本質就是一段二進位制**,我們可以通過指標指向這段**的開頭,計算機就會從這個開頭一直往下執行,直到函式結束,並且通過指令返回回來。函式的指標與普通的指標本質上是一樣的,也是由四個基本的記憶體單元組成,儲存著記憶體的位址,這個位址就是函式的首位址。
【:-》多型的實現原理
虛函式表指標:類中除了定義的函式成員,還有乙個成員是虛函式表指標(佔四個基本記憶體單元),這個指標指向乙個虛函式表的起始位置,這個表會與類的定義同時出現,這個表存放著該類的虛函式指標,呼叫的時候可以找到該類的虛函式表指標,通過虛函式表指標找到虛函式表,通過虛函式表的偏移找到函式的入口位址,從而找到要使用的虛函式。
當例項化乙個該類的子類物件的時候,(如果)該類的子類並沒有定義虛函式,但是卻從父類中繼承了虛函式,所以在例項化該類子類物件的時候也會產生乙個虛函式表,這個虛函式表是子類的虛函式表,但是記錄的子類的虛函式位址卻是與父類的是一樣的。所以通過子類物件的虛函式表指標找到自己的虛函式表,在自己的虛函式表找到的要執行的函式指標也是父類的相應函式入口的位址。
如果我們在子類中定義了從父類繼承來的虛函式,對於父類來說情況是不變的,對於子類來說它的虛函式表與之前的虛函式表是一樣的,但是此時子類定義了自己的(從父類那繼承來的)相應函式,所以它的虛函式表當中管於這個函式的指標就會覆蓋掉原有的指向父類函式的指標的值,換句話說就是指向了自己定義的相應函式,這樣如果用父類的指標,指向子類的物件,就會通過子類物件當中的虛函式表指標找到子類的虛函式表,從而通過子類的虛函式表找到子類的相應虛函式位址,而此時的位址已經是該函式自己定義的虛函式入口位址,而不是父類的相應虛函式入口位址,所以執行的將會是子類當中的虛函式。這就是多型的原理。
函式的覆蓋和隱藏
父類和子類出現同名函式稱為隱藏。
父類和子類出現同名虛函式稱為覆蓋
虛析構函式的實現原理
[:->虛析構函式的特點:
[:->理論前提:
原理:如果父類當中定義了虛析構函式,那麼父類的虛函式表當中就會有乙個父類的虛析構函式的入口指標,指向的是父類的虛析構函式,子類虛函式表當中也會產生乙個子類的虛析構函式的入口指標,指向的是子類的虛析構函式,這個時候使用父類的指標指向子類的物件,delete接父類指標,就會通過指向的子類的物件找到子類的虛函式表指標,從而找到虛函式表,再虛函式表中找到子類的虛析構函式,從而使得子類的析構函式得以執行,子類的析構函式執行之後系統會自動執行父類的虛析構函式。這個是虛析構函式的實現原理。
純虛函式:
純虛函式的定義
class shape
virtual double calcperimeter()=0;//純虛函式
....
};
純虛函式沒有函式體,同時在定義的時候函式名後面要加「=0」。
純虛函式的實現原理:
在虛函式原理的基礎上,虛函式表中,虛函式的位址是乙個有意義的值,如果是純虛函式就實實在在的寫乙個0。
含有純虛函式的類被稱為抽象類
含有純虛函式的類被稱為抽象類,比如上面**中的類就是乙個抽象類,包含乙個計算周長的純虛函式。哪怕只有乙個純虛函式,那麼這個類也是乙個抽象類,純虛函式沒有函式體,所以抽象類不允許例項化物件,抽象類的子類也可以是乙個抽象類。抽象類子類只有把抽象類當中的所有的純虛函式都做了實現才可以例項化物件。
對於抽象的類來說,我們往往不希望它能例項化,因為例項化之後也沒什麼用,而對於一些具體的類來說,我們要求必須實現那些要求(純虛函式),使之成為有具體動作的類。
近含有純虛函式的類稱為介面類
如果在抽象類當中僅含有純虛函式而不含其他任何東西,我們稱之為介面類。
沒有任何資料成員
僅有成員函式
成員函式都是純虛函式
class shape
;
實際的工作中介面類更多的表達一種能力或協議
比如
class flyable//會飛
;class bird:public flyable
virtual void land()
private:
....
};void flymatch(flyable *a,flyable *b)//飛行比賽
//要求傳入乙個會飛物件的指標,此時鳥類的物件指標可以傳入進來
例如上面的**,定義乙個會飛的介面,凡是實現這個介面的都是會飛的,飛行比賽要求會飛的來參加,鳥實現了會飛的介面,所以鳥可以參加飛行比賽,如果複雜點定義乙個能夠射擊的介面,那麼實現射擊介面的類就可以參加戰爭之類需要會射擊的物件,有乙個戰鬥機類通過多繼承實現會飛的介面和射擊的介面還可以參加空中作戰的函式呢
文章**:
c 深入理解虛函式
物件導向的三大特徵 相同物件收到不同訊息或不同物件收到相同訊息時產生的不同的動作。ifndef rect h define rect h include include using namespace std class rect 矩形類 endif shape h 如上面的 他們函式名相同,引數個...
C 深入理解虛函式
c 深入理解虛函式 1 在基類用virtual宣告成員函式為虛函式。這樣就可以在派生類中重新定義此函式,為它賦予新的功能,並能方便被呼叫。在類外定義虛函式時,不必在定義virtual 2 在派生類中重新定義此函式,要求函式名,函式型別,函式引數個數和型別全部與基類的虛函式相同,並根據派 生類的需要重...
深入理解虛函式
在c 中,在基類中被宣告為virtual並在在乙個或多個派生類中被重新定義的成員函式就是虛函式。基本格式如下 virtual return type func name arg 我們可以通過指向派生類的基類指標或引用來呼叫派生類中同名覆蓋的成員函式。如下 class a 究竟虛函式底層是如何實現多型...