C++中關(guān)于多態(tài)實(shí)現(xiàn)和使用方法
都說 C++ 是面向?qū)ο蟮恼Z言,其中的面向?qū)ο笾饕ㄈ糠郑豪^承,封裝,多態(tài)。繼承和封裝我們之前就簡(jiǎn)單介紹過,這里主要對(duì)多態(tài)的使用方法做一個(gè)簡(jiǎn)單說明。
賦值兼容
賦值兼容說的是在使用基類對(duì)象的地方可以使用公有繼承類的對(duì)象來代替。賦值兼容是一種默認(rèn)的行為,不需要進(jìn)行顯式轉(zhuǎn)換就能夠?qū)崿F(xiàn)。
就比如在派生類拷貝構(gòu)造函數(shù)的參數(shù)初始化列表中,我們會(huì)直接使用派生類對(duì)象作為基類拷貝構(gòu)造函數(shù)的參數(shù),而不會(huì)報(bào)錯(cuò),這就是賦值兼容的表現(xiàn)。賦值兼容主要表現(xiàn)在:
- 派生類的對(duì)象可以賦值給基類對(duì)象
- 派生類的對(duì)象可以初始化基類的引用
- 派生類對(duì)象的地址可以賦值給指向基類的指針
- 但,發(fā)生賦值兼容之后,只能使用從基類繼承的成員
實(shí)例
#include <iostream> ? using namespace std; ? class PERSON { public: ? ? PERSON(char *name_ = "***",char sex_ = '*') ? ? ? ? :name(name_),sex(sex_){} ? ? void display() ? ? { ? ? ? ? cout<<"The name is "<<name<<endl; ? ? ? ? cout<<"The sex is "<<sex<<endl; ? ? } protected: ? ? char *name; ? ? char sex; }; ? class STUDENT:public PERSON { public: ? ? STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100") ? ? ? ? :PERSON(name_,sex_),num(num_){} ? ? void display() ? ? { ? ? ? ? cout<<"The name is "<<name<<endl; ? ? ? ? cout<<"The sex is "<<sex<<endl; ? ? ? ? cout<<"The num is "<<num<<endl; ? ? } private: ? ? char *num; }; ? int main() { ? ? STUDENT st("zhangsan",'x',"100"); ? ? st.display(); ? ? PERSON per = st; ? ? per.display(); ? ? PERSON &per2 = st; ? ? per2.display(); ? ? PERSON *per3 = &st; ? ? per3->display(); ? ? ? return 0; }
結(jié)果為:
The name is zhangsan
The sex is x
The num is 100
The name is zhangsan
The sex is x
The name is zhangsan
The sex is x
The name is zhangsan
The sex is x
上邊的程序可以看出,基類對(duì)象,引用和指針都可以使用派生類對(duì)象或者指針進(jìn)行賦值,從而進(jìn)行訪問。
其實(shí)也可以將基類指針強(qiáng)制轉(zhuǎn)換為派生類指針,進(jìn)行訪問,但這種形式絕不是賦值兼容:
int main() { ? ? PERSON per("zhangsan",'x'); ? ? per.display(); ? ? ? STUDENT *st = static_cast<STUDENT *>(&per); ? ? st->display(); ? ? ? return 0; }
結(jié)果為:
The name is zhangsan
The sex is x
The name is zhangsan
The sex is x
The num is 夽@
上邊的程序中,是將基類的指針強(qiáng)制轉(zhuǎn)換派生類的指針,從而調(diào)用派生類的對(duì)象。
- 從實(shí)際上來說,該過程只是將以基類地址其實(shí)的一段內(nèi)存交給了派生類的指針,因?yàn)轭悓?duì)象只存儲(chǔ)數(shù)據(jù)成員,因此能夠?qū)?yīng)訪問到從基類繼承到的數(shù)據(jù)成員。
- 但同時(shí)不確定原來基類成員后邊的空間有什么東西,結(jié)果為出現(xiàn)部分亂碼。
- 如果將基類和派生類位置對(duì)調(diào)就是賦值兼容了。
多態(tài)
C++ 中的多態(tài)主要說的是,在面向?qū)ο笾?,接口的多種不同的實(shí)現(xiàn)方式。
靜多態(tài)
C++ 中的多態(tài)是接口多種不同的實(shí)現(xiàn)方式。而我們之前提到過的函數(shù)重載也是接口的多種不同的實(shí)現(xiàn)方式,因此也可以稱之為多態(tài),只是函數(shù)重載是在編譯階段通過 name mangling 實(shí)現(xiàn)的,所以叫做靜多態(tài)。
動(dòng)多態(tài)
而不在編譯階段而是在運(yùn)行階段決定的多態(tài)就稱為動(dòng)多態(tài)。動(dòng)多態(tài)的形成條件為:
- 父類中有虛函數(shù)
- 子類 override 父類中的的虛函數(shù)
- 通過已被子類對(duì)象賦值的父類指針或引用,調(diào)用公有接口
格式
class classname { ? ? virtual datatype func(argu); }
實(shí)例
#include <iostream> ? using namespace std; ? class PERSON { public: ? ? PERSON(char *name_ = "***",char sex_ = '*') ? ? ? ? :name(name_),sex(sex_){} ? ? virtual void display() ? ? { ? ? ? ? cout<<"The name is "<<name<<endl; ? ? ? ? cout<<"The sex is "<<sex<<endl; ? ? } protected: ? ? char *name; ? ? char sex; }; ? class STUDENT:public PERSON { public: ? ? STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100") ? ? ? ? :PERSON(name_,sex_),num(num_){} ? ? virtual void display() ? ? { ? ? ? ? cout<<"The name is "<<name<<endl; ? ? ? ? cout<<"The sex is "<<sex<<endl; ? ? ? ? cout<<"The num is "<<num<<endl; ? ? } protected: ? ? char *num; }; ? class POSTGRADUATE:public STUDENT { public: ? ? POSTGRADUATE(char *name_ = "***",char sex_ = '*',char *num_ = "***",char *job_ = "***") ? ? ? ? :STUDENT(name_,sex_,num_),job(job_){} ? ? virtual void display() ? ? { ? ? ? ? cout<<"The name is "<<name<<endl; ? ? ? ? cout<<"The sex is "<<sex<<endl; ? ? ? ? cout<<"The num is "<<num<<endl; ? ? ? ? cout<<"The job is "<<job<<endl; ? ? } protected: ? ? char *job; }; ? int main() { ? ? POSTGRADUATE po("zhsangsan",'x',"100","paper"); ? ? po.display(); ? ? ? PERSON *per = &po; ? ? per->display(); ? ? STUDENT *st = &po; ? ? st->display(); ? ? ? return 0; }
結(jié)果為:
The name is zhsangsan
The sex is x
The num is 100
The job is paper
The name is zhsangsan
The sex is x
The num is 100
The job is paper
The name is zhsangsan
The sex is x
The num is 100
The job is paper
在基類中聲明虛函數(shù)時(shí)需要使用 virtual 關(guān)鍵字,在類外實(shí)現(xiàn)虛函數(shù)時(shí),不用再加 virtual
在派生類中重新定義此函數(shù)的過程稱為 override,此過程要求函數(shù)的要素全都不能發(fā)生改變,包括函數(shù)名,返回值類型,形參個(gè)數(shù)和類型,只有函數(shù)體可以改變
當(dāng)基類中的函數(shù)成員被聲明為 virtual 時(shí),其派生類中完全相同的函數(shù)都會(huì)變?yōu)樘摵瘮?shù),原則上派生類中的虛函數(shù)不用使用 virtual 關(guān)鍵字,但是為了程序的可讀性,可以在派生類的對(duì)應(yīng)函數(shù)前加上 virtual
定義一個(gè)指向基類的指針,并使其指向其子類對(duì)象的地址,通過該指針調(diào)用虛函數(shù),此時(shí)調(diào)用的就是指針變量指向的對(duì)象
子類中 override 的函數(shù),可以為任意訪問類型
通過多態(tài)就避免了賦值兼容的問題
override
在虛函數(shù)的使用中,需要在派生類中 override 基類中的虛函數(shù),表明該函數(shù)是從基類 override 得到的,override 的含義表明:
- override 的函數(shù)要素全都不能發(fā)生改變
- 包括函數(shù)名,返回值類型,形參個(gè)數(shù)和類型
- 只有函數(shù)體可以改變
而有時(shí)為了可讀性,也為了防止編寫時(shí)出錯(cuò),可以通過在函數(shù)后添加 override 關(guān)鍵字表明這是 override 得到的。如上邊的例子中:
virtual void display() override
使用上邊的形式可以嚴(yán)格語法書寫。
純虛函數(shù)
對(duì)于一些抽象基類來說,我們并不需要在其中的虛函數(shù)中編寫什么語句,因此可以將之寫成純虛函數(shù)。
class classname { ? ? virtual datatype func(argu) = 0; }
如上例所示,可以將 STUDENT 中的 display 函數(shù)定義為純虛函數(shù):
virtual void display() = 0;
只是此時(shí)不能夠調(diào)用 STUDENT 中的該函數(shù)了。
對(duì)于純虛函數(shù)而言:
- 含有純虛函數(shù)的類,稱為抽象基類,不能夠創(chuàng)建該類對(duì)象,該類只能被繼承,提供公共接口
- 純虛函數(shù)的聲明形式就包含了聲明和實(shí)現(xiàn)
- 如果一個(gè)類中聲明了純虛函數(shù),而在派生類中沒有定義該函數(shù),則該虛函數(shù)在派生類中仍然是純虛函數(shù),派生類仍然為抽象基類,這意味著在第一次繼承的時(shí)候一定要定義該函數(shù)
- 從這個(gè)角度看,才算是虛函數(shù)的正確用法,直接用來聲明為純虛基類,從而被繼承
含有虛函數(shù)的析構(gòu)函數(shù)
含有虛函數(shù)的類,析構(gòu)函數(shù)也應(yīng)該聲明為虛函數(shù)。
#include <iostream> ? using namespace std; ? class PERSON { public: ? ? PERSON(char *name_ = "***",char sex_ = '*') ? ? ? ? :name(name_),sex(sex_){} ? ? virtual void display() ? ? { ? ? ? ? cout<<"The name is "<<name<<endl; ? ? ? ? cout<<"The sex is "<<sex<<endl; ? ? } ? ? ~PERSON(){cout<<"PERSON"<<endl;} protected: ? ? char *name; ? ? char sex; }; ? class STUDENT:public PERSON { public: ? ? STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100") ? ? ? ? :PERSON(name_,sex_),num(num_){} ? ? virtual void display() ? ? { ? ? ? ? cout<<"The name is "<<name<<endl; ? ? ? ? cout<<"The sex is "<<sex<<endl; ? ? ? ? cout<<"The num is "<<num<<endl; ? ? } ? ? ~STUDENT(){cout<<"STUDENT"<<endl;} protected: ? ? char *num; }; ? int main() { ? ? { ? ? STUDENT st("zhsangsan",'x',"100"); ? ? st.display(); ? ? } ? ? ? cout<<"****************"<<endl; ? ? ? PERSON *p = new STUDENT("zhsangsan",'x',"100"); ? ? p->display(); ? ? delete p; ? ? ? return 0; }
結(jié)果為:
The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON
****************
The name is zhsangsan
The sex is x
The num is 100
PERSON
此時(shí)如果將析構(gòu)函數(shù)聲明為 virtual:
virtual ~PERSON(){cout<<"PERSON"<<endl;}
結(jié)果為:
The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON
****************
The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON
可以看出,對(duì)于堆對(duì)象來說,含有虛函數(shù)的類對(duì)象析構(gòu)與棧對(duì)象析構(gòu)是有所差別的。為了防止這種情況出現(xiàn),最好是將含有虛函數(shù)的析構(gòu)函數(shù)聲明為 virtual。
注意事項(xiàng)
- 因?yàn)樘摵瘮?shù)是用在繼承中的,因此只有類成員函數(shù)才能聲明為虛函數(shù)
- 靜態(tài)成員函數(shù)不能是虛函數(shù)
- 內(nèi)聯(lián)函數(shù)不能是虛函數(shù)
- 構(gòu)造函數(shù)不能是虛函數(shù)
- 析構(gòu)函數(shù)可以是虛函數(shù)且通常聲明為虛函數(shù)
RTTI
(Run Time Type Identification,RTTI) 也叫運(yùn)行時(shí)類型信息,也是通過多態(tài)實(shí)現(xiàn)的。
typeid
typeid 返回包含操作數(shù)數(shù)據(jù)類型信息的 type_info 對(duì)象的一個(gè)引用,信息中包括數(shù)據(jù)類型的名稱。要使用 typeid,需要在函數(shù)中包含:
#include <typeinfo>
- type_info 重載了操作符 ==,!= 用來進(jìn)行比較
- 函數(shù) name() 返回類型名稱
- type_info 的拷貝和賦值都是私有的,因此不可拷貝和賦值
#include <iostream> #include <typeinfo> ? using namespace std; ? typedef void (*Func)(); ? class Base1 { }; ? class Base2 { public: ? ? virtual ~Base2(){} }; ? class Derive1:public Base1 { }; ? class Derive2:public Base2 { }; ? int main() { ? ? cout<<typeid(int).name()<<endl; ? ? cout<<typeid(double).name()<<endl; ? ? cout<<typeid(char *).name()<<endl; ? ? cout<<typeid(char **).name()<<endl; ? ? cout<<typeid(const char *).name()<<endl; ? ? cout<<typeid(const char * const ).name()<<endl; ? ? cout<<"********************"<<endl; ? ? ? cout<<typeid(Func).name()<<endl; ? ? cout<<typeid(Base1).name()<<endl; ? ? cout<<typeid(Base2).name()<<endl; ? ? cout<<typeid(Derive1).name()<<endl; ? ? cout<<typeid(Derive2).name()<<endl; ? ? cout<<"********************"<<endl; ? ? ? Derive1 d; ? ? Base1 &b = d; ? ? cout<<typeid(b).name()<<endl; ? ? cout<<typeid(d).name()<<endl; ? ? cout<<"********************"<<endl; ? ? ? Derive2 dd; ? ? Base2 &bb = dd; ? ? cout<<typeid(bb).name()<<endl; ? ? cout<<typeid(dd).name()<<endl; ? ? cout<<"********************"<<endl; ? ? ? Base1 *p = &d; ? ? cout<<typeid(p).name()<<endl; ? ? cout<<typeid(*p).name()<<endl; ? ? cout<<typeid(d).name()<<endl; ? ? cout<<boolalpha<<(typeid(*p)== typeid(d))<<endl; ? ? cout<<"********************"<<endl; ? ? ? Base2 *pp = ⅆ ? ? cout<<typeid(pp).name()<<endl; ? ? cout<<typeid(*pp).name()<<endl; ? ? cout<<typeid(dd).name()<<endl; ? ? cout<<boolalpha<<(typeid(*pp)== typeid(dd))<<endl; ? ? cout<<"********************"<<endl; ? ? ? return 0; }
結(jié)果為:
i
d
Pc
PPc
PKc
PKc
********************
PFvvE
5Base1
5Base2
7Derive1
7Derive2
********************
5Base1
7Derive1
********************
7Derive2
7Derive2
********************
P5Base1
5Base1
7Derive1
false
********************
P5Base2
7Derive2
7Derive2
true
********************
從上邊可以看出,在 typeid 涉及到虛函數(shù)時(shí),利用指針得到的結(jié)果就可能出現(xiàn)差別,因此在使用 typeid 時(shí)需要注意:
- 確保基類中至少定義了一個(gè)虛函數(shù)(虛析構(gòu)也可)
- 在涉及到虛函數(shù)時(shí),盡量不要將 typeid 應(yīng)用于指針,而是應(yīng)用于引用,或者解引用的指針
- typeid 是一個(gè)運(yùn)算符,而不是函數(shù)
- typeid 運(yùn)算符返回的 type_info 類型,其拷貝構(gòu)造函數(shù)和賦值運(yùn)算函數(shù)都聲明為 private,因此不能用于 stl 容器。也因此我們一般不直接保存 type_info,而是保存 type_info 的 name 信息
Notice how the type that typeid considers for pointers is the pointer type itself(both a and b are of type class Base *). However, when typeid is applied to objects(like *a and *b) typeid yields their dynamic type (i.e. the type of their most derived complete object).
If the type typeid evaluates is a pointer preceded by the dereference operator (*), and this pointer has a null value, typeid throws a bad_typeid exception.
typecast
在之前的文章中,我們簡(jiǎn)單介紹過 static_cast,reininterpreter_cast,const_cast 的用法,當(dāng)時(shí)還剩下一個(gè) dynamic_cast。
dynamic_cast 是一種運(yùn)行時(shí)的類型轉(zhuǎn)換方式,因此用于運(yùn)行時(shí)的轉(zhuǎn)換判斷。該轉(zhuǎn)換能夠檢查指針?biāo)赶虻念愋?,然后判斷這一類型與轉(zhuǎn)換的目標(biāo)類型是否相同,如果是返回對(duì)象地址,如果不是返回 NULL。
dynamic_cast 常用于多態(tài)繼承中,來判斷父類指針的真實(shí)指向。
#include <iostream> #include <typeinfo> ? using namespace std; ? class A { public: ? ? virtual ~A(){} }; ? class B:public A { }; ? class C:public A { }; ? class D { }; ? int main() { ? ? B b; ? ? A *pa = &b; ? ? ? B *pb = dynamic_cast<B*>(pa); //成功 ? ? cout<<pb<<endl; ? ? ? C *pc = dynamic_cast<C*>(pa); //成功 安全 ? ? cout<<pc<<endl; ? ? ? D *pd = dynamic_cast<D*>(pa); //成功 安全 ? ? cout<<pd<<endl; ? ? ? pb = static_cast<B*>(pa); //成功 ? ? cout<<pb<<endl; ? ? ? pc = static_cast<C*>(pa); //成功 不安全 ? ? cout<<pc<<endl; ? ? ? pb = reinterpret_cast<B*>(pa); //成功 不安全 ? ? cout<<pb<<endl; ? ? ? pc = reinterpret_cast<C*>(pa); //成功 不安全 ? ? cout<<pc<<endl; ? ? ? pd = reinterpret_cast<D*>(pa); //成功 不安全 ? ? cout<<pd<<endl; ? ? ? return 0; }
結(jié)果為:
0x61fe8c
0
0
0x61fe8c
0x61fe8c
0x61fe8c
0x61fe8c
0x61fe8c
在上述幾種類型轉(zhuǎn)換中,dynamic_cast 的轉(zhuǎn)換用法算是比較安全的,因?yàn)檫@種轉(zhuǎn)換方式是先比較再返回的,而 reininterpreter_cast 則是最不安全的,因?yàn)檫@種轉(zhuǎn)換方式不做類型檢查直接將源類型重新解釋為目標(biāo)類型,容易出錯(cuò)。
但 dynamic_cast 的目標(biāo)類型必須是類的指針或者引用。
多態(tài)實(shí)現(xiàn)
虛函數(shù)
之前介紹函數(shù)重載,也就是靜多態(tài)是通過 name mangling 實(shí)現(xiàn)的,而 C++ 的動(dòng)多態(tài)則是通過虛函數(shù)表(virtual table)實(shí)現(xiàn)的。這個(gè)表中主要是一個(gè)類的虛函數(shù)的地址表,這張表包含了繼承,override 的情況。在實(shí)際使用中,在含有虛函數(shù)的類對(duì)象中,該表會(huì)被分配到該對(duì)象的內(nèi)存中,用于指明實(shí)際所要調(diào)用的函數(shù)。
C++ 編譯器保證虛函數(shù)表的指針存在于實(shí)例對(duì)象的最前面,這表示實(shí)例對(duì)象的地址就是該虛函數(shù)表的位置,然后就可以遍歷其中的函數(shù)指針,進(jìn)行調(diào)用。
#include <iostream> #include <typeinfo> ? using namespace std; ? class Base { public: ? ? void f() { cout << "Base::f" << endl; } ? ? void g() { cout << "Base::g" << endl; } ? ? void h() { cout << "Base::h" << endl; } private: ? ? int data; }; ? int main() { ? ? Base b; ? ? cout<<"sizeof(Base) = "<<sizeof(Base)<<endl; ? ? cout<<"sizeof(b) = "<<sizeof(b)<<endl; ? ? ? return 0; }
結(jié)果為:
sizeof(Base) = 4
sizeof(b) = 4
如果基類中存在虛函數(shù),則為:
#include <iostream> #include <typeinfo> ? using namespace std; ? class Base { public: ? ? virtual void f() { cout << "Base::f" << endl; } ? ? virtual void g() { cout << "Base::g" << endl; } ? ? virtual void h() { cout << "Base::h" << endl; } private: ? ? int data; }; ? int main() { ? ? Base b; ? ? cout<<"sizeof(Base) = "<<sizeof(Base)<<endl; ? ? cout<<"sizeof(b) = "<<sizeof(b)<<endl; ? ? ? return 0; }
結(jié)果為:
sizeof(Base) = 8
sizeof(b) = 8
可以看出有虛函數(shù)的基類的大小會(huì)比沒有虛函數(shù)的基類大小多出一個(gè)指針的大小。這個(gè)多出來的指針就是虛函數(shù)表的位置。
#include <iostream> #include <typeinfo> ? using namespace std; ? class Base { public: ? ? virtual void f() { cout << "Base::f" << endl; } ? ? virtual void g() { cout << "Base::g" << endl; } ? ? virtual void h() { cout << "Base::h" << endl; } private: ? ? int data; }; ? typedef void (*FUNC)(void); ? int main() { ? ? Base b; ? ? cout<<"sizeof(Base) = "<<sizeof(Base)<<endl; ? ? cout<<"sizeof(b) = "<<sizeof(b)<<endl; ? ? ? cout<<&b<<endl; ? ? cout<<*((int **)*(int *)(&b)+0)<<endl; ? ? cout<<*((int **)*(int *)(&b)+1)<<endl; ? ? cout<<*((int **)*(int *)(&b)+2)<<endl; ? ? cout<<*((int **)*(int *)(&b)+3)<<endl; ? ? ? FUNC pf = NULL; ? ? pf = (FUNC)*((int **)*(int *)(&b)+0); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+1); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+2); ? ? pf(); ? ? ? return 0; }
結(jié)果為:
sizeof(Base) = 8
sizeof(b) = 8
0x61fe94
0x4029f0
0x402a24
0x402a58
0x3a434347
Base::f
Base::g
Base::h
上面的程序中:
- 先將 &b 轉(zhuǎn)換為 int *(這樣能夠保證 +1 一次增加一個(gè)指針的大小),取得虛函數(shù)表的地址
- 然后,再次取址就得到了第一個(gè)虛函數(shù)的地址,也就是 Base::f
- 最后再轉(zhuǎn)換為 int **,通過 +1,+2 后取址,就能夠得到 Base::g,Base::h
一般繼承(no override)
#include <iostream> #include <typeinfo> ? using namespace std; ? class Base { public: ? ? virtual void f() { cout << "Base::f" << endl; } ? ? virtual void g() { cout << "Base::g" << endl; } ? ? virtual void h() { cout << "Base::h" << endl; } private: ? ? int data; }; ? class Derive:public Base { ? ? virtual void f1() { cout << "Base::f1" << endl; } ? ? virtual void g1() { cout << "Base::g1" << endl; } ? ? virtual void h1() { cout << "Base::h1" << endl; } }; ? typedef void (*FUNC)(void); ? int main() { ? ? Derive b; ? ? cout<<"sizeof(Base) = "<<sizeof(Base)<<endl; ? ? cout<<"sizeof(Derive) = "<<sizeof(Derive)<<endl; ? ? cout<<"sizeof(b) = "<<sizeof(b)<<endl; ? ? ? cout<<&b<<endl; ? ? cout<<*((int **)*(int *)(&b)+0)<<endl; ? ? cout<<*((int **)*(int *)(&b)+1)<<endl; ? ? cout<<*((int **)*(int *)(&b)+2)<<endl; ? ? cout<<*((int **)*(int *)(&b)+3)<<endl; ? ? cout<<*((int **)*(int *)(&b)+4)<<endl; ? ? cout<<*((int **)*(int *)(&b)+5)<<endl; ? ? cout<<*((int **)*(int *)(&b)+6)<<endl; ? ? ? FUNC pf = NULL; ? ? pf = (FUNC)*((int **)*(int *)(&b)+0); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+1); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+2); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+3); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+4); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+5); ? ? pf(); ? ? ? return 0; }
結(jié)果為:
sizeof(Base) = 8
sizeof(Derive) = 8
sizeof(b) = 8
0x61fe94
0x402ae0
0x402b14
0x402b48
0x402b94
0x402bc8
0x402bfc
0x3a434347
Base::f
Base::g
Base::h
Base::f1
Base::g1
Base::h1
在上邊的例子中,派生類沒有 override 任何父類的函數(shù),并又重新定義了幾個(gè)虛函數(shù),因此對(duì)于派生類來說:
- 虛函數(shù)按照其聲明順序在表中存放
- 父類的虛函數(shù)在子類的虛函數(shù)前邊
一般繼承(override)
#include <iostream> #include <typeinfo> ? using namespace std; ? class Base { public: ? ? virtual void f() { cout << "Base::f" << endl; } ? ? virtual void g() { cout << "Base::g" << endl; } ? ? virtual void h() { cout << "Base::h" << endl; } private: ? ? int data; }; ? class Derive:public Base { ? ? virtual void f() { cout << "Base::f1" << endl; } ? ? virtual void g1() { cout << "Base::g1" << endl; } ? ? virtual void h1() { cout << "Base::h1" << endl; } }; ? typedef void (*FUNC)(void); ? int main() { ? ? Derive b; ? ? cout<<"sizeof(Base) = "<<sizeof(Base)<<endl; ? ? cout<<"sizeof(Derive) = "<<sizeof(Derive)<<endl; ? ? cout<<"sizeof(b) = "<<sizeof(b)<<endl; ? ? ? cout<<&b<<endl; ? ? cout<<*((int **)*(int *)(&b)+0)<<endl; ? ? cout<<*((int **)*(int *)(&b)+1)<<endl; ? ? cout<<*((int **)*(int *)(&b)+2)<<endl; ? ? cout<<*((int **)*(int *)(&b)+3)<<endl; ? ? cout<<*((int **)*(int *)(&b)+4)<<endl; ? ? cout<<*((int **)*(int *)(&b)+5)<<endl; ? ? ? FUNC pf = NULL; ? ? pf = (FUNC)*((int **)*(int *)(&b)+0); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+1); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+2); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+3); ? ? pf(); ? ? pf = (FUNC)*((int **)*(int *)(&b)+4); ? ? pf(); ? ? ? return 0; }
結(jié)果為:
sizeof(Base) = 8
sizeof(Derive) = 8
sizeof(b) = 8
0x61fe94
0x402b54
0x402ad4
0x402b08
0x402b88
0x402bbc
0x3a434347
Base::f1
Base::g
Base::h
Base::g1
Base::h1
在上邊的例子中,派生類 override 了父類的 f 函數(shù),并又重新定義了幾個(gè)虛函數(shù),因此對(duì)于派生類來說:
- override 的 f 函數(shù)被放到了虛函數(shù)表中原來基類虛函數(shù)的位置
- 沒有 override 的函數(shù)不變
過程推斷
Base *b = new Derive(); b->f();
這段代碼的實(shí)際過程為:
- 明確 b 類型
- 通過指向虛函數(shù)表的指針和偏移量,來匹配虛函數(shù)的地址
- 根據(jù)地址調(diào)用虛函數(shù)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++ opencv ffmpeg圖片序列化實(shí)現(xiàn)代碼解析
這篇文章主要介紹了C++ opencv ffmpeg圖片序列化實(shí)現(xiàn)代碼解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08C++ 構(gòu)造函數(shù)中使用new時(shí)注意事項(xiàng)
本文主要介紹了C++ 構(gòu)造函數(shù)中使用new時(shí)注意事項(xiàng)。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02C語言實(shí)現(xiàn)進(jìn)程間通信原理解析
這篇文章主要介紹了C語言實(shí)現(xiàn)進(jìn)程間通信原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Qt掃盲篇之QRegularExpression正則匹配總結(jié)
QRegularExpression是Qt5.0引進(jìn)的,修復(fù)了很多bug,提高了效率,使用時(shí)建議使用QRegularExpression,下面這篇文章主要給大家介紹了關(guān)于Qt掃盲篇之QRegularExpression正則匹配的相關(guān)資料,需要的朋友可以參考下2023-03-03