虛函數(shù)是面向?qū)ο蟪绦蛟O(shè)計(jì)中的一個(gè)重要概念,它允許基類的方法在派生類中被重寫并實(shí)現(xiàn)多態(tài)性。在對(duì)象的繼承鏈中,派生類可以對(duì)虛函數(shù)進(jìn)行重寫,使得程序在運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類型來調(diào)用適當(dāng)?shù)暮瘮?shù)實(shí)現(xiàn)。
在C++語(yǔ)言中,通過在函數(shù)聲明中使用關(guān)鍵字virtual來定義虛函數(shù)。虛函數(shù)通過解決運(yùn)行時(shí)多態(tài)性問題,在對(duì)象的繼承層次結(jié)構(gòu)中起到了重要作用。
在C++中,虛函數(shù)的調(diào)用是通過虛函數(shù)表(virtual function table)來實(shí)現(xiàn)的。每個(gè)含有虛函數(shù)的類都會(huì)在內(nèi)存中維護(hù)一個(gè)虛函數(shù)表,該表存儲(chǔ)了虛函數(shù)的地址。當(dāng)對(duì)象被創(chuàng)建時(shí),會(huì)分配一個(gè)指向虛函數(shù)表的指針,稱為虛函數(shù)表指針。
當(dāng)通過基類指針或引用調(diào)用虛函數(shù)時(shí),編譯器會(huì)根據(jù)對(duì)象的實(shí)際類型查找虛函數(shù)表,并調(diào)用正確的實(shí)現(xiàn)。這種動(dòng)態(tài)綁定的特性使得程序可以根據(jù)對(duì)象的實(shí)際類型來決定函數(shù)的調(diào)用。
虛函數(shù)的使用具有多種優(yōu)點(diǎn):
在使用虛函數(shù)時(shí),需要注意以下幾點(diǎn):
下面通過一個(gè)簡(jiǎn)單的例子來演示虛函數(shù)的使用:
#include<iostream>
class Shape {
public:
virtual void draw() {
std::cout << "Shape::draw() called" << std::endl;
}
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Circle::draw() called" << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Rectangle::draw() called" << std::endl;
}
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Rectangle();
shape1->draw();
shape2->draw();
delete shape1;
delete shape2;
return 0;
}
運(yùn)行結(jié)果:
Circle::draw() called
Rectangle::draw() called
從上述例子可以看出,通過基類指針調(diào)用虛函數(shù)時(shí),根據(jù)對(duì)象的實(shí)際類型分別調(diào)用了相應(yīng)的派生類函數(shù)實(shí)現(xiàn)。
虛函數(shù)是C++中一種強(qiáng)大的特性,通過實(shí)現(xiàn)多態(tài)性提高了程序的靈活性、可維護(hù)性和擴(kuò)展性。雖然虛函數(shù)的使用會(huì)帶來一定的性能開銷,但在大多數(shù)情況下,這個(gè)開銷是可以接受的。在設(shè)計(jì)面向?qū)ο蟮某绦驎r(shí),合理地使用虛函數(shù)可以使程序更加清晰、簡(jiǎn)潔和可擴(kuò)展。
虛函數(shù)是在基類中作總體框架定義,定義時(shí)在函數(shù)的返回類型名前加上virtual構(gòu)成。它的具體不同實(shí)現(xiàn)版本是在其類的派生類里實(shí)現(xiàn)的。
純虛函數(shù)是在其類中連基本框架都定義不出來,所以只是用“virtual 類型名 函數(shù)名()=0;”的形式來聲明基類中有這么一個(gè)函數(shù),而它的實(shí)現(xiàn)則完全由基類的派生類根據(jù)不同需要來完成。
有純虛函數(shù)的基類叫抽象類,不能被實(shí)例化(即不能生成對(duì)象),只能被繼承。我的理解是:虛函數(shù)實(shí)現(xiàn)的具體版本中總是有通用的部分,這些通用部分可以在基類中定義,而純虛函數(shù)則完全沒有能共用的部分,完全是由派生類中不同的實(shí)現(xiàn)完成的
虛函數(shù)和純虛函數(shù)都是C++中的概念,用于實(shí)現(xiàn)多態(tài)性。它們的主要區(qū)別在于:
函數(shù)是數(shù)學(xué)中的一個(gè)概念,它是一種特殊的映射,將輸入值映射到輸出值。函數(shù)可以看作是一種規(guī)則或算法,給定輸入值,根據(jù)該規(guī)則或算法計(jì)算出唯一的輸出值。
在數(shù)學(xué)中,函數(shù)通常表示為 f(x),其中 f 是函數(shù)的名稱,x 是輸入變量。函數(shù)可以是一元函數(shù)(一個(gè)輸入變量)或多元函數(shù)(多個(gè)輸入變量)。
函數(shù)的定義通常包括以下三個(gè)要素:
1. 定義域:輸入值的集合。
2. 值域:輸出值的集合。
3. 對(duì)應(yīng)關(guān)系:將輸入值映射到輸出值的規(guī)則或算法。
根據(jù)對(duì)應(yīng)關(guān)系的不同,函數(shù)可以分為不同的類型,如線性函數(shù)、多項(xiàng)式函數(shù)、三角函數(shù)、指數(shù)函數(shù)等。
在實(shí)際應(yīng)用中,函數(shù)可以用于描述各種規(guī)律和關(guān)系,如物理定律、經(jīng)濟(jì)模型、生物模型等。通過函數(shù),我們可以將實(shí)際問題轉(zhuǎn)化為數(shù)學(xué)問題,從而更好地理解和解決這些問題。
1. 虛函數(shù)可以在基類中有默認(rèn)實(shí)現(xiàn),子類可以選擇性地覆蓋它們的實(shí)現(xiàn)。而純虛函數(shù)則沒有默認(rèn)實(shí)現(xiàn),必須在派生類中實(shí)現(xiàn)。
2. 虛函數(shù)可以被直接使用,也可以被子類重載以后以多態(tài)的形式調(diào)用。而純虛函數(shù)不能被直接調(diào)用,必須在子類中實(shí)現(xiàn)該函數(shù)才可以使用。
3. 如果一個(gè)類中包含純虛函數(shù),則該類被稱為抽象類,不能被實(shí)例化。而只含有虛函數(shù)的類可以被實(shí)例化。
總的來說,虛函數(shù)和純虛函數(shù)都可以在子類中被重載,以多態(tài)的形式被調(diào)用,但純虛函數(shù)在基類只有聲明而沒有定義,且只存在于抽象基類中。
今天我們來掆討一個(gè)關(guān)于 Java 的熱門話題: java 中有虛函數(shù)嗎。Java 是一門面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言,同時(shí)也是一門非常流行和廣泛應(yīng)用的語(yǔ)言。在 Java 中,虛函數(shù)的概念與 C++ 等語(yǔ)言中的虛函數(shù)有所不同。本文將深入探討 Java 中關(guān)于虛函數(shù)的應(yīng)用和相關(guān)概念。
在面向?qū)ο蟮木幊陶Z(yǔ)言中,虛函數(shù)是一種允許在派生類中重寫的函數(shù)。通過使用虛函數(shù),可以實(shí)現(xiàn)多態(tài)性,使得程序根據(jù)對(duì)象的實(shí)際類型來調(diào)用相應(yīng)的函數(shù)。在 C++ 中,通過將基類的成員函數(shù)聲明為虛函數(shù),可以實(shí)現(xiàn)運(yùn)行時(shí)多態(tài)。那么在 Java 中,虛函數(shù)是如何實(shí)現(xiàn)的呢?
與 C++ 不同,Java 中的所有方法默認(rèn)都是虛函數(shù)。也就是說,Java 中的方法都是動(dòng)態(tài)綁定的。在 Java 中,可以通過使用 @Override 注解來明確地告訴編譯器,該方法是重寫了父類中的方法。這樣一來,即使在父類中使用父類類型聲明對(duì)象,調(diào)用被子類重寫的方法時(shí),仍會(huì)根據(jù)實(shí)際的對(duì)象類型來執(zhí)行相應(yīng)的方法。
另外,Java 中還有一種特殊的方法叫做靜態(tài)方法。靜態(tài)方法是屬于類的方法,而不是屬于對(duì)象的方法。因此,靜態(tài)方法不能被子類重寫。即使子類中定義了與父類靜態(tài)方法相同的方法名和參數(shù),實(shí)際調(diào)用時(shí)仍會(huì)執(zhí)行父類的靜態(tài)方法。
虛函數(shù)在面向?qū)ο蟮某绦蛟O(shè)計(jì)中扮演著重要的角色。它使得程序可以根據(jù)對(duì)象的實(shí)際類型來動(dòng)態(tài)地調(diào)用相應(yīng)的函數(shù),實(shí)現(xiàn)多態(tài)性。通過虛函數(shù)的應(yīng)用,可以簡(jiǎn)化程序的設(shè)計(jì),增強(qiáng)程序的靈活性,便于擴(kuò)展和維護(hù)。
另外,虛函數(shù)還有助于實(shí)現(xiàn)抽象類和接口的概念。抽象類是包含抽象方法的類,無法被實(shí)例化,只能被繼承。接口是一種更加抽象的概念,定義了一組抽象方法的集合,任何實(shí)現(xiàn)了該接口的類都必須實(shí)現(xiàn)這些方法。通過虛函數(shù)的機(jī)制,可以很好地支持抽象類和接口的設(shè)計(jì)。
讓我們通過一個(gè)簡(jiǎn)單的示例來說明在 Java 中虛函數(shù)的應(yīng)用。
假設(shè)我們有一個(gè)動(dòng)物( Animal )類,其中定義了一個(gè) eat 方法:
Animal { void eat() { System.out.println("Animal is eating"); } }現(xiàn)在我們定義一個(gè)子類 Dog 繼承自 Animal,并重寫 eat 方法:
Dog extends Animal { void eat() { System.out.println("Dog is eating"); } }
在程序中,我們可以這樣使用這兩個(gè)類:
Animal animal = new Dog(); animal.eat(); // 輸出:Dog is eating
在上面的示例中,我們使用父類類型聲明了一個(gè)對(duì)象 animal,但實(shí)際上對(duì)象是子類 Dog 的實(shí)例。當(dāng)調(diào)用 eat 方法時(shí),由于 eat 方法是虛函數(shù),所以最終執(zhí)行的是子類 Dog 中的 eat 方法。
虛函數(shù)是面向?qū)ο缶幊讨兄匾母拍睿鼘?shí)現(xiàn)了多態(tài)性,使程序更加靈活和易擴(kuò)展。在 Java 中,虛函數(shù)的應(yīng)用與 C++ 中的虛函數(shù)略有不同,但通過動(dòng)態(tài)綁定的方式實(shí)現(xiàn)了類似的功能。通過本文的討論,希望讀者對(duì) Java 中虛函數(shù)的概念有了更深入的理解,并能夠在實(shí)際開發(fā)中靈活運(yùn)用。
虛函數(shù)定義:如果在基類中將某個(gè)函數(shù)指定為并且派生類中有另外一個(gè)該函數(shù)的定義,則編譯器將知道我們不想靜態(tài)連接該函數(shù)。我們真正需要的是基于調(diào)用該函數(shù)的對(duì)象種類,在程序的特定位置選擇調(diào)用哪一個(gè)函數(shù)。
作用:虛函數(shù)的作用用專業(yè)術(shù)語(yǔ)來解釋就是實(shí)現(xiàn)多態(tài)性,多態(tài)性是將接口與實(shí)現(xiàn)進(jìn)行分離;用形象的語(yǔ)言來解釋就是實(shí)現(xiàn)以共同的方法,但因個(gè)體差異,而采用不同的策略。
A)虛函數(shù)是一個(gè)靜態(tài)成員函數(shù)(靜態(tài)是編譯是實(shí)現(xiàn) X)
B)虛函數(shù)是一個(gè)非成員函數(shù)(這里意思是全局函數(shù) X)
C)虛函數(shù)既可以在函數(shù)說明時(shí)定義,也可以在函數(shù)實(shí)現(xiàn)時(shí)定義(這是純虛函數(shù) X)
D)派生類的虛函數(shù)與基類中對(duì)應(yīng)的虛函數(shù)具有相同的參數(shù)個(gè)數(shù)和類型
虛函數(shù)是由指針地址中的數(shù)據(jù)類型來判斷函數(shù)使用,而非虛函數(shù)則是僅僅看指針類型來調(diào)用;因此在非虛的情況下調(diào)用的是Animal類型,因?yàn)橹羔樢彩莂nimal類型;而在虛函數(shù)的情況下調(diào)用的是fish類型的breathe().
實(shí)函數(shù)(Non-virtual Function)和虛函數(shù)(Virtual Function)是面向?qū)ο缶幊讨械膬蓚€(gè)概念,它們?cè)诶^承和多態(tài)性方面有所區(qū)別。
1. 實(shí)函數(shù)(非虛函數(shù)):
- 實(shí)函數(shù)是在基類中定義的普通成員函數(shù),在子類中可以通過重寫(覆蓋)的方式進(jìn)行修改或擴(kuò)展,但并非必須。
- 在運(yùn)行時(shí),實(shí)函數(shù)的調(diào)用是靜態(tài)綁定的,即根據(jù)函數(shù)的聲明類型,而不考慮實(shí)際對(duì)象的類型。
- 編譯器在編譯時(shí)就確定調(diào)用的函數(shù),在程序運(yùn)行時(shí)效率較高。
2. 虛函數(shù):
- 虛函數(shù)是在基類中使用virtual關(guān)鍵字聲明的成員函數(shù),它可以在子類中重寫(覆蓋),實(shí)現(xiàn)多態(tài)性。
- 在運(yùn)行時(shí),通過對(duì)象的實(shí)際類型動(dòng)態(tài)綁定,根據(jù)對(duì)象的實(shí)際類型調(diào)用相應(yīng)的函數(shù)實(shí)現(xiàn)動(dòng)態(tài)多態(tài)性。
- 虛函數(shù)通過使用基類指針或引用調(diào)用子類對(duì)象的方法,可以實(shí)現(xiàn)多態(tài)性,提高代碼的靈活性和擴(kuò)展性。
總結(jié):
實(shí)函數(shù)和虛函數(shù)的區(qū)別在于,實(shí)函數(shù)在運(yùn)行時(shí)根據(jù)函數(shù)的聲明類型來確定調(diào)用的函數(shù),而虛函數(shù)在運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類型動(dòng)態(tài)綁定調(diào)用相應(yīng)的函數(shù)。虛函數(shù)的使用可以實(shí)現(xiàn)多態(tài)性,使得子類對(duì)象可以按照基類對(duì)象的方式被操作和調(diào)用。
1.如果通過對(duì)象調(diào)用虛函數(shù),編譯器直接找到虛函數(shù)的地址。
2.對(duì)于虛函數(shù)和成員函數(shù),編譯器都會(huì)隱式的傳入this指針。
3.對(duì)于指針和引用的形式來調(diào)用虛函數(shù),編譯器走的則是虛函數(shù)表的路線。
4.無論是成員函數(shù)還是虛函數(shù),他的地址都是在編譯期間就已經(jīng)確定下來了,接下來就看你怎么去找到這個(gè)虛函數(shù)的地址,可以直接找,也可以通過虛函數(shù)表.
純虛函數(shù)跟其他函數(shù)的不同之處是,其它虛函數(shù)都是把函數(shù)地址放在虛表中,調(diào)用的時(shí)候根據(jù)地址調(diào)用函數(shù),而純虛函數(shù)因?yàn)闆]有實(shí)現(xiàn),虛表中第一項(xiàng)放的地址是_purecall這個(gè)函數(shù),用于在非法調(diào)用的時(shí)候彈出出錯(cuò)信息;實(shí)際上抽象類中的純虛函數(shù)也是可以實(shí)現(xiàn)的(注意不要在聲明處實(shí)現(xiàn),雖然vc支持)。類似這樣:
但是實(shí)際在派生類調(diào)用的時(shí)候,上面聲明的函數(shù)并不在虛表中,它本身也不在VBase的虛表中,VBase的虛表中放的還是_purecall這個(gè)函數(shù)。因?yàn)檫@種定義行為本身并不是被c++支持的。我的理解是可以像調(diào)用普通函數(shù)一樣調(diào)用它,比如:輸出依然是base call,但是如果你這調(diào)用(當(dāng)然這是很不好的規(guī)范!)就會(huì)發(fā)現(xiàn)VBase的f依然是_purecall執(zhí)行到f()的時(shí)候,會(huì)彈出錯(cuò)誤提示pure virtual function call