成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

python多態(tài)和虛函數(shù) python純虛函數(shù)

虛函數(shù)有什么作用啊?和多態(tài)性有什么關(guān)系?

虛函數(shù)最大的好處是可以保持驅(qū)動程序的變量數(shù)據(jù)在增加派生類后可以不發(fā)生修改,已經(jīng)統(tǒng)一程序接口,如:

站在用戶的角度思考問題,與客戶深入溝通,找到陳倉網(wǎng)站設(shè)計與陳倉網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:做網(wǎng)站、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、空間域名、網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋陳倉地區(qū)。

1.

class B

{

public:

virtual void Run()=0;

};

class A : public B

{

public:

virtual void Run(){ cout"run A"endl; }

};

class C: public B

{

public:

virtual void Run(){ cout"run B"endl; }

};

現(xiàn)在有一個驅(qū)動模塊,如類 Drv

class Drv

{

public:

void Execute()

{

b-Run();

}

private:

B *b;

};

即使以后派生類擴展了,這個驅(qū)動程序的數(shù)據(jù)程序,已經(jīng)主要函數(shù)基本不做修改,使得出錯率降低。如果不用虛函數(shù),可能的代碼為:

class Drv

{

public:

void Execute()

{

switch(a)

{

case 0: a.Run(); break;

case 1: c.Run(); break;

//以后可能還要增加其它派生類對象

}

}

private:

int sel

A a;

B b;

//以后可能還要增加其它派生類對象

};

顯然每增加一個派生類,就要對上面做多處修改?。。?/p>

2. 通用接口,如:

void Execute(B b)

{

b.Run();

}

這樣可以使用如:

A a;

Execute(a);

C c;

Execute(c);

使得程序降低,否則需要定義給每個類為參數(shù)的函數(shù),如:

void Execute(A a)

{

a.Run();

}

void Execute(C c)

{

c.Run();

}

上面的兩個例子可以看到虛函數(shù)的好處??!

Python中的多態(tài)?

times函數(shù)為例談?wù)劧鄳B(tài),就像我們看到的那樣,times函數(shù)中表達式×*y的意義完全取決于x和y的對象類型,同樣的函數(shù),在一個實例下執(zhí)行的是乘法,在另一個實例下執(zhí)行的卻是重復(fù)。Python把對某一對象在某種語法下的合理性交給那個對象自身來判斷。實際上,*作為一個分派機制,將執(zhí)行的控制權(quán)移交給被處理的對象。

這種依賴類型的行為稱為多態(tài),其含義就是一個操作的意義取決于被操作對象的類型。因為Python是動態(tài)類型語言,所以多態(tài)在Python中隨處可見。事實上,在 Python中所有操作都是多態(tài)的操作:print、index、*運算符,以及更多。這實際上是有意而為的,并且從很大程度上算作是這門語言簡潔性和靈活性的一個表現(xiàn)。例如,函數(shù)可以自動地應(yīng)用到所有類別的對象上。只要對象支持所預(yù)期的接口(也稱為協(xié)議),函數(shù)就能處理它們。也就是說,如果傳給函數(shù)的對象支持預(yù)期的方法和表達式運算符,那么它們對函數(shù)的邏輯來說就是有著即插即用的兼容性。

即使是簡單的times 函數(shù),對任意兩個支持*的對象都可以執(zhí)行,無論它是哪種類型,也不管它是何時編寫的。這個函數(shù)對于數(shù)字來說是有效的(執(zhí)行乘法),或者一個字符串和一個數(shù)字(執(zhí)行重復(fù)),或者任意其他支持?jǐn)U展接口的兼容對象——甚至是我們尚未編寫過的基于類的對象。

python 多態(tài) 協(xié)議詳解

接口(python 中的協(xié)議)的多種不同的實現(xiàn)方式即為多態(tài)。多態(tài)的作用,就是為了類在繼承和派生的時候,保證使用“家譜”中任一類的實例的某一屬性時的正確調(diào)用。

可以看到,在上面的代碼中,只要實現(xiàn)了 Dock 類中的 swimming 和 Walk 方法,那么這個類就可以被叫做 Dock 類

應(yīng)用場景 如: for 循環(huán), 在python 中 for 循環(huán)只能用于可迭代對象, 那么, 我自己定義的類實現(xiàn)了 __iter__協(xié)議(接口),這個實例類就是一個可迭代對象,可以被for 循環(huán)使用

