多态是面向对象编程内极为重要的一种特征,它能极大的简化我们的代码
先来看一段类的原型
class Animal //基类
{
public:
void voice()
{
std::cout << "Voiceless" << std::endl; //不知道该叫啥
};
};
class Dog : public Animal //狗派生类,继承了Animal
{
public:
void voice()
{
std::cout << "Woof" << std::endl; //汪汪叫
}
};
class Cat : public Animal //猫派生类,继承了Animal
{
public:
void voice()
{
std::cout << "Meow" << std::endl; //喵喵叫
}
};
我们可以看到,基类和派生类中都有函数voice(),它可以让不同的动物发出不同的叫声,其中,Dog类和Cat类的voice()函数是覆写了基类中的同名函数。
当然,还可以有很多基于Animal类的派生类,都可以有叫声,但是我们如果想统一一下让它们叫的方法,比如用个train函数,传入对象或者它的指针就可以自动调用它的voice函数。虽然Cpp支持重载函数,但是为每个不同的类各写一个这样的函数,工作量不仅大,而且枯燥。多态就是帮我们解决这个问题的。
多态 Polymorphism
表现方式有两种
- 重载多态,比如函数重载(本文不介绍了),属于静态联编
- 子类型多态,属于动态联编,就是我接下来要说的那些
子类型多态有两个要点:
- 虚函数
- 函数覆写override
我们来区分涉及继承链情况下的两种联编:
- 通过派生类对象访问同名函数,是静态联编
- 通过基类对象的指针访问同名函数,是静态联编
- 通过基类对象的指针或引用访问同名虚函数,是动态联编
那我们现在改一下原来的类的定义让它符合子类型多态的要求
class Animal
{
public:
virtual void voice() //此函数声明为虚函数后,派生类中无须再注明
{
std::cout << "Voiceless" << std::endl;
};
};
class Dog : public Animal
{
public:
void voice() override //覆写了voice函数
{
std::cout << "Woof" << std::endl;
}
};
class Cat : public Animal
{
public:
void voice() override //覆写了voice函数
{
std::cout << "Meow" << std::endl;
}
};
那么还是刚刚那个例子,我们设计一个train函数
void train(Animal animal)
{
animal.voice();
}
使用这个函数,你会发现无论你往参数里塞了什么阿猫阿狗的对象,输出的永远是“Voiceless”,这便是静态联编的问题了,它使用的是基类的对象而非基类对象的指针或引用,你需要把train修改成这样:
void train(Animal& animal)
{
animal.voice();
}
//或者这样
void train(Animal* animal)
{
animal->voice();
}
然后你的子类对象在调用这个train函数之后就可以正常发出正确的声音了
需要注意的是,如果voice函数不是虚函数,那么即使你用了派生类对象的指针、引用,只要那个进入函数的东西的类型是基类,它就会调用基类的函数
一句话总结一下,就是:函数虚,看对象;函数实,看类型。
但是,这个东西它也有问题,就是你使用基类的指针(或者引用)指向了派生类的对象的时候,你只能调用派生类的虚函数,无法调用非虚函数,例如我改一下Dog类的定义:
class Dog : public Animal
{
public:
void voice() override
{
std::cout << "Woof" << std::endl;
}
void foo() //这个不是虚函数了
{
std::cout << "Foo!" << std::endl;
}
};
但是我在main函数里这样定义:
Animal ptr;
Dog dog = new Dog();
ptr = &dog;
ptr->voice(); //调用的是dog对象的voice函数
ptr->foo(); //无法调用,No member named foo in Animal
Animal类的指针虽然指向了dog,但是它将无法调用dog的非虚函数,但是如果我们偏要调用属于子类的非虚函数呢?
Introducing dynamic_cast!
Cpp的dynamic_cast<>运算符可以把基类类型的指针转换为派生类的类型的指针,就可以使调用子类的非虚函数称为可能,方法如下
ptr->foo(); //它出了问题
dynamic_cast<Dog*>(ptr)->foo(); //正常调用!
虽然建议在任何基类与派生类间的指针、引用的类型转换都使用dynamic_cast运算符,但是实际上,派生类的指针(引用)转为基类类型的指针(引用)可以不使用该运算符而隐式转换,而基类的指针(引用)转为派生类类型的指针(引用)必须显式使用该运算符以完成类型转换。
比如我们有:
Animal animal;
Animal* pa;
Cat cat;
Cat* pc;
pa = &cat; //可以隐式转换(下转上)
pc = dynamic_cast<Cat*>(animal); //必须显式转换(上转下)
多态就先写这么多~