class Pet {
protected:
字串名稱。
public:
Pet(string n)
{
name = n;
}
void run()
{
cout << name << " 。我正在運行" <<endl。
}
};
class Dog : public Pet{
public:
Dog(string n) : Pet(n) {};
void make_sound()
{
cout << name << " 。汪! 汪!" <<endl。
}
};
class Cat : public Pet {
public:
Cat(string n) : Pet(n) {};
void make_sound()
{
cout << name << " 。喵! 喵!"<<endl。
}
};
int main()
{
寵物 *a_pet1 = new Cat("Tom"/span>)。
寵物 *a_pet2 = new Dog("Spike") 。
a_pet1 -> run()。
// 'a_pet1 -> make_sound();'在這里是不允許的!。
a_pet2 -> run()。
// 'a_pet2 -> make_sound();' 這里不允許!
}
我無法弄清楚為什么這一點是無效的。請推薦合適的參考資料,這些資料對發生這種情況的原因有充分的解釋。
uj5u.com熱心網友回復:
在C 中,變數的型別和名稱在任何時候都是編譯器允許自己知道的。
每一行代碼都要根據當前范圍內的變數型別和名稱進行檢查。
當你有一個指向基類的指標時,該變數的型別仍然是指向基類的指標。 它所指向的實際物件可能是一個派生類,但該變數仍然是指向基類的指標。
寵物 *a_pet1 = new Cat("Tom"/span>) 。
a_pet1 -> run()。
// 'a_pet1 -> make_sound();' 在這里是不允許的!
a_pet1的型別是Pet*。 它可能指向一個實際的Cat物件,但這并不是a_pet1的型別所具有的資訊。
在下一行,您正在使用a_pet1。 你可以只以對Pet指標有效的方式在這一行使用它。 a_pet1->make_sound()不是對Pet指標的有效操作,因為Pet型別沒有一個make_sound方法。
你可以這樣做:
Cat *a_pet1 = new Cat("Tom"/span>) 。
a_pet1 -> run()。
a_pet1 -> make_sound(); // it now works!
因為我們把a_pet1的型別從Pet*改為Cat*。 現在編譯器允許自己知道a_pet1是一只Cat,所以呼叫Cat方法是允許的。
如果你不想改變a_pet1的型別(這是一個合理的要求),這意味著你想在Pet上支持make_sound,你必須把它添加到Pet型別中:
class Pet {
protected:
字串名稱。
public:
Pet(string n)
{
name = n;
}
void make_sound。
void run()
{
cout << name << " 。我正在運行" <<endl。
}
};
現在,a_pet1->make_sound()將被允許。 它將試圖呼叫 Pet::make_sound,這不是 Dog::make_sound,由于我們沒有為Pet::make_sound提供一個定義,這將導致在鏈接時出現錯誤。
如果您希望 Pet::make_sound 派遣到其派生方法,您必須告訴編譯器這是您想要的。 如果你正確地使用virtual關鍵字,C 將為你寫出調度代碼,就像這樣:
class Pet {
protected:
字串名稱。
public:
Pet(string n)
{
name = n;
}
virtual void make_sound()= 0;
void run()
{
cout << name << " 。我正在運行" <<endl。
}
};
在這里,我既讓make_sound virtual,又讓它成為純虛擬。 使其成為虛擬的意味著編譯器為每個Pet和Pet派生物件添加資訊,因此,當它實際指向派生物件型別而不是Pet時,呼叫者可以找到正確的派生方法。
純虛擬(=0)只是告訴編譯器基類方法Pet::make_sound故意沒有實作,這也意味著沒有人被允許創建一個Pet,甚至是一個Pet派生物件實體,而不為其實際型別提供一個make_sound實作。
最后,請注意,我提到了 "允許自己知道"。 編譯器在編譯的某些階段會限制它所知道的內容。 你說a_pet1是一個Pet*,這告訴編譯器 "我不希望你認為這是一個Cat,盡管我把一個Cat放在這里"。 在編譯的后期階段,編譯器可以記住這個事實。 即使在運行時,有時也可以確定一個物件的實際型別(使用RTTI)。 對物件型別的遺忘既是有意的,也是有限的。
事實證明,"遺忘 "是指對物件型別的遺忘。
事實證明,"強制遺忘 "在許多軟體工程問題中是相當有用的。
還有一些語言,對所有物件的所有方法呼叫都要經過動態調度系統,除了在運行時進行嘗試,你永遠不知道一個物件是否能接受方法呼叫。 在這樣的語言中,在任何物件上呼叫make_sound都會被編譯,而在運行時,它要么失敗,要么不失敗,取決于該物件是否真的有make_sound方法。 C 故意不這樣做。 有一些方法可以獲得這種能力,但它們相對來說比較深奧。
uj5u.com熱心網友回復:
在你的例子中a_pet1和a_pet2是指向'Pet'類物件的指標,所以你的編譯器只允許你訪問該類中實際可用的函式。在這種情況下,'寵物'類本身并不包含'make_sound'函式。為了解決這個問題,你可以在基類中定義一個 "make_sound "函式,并將其標記為 "虛擬"。這將使基指標上的函式呼叫總是呼叫繼承類中的相應函式的執行。
class Pet {
protected:
字串名稱。
public:
Pet(string n)
{
name = n;
}
void run()
{
cout << name << " 。我正在運行" <<endl。
}
virtual void make_sound()<}{}。
};
class Dog : public 寵物 {
public:
Dog(string n) : Pet(n) {};
void make_sound() override
{
cout << name << " 。汪! 汪!" <<endl。
}
};
class Cat : public Pet {
public:
Cat(string n) : Pet(n) {};
void make_sound() override
{
cout << name << " 。喵! 喵!"<<endl。
}
};
int main()
{
寵物* a_pet1 = new Cat("Tom"/span>)。
寵物* a_pet2 = new Dog("Spike") 。
a_pet1->run()。
a_pet1->make_sound()。
a_pet2->run()。
a_pet2->make_sound()。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/320059.html
標籤:
