最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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í)需要注意:

  • 確?;愔兄辽俣x了一個(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語言中位域的使用詳解

    C語言中位域的使用詳解

    位域是C語言中的一種高級(jí)功能,允許程序員為結(jié)構(gòu)體的成員分配特定數(shù)量的位,本文主要為大家介紹了位域的使用以及優(yōu)缺點(diǎn),希望對(duì)大家有所幫助
    2023-07-07
  • C++圖文并茂講解繼承

    C++圖文并茂講解繼承

    繼承是C++面向?qū)ο缶幊讨械囊婚T。繼承是子類繼承父類的特征和行為,或者是繼承父類得方法,使的子類具有父類得的特性和行為。重寫是子類對(duì)父類的允許訪問的方法實(shí)行的過程進(jìn)行重新編寫,返回值和形參都不能改變。就是對(duì)原本的父類進(jìn)行重新編寫,但是外部接口不能被重寫
    2022-05-05
  • C語言編程深入理解取整取余取模問題示例分析

    C語言編程深入理解取整取余取模問題示例分析

    這篇文章主要為大家介紹了C語言編程深入理解取整取余取模問題的示例分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2021-11-11
  • 淺談C++中的mutable和volatile關(guān)鍵字

    淺談C++中的mutable和volatile關(guān)鍵字

    下面小編就為大家?guī)硪黄獪\談C++中的mutable和volatile關(guān)鍵字。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-06-06
  • Opencv 馬賽克和毛玻璃效果與圖片融合的實(shí)現(xiàn)

    Opencv 馬賽克和毛玻璃效果與圖片融合的實(shí)現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了通過OpenCV實(shí)現(xiàn)馬賽克和毛玻璃濾鏡效果與圖片的融合,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • C語言 指針變量作為函數(shù)參數(shù)詳解

    C語言 指針變量作為函數(shù)參數(shù)詳解

    本文主要介紹C語言 指針變量作為函數(shù)參數(shù),這里整理了相關(guān)資料和示例代碼,以便大家學(xué)習(xí)參考理解知識(shí)點(diǎn),有需要的小伙伴可以參考下
    2016-08-08
  • 淺談C/C++中指針和數(shù)組的不同

    淺談C/C++中指針和數(shù)組的不同

    本文主要解析了C/C++中數(shù)組和指針的區(qū)別,文章簡(jiǎn)單易懂,對(duì)各位的工作學(xué)習(xí)有所幫助,有需求的朋友可以了解下
    2020-05-05
  • 深入N皇后問題的兩個(gè)最高效算法的詳解

    深入N皇后問題的兩個(gè)最高效算法的詳解

    本篇文章是對(duì)N皇后問題的兩個(gè)最高效的算法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • 利用Matlab制作抖音同款含褶皺面料圖

    利用Matlab制作抖音同款含褶皺面料圖

    這篇文章主要介紹了如何利用Matlab制作抖音的同款含褶皺面料圖,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Matlab有一定幫助,需要的可以參考一下
    2022-03-03
  • OpenCV實(shí)現(xiàn)圖像細(xì)化算法

    OpenCV實(shí)現(xiàn)圖像細(xì)化算法

    本文主要介紹了OpenCV實(shí)現(xiàn)圖像細(xì)化算法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08

最新評(píng)論

安义县| 招远市| 牡丹江市| 那坡县| 武安市| 古丈县| 汉阴县| 聊城市| 西平县| 张家口市| 滕州市| 焦作市| 德州市| 隆回县| 平武县| 全州县| 宜丰县| 双牌县| 杭州市| 宝清县| SHOW| 鄂托克旗| 盱眙县| 平阴县| 象山县| 察哈| 长白| 大渡口区| 长沙市| 丽水市| 巴马| 丹江口市| 顺义区| 景洪市| 育儿| 茶陵县| 五峰| 商水县| 堆龙德庆县| 车险| 阳谷县|