C++的多態(tài)與虛函數(shù)你了解嗎
多態(tài)性
多態(tài)性是面向?qū)ο蟪绦蛟O(shè)計(jì)的關(guān)鍵技術(shù)之一,若程序設(shè)計(jì)語言不支持多態(tài)性,不能稱為面向?qū)ο蟮恼Z言,利用多態(tài)性技術(shù),可以調(diào)用同一個(gè)函數(shù)名的函數(shù),實(shí)現(xiàn)完全不同的功能
在C++中有兩種多態(tài)性:
- 編譯時(shí)的多態(tài)
通過函數(shù)的重載和運(yùn)算符的重載來實(shí)現(xiàn)的
- 運(yùn)行時(shí)的多態(tài)性
運(yùn)行時(shí)的多態(tài)性是指在程序執(zhí)行前,無法根據(jù)函數(shù)名和參數(shù)來確定該調(diào)用哪一個(gè)函數(shù),必須在程序執(zhí)行過程中,根據(jù)執(zhí)行的具體情況來動(dòng)態(tài)地確定;它是通過類繼承關(guān)系public和虛函數(shù)來實(shí)現(xiàn)的,目的也是建立一種通用的程序;通用性是程序追求的主要目標(biāo)之一
通過引用或指針調(diào)用時(shí),才可以達(dá)到運(yùn)行時(shí)的多態(tài)
虛函數(shù)
虛函數(shù)是一個(gè)類的成員函數(shù),定義格式如下:
virtual 返回類型 函數(shù)名(參數(shù)表);
關(guān)鍵字virtual指明該成員函數(shù)為虛函數(shù),virtual僅用于類定義中,如虛函數(shù)在類外定義,不可加virtual
我們來看下面代碼
class Animal
{
private:
string name;
public:
Animal(const string& na):name(na)
{}
public:
virtual void eat(){}
virtual void walk(){}
virtual void tail(){}
virtual void PrintInfo(){}
string& get_name()
{
return name;
}
const string& get_name()const
{
return name;
}
};
class Dog :public Animal
{
private:
string owner;
public:
Dog(const string& ow, const string na) :Animal(na), owner(ow)
{}
virtual void eat()
{
cout << "Dog Eat: bone" << endl;
}
virtual void walk()
{
cout << "Dog Walk: run" << endl;
}
virtual void tail()
{
cout << "Dog Tail: wangwang" << endl;
}
virtual void PrintInfo()
{
cout << "Dog owner" << owner << endl;
cout << "Dog name:" << get_name() << endl;
}
};
class Cat :public Animal
{
private:
string owner;
public:
Cat(const string& ow, const string na) :Animal(na), owner(ow)
{}
virtual void eat()
{
cout << "Cat Eat: fish" << endl;
}
virtual void walk()
{
cout << "Cat Walk: silent" << endl;
}
virtual void tail()
{
cout << "Cat Tail: miaomiao" << endl;
}
virtual void PrintInfo()
{
cout << "Cat owner: " << owner << endl;
cout << "Cat name: " << get_name() << endl;
}
};
// 需要公有繼承 公有繼承代表是一個(gè)的意思
// 需要引用或指針調(diào)用
void fun(Animal& animal)
{
animal.eat(); //對(duì)象名稱.虛方法()
animal.walk();
animal.tail();
animal.PrintInfo();
}
int main()
{
Dog dog("zyq", "hashiqi"); //const string& ow = "zyq"
Cat cat("zyq", "bosimao");
fun(dog);
fun(cat);
return 0;
}

