2010-04-23 140 views
5

假設我有一個從類Animal繼承的類Dog。這兩行代碼有什麼區別?基類指針vs繼承類指針?

Animal *a = new Dog(); 
    Dog *d = new Dog(); 

其中一個指針指向基類,另一指針指向派生類。但是,這種區分何時變得重要?對於多態性,任何一個都可以完全相同,對嗎?

+1

對於這種特殊情況,它的工作原理是一樣的。但是,假設你有另一個繼承自Animal的類Cat。你不能將貓傳遞給期望狗的功能(很容易),但是你可以將貓傳給動物。多態性在多個派生類中「唯一」有意義 – DaClown 2010-04-23 18:02:40

回答

11

對於類型檢查的所有目的,編譯器將a,如果它能夠指向任何動物,即使你知道它指向一個狗:

  • 你不可錯過a的功能期待Dog*
  • 您不能做a->fetchStick(),其中fetchStickDog的成員函數,但不是Animal
  • Dog *d2 = dynamic_cast<Dog*>(d)可能只是您的編譯器的指針副本。 Dog *d3 = dynamic_cast<Dog*>(a)可能不是(我在這裏推測,我不打算檢查任何編譯器,問題是:編譯器在轉換代碼時可能會對ad做出不同的假設)。

可以調用虛擬功能(即,所定義的多晶型接口)動物的通過它們中的同樣,具有相同的效果。假設Dog還沒有隱藏它們,無論如何(好點,JaredPar)。

對於在Animal中定義並在Dog中定義(重載)的非虛函數,通過a調用該函數與通過d調用該函數不同。

1

不,他們不一樣。

狗指針不像動物那樣多態。它只能在運行時指向Dog或Dog的子類。如果沒有Dog的子類,那麼Dog運行時類型和編譯時類型是相同的。

的動物指針可以指動物的任何子類:狗,貓,Wildebeast等

-1

這使得在運行時沒有真正的區別,因爲這兩種情況下是相同的。唯一的區別是在編譯時,你可以調用例如d-> bark()而不是a-> bark(),即使一個實際上包含一個狗。編譯器認爲這個變量只是一個動物而已。

+0

如果您要調用對象的非虛函數,則它很重要。 – tloach 2010-04-23 17:56:19

+0

*「它在運行時沒有實際區別,......唯一的區別是在運行時** – 2010-04-23 17:56:22

+0

是啊......我的意思是編譯時間對不起:] – Shtong 2010-04-23 17:57:12

4

回答這個問題是一個巨大的:這取決於

有許多方法,其中指針的類型可能會成爲重要的。 C++是一種非常複雜的語言,它顯示的方式之一就是繼承。

讓我們舉一個簡短的例子來說明這可能很重要的許多方法之一。

class Animal { 
public: 
    virtual void MakeSound(const char* pNoise) { ... } 
    virtual void MakeSound() { ... } 
}; 

class Dog : public Animal { 
public: 
    virtual void MakeSound() {... } 
}; 

int main() { 
    Animal* a = new Dog(); 
    Dog* d = new Dog(); 
    a->MakeSound("bark"); 
    d->MakeSound("bark"); // Does not compile 
    return 0; 
} 

爲什麼C++的名字查找方式很怪異。簡而言之:當尋找調用C++的方法時,會遍歷類型層次結構,尋找具有匹配名稱方法的第一個類型。然後它會從該類型聲明的名稱的方法中尋找正確的重載。由於Dog只聲明一個不帶參數的MakeSound方法,所以沒有過載匹配,並且編譯失敗。當您嘗試調用狗的方法,這些方法不是動物的方法

+0

這可以通過堅持一個」使用Animal :: MakeSound (const char *)「,我相信 – 2010-04-23 19:15:29

+0

@ dash-tom-bang它當然可以,但它只是行爲可以不同的一個例子 – JaredPar 2010-04-23 19:33:30

+0

沒有任何參數;;) – 2010-04-23 21:31:59

0

的區別是很重要的。在第一種情況下(指向Animal的指針),您必須先將指針指向Dog。另一個區別是如果你碰巧超載非虛擬方法。然後,將調用Animal :: non_virtual_method()(指向Animal的指針)或Dog :: non_virtual_method(指向Dog的指針)。

2

第一行允許您調用只動物類的成員上:

Animal *a = new Dog(); 
a->eat(); // assuming all Animal can eat(), here we will call Dog::eat() implementation. 
a->bark(); // COMPILATION ERROR : bark() is not a member of Animal! Even if it's available in Dog, here we manipulate an Animal. 

雖然(由別人指出的),在此作爲中科院是a還是一種動物,你不能提供a作爲函數的要求更具體的子類,它是狗的參數:

void toy(Dog* dog); 

toy(a); // COMPILATION ERROR : we want a Dog! 

第二行允許您使用子類的具體功能:

Dog *a = new Dog(); 
a->bark(); // works, but only because we're manipulating a Dog 

所以使用基類的類層次結構的「通用」接口(讓你讓所有的動物爲食()困擾內部消除有關如何)。當你調用使用指針虛函數

2

的區別是很重要的。假設動物和狗都有稱爲do_stuff()的函數。

  1. 如果動物:: do_stuff()被宣告虛擬的,調用do_stuff()在動物指針會叫的狗:: do_stuff()。

  2. 如果Animal :: do_stuff()未聲明爲虛擬,則在動物指針上調用do_stuff()將調用Animal :: do_stuff()。

這裏有一個完整的工作程序來演示:

#include <iostream> 

class Animal { 
public: 
     void do_stuff() { std::cout << "Animal::do_stuff\n"; } 
     virtual void virt_stuff() { std::cout << "Animal::virt_stuff\n"; } 
}; 

class Dog : public Animal { 
public: 
     void do_stuff() { std::cout << "Dog::do_stuff\n"; } 
     void virt_stuff() { std::cout << "Dog::virt_stuff\n"; } 
}; 

int main(int argc, char *argv[]) 
{ 
     Animal *a = new Dog(); 
     Dog *b = new Dog(); 

     a->do_stuff(); 
     b->do_stuff(); 
     a->virt_stuff(); 
     b->virt_stuff(); 
} 

輸出:

Animal::do_stuff 
Dog::do_stuff 
Dog::virt_stuff 
Dog::virt_stuff 

這只是一個例子。其他答案列出了其他重要的區別。

0

你必須始終記住有2件在每類中,數據和接口。

您的代碼真正創建堆上2級狗的對象。這意味着數據是Dog。 此對象的大小是所有數據成員Dog + Animal + vtable指針的總和。

的ponters a和d(左值)爲從一個接口點不同。這決定了你如何以明智的方式對待他們。所以即使Animal * a真的是一隻狗,即使Dog :: Bark()存在,也無法訪問a-> Bark()。 d-> Bark()可以正常工作。

添加虛函數表放回畫面,假設動物的接口已經動物::移動通用移動()和狗真的自動覆蓋與狗::移動(){像狗}。

即使你有Animal a *並執行了a-> Move()感謝vtable,你實際上會移動(){就像一隻狗}。發生這種情況是因爲在構建Dog()時,Animal :: Move()是重新指向Dog's :: Move()的(虛擬)函數指針。