[CPP]多态
多态
什么是多态
允许不同类的对象对同一消息(函数调用)做出不同的响应。简单来说,多态性是指同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
构成多态的条件
- 必须通过基类的指针或引用调用虚函数。
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。
静态多态和动态多态
静态多态
说白了就是函数的重载,当调用这个函数时,编译器会根据传入的实际参数来确定调用哪个具体的函数版本。是在程序编译期间确定了程序的行为。
动态多态
在基类中定义一个虚函数,然后在派生类中重写这个虚函数。通过基类指针或引用调用这个虚函数时,实际执行的函数版本是根据对象的实际类型(而不是指针或引用的类型)来决定的。
虚函数与重写
虚函数
虚函数是指被virtual关键字修饰的函数。如:
class Person
{
public:
virtual void BuyTicket()
{
std::cout << "全价票" << std::endl;
}
};
其中BuyTicket()就是虚函数。
重写(覆盖)
派生类中有一个跟基类完全相同的虚函数(函数名、参数列表(参数的类型、个数和顺序)以及返回值类型),称子类的虚函数重写了基类的虚函数。
class Student : public Person
{
public:
virtual void BuyTicket()
{
std::cout << "半价票" << std::endl;
}
};
在这两个代码片段中,Student类中的BuyTicket()重写了Person类的BuyTicket()。
虚函数重写的两个例外
-
协变
派生类重写基类虚函数时,与基类虚函数返回值类型不同。当派生类重写基类的虚函数时,协变允许派生类重写函数的返回值类型是基类虚函数返回值类型的派生类型。
class A {}; class B : public A {}; class Person { public: virtual A* f() { return new A; } }; class Student : public Person { public: virtual B* f() { return new B; } };在这个例子中,基类Person有一个虚函数f(),它返回一个A类的指针。派生类Student重写了这个函数,它返回一个B类型的指针。而B类又是A类的派生类,这就符合协变。
-
析构函数的重写
虽然派生类和基类析构函数名字不同,看起来违背了重写的规则,但是在编译后析构函数的名称统一处理为
destructor。
override 和 final 两个关键字
-
override
检查派生类虚函数是否重写了基类的某个虚函数,如果没有重写则报错。
class Person { public: virtual void BuyTicket() { std::cout << "全价票" << std::endl; } }; class Student : public Person { public: virtual void BuyTicket() override { std::cout << "半价票" << std::endl; } }; -
final
表示虚函数不能再被重写。
抽象类
在虚函数的后面加上 = 0,那么这个函数就是纯虚函数。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象。继承后的派生类也不能实例化出对象,只有当派生类重写了这个纯虚函数才能实例化出对象。
class Person
{
public:
virtual void BuyTicket() = 0;//纯虚函数
};
class Student : public Person
{
public:
virtual void BuyTicket() override
{
std::cout << "半价票" << std::endl;
}
};
多态的原理
首先,我们来看看Base类的大小是多少。(在32位的机器上)
class Base
{
public:
virtual void f1()
{
std::cout << "f1()" << std::endl;
}
private:
int _num;
};
答案是8个字节。成员变量不是只有一个int类型的吗?为什么是8个字节而不是4个字节呢?那是因为在Base类中,除了本身的int类型的变量外,还会有一个指针,这个指针指向虚函数表,虚函数表内存放的是虚函数的地址。这个指针叫虚函数表指针,这个表简称虚表。(注意和虚基表区分)
那么在派生类的虚表中又放了什么呢?
class Base
{
public:
virtual void f1()
{
std::cout << "Base::f1()" << std::endl;
}
virtual void f2()
{
std::cout << "Base::f2()" << std::endl;
}
private:
int _b;
};
class Derive : public Base
{
public:
virtual void f1()
{
std::cout << "Derive::f1()" << std::endl;
}
private:
int _d;
};

如上,我们有以下结论:
- 派生类对像也有一个虚表指针。
- 基类b对象和派生类d对象虚表是不一样的,这里我们发现
f1完成了重写,所以d的虚表中存的是重写的Derive::f1,所以虚函数的重写也叫作覆盖。 - 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个
nullptr。 - 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
此时,我们再来看买票的例子。
当传给func()函数的是父类对象的指针或引用时,p就在父类的虚函数表中找到对应的虚函数。当传的是子类对象时,就在子类的虚函数表中找到对应的虚函数。这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

浙公网安备 33010602011771号