目录
前言:虚函数机制为C++引入了一些动态特性,使得C++编程更加安全,拓展性更强,可维护性更好
1.函数的动态绑定(多态实现,动态获取对象类型,从而进行后续的行为)
什么是函数的绑定:一个函数的调用和函数的实现关联起来的过程;
为什么需要动态绑定,一般A类型指针指向A类型对象,B类型指针指向B类型对象,但在多态情况下,需要A类型指针指向B类型对象,这时候就需要函数的动态绑定
前言:虚函数机制为C++引入了一些动态特性,使得C++编程更加安全,拓展性更强,可维护性更好
1.什么是虚函数
虚函数:C++中使用virtual声明的函数,其背后会有复杂的机制,通过这套机制为C++引入了一些动态特性;
2.2个重要的动态特性
1.函数的动态绑定(多态实现,动态获取对象类型,从而进行后续的行为)
2.运行时候类型识别
3.函数的绑定含义和静态绑定
什么是函数的绑定:一个函数的调用和函数的实现关联起来的过程;
C++默认的静态绑定,在查找和关联的过程中是在编译期间完成的,普通函数,重载函数和成员函数都是静态绑定;
#include <iostream>
using namespace std;
//普通函数
void dosomthing(){
}
//重载函数
void func(int){}
void func(double){}
//成员函数
struct _myClass
{
void dosomthing(){}
};
void test(){
//都是静态绑定,能够提高程序的性能
dosomthing();
func(100);
_myClass my;
my.dosomthing();
}
4.函数动态绑定
为什么需要动态绑定,一般A类型指针指向A类型对象,B类型指针指向B类型对象,但在多态情况下,需要A类型指针指向B类型对象,这时候就需要函数的动态绑定
#include <iostream>
using namespace std;
struct _animal
{
void speak() {
cout << "Animal speak" << endl;
}
};
struct _dog : _animal
{
void speak() {
cout << "Dog speak" << endl;
}
};
struct _cat : _animal
{
void speak() {
cout << "Cat speak" << endl;
}
};
void test()
{
_animal* animal = nullptr;
animal = new _dog;
animal->speak();
animal = new _cat;
animal->speak();
}
int main()
{
test();
return 0;
}
运行结果
为什么会这样
原因是这时候还是静态绑定,编译的时候指针类型会指向什么类型的对象
那怎么解决这个问题,这时候就需要函数的动态绑定;
怎么解决,函数的绑定的行为不在编译期间完成,在运行的时候检查指针类型的运行空间进行函数的动态绑定即可,这时候就需要用到虚函数机制;
1.编译期间,编译器检查到类有虚函数的时候,就不会简单的根据对象类型进行函数绑定
2.运行阶段,会根据实际的对像类型进行函数的绑定,然后调用;
struct _animal
{
virtual void speak() {
cout << "Animal speak" << endl;
}
};
子类可以加上virtual,也可以不加;
4.虚函数实现动态绑定的原理
当类中有虚函数的时候,编译器会在对象的第一个数据成员的位置生成一个vfptr指针(构造函数中进行初始化),这个指针指向虚函数的地址的一个数组,这个数组就是虚函数表;
子类如果没有重写基类的虚函数的时候,子类继承基类的虚函数表,vfptr指针指向基类的函数;
子类如果重写了基类的虚函数的时候,虚函数表就会重新被覆盖,vfptr指针会指向子类的虚函数表中对应的函数;调用对应的函数的时候,会调用虚函数指针去找到虚函数表,在虚函数表中找到要执行函数的地址然后执行;
注意的地方:函数的静态绑定的效率比动态绑定的效率高;
4.1虚函数表的生成与共享
编译器会为每一个包含虚函数的类生成虚函数表,该类型的所有对象共享一个虚函数表,这个表包含了该类还有基类的所有虚函数的地址;
#include <iostream>
using namespace std;
struct _animal
{
virtual void speak() {
cout << "Animal speak" << endl;
}
};
struct _dog : _animal
{
void speak() {
cout << "Dog speak" << endl;
}
};
struct _cat : _animal
{
void speak() {
cout << "Cat speak" << endl;
}
};
void test()
{
//虚函数表中一个地址是一级指针,vfptr指向的数组是二级指针,那么vfptr是三级指针
_animal* animal = nullptr;
animal = new _animal;
void** vfptr = *(void***)animal;
cout << "Animal 虚函数表地址" << vfptr << endl;
animal = new _dog;
vfptr = *(void***)animal;
cout << "Dog 虚函数表地址" << vfptr << endl;
animal = new _cat;
vfptr = *(void***)animal;
cout << "Cat 虚函数表地址" << vfptr << endl;
// vfptr = *(void***)animal;
//void(*p_func)() = reinterpret_cast<void(*)()>(vfptr[1]);
}
int main()
{
test();
return 0;
}
运行结果如下,发现每个类的虚函数地址都是不一样的
4.2虚函数表的初始化与销毁
虚函数表的初始化和销毁由编译器完成,在对象的构造过程中,编译器会确保对象的虚函数指针指向正确的虚函数表,一般程序结束的时候会销毁虚函数表;
4.3多重继承和虚函数表
多重继承的情况下,可能存在多个虚函数表
5.C++运行时候的类型识别(RTTI)
1.RTTI应用场景
C++ 中用typeid和dynamic_cadt时,涉及到运动时类型识别,也就是运行的时候确定变量或者对象的类型
2.typeid
通国typeid运算符可以获取变量的类型,可以在编译或者运行的时候都可以获得变量的类型
int a = 10;
cout << typeid(a).name << endl;
int*p = &a;
cout << typeid(*p).name << endl;
Box*box = new Box;
cout << typeid(*box ).name << endl;
什么情况下需要在运行的时候获取变量的类型?
在多态的情况下 ,typeid才能在运行期间进行变量类型的获取,只要基类包含了虚函数,构造函数和析构函数都行,就启动了虚函数机制
3.dynamic_cast
首先了解指针的类型,决定了指针的操作范围,A类型指针double能操作8字节的范围,B类型int能操作4字节的范围,存在越界的情况
dynamic_cast能够去检验具有继承关系的父子类型的指针,引用的转换是否安全,也分为编译器转换和运行期转换
子类大小>= 父类大小 也就是子类的指针的操作范围>= 父类的指针的操作范围,所以子类转换为父类是安全的,但反过来就是不安全的,可能存在越界的情况;
编译时的类型转换
#include <iostream>
using nameSpace std;
//这是一种向上的安全的类型转换
class Animal{};
class Dog : public Animal {};
void test()
{
Dog* dog = new Dog;
Animal* animal = dynameic_cast<Animal*>(dog);
}
运行时的类型转换
#include <iostream>
using nameSpace std;
//这是一种向上的安全的类型转换
class Animal{
~Animal() = default;
};
class Dog : public Animal {};
void test()
{
Animal* animal= new Animal;
Dog* dog = new Dog;
Animal* animal = dynameic_cast<Animal*>(dog);
//编译器转换失败
Dog* dog = dynameic_cast<Dog*>(animal);
}
这时候需要多态的方式不会报错
#include <iostream>
using nameSpace std;
//这是一种向上的安全的类型转换
class Animal{
virtual ~Animal() = default;
};
class Dog : public Animal {};
void test()
{
Animal* animal= new Animal;
Dog* dog = new Dog;
Animal* animal = dynameic_cast<Animal*>(dog);
//编译器转换失败
Dog* dog = dynameic_cast<Dog*>(animal);
}
这样就不会报错,通过返回值就可以判断是否转换成功
4.RTTI和虚函数
typeid和dynamic_cast在运行时候类型识别的时候,依赖虚函数机制;
为什么?
C++中,在编译期间对象的类型都是明确的,指针指向不明确的情况下,需要获取类型,干脆把信息合并到虚函数表中,减少复杂度