python 中定義協(xié)議類協(xié)議使用 @abstractmethod 裝飾器,@abstractmethod 裝飾過的類是不能進行初始化的,相對于c++中的純虛函數(shù)類

這個類只能當(dāng)做協(xié)議(接口)類

虛函數(shù)和多態(tài)如何理解?

多態(tài)的實現(xiàn)主要分為靜態(tài)多態(tài)和動態(tài)多態(tài),靜態(tài)多態(tài)主要是重載,在編譯的時候就已經(jīng)確定;動態(tài)多態(tài)是用虛函數(shù)機制實現(xiàn)的,在運行期間動態(tài)綁定。

舉個例子:一個父類類型的指針指向一個子類對象時候,使用父類的指針去調(diào)用子類中重寫了的父類中的虛函數(shù)的時候,會調(diào)用子類重寫過后的函數(shù),在父類中聲明為加了virtual關(guān)鍵字的函數(shù),在子類中重寫時候不需要加virtual也是虛函數(shù)。

虛函數(shù)的作用是什么?有哪些用處?何處體現(xiàn)多態(tài)?

虛函數(shù)聯(lián)系到多態(tài),多態(tài)聯(lián)系到繼承。所以本文中都是在繼承層次上做文章。沒了繼承,什么都沒得談。

下面是對C++的虛函數(shù)這玩意兒的理解。

一, 什么是虛函數(shù)(如果不知道虛函數(shù)為何物,但有急切的想知道,那你就應(yīng)該從這里開始)

簡單地說,那些被virtual關(guān)鍵字修飾的成員函數(shù),就是虛函數(shù)。虛函數(shù)的作用,用專業(yè)術(shù)語來解釋就是實現(xiàn)多態(tài)性(Polymorphism),多態(tài)性是將接口與實現(xiàn)進行分離;用形象的語言來解釋就是實現(xiàn)以共同的方法,但因個體差異而采用不同的策略。下面來看一段簡單的代碼

class A{

public:

void print(){ cout”This is A”endl;}

};

class B:public A{

public:

void print(){ cout”This is B”endl;}

};

int main(){ //為了在以后便于區(qū)分,我這段main()代碼叫做main1

A a;

B b;

a.print();

b.print();

}

通過class A和class B的print()這個接口,可以看出這兩個class因個體的差異而采用了不同的策略,輸出的結(jié)果也是我們預(yù)料中的,分別是This is A和This is B。但這是否真正做到了多態(tài)性呢?No,多態(tài)還有個關(guān)鍵之處就是一切用指向基類的指針或引用來操作對象。那現(xiàn)在就把main()處的代碼改一改。

int main(){ //main2

A a;

B b;

A* p1=a;

A* p2=b;

p1-print();

p2-print();

}

運行一下看看結(jié)果,喲呵,驀然回首,結(jié)果卻是兩個This is A。問題來了,p2明明指向的是class B的對象但卻是調(diào)用的class A的print()函數(shù),這不是我們所期望的結(jié)果,那么解決這個問題就需要用到虛函數(shù)

class A{

public:

virtual void print(){ cout”This is A”endl;} //現(xiàn)在成了虛函數(shù)了

};

class B:public A{

public:

void print(){ cout”This is B”endl;} //這里需要在前面加上關(guān)鍵字virtual嗎?

};

毫無疑問,class A的成員函數(shù)print()已經(jīng)成了虛函數(shù),那么class B的print()成了虛函數(shù)了嗎?回答是Yes,我們只需在把基類的成員函數(shù)設(shè)為virtual,其派生類的相應(yīng)的函數(shù)也會自動變?yōu)樘摵瘮?shù)。所以,class B的print()也成了虛函數(shù)。那么對于在派生類的相應(yīng)函數(shù)前是否需要用virtual關(guān)鍵字修飾,那就是你自己的問題了。

現(xiàn)在重新運行main2的代碼,這樣輸出的結(jié)果就是This is A和This is B了。

現(xiàn)在來消化一下,我作個簡單的總結(jié),指向基類的指針在操作它的多態(tài)類對象時,會根據(jù)不同的類對象,調(diào)用其相應(yīng)的函數(shù),這個函數(shù)就是虛函數(shù)。

二, 虛函數(shù)是如何做到的(如果你沒有看過《Inside The C++ Object Model》這本書,但又急切想知道,那你就應(yīng)該從這里開始)

