一個函數接受一個基類的指針或者引用,傳入一個子類的指針或者引用(向上類型轉換),希望調用子類的相應函數。目的:以后添加新的子類,都可以傳入該函數。
早綁定:編譯器通過上下文,判斷該函數屬于哪個對象,并在編譯期將函數名與函數地址綁定。
晚綁定:在運行的時候,判斷該函數屬于哪個對象,并在運行時將函數名與函數地址綁定。必須有類型信息裝在對象自身中。
聲明時添加virtual關鍵字,定義時不需要。
使用指針和引用的目的是讓編譯器不能完全知道該對象的確切類型,不然就會調用早綁定。晚綁定是根據VTABLE來實現,并且基類和子類的每個虛函數的排列順序都是相同的,所以調用函數的時候已經不是通過名字來調用,而是通過指令,通過函數地址的偏移量來調用了。
抽象基類的意義,為子類提供一個公共的接口。
通過基類指針調用基類中不存在的函數是危險的,因為,也許你恰好知道子類對象中有這個函數,那你的調用時成功的,但是萬一木有呢?
class Base(){
public:
virtual void f(){}
};
class Derived1: public Base
{
public:
virtual void f(){}
virtual void g(){}
};
class Derived2: public Base
{
public:
virtual void f(){}
virtual void m(){}
};
void func(Base* b){
b->g();
}
int main{
Base*Test1 = new Derived1;
Base*Test2 = new Derived2;
func(Test1);// right;
func(Test2);// crash
}
這里涉及到運行時類型識別(RTTI)和向下類型轉換問題。向下類型轉換不安全,因為沒有類型信息,基類指針不知道基類的內存塊之后的東西是屬于哪個子類的,如果轉錯,將會比較麻煩。
在編程時注意防止對象的切片,如果按傳值方式而不是傳址和傳引用方式
將子類對象傳入一個接受基類對象的函數中去的話,那么,只拷貝子類對象中基類的部分數據,又因為編譯器能明確地知道該對象的類型,所以不會產生晚綁定,而是早綁定。我們應該避免在這種情況下傳值。
如果重新定義了基類中的虛函數,則基類中其他重載版本將被隱藏。(同非虛函數一樣)
如果重載了基類中的虛函數,則基類中其他版本將被隱藏(同非虛函數一樣)
不能在子類中修改基類中虛函數的返回值(非虛函數可以修改返回值,并且隱藏其他重載版本)
但是,也有特例
class PetFood{
public:
virtual string foodType() const = 0;
};
class Pet{
public:
virtual string type() const = 0;
virtual PetFood* eat() = 0;
};
class Bird : public Pet{
public:
string type()const {return "bird";}
class BirdFood : public PetFood{
public:
string foodType()const{
return "Bird food";
}
};
PetFood* eat(){ return &bf;}
private:
BirdFood bf;
};
class Cat: public Pet{
public:
string type()const {return "cat";}
class CatFood : public PetFood{
public:
string foodType()const{
return "Cat food";
}
};
CatFood* eat(){ return &cf;}// Here, you can return a CatFood*, because it's a PetType* type. Why don't return a type as PetFood? See segment in main()
private:
CatFood cf;
};
int main(){
Bird b;
Cat c;
Cat::CatFood* cf = c.eat();
Bird::BirdFood* bf = b.eat();//downcast, warning!!!Cast PetFood* to BirdFood. So you better return a special pointer, not a base type.
}
}
返回確切的類型要更通用些。
vptr vtable由誰來初始化?構造函數?是編譯器插入一小段代碼在構造函數中初始化。
派生類只訪問它自己的成員,而不訪問基類的成員。只有基類的構造函數才能正確地初始化自己的成員。所以要在構造函數中:子要可能,我們應該在這個構造函數初始化列表中從初始化所有的成員對象(通過組合置于類中),因為你必須保證所有的東西都被初始化了,才能使用該對象。
在構造函數中調用虛函數,調用的只是本地版本。
原因:該對象還未初始完畢,但是vptr已經初始化,而且指向自己的vtable,所以調用的只是本地的函數。
1
posted on 2012-06-04 16:27
Dino-Tech 閱讀(238)
評論(0) 編輯 收藏 引用