一个函数接受一个基类的指针或者引用,传入一个子类的指针或者引用(向上类型转换),希望调用子类的相应函数。目的:以后添加新的子类,都可以传入该函数。
早绑定:编译器通过上下文,判断该函数属于哪个对象,并在编译期将函数名与函数地址绑定。
晚绑定:在运行的时候,判断该函数属于哪个对象,并在运行时将函数名与函数地址绑定。必须有类型信息装在对象自身中。
声明时添加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 阅读(235)
评论(0) 编辑 收藏 引用