多态

本文详细探讨了C++中的多态性,包括向上类型转换、虚函数、抽象基类和纯虚函数的概念及实现。通过实例展示了如何通过虚函数实现动态绑定,解释了类型信息的存放和VTABLE的作用。同时,文章讨论了构造函数和析构函数中的虚函数行为,以及向下类型转换的安全方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多态


向上类型转换

对象可以作为它自己的类或它的基类的对象来使用。另外,还能通过基类的地址操作它。取一个对象的地址(指针或引用),并将其作为基类的地址来使用,这被称为向上类型转换(upcasting)。甚至可以直接把一个派生类的对象赋值给基类的对象。

#include <iostream>
using namespace std;
enum note{ middleC, Csharp, Eflat };

class Instrument{
public:
    void play(note) const{
        cout << "Instrument::play" << endl;
    }
};

class Wind: public Instrument{
public:
    void play(note) const{
        cout << "Wind::play" << endl;
    }
};

void tune(Instrument& i){
    i.play(middleC);
}
int main(){
    Wind flute;
    tune(flute);    //Upcasting
}

函数tune()通过引用接受一个Instrument,同样能够接受一个从Instrument继承而来的类的对象。这里无需类型转换,就能将wind对象传给tune()。在instrument中的接口必定存在于wind中。windinstrument的向上类型转换会使wind的接口变窄,但是不会窄过instrument的整个接口。
上面程序的输出是:

Instrument::play

我们传递给tune()的参数是Wind&,他也是Instrument&,所以它调用了Instrument::play()。然而实际中,我们可能并不想这么做,我们想要派生类对象使用派生类的方法,即调用Wind::play(),虽然tune()函数的形参类型是基类指针。

仔细思考一下tune()函数的执行过程。它接受一个Instrument地址,由于继承是一种is-a的关系,它也可以接受一个instrument派生类对象的地址,这种向上类型转换是自然的。在tune()函数内部,它调用这个对象的play()方法。这个函数不知道Instrument有多少种派生类,但是它需要对每一个派生类做出相应的play()调用,那么它怎么由一个对象地址得到该对象所需要的play函数地址呢?程序编译期间是无法确定的,因为只有在程序执行的过程中才知道传递给tune()的形参的类型(我们就是要这种功能)。这就需要这样一个地址能够自己说明自己的类型信息,即它在继承树的位置(它是Instrument还是Wind)。

虚函数

上面讲到的是动态绑定技术,一个函数调用只有在实际运行期间才能知道函数入口地址。它与普通的函数调用有很大差别,前面的例子程序就是普通函数。为了引起动态绑定,C++要求在基类中声明这个函数的时候使用virtual关键字(定义的时候并不需要)。动态绑定只对virtual函数起作用。如果一个基类的函数是virtual的,那么他的派生类中的此函数也是virtual的(可以不用virtual关键字声明)。在派生类中virtual的函数的定义叫重写(overriding)(一般的函数的定义叫重定义)。

前面的例子中在Instrument类中play()的声明前加virtual,程序的输出就变成了Wind::play.

#include <iostream>

using namespace std;

enum note{ middleC, Csharp, Cflat};

class Instrument{
public:
    virtual void play(note) const {
        cout << "Instrument::play\n";
    }
    virtual char* what() const{
        return "Instrument";
    }
    virtual void adjust(int){}
};

class Wind: public Instrument{
public:
    void play(note)const {
        cout << "Wind::play\n";
    }
    char* what() const{
        return "Wind";
    }
    void adjust(int){}
};

class Percussion: public Instrument{
public:
    void play(note) const {
        cout << "Percussion::play\n";
    }
    char* what() const { return "Percussion";}
    void adjust(int){}
};

class Stringed: public Instrument{
public:
    void play(note) const {
        cout << "Stringer::play\n";
    }
    char* what() const {
  return "Stringed";}
    void adjust(int){}
};

class Brass: public Wind{
public:
    void play(note) const{
        cout << "Brass::play" << endl;
    }
    char* what() const { return "Brass";}
};

class WoodWind: public Wind{
public:
    void play(note) const {
        cout << "WoodWind::play" << endl;
    }
    char* what() const { return "WoodWind";}
};

void tune(Instrument& i){
    i.play(middleC);
}

void f(Instrument& i){ i.adjust(1);}

Instrument* a[] = {
        new Wind,
        new Percussion,
        new Stringed,
        new Brass
};

int main(){
    Wind flute;
    Percussion drum;
    Stringed violin;
    Brass flugelhorn;
    WoodWind recorder;
    tune(flute);
    tune(drum);
    tune(violin);
    tune(flugelhorn);
    tune(recorder);
    f(flugelhorn);
    for(int i = 0; i < 4; i++)
        tune(*a[i]);
}

运行结果:

Wind::play
Percussion::play
Stringer::play
Brass::play
WoodWind::play
Wind::play
Percussion::play
Stringer::play
Brass::play

这个例子只是多了几个继承类和层次࿰

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值