2012-01-11 167 views
30

可能重複:
Can someone explain C++ Virtual Methods?爲什麼使用虛擬功能?

我有一個關於在C++虛函數的問題。

爲什麼以及何時做,我們使用虛擬函數?任何人都可以給我一個實時的實現或使用虛擬功能?

+1

我爲此創建了一篇博文,因爲當一個小孩問我「爲什麼虛擬功能」時,我很難解釋它。 http://nrecursions.blogspot.in/2015/06/so-why-do-we-need-virtual-functions.html – Nav 2015-06-07 11:23:17

回答

45

當你想覆蓋某種行爲(讀法)爲您的派生類而不是基類中實現了一個可以使用的虛擬功能,你想在運行時通過指針基類這樣做。

典型的例子是,當你有一個基類叫做Shape和具體形狀(類),從它派生。每個具體類覆蓋(實現一個虛擬方法),稱爲Draw()

的類層次結構如下:

Class hierarchy

如下片段展示的實例中的使用情況;它創建一個Shape類指針數組,其中每個指向一個不同的派生類對象。在運行時,調用Draw()方法會導致調用由該派生類覆蓋的方法,並且繪製(或呈現)特定的Shape

Shape *basep[] = { &line_obj, &tri_obj, 
        &rect_obj, &cir_obj}; 
for (i = 0; i < NO_PICTURES; i++) 
    basep[i] -> Draw(); 

上述程序只是使用指向基類的指針來存儲派生類對象的地址。這提供了一個鬆耦合,因爲如果隨時添加一個新的具體派生類shape,程序不需要劇烈改變。原因是有實際使用(取決於)具體的Shape類型的最小代碼段。

以上是著名SOLID設計原則Open Closed Principle的一個很好的例子。

+7

如果我們不使用'virtual'並且只是在子類中重新定義它們,例如'Line '和'三角形'。那裏會有什麼不同? – bluejamesbond 2013-11-27 05:31:32

+4

如果該函數在基類中不是虛擬的,則無法通過基類指針訪問派生類版本。 – radensb 2014-07-08 06:03:27

+0

對不起,這裏還是有點困惑。如果說虛擬函數用於在運行時不知道需要哪個版本的函數時,是否正確,但在此之前還需要基類的實例化?因爲看起來我仍然可以在沒有虛函數的情況下獲得它,並且只要創建子類並實例化恰當的類就可以知道我需要什麼,直到運行時爲止。所以看起來你*還需要在基礎類的實例化之前的一段時間*是關鍵的,對吧?又名:通過基址指針調用派生函數 – krb686 2015-11-03 01:42:51

1

你會使用虛擬函數來實現「多態」,特別是當你有一個對象,不知道實際的基礎類型是什麼,但知道你要在其上執行什麼操作,並且執行這(它是如何做的)根據你實際擁有的類型而不同。

本質上通常所說的「里氏替換原則」芭芭拉里氏誰談到這個1983年左右

,你需要使用動態運行時決定,其中,在該點調用該函數的代碼被稱爲得名,您不知道現在或將來可能通過哪些類型,這是一個很好的使用模式。

雖然這不是唯一的方法。有各種各樣的「回調」可能會帶來「斑點」的數據,並且您可能有回調錶取決於數據中的頭塊,例如,一個消息處理器。爲此,不需要使用虛函數,實際上你可能會用到的只是一個v表如何只用一個入口(例如只有一個虛函數的類)來實現。

20

思考的動物類的,以及它的衍生是貓,狗和牛。動物類有一個

virtual void SaySomething() 
{ 
    cout << "Something"; 
} 

函數。

Animal *a; 
a = new Dog(); 
a->SaySomething(); 

而不是打印「東西」,狗應該說「樹皮」,貓應該說「喵」。在這個例子中,你看到a是一隻狗,但有時候你有一隻動物指針,不知道它是哪個動物。你不想知道它是哪個動物,你只是想讓動物說些什麼。所以你只是稱虛擬功能,貓會說「喵」,狗會說「樹皮」。

當然,SaySomething函數應該是純虛擬的以避免可能的錯誤。

+2

謝謝你的回答令人滿意.... – haris 2012-01-11 18:28:10

+0

我不太關注你的最後一句「SaySomething函數應該是純虛擬的,以避免可能的錯誤。」請你舉個例子吧。 – user454083 2013-12-17 09:33:06

+3

對不起,延遲迴復。 考慮到這一點,開發人員創建一個繼承我們動物類的新類(比如'Fox'),並忘記重寫SaySomething方法。如果你使用我提供的虛擬方法,'Fox'實例會說「Something」,這是不對的。如果我們將SaySomething聲明爲純虛函數,我們將無法實例化Fox,包含「new Fox(...)」的代碼將引發錯誤。這樣,創建Fox類的開發人員將在編譯時通知他/她的錯誤。編譯時間錯誤是很好的,因爲它們不會浪費時間:) – holgac 2014-06-13 05:16:29

20

當您需要以相同的方式處理不同的對象時,您可以使用虛函數。它叫做多態。讓我們想象一下,你有一些基類 - 有點像經典外形:

class Shape 
    { 
     public: 
      virtual void draw() = 0; 
      virtual ~Shape() {} 
    }; 

    class Rectange: public Shape 
    { 
     public: 
      void draw() { // draw rectangle here } 
    }; 


    class Circle: public Shape 
    { 
     public: 
      void draw() { // draw circle here } 
    }; 

現在你可以有不同形狀的載體:

vector<Shape*> shapes; 
    shapes.push_back(new Rectangle()); 
    shapes.push_back(new Circle()); 

,你能畫各種形狀是這樣的:

for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++) 
    { 
      (*i)->draw(); 
    } 

通過這種方式,您可以使用一種虛擬方法繪製不同的形狀 - draw()。方法的正確版本是根據關於指針後面的對象類型的運行時間信息來選擇的。

通知 當使用虛擬函數可以聲明爲純虛(類形狀,只是代替「= 0」的方法的原之後等)。在這種情況下,您將無法使用純虛函數創建對象實例,並將其稱爲Abstract類。

也注意析構函數之前的「虛擬」。如果您計劃通過指向其基類的指針來處理對象,則應該聲明析構函數爲虛擬的,因此,當您爲基類指針調用「delete」時,將調用所有析構函數鏈,並且不會有內存泄漏。