在這里我們可以看到,當(dāng)我們調(diào)用fun()函數(shù)時(shí),傳入dog對(duì)象則調(diào)用Dog的方法,傳入cat調(diào)用Cat方法;這就是所謂的運(yùn)行時(shí)的多態(tài)
要想達(dá)到運(yùn)行時(shí)的多態(tài)(晚綁定)需要滿足:
- 公有繼承
- 有虛函數(shù)
- 必須以指針或引用方式調(diào)用虛函數(shù)
若發(fā)生早綁定,則會(huì)調(diào)用Animal類型的方法
成員函數(shù)應(yīng)盡可能的設(shè)置為虛函數(shù),但必須注意一下幾條:
1.派生類中定義虛函數(shù)必須與基類中的虛函數(shù)同名外,還必須同參數(shù)表,同返回類型;否則被認(rèn)為是重載,而不是虛函數(shù)。如基類中返回基類指針,派生類中返回派生類指針是允許的,這是一個(gè)例外
2.只有類的成員函數(shù)才能說明為虛函數(shù),這是因?yàn)樘摵瘮?shù)僅適用于有繼承關(guān)系的類對(duì)象
3.靜態(tài)成員函數(shù),是所有同一類對(duì)象公有,不受限于某個(gè)對(duì)象,不能作為虛函數(shù)(友元函數(shù)也不可以)
4.實(shí)現(xiàn)動(dòng)態(tài)多態(tài)性時(shí),必須使用基類類型的指針變量或引用,使該指針指向該基類的不同派生類的對(duì)象,并通過該指針指向虛函數(shù),才能實(shí)現(xiàn)動(dòng)態(tài)的多態(tài)性
5.內(nèi)聯(lián)函數(shù)每個(gè)對(duì)象一個(gè)拷貝,無映射關(guān)系,不能作為虛函數(shù)
6.析構(gòu)函數(shù)可定義為虛函數(shù),構(gòu)造函數(shù)不可以定義為虛函數(shù),因?yàn)樵谡{(diào)用構(gòu)造函數(shù)時(shí)對(duì)象還沒有完成實(shí)例化;在基類中及其派生類中都動(dòng)態(tài)分配的內(nèi)存空間時(shí),必須把析構(gòu)函數(shù)定義為虛函數(shù),實(shí)現(xiàn)撤銷對(duì)象時(shí)的多態(tài)性
7.函數(shù)執(zhí)行速度要稍慢一些,為了實(shí)現(xiàn)多態(tài)性,每一個(gè)派生類中均要保存相應(yīng)虛函數(shù)的入口地址表,函數(shù)的調(diào)用機(jī)制也是間接實(shí)現(xiàn);所以多態(tài)性總要付出一定代價(jià),但通用性是一個(gè)更高的目標(biāo)
8.如果定義放在類外,virtual只能加在函數(shù)聲明前面,不能加載函數(shù)定義前面;正確的定義必須不包括virtual
虛函數(shù)是覆蓋,同名函數(shù)是隱藏
虛函數(shù)編譯過程
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x)
{}
virtual void add()
{
cout << "Object::add" << endl;
}
virtual void fun()
{
cout << "Object::fun" << endl;
}
virtual void print()const
{
cout << "Object::print" << endl;
}
};
class Base:public Object
{
private:
int sum;
public:
Base(int x = 0) :Object(x+10),sum(x)
{}
virtual void add()
{
cout << "Base::add" << endl;
}
virtual void fun()
{
cout << "Base::fun" << endl;
}
virtual void print()const
{
cout << "Base::print" << endl;
}
};
int main()
{
}

此處虛函數(shù)表中進(jìn)行的是同名覆蓋,而不像繼承關(guān)系中,同名成員進(jìn)行隱藏,就近處理;虛函表僅有一份,存在數(shù)據(jù)區(qū)
在主函數(shù)創(chuàng)建對(duì)象
int main()
{
Base base(10);
Object* op = &base;
}

可以看到base的大小為12字節(jié),因?yàn)槠渲谢悓?duì)象Object,添加了虛表變?yōu)榱?字節(jié),且在構(gòu)建過程,首先構(gòu)建Object基類,此時(shí)虛表指針指向Object的虛表,而接著構(gòu)建Base類的時(shí)候,會(huì)將虛表指針修改為指向Base的虛表
也就是,當(dāng)有虛函數(shù)時(shí),構(gòu)造函數(shù)除了構(gòu)建對(duì)象初始化對(duì)象的數(shù)據(jù)成員外,還會(huì)將虛表的地址給到虛表指針;同時(shí)這也是構(gòu)造函數(shù)不可以作為虛函數(shù)的原因
int main()
{
Base base(10);
Object* op = NULL;
Object obj(0);
op = &base;
op->add(); //指針或引用調(diào)動(dòng),則采用運(yùn)行時(shí)多態(tài)
op->fun();
op->print();
obj = base;
obj.add(); //對(duì)象直接調(diào)動(dòng),則采用編譯時(shí)多態(tài)
obj.fun();
obj.print();
}

