本篇涉及内容:
- 多态的定义
- 虚函数
- 静态绑定和动态绑定
多态的定义:
多态分为两类
- 静态多态:在同一个类中,函数重载和运算符重载都属于静态多态,即函数在相同的函数名情况下,存在着不同的状态,体现了多态特征.
- 动态多态:一个父类可能会有多个子类,当子类重写了父类的虚函数后,用父类引用or指针指向子类对象,调用重写的函数时,会根据子类对象的不同来动态调用被重写的函数.
虚函数:
- 虚函数指针:当一个类中存在虚函数时,这个类就当且仅当存在一个虚函数指针(4个字节大小),虚函数指针指向的是这个类独有虚函数表.
- 虚函数表:一个类对应一个虚函数表,这个表中记录了本类中所有虚函数地址.
我们来看一段代码:
class Animal {
public:
virtual void speak() {
cout << "动物在说话!" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "猫咪在说话!" << endl;
}
};
根据这段代码来画一下Animal类和Cat类中的内部结构
有几点需要注意:
- 父类中的虚函数指针也能被子类继承过去
- 一个类中无论有多少个虚函数,这个类中的虚函数指针当且仅当只有一个.
- 一个类有多个实例化对象时,每个对象的虚函数指针指向的都是同一张虚函数表.
- 子类从父类那边继承过来的虚函数,在子类虚函数表中的地址依旧是父类中的地址.
- 子类如果重写了父类的虚函数,那么子类虚函数表中的原来的父类虚函数的地址会被重写的函数地址覆盖掉,如果子类中没有实现虚函数重写,那么其虚函数表保存的还是父类中的虚函数地址.
静态绑定和动态绑定:
-
静态绑定
在编译期就确定调用具体函数称为“静态绑定”又称先期联编,实现”静态绑定“的机制有:函数重载、运算符重载 -
动态绑定
在运行期才确定调用哪个函数称为“动态绑定”又称迟后联编,实现”动态绑定“的机制是通过虚函数
我们先来看一段代码
class Animal {
public:
void speak() {
cout << "动物在说话!" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "猫咪在说话!" << endl;
}
};
void main() {
Cat cat;
Animal& animal = cat;
animal.speak();
}
我们可以看见Animal类和Cat类之间是父类和子类关系,因此可以有Animal& animal = cat;
,在Animal类和Cat类中都有speak函数,那么用animal这个引用调用speak函数,是执行的Animal类中的speak函数还是Cat类中的speak函数呢?
由打印结果可以知道执行的Animal类中的speak函数,下面让我们修改一下代码,把Animal类中的speak函数前上virtual
关键字,再来看看打印结果是啥.
class Animal {
public:
virtual void speak() {
cout << "动物在说话!" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "猫咪在说话!" << endl;
}
};
void main() {
Cat cat;
Animal& animal = cat;
animal.speak();
}
此时用animal引用调用的speak函数是Cat类中的speak函数,为什么会出现这样的情况?下面来详细介绍.
1)当成员函数不是虚函数的时候
//此时Animal类中的speak函数为普通成员函数
Cat cat;
Animal& animal = cat;
animal.speak();
此时在编译期会根据引用or指针类型来确定调用哪个函数,由于animal这个引用的类型为Animal,而speak函数又为普通的成员函数,所以通过静态绑定确定调用的函数为Animal类中的speak函数.
2)当成员函数为虚函数时
//此时Animal类中的speak函数为普通成员函数
Cat cat;
Animal& animal = cat;
animal.speak();
此时在编译期阶段上述代码并不能确定调用的是父类还是子类中的speak函数,只有在程序运行期阶段,根据引用or指针指向的对象中的虚函数指针,找到对应的虚函数表,得到函数地址才能知道调用哪个方法,此时通过引用or指针调用的函数确定与引用or指针类型无关.
简单的说
当用父类型引用or指针指向子类型对象时:
- 通过引用or指针调用普通成员函数,在编译期已经确定了调用的是父类的函数.
- 通过引用or指针调用虚函数,在运行期根据指向的对象来调用子类对象中的函数.
注意动态绑定的触发条件:
- 要求父类型引用or指针指向子类型对象
- 父类型中有虚函数
- 子类重写父类的虚函数
- 用这个父类型引用or指针调用虚函数