欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++中關(guān)于多態(tài)實(shí)現(xiàn)和使用方法

 更新時(shí)間:2022年07月25日 09:52:42   作者:止步聽風(fēng)  
這篇文章主要介紹了C++中關(guān)于多態(tài)實(shí)現(xiàn)和使用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

都說 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 = &dd;
? ? 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)代碼解析

    這篇文章主要介紹了C++ opencv ffmpeg圖片序列化實(shí)現(xiàn)代碼解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08
  • C++ 構(gòu)造函數(shù)中使用new時(shí)注意事項(xiàng)

    C++ 構(gòu)造函數(shù)中使用new時(shí)注意事項(xiàng)

    本文主要介紹了C++ 構(gòu)造函數(shù)中使用new時(shí)注意事項(xiàng)。具有很好的參考價(jià)值,下面跟著小編一起來看下吧
    2017-02-02
  • C語言解數(shù)獨(dú)程序的源碼

    C語言解數(shù)獨(dú)程序的源碼

    這篇文章主要為大家詳細(xì)介紹了C語言解數(shù)獨(dú)程序的源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-01-01
  • C++ auto類型說明符

    C++ auto類型說明符

    在C++11中引入了auto類型說明符,用它就能讓編譯器替我們?nèi)シ治霰磉_(dá)式所屬的類型。當(dāng)然,auto變量必須有初始值,這樣編譯器才能推斷其類型
    2016-03-03
  • C語言實(shí)現(xiàn)進(jìn)程間通信原理解析

    C語言實(shí)現(xiàn)進(jìn)程間通信原理解析

    這篇文章主要介紹了C語言實(shí)現(xiàn)進(jìn)程間通信原理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • C語言實(shí)現(xiàn)簡(jiǎn)單掃雷小程序

    C語言實(shí)現(xiàn)簡(jiǎn)單掃雷小程序

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)簡(jiǎn)單掃雷小程序,一款大眾類的益智小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • C++ 中構(gòu)造函數(shù)的實(shí)例詳解

    C++ 中構(gòu)造函數(shù)的實(shí)例詳解

    這篇文章主要介紹了C++ 中構(gòu)造函數(shù)的實(shí)例詳解的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下
    2017-10-10
  • c++模板自定義數(shù)組

    c++模板自定義數(shù)組

    這篇文章主要介紹了c++模板自定義數(shù)組,通過制造通用模板,創(chuàng)建自定義的數(shù)組展開文章相關(guān)內(nèi)容,具有一的參考價(jià)值,需要的小伙伴可以參考一下
    2022-03-03
  • 使用C語言實(shí)現(xiàn)字符串左旋和右旋問題

    使用C語言實(shí)現(xiàn)字符串左旋和右旋問題

    這篇文章主要介紹了使用C語言實(shí)現(xiàn)字符串左旋和右旋問題,需要的朋友可以參考下
    2018-07-07
  • Qt掃盲篇之QRegularExpression正則匹配總結(jié)

    Qt掃盲篇之QRegularExpression正則匹配總結(jié)

    QRegularExpression是Qt5.0引進(jìn)的,修復(fù)了很多bug,提高了效率,使用時(shí)建議使用QRegularExpression,下面這篇文章主要給大家介紹了關(guān)于Qt掃盲篇之QRegularExpression正則匹配的相關(guān)資料,需要的朋友可以參考下
    2023-03-03

最新評(píng)論