虛函數(shù)是如何做到因?qū)ο蟮牟煌{(diào)用其相應(yīng)的函數(shù)的呢?現(xiàn)在我們就來剖析虛函數(shù)。我們先定義兩個類

class A{ //虛函數(shù)示例代碼

public:

virtual void fun(){cout1endl;}

virtual void fun2(){cout2endl;}

};

class B:public A{

public:

void fun(){cout3endl;}

void fun2(){cout4endl;}

};

由于這兩個類中有虛函數(shù)存在,所以編譯器就會為他們兩個分別插入一段你不知道的數(shù)據(jù),并為他們分別創(chuàng)建一個表。那段數(shù)據(jù)叫做vptr指針,指向那個表。那個表叫做vtbl,每個類都有自己的vtbl,vtbl的作用就是保存自己類中虛函數(shù)的地址,我們可以把vtbl形象地看成一個數(shù)組,這個數(shù)組的每個元素存放的就是虛函數(shù)的地址,請看圖

通過上圖,可以看到這兩個vtbl分別為class A和class B服務(wù)?,F(xiàn)在有了這個模型之后,我們來分析下面的代碼

A *p=new A;

p-fun();

毫無疑問,調(diào)用了A::fun(),但是A::fun()是如何被調(diào)用的呢?它像普通函數(shù)那樣直接跳轉(zhuǎn)到函數(shù)的代碼處嗎?No,其實是這樣的,首先是取出vptr的值,這個值就是vtbl的地址,再根據(jù)這個值來到vtbl這里,由于調(diào)用的函數(shù)A::fun()是第一個虛函數(shù),所以取出vtbl第一個slot里的值,這個值就是A::fun()的地址了,最后調(diào)用這個函數(shù)?,F(xiàn)在我們可以看出來了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里裝著對應(yīng)類的虛函數(shù)地址,所以這樣虛函數(shù)就可以完成它的任務(wù)。

而對于class A和class B來說,他們的vptr指針存放在何處呢?其實這個指針就放在他們各自的實例對象里。由于class A和class B都沒有數(shù)據(jù)成員,所以他們的實例對象里就只有一個vptr指針。通過上面的分析,現(xiàn)在我們來實作一段代碼,來描述這個帶有虛函數(shù)的類的簡單模型。

#includeiostream

using namespace std;

//將上面“虛函數(shù)示例代碼”添加在這里

int main(){

void (*fun)(A*);

A *p=new B;

long lVptrAddr;

memcpy(lVptrAddr,p,4);

memcpy(fun,reinterpret_castlong*(lVptrAddr),4);

fun(p);

delete p;

system("pause");

}

用VC或Dev-C++編譯運行一下,看看結(jié)果是不是輸出3,如果不是,那么太陽明天肯定是從西邊出來。現(xiàn)在一步一步開始分析

void (*fun)(A*); 這段定義了一個函數(shù)指針名字叫做fun,而且有一個A*類型的參數(shù),這個函數(shù)指針待會兒用來保存從vtbl里取出的函數(shù)地址

A* p=new B; new B是向內(nèi)存(內(nèi)存分5個區(qū):全局名字空間,自由存儲區(qū),寄存器,代碼空間,棧)自由存儲區(qū)申請一個內(nèi)存單元的地址然后隱式地保存在一個指針中.然后把這個地址附值給A類型的指針P.

.

long lVptrAddr; 這個long類型的變量待會兒用來保存vptr的值

memcpy(lVptrAddr,p,4); 前面說了,他們的實例對象里只有vptr指針,所以我們就放心大膽地把p所指的4bytes內(nèi)存里的東西復(fù)制到lVptrAddr中,所以復(fù)制出來的4bytes內(nèi)容就是vptr的值,即vtbl的地址

現(xiàn)在有了vtbl的地址了,那么我們現(xiàn)在就取出vtbl第一個slot里的內(nèi)容

memcpy(fun,reinterpret_castlong*(lVptrAddr),4); 取出vtbl第一個slot里的內(nèi)容,并存放在函數(shù)指針fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指針,所以我們要把它先轉(zhuǎn)變成指針類型

fun(p); 這里就調(diào)用了剛才取出的函數(shù)地址里的函數(shù),也就是調(diào)用了B::fun()這個函數(shù),也許你發(fā)現(xiàn)了為什么會有參數(shù)p,其實類成員函數(shù)調(diào)用時,會有個this指針,這個p就是那個this指針,只是在一般的調(diào)用中編譯器自動幫你處理了而已,而在這里則需要自己處理。

delete p;和system("pause"); 這個我不太了解,算了,不解釋這個了

如果調(diào)用B::fun2()怎么辦?那就取出vtbl的第二個slot里的值就行了

memcpy(fun,reinterpret_castlong*(lVptrAddr+4),4); 為什么是加4呢?因為一個指針的長度是4bytes,所以加4?;蛘適emcpy(fun,reinterpret_castlong*(lVptrAddr)+1,4); 這更符合數(shù)組的用法,因為lVptrAddr被轉(zhuǎn)成了long*型別,所以+1就是往后移sizeof(long)的長度

三, 以一段代碼開始

#includeiostream

using namespace std;

class A{ //虛函數(shù)示例代碼2

public:

virtual void fun(){ cout"A::fun"endl;}

virtual void fun2(){cout"A::fun2"endl;}

};

class B:public A{

public:

void fun(){ cout"B::fun"endl;}

void fun2(){ cout"B::fun2"endl;}

}; //end//虛函數(shù)示例代碼2

int main(){

void (A::*fun)(); //定義一個函數(shù)指針

A *p=new B;

fun=A::fun;

(p-*fun)();

fun = A::fun2;

(p-*fun)();

delete p;

system("pause");

}

你能估算出輸出結(jié)果嗎?如果你估算出的結(jié)果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了。其實真正的結(jié)果是B::fun和B::fun2,如果你想不通就接著往下看。給個提示,A::fun和A::fun2是真正獲得了虛函數(shù)的地址嗎?

首先我們回到第二部分,通過段實作代碼,得到一個“通用”的獲得虛函數(shù)地址的方法

#includeiostream

using namespace std;

//將上面“虛函數(shù)示例代碼2”添加在這里

void CallVirtualFun(void* pThis,int index=0){

void (*funptr)(void*);

long lVptrAddr;

memcpy(lVptrAddr,pThis,4);

memcpy(funptr,reinterpret_castlong*(lVptrAddr)+index,4);

funptr(pThis); //調(diào)用

}

int main(){

A* p=new B;

CallVirtualFun(p); //調(diào)用虛函數(shù)p-fun()

CallVirtualFun(p,1);//調(diào)用虛函數(shù)p-fun2()

system("pause");

}

現(xiàn)在我們擁有一個“通用”的CallVirtualFun方法。

這個通用方法和第三部分開始處的代碼有何聯(lián)系呢?聯(lián)系很大。由于A::fun()和A::fun2()是虛函數(shù),所以A::fun和A::fun2獲得的不是函數(shù)的地址,而是一段間接獲得虛函數(shù)地址的一段代碼的地址,我們形象地把這段代碼看作那段CallVirtualFun。編譯器在編譯時,會提供類似于CallVirtualFun這樣的代碼,當(dāng)你調(diào)用虛函數(shù)時,其實就是先調(diào)用的那段類似CallVirtualFun的代碼,通過這段代碼,獲得虛函數(shù)地址后,最后調(diào)用虛函數(shù),這樣就真正保證了多態(tài)性。同時大家都說虛函數(shù)的效率低,其原因就是,在調(diào)用虛函數(shù)之前,還調(diào)用了獲得虛函數(shù)地址的代碼。

最后的說明:本文的代碼可以用VC6和Dev-C++4.9.8.0通過編譯,且運行無問題。其他的編譯器小弟不敢保證。其中,里面的類比方法只能看成模型,因為不同的編譯器的低層實現(xiàn)是不同的。例如this指針,Dev-C++的gcc就是通過壓棧,當(dāng)作參數(shù)傳遞,而VC的編譯器則通過取出地址保存在ecx中。所以這些類比方法不能當(dāng)作具體實現(xiàn)

網(wǎng)頁名稱:python多態(tài)和虛函數(shù) python純虛函數(shù)
本文鏈接:http://jinyejixie.com/article28/dodhsjp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、品牌網(wǎng)站建設(shè)、面包屑導(dǎo)航移動網(wǎng)站建設(shè)、手機網(wǎng)站建設(shè)、定制網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司
巴南区| 崇阳县| 西城区| 洛扎县| 永新县| 偏关县| 古交市| 印江| 桐庐县| 韶关市| 高安市| 固始县| 涡阳县| 白水县| 枣庄市| 准格尔旗| 漠河县| 德阳市| 潢川县| 香港| 黄陵县| 仪征市| 巴中市| 阿荣旗| 新兴县| 炉霍县| 衡水市| 东乌珠穆沁旗| 峡江县| 东兴市| 涞源县| 沭阳县| 淮北市| 岱山县| 郯城县| 铅山县| 白河县| 柞水县| 枣阳市| 监利县| 东安县|