也就是我們通過,對(duì)象名.方法 的方式調(diào)用虛函數(shù),則通過編譯時(shí)多態(tài)的方式

運(yùn)行時(shí)的多態(tài),是通過查詢虛表進(jìn)行調(diào)用;下面通過匯編進(jìn)一步查看

只有進(jìn)行以指針調(diào)用或引用調(diào)用的時(shí)候才會(huì)對(duì)虛表進(jìn)行查詢
三層繼承
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x)
{}
virtual void add()
{
cout << "Object::add" << endl;
}
virtual void fun()
{
cout << "Object::fun" << endl;
}
virtual void print()const
{
cout << "Object::print" << endl;
}
void fn_a()
{
fun();
}
};
class Base:public Object
{
private:
int sum;
public:
Base(int x = 0) :Object(x+10),sum(x)
{}
virtual void add()
{
cout << "Base::add" << endl;
}
virtual void fun()
{
cout << "Base::fun" << endl;
}
virtual void show()
{
cout << "Base::show" << endl;
}
};
class Test :public Base
{
private:
int num;
public:
Test(int x = 0) :Base(x + 10)
{}
virtual void add()
{
cout << "Test::add" << endl;
}
virtual void print() const
{
cout << "Test::print" << endl;
}
virtual void show()
{
cout << "Test::show" << endl;
}
};

我們可以看到虛函數(shù)表,當(dāng)我們構(gòu)建派生類,會(huì)復(fù)制基類的虛函數(shù)表,將虛表指針指向新的虛函數(shù)表,并且將同名的虛函數(shù)進(jìn)行覆蓋
依舊使用上面代碼
/*
void fn_a()
{
fun(); //this->fun(); 屬于動(dòng)態(tài)綁定!
}
*/
int main()
{
Test t1;
Base base;
Object obj;
t1.fn_a(); //fn_a(&t1);
base.fun_a();
obj.fn_a();
return 0;
}

這里依然屬于動(dòng)態(tài)綁定,所以調(diào)用虛表指針指向的相對(duì)應(yīng)類的虛表
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Qt中QTextEdit和QPlainTextEdit控件的實(shí)現(xiàn)
本文主要介紹了Qt中QTextEdit和QPlainTextEdit控件的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
C++設(shè)計(jì)一個(gè)簡(jiǎn)單內(nèi)存池的全過程
利用C/C++開發(fā)大型應(yīng)用程序中,內(nèi)存的管理與分配是一個(gè)需要認(rèn)真考慮的部分,下面這篇文章主要給大家介紹了關(guān)于C++設(shè)計(jì)一個(gè)簡(jiǎn)單內(nèi)存池的全過程,需要的朋友可以參考下2021-09-09
C語言 動(dòng)態(tài)內(nèi)存開辟常見問題解決與分析流程
動(dòng)態(tài)內(nèi)存是相對(duì)靜態(tài)內(nèi)存而言的。所謂動(dòng)態(tài)和靜態(tài)就是指內(nèi)存的分配方式。動(dòng)態(tài)內(nèi)存是指在堆上分配的內(nèi)存,而靜態(tài)內(nèi)存是指在棧上分配的內(nèi)存2022-03-03
詳解C語言求兩個(gè)數(shù)的最大公約數(shù)及最小公倍數(shù)的方法
這篇文章主要介紹了C語言求兩個(gè)數(shù)的最大公約數(shù)及最小公倍數(shù)的方法,輾轉(zhuǎn)相除法和輾轉(zhuǎn)相減法在解決這種問題時(shí)最常用到,需要的朋友可以參考下2016-03-03
C++設(shè)計(jì)類不能被繼承的方法實(shí)例講解
在Java 中定義了關(guān)鍵字final,被final修飾的類不能被繼承,C++中如何實(shí)現(xiàn),下面我們來看一個(gè)例子2013-12-12
C語言中strlen() strcpy() strcat() strcmp()函數(shù)的實(shí)現(xiàn)方法
這篇文章主要介紹了C語言中strlen() strcpy() strcat() strcmp()函數(shù)的實(shí)現(xiàn)方法,需要的朋友可以參考下2017-08-08

