多态
向上类型转换
对象可以作为它自己的类或它的基类的对象来使用。另外,还能通过基类的地址操作它。取一个对象的地址(指针或引用),并将其作为基类的地址来使用,这被称为向上类型转换(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
中。wind
到instrument
的向上类型转换会使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
这个例子只是多了几个继承类和层次