您好,登錄后才能下訂單哦!
這篇文章主要介紹“C++ Virtual是什么”,在日常操作中,相信很多人在C++ Virtual是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”C++ Virtual是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
Virtual是C++ OO(面向?qū)ο髾C制)機制中很重要的一個關(guān)鍵字。虛函數(shù)就是因為成員函數(shù)加了關(guān)鍵字virtual,可見它的重要性。
只要是學(xué)過C++的人都知道在類Base中加了Virtual關(guān)鍵字的函數(shù)就是虛擬函數(shù)(例如函數(shù)print),于是在Base的派生類Derived中就可以通過重寫虛擬函數(shù)來實現(xiàn)對基類虛擬函數(shù)的覆蓋。當(dāng)基類Base的指針point指向派生類Derived的對象時,對point的print函數(shù)的調(diào)用實際上是調(diào)用了Derived的print函數(shù)而不是Base的print函數(shù)。這是面向?qū)ο笾械亩鄳B(tài)性的體現(xiàn)。
//--------------------------------------------------------- class Base { public:Base(){} public: virtual void print(){cout<<"Base";} }; class Derived:public Base { public:Derived(){} public: void print(){cout<<"Derived";} }; int main() { Base *point=new Derived(); point->print(); } //--------------------------------------------------------- Output: Derived
這也許會使人聯(lián)想到函數(shù)的重載,但稍加對比就會發(fā)現(xiàn)兩者是完全不同的:
(1) 重載的幾個函數(shù)必須在同一個類中;
覆蓋的函數(shù)必須在有繼承關(guān)系的不同的類中
(2) 覆蓋的幾個函數(shù)必須函數(shù)名、參數(shù)、返回值都相同;
重載的函數(shù)必須函數(shù)名相同,參數(shù)不同。參數(shù)不同的目的就是為了在函數(shù)調(diào)用的時候編譯器能夠通過參數(shù)來判斷程序是在調(diào)用的哪個函數(shù)。這也就很自然地解釋了為什么函數(shù)不能通過返回值不同來重載,因為程序在調(diào)用函數(shù)時很有可能不關(guān)心返回值,編譯器就無法從代碼中看出程序在調(diào)用的是哪個函數(shù)了。
(3) 覆蓋的函數(shù)前必須加關(guān)鍵字Virtual;
重載和Virtual沒有任何瓜葛,加不加都不影響重載的運作。
關(guān)于C++的隱藏規(guī)則(引用自《高質(zhì)量C++/C 編程指南》林銳 2001):
(1)如果派生類的函數(shù)與基類的函數(shù)同名,但是參數(shù)不同。此時,不論有無virtual
關(guān)鍵字,基類的函數(shù)將被隱藏(注意別與重載混淆)。
(2)如果派生類的函數(shù)與基類的函數(shù)同名,并且參數(shù)也相同,但是基類函數(shù)沒有virtual
關(guān)鍵字。此時,基類的函數(shù)被隱藏(注意別與覆蓋混淆)。
這里,林銳博士好像犯了個錯誤。C++并沒有隱藏規(guī)則,林銳博士所總結(jié)的隱藏規(guī)則是他錯誤地理解C++多態(tài)性所致。下面請看林銳博士給出的隱藏規(guī)則的例證:
#include <iostream.h> class Base { public: virtual void f(float x){ cout << "Base::f(float) " << x << endl; } void g(float x){ cout << "Base::g(float) " << x << endl; } void h(float x){ cout << "Base::h(float) " << x << endl; } }; class Derived : public Base { public: virtual void f(float x){ cout << "Derived::f(float) " << x << endl; } void g(int x){ cout << "Derived::g(int) " << x << endl; } void h(float x){ cout << "Derived::h(float) " << x << endl; } }; void main(void) { Derived d; Base *pb = &d; Derived *pd = &d; // Good : behavior depends solely on type of the object pb->f(3.14f); // Derived::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 // Bad : behavior depends on type of the pointer pb->g(3.14f); // Base::g(float) 3.14 pd->g(3.14f); // Derived::g(int) 3 (surprise!) // Bad : behavior depends on type of the pointer pb->h(3.14f); // Base::h(float) 3.14 (surprise!) pd->h(3.14f); // Derived::h(float) 3.14 }
林銳博士認(rèn)為bp 和dp 指向同一地址,按理說運行結(jié)果應(yīng)該是相同的,而事實上運行結(jié)果不同,所以他把原因歸結(jié)為C++的隱藏規(guī)則,其實這一觀點是錯的。決定bp和dp調(diào)用函數(shù)運行結(jié)果的不是他們指向的地址,而是他們的指針類型?!爸挥性谕ㄟ^基類指針或引用間接指向派生類子類型時多態(tài)性才會起作用”(C++ Primer 3rd Edition)。pb是基類指針,pd是派生類指針,pd的所有函數(shù)調(diào)用都只是調(diào)用自己的函數(shù),和多態(tài)性無關(guān),所以pd的所有函數(shù)調(diào)用的結(jié)果都輸出Derived::是完全正常的;pb的函數(shù)調(diào)用如果有virtual則根據(jù)多態(tài)性調(diào)用派生類的,如果沒有virtual則是正常的靜態(tài)函數(shù)調(diào)用,還是調(diào)用基類的,所以有virtual的f函數(shù)調(diào)用輸出Derived::,其它兩個沒有virtual則還是輸出Base::很正常啊,nothing surprise!
所以并沒有所謂的隱藏規(guī)則,雖然《高質(zhì)量C++/C 編程指南》是本很不錯的書,可大家不要迷信哦。記住“只有在通過基類指針或引用間接指向派生類子類型時多態(tài)性才會起作用”。
純虛函數(shù):
C++語言為我們提供了一種語法結(jié)構(gòu),通過它可以指明,一個虛擬函數(shù)只是提供了一個可被子類型改寫的接口。但是,它本身并不能通過虛擬機制被調(diào)用。這就是純虛擬函數(shù)(pure virtual function)。 純虛擬函數(shù)的聲明如下所示:
class Query { public: // 聲明純虛擬函數(shù) virtual ostream& print( ostream&=cout ) const = 0; // ... };
這里函數(shù)聲明后面緊跟賦值0。
包含(或繼承)一個或多個純虛擬函數(shù)的類被編譯器識別為抽象基類。試圖創(chuàng)建一個抽象基類的獨立類對象會導(dǎo)致編譯時刻錯誤。(類似地通過虛擬機制調(diào)用純虛擬函數(shù)也是錯誤的例如)
// Query 聲明了純虛擬函數(shù) // 所以, 程序員不能創(chuàng)建獨立的 Query 類對象 // ok: NameQuery 中的 Query 子對象 Query *pq = new NameQuery( "Nostromo" ); // 錯誤: new 表達(dá)式分配 Query 對象 Query *pq2 = new Query;
抽象基類只能作為子對象出現(xiàn)在后續(xù)的派生類中。
如果只知道virtual加在函數(shù)前,那對virtual只了解了一半,virtual還有一個重要用法是virtual public,就是虛擬繼承。虛擬繼承在C++ Primer中有詳細(xì)的描述,下面稍作修改的闡釋一下:
在缺省情況下C++中的繼承是“按值組合”的一種特殊情況。當(dāng)我們寫
class Bear : public ZooAnimal { ... };
每個Bear 類對象都含有其ZooAnimal 基類子對象的所有非靜態(tài)數(shù)據(jù)成員以及在Bear中聲明的非靜態(tài)數(shù)據(jù)成員類似地當(dāng)派生類自己也作為一個基類對象時如:
class PolarBear : public Bear { ... };
則PolarBear 類對象含有在PolarBear 中聲明的所有非靜態(tài)數(shù)據(jù)成員以及其Bear 子對象的所有非靜態(tài)數(shù)據(jù)成員和ZooAnimal 子對象的所有非靜態(tài)數(shù)據(jù)成員。在單繼承下這種由繼承支持的特殊形式的按值組合提供了最有效的最緊湊的對象表示。在多繼承下當(dāng)一個基類在派生層次中出現(xiàn)多次時就會有問題最主要的實際例子是iostream 類層次結(jié)構(gòu)。ostream 和istream 類都從抽象ios 基類派生而來,而iostream 類又是從ostream 和istream 派生
class iostream :public istream, public ostream { ... };
缺省情況下,每個iostream 類對象含有兩個ios 子對象:在istream 子對象中的實例以及在ostream 子對象中的實例。這為什么不好?從效率上而言,存儲ios 子對象的兩個復(fù)本,浪費了存儲區(qū),因為iostream 只需要一個實例。而且,ios 構(gòu)造函數(shù)被調(diào)用了兩次每個子對象一次。更嚴(yán)重的問題是由于兩個實例引起的二義性。例如,任何未限定修飾地訪問ios 的成員都將導(dǎo)致編譯時刻錯誤:到底訪問哪個實例?如果ostream 和istream 對其ios 子對象的初始化稍稍不同,會怎樣呢?怎樣通過iostream 類保證這一對ios 值的一致性?在缺省的按值組合機制下,真的沒有好辦法可以保證這一點。
C++語言的解決方案是,提供另一種可替代按“引用組合”的繼承機制虛擬繼承(virtual inheritance )在虛擬繼承下只有一個共享的基類子對象被繼承而無論該基類在派生層次
中出現(xiàn)多少次共享的基類子對象被稱為虛擬基類。
通過用關(guān)鍵字virtual 修政一個基類的聲明可以將它指定為被虛擬派生。例如,下列聲明使得ZooAnimal 成為Bear 和Raccoon 的虛擬基類:
// 關(guān)鍵字 public 和 virtual的順序不重要 class Bear : public virtual ZooAnimal { ... }; class Raccoon : virtual public ZooAnimal { ... };
虛擬派生不是基類本身的一個顯式特性,而是它與派生類的關(guān)系如前面所說明的,虛擬繼承提供了“按引用組合”。也就是說,對于子對象及其非靜態(tài)成員的訪問是間接進(jìn)行的。這使得在多繼承情況下,把多個虛擬基類子對象組合成派生類中的一個共享實例,從而提供了必要的靈活性。同時,即使一個基類是虛擬的,我們?nèi)匀豢梢酝ㄟ^該基類類型的指針或引用,來操縱派生類的對象。
沒有虛函數(shù)的C++不能面向?qū)ο?。從商業(yè)的角度看,面向?qū)ο竽苁瓜到y(tǒng)具有可擴展性和可適應(yīng)性,但只有C++類的語法而沒有面向?qū)ο蟮脑挘筒粫p少維護(hù)成本,而實際上會增加成本。所以沒有虛函數(shù)是萬萬不能的,而關(guān)鍵字virtual則是關(guān)鍵。
到此,關(guān)于“C++ Virtual是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。