virtual关键字

一、基本概念

多态,是C++中一个很棒的特性——“无论发送消息的对象属于什么类,它们均发送具有同一形式的消息,对消息的处理方式可能随接手消息的对象而变”的处理方式被称为多态性。其实现是基于虚函数机制的。这是一种泛型技术。所谓泛型技术,说简单些就是使用不变的代码实现可变的算法。比如:模板、RTTI、虚函数。

虚函数,是一种成员函数,它可以在该类的派生类中被重新定义并被赋予另外一种处理功能。示例如下:

class Base
{
public:
    ...
    virtual void Func1(int a){ cout << "Base::Func1()" << endl; }
};

class Derive : public Base
{
public:
    ...
    virtual void Func1(int a){ cout << "Derive::Func1()" << endl; }
};


二、含有虚函数的类对象在内存中的结构

先来看一段示例代码:

class Base0
{
public:
    ...
    virtual void Func0(int a){ cout << "Base0::Func0()" << endl; }
};

class Base1
{
public:
    ...
    virtual void Func1(int a){ cout << "Base1::Func1()" << endl; }
};

class Base2
{
public:
    ...
    virtual void Func1(int a){ cout << "Base2::Func1()" << endl; }
    virtual void Func2(int a){ cout << "Base2::Func2()" << endl; }
};

class Derive1 : public Base1
{
public:
    ...
};

class Derive2 : public Base1
{
public:
    ...
    virtual void Func1(int a){ cout << "Derive2::Func1()" << endl; }
};

class Derive3 : public Base1
{
public:
    ...
    virtual void Func1(int a){ cout << "Derive3::Func1()" << endl; }
    virtual void Func2(int a){ cout << "Derive3::Func2()" << endl; }
};

class Derive4 : public Base0, public Base1
{
public:
    ...
    virtual void Func3(int a){ cout << "Derive4::Func3()" << endl; }
};

int main(int argc, char* argv[])
{
    cout << "Size of Base0 = " << sizeof(Base0) << endl;
    cout << "Size of Base2 = " << sizeof(Base2) << endl;
    cout << "Size of Derive1 = " << sizeof(Derive1) << endl;
    cout << "Size of Derive2 = " << sizeof(Derive2) << endl;
    cout << "Size of Derive3 = " << sizeof(Derive3) << endl;
    cout << "Size of Derive4 = " << sizeof(Derive4) << endl;
    return 0;
}

上面代码的运行结果如下:

Size of Base0 = 4

Size of Base2 = 4

Size of Derive1 = 4

Size of Derive2 = 4

Size of Derive3 = 4

Size of Derive4 = 8

为什么会出现这个情况呢?编译器做了些什么呢?

实际上,在含有虚函数的类中,就必然存在一个虚函数表的指针vp,而在32位系统中,指针是占4个字节的。那么,上面的运行结果也就不是那么难以接受了。

另外,还有一些额外的发现:在单继承体系中,只有一张虚函数表vtbl,也就只有一个虚函数表指针vp;而在多继承体系中,却是有多个vtbl的,也就有多个vp。

PS:关于多重继承,请慎用!

 

三、什么情况下使用virtual

上面啰嗦了一堆,那么,到底什么情况下需要用到virtual关键字呢?

1.修饰成员函数

当virtual用来修饰成员函数时,就是它对多态性支持的最佳体现。

举个例子,有几个类:Dog、Pig、Turkey、Cat,他们都有相同的成员函数Eat(food)。当到了饭点,大家要吃饭时,编写的代码是如下的:

Dog dog;
Pig pig;
Turkey turkey;
Cat cat;
dog.Eat(food);
pig.Eat(food);
turkey.Eat(food);
cat.Eat(food);

动物种类少的时候,这样还看不出什么,如果有上千种动物呢?这么一行一行写下去,简直不敢想象。如此缺乏灵活性的代码,是优雅的C++程序员不可忍受的。

那么,如何解决呢?

我们可以添加一个类Animal,如下:

class Animal
{
public:
    ...
    virtual void Eat(Food food) = 0;
};

再让上面的Dog、Pig、Turkey、Cat继承自Animal。这样,在使用时,代码就变成了:

Animal* arrayAnimal[MAX_ANIMAL_KINDS];

arrayAnimal[0] = new Dog;
arrayAnimal[1] = new Pig;
arrayAnimal[2] = new Turkey;
arrayAnimal[3] = new Cat;

for (int i=0; i<MAX_ANIMAL_KINDS; i++)
{
    if (arrayAnimal[i])
        arrayAnimal[i]->Eat(food);
}

细心的朋友应该发现了上面Animla类中的虚函数有所不同,

virtual void Eat(Food food) = 0;

这是纯虚函数,简而言之,纯虚函数为派生类提供了可覆盖的接口,但是基类中的版本决不会被调用。

含有一个或多个纯虚函数的类叫做抽象基类。抽象基类不能实例化。派生类只有重写这个接口才能实例化。

PS:友元函数、static函数、构造函数不能用virtual;普通成员函数和析构函数可以用virtual关键字修饰。


2.虚继承

虚继承是用来解决菱形继承带来的问题。示例代码如下:

class Base
{
public:
    ...
    void func(){}
};

class Derive1 : public Base
{
public:
    ...
    void func(){}
};

class Derive2 : public Base
{
public:
    ...
    void func(){}
};

class Derive3 : public Derive1, public Derive2
{
public:
    ...
};

void main()
{
    Derive3* p = new Derive3;
    p->func();
    delete p;
    return 0;
}

编译时报错提示对func的访问不明确。如果Derive1和Derive2都用虚继承继承Base,则可以解决这个问题。

 

四、虚析构函数

虚析构函数可以确保在对象析构时,按照派生类->基类的顺序依次执行析构函数。

 

五、构造函数和析构函数中调用虚函数

如果在构造函数和析构函数中调用虚函数,则执行的是构造函数和析构函数所在类型定义的版本。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值