假設我有一個從類Animal
繼承的類Dog
。這兩行代碼有什麼區別?基類指針vs繼承類指針?
Animal *a = new Dog();
Dog *d = new Dog();
其中一個指針指向基類,另一指針指向派生類。但是,這種區分何時變得重要?對於多態性,任何一個都可以完全相同,對嗎?
假設我有一個從類Animal
繼承的類Dog
。這兩行代碼有什麼區別?基類指針vs繼承類指針?
Animal *a = new Dog();
Dog *d = new Dog();
其中一個指針指向基類,另一指針指向派生類。但是,這種區分何時變得重要?對於多態性,任何一個都可以完全相同,對嗎?
對於類型檢查的所有目的,編譯器將a
,如果它能夠指向任何動物,即使你知道它指向一個狗:
a
的功能期待Dog*
。a->fetchStick()
,其中fetchStick
是Dog
的成員函數,但不是Animal
。Dog *d2 = dynamic_cast<Dog*>(d)
可能只是您的編譯器的指針副本。 Dog *d3 = dynamic_cast<Dog*>(a)
可能不是(我在這裏推測,我不打算檢查任何編譯器,問題是:編譯器在轉換代碼時可能會對a
和d
做出不同的假設)。可以調用虛擬功能(即,所定義的多晶型接口)動物的通過它們中的同樣,具有相同的效果。假設Dog
還沒有隱藏它們,無論如何(好點,JaredPar)。
對於在Animal中定義並在Dog中定義(重載)的非虛函數,通過a
調用該函數與通過d
調用該函數不同。
不,他們不一樣。
狗指針不像動物那樣多態。它只能在運行時指向Dog或Dog的子類。如果沒有Dog的子類,那麼Dog運行時類型和編譯時類型是相同的。
的動物指針可以指動物的任何子類:狗,貓,Wildebeast等
回答這個問題是一個巨大的:這取決於
有許多方法,其中指針的類型可能會成爲重要的。 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
方法,所以沒有過載匹配,並且編譯失敗。當您嘗試調用狗的方法,這些方法不是動物的方法
這可以通過堅持一個」使用Animal :: MakeSound (const char *)「,我相信 – 2010-04-23 19:15:29
@ dash-tom-bang它當然可以,但它只是行爲可以不同的一個例子 – JaredPar 2010-04-23 19:33:30
沒有任何參數;;) – 2010-04-23 21:31:59
的區別是很重要的。在第一種情況下(指向Animal的指針),您必須先將指針指向Dog。另一個區別是如果你碰巧超載非虛擬方法。然後,將調用Animal :: non_virtual_method()(指向Animal的指針)或Dog :: non_virtual_method(指向Dog的指針)。
第一行允許您調用只動物類的成員上:
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
所以使用基類的類層次結構的「通用」接口(讓你讓所有的動物爲食()困擾內部消除有關如何)。當你調用使用指針虛函數
的區別是很重要的。假設動物和狗都有稱爲do_stuff()的函數。
如果動物:: do_stuff()被宣告虛擬的,調用do_stuff()在動物指針會叫的狗:: do_stuff()。
如果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
這只是一個例子。其他答案列出了其他重要的區別。
你必須始終記住有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()的(虛擬)函數指針。
對於這種特殊情況,它的工作原理是一樣的。但是,假設你有另一個繼承自Animal的類Cat。你不能將貓傳遞給期望狗的功能(很容易),但是你可以將貓傳給動物。多態性在多個派生類中「唯一」有意義 – DaClown 2010-04-23 18:02:40