虚函数表。
我们知道要想运用多态技术,必须在基类以及其派生类中必须定义同名的虚函数。而在定义一个类的时候,如果类里面的成员函数有虚函数的话,编译器在编译时都会生成一个隐藏的指针以及一个虚函数表,而这个指针就是指向这个虚函数表的,这个虚函数表里面记录了多个指针,第一个指针指向了一个type_info类类型的常量(记录了这个类的一些信息),其他指针记录了 这个类的虚函数的入口地址。
下面我们来看一个典型的多态案例:
#include <iostream>
using namespace std;
class animal {
public:
virtual void eat() {
cout << "动物在吃东西" << endl;
}
};
class cat :public animal {
public:
virtual void eat() {
cout << "小猫在吃猫粮" << endl;
}
};
class dog :public animal {
public:
virtual void eat() {
cout << "小狗在吃狗粮" << endl;
}
};
void test(animal &a) {
a.eat();
}
void main() {
animal a;
cat c;
dog d;
test(a);//输出:动物在吃东西
test(c);//输出:小猫在吃猫粮
test(d);//输出:小狗在吃狗粮
system("pause");
}
上面是一个典型的多态运用,代码中,test通过定义一个基类类型的引用作为参数,在程序运行时,通过传入不同类型引用,程序会动态的选择执行对应类中的eat函数。实现这种动态的选择原理是:
一般我们运用多态时会写类似的如下的代码:
void main() {
animal *a = new cat;
//或者
cat b;
animal &c = b;
system("pause");
}
上面两种代码是利用animal类指针/引用指向cat类。写了着这样的代码,在编译的时候,a对象里也会有一个隐藏的指针,这个指针会指向等号右边的类里的虚函数表,也就是cat类里面的虚函数表。那么通过指向的虚函数表就可以找到需要执行的函数入口,就实现了程序在运行时的动态选择。
dynamic_cast。
上面说了,在运用多态的时候会写如下的类似代码,就是用基类的指针指向其派生类。但是这时如果子类有自己独有的成员函数的时候,想要通过对象a去调用,编译器是不支持的。
#include <iostream>
using namespace std;
class animal {
public:
virtual void eat() {
cout << "动物在吃东西" << endl;
}
};
class cat :public animal {
public:
virtual void eat() {
cout << "小猫在吃猫粮" << endl;
}
void work() {
cout << "小猫爱睡觉" << endl;
}
};
void main() {
animal *a = new cat;
a.work();//错误,编译器不支持
}
这其中的原因我认为没有为什么,就是编译器不支持,语法上的问题,因为即使对象a指向cat类型的内存,但是它定义的是animal类型的指针。这时如果想对cat类的独有成员函数work()进行调用的话,需要进行类型的转换。如下代码所示:
void main() {
animal *a = new cat;
//a.work();//调用cat的独有成员函数错误
//c语言风格的强制转换,即使不能转成功也会强制瞎转
cat *c1 = (cat *)a;
//c++中的正确转行,转换失败会返回空指针
cat *c2 = dynamic_cast<cat *>(a);
if (c2 == nullptr)
cout << "空指针" << endl;
else
cout << "转换成功" << endl;
c2->work();//转换类型后可以调用cat类的独有成员函数
system("pause");
}
可以看到有两种转换方式,一种是c语言的风格,强制转换,但是这样做不安全,即使不同的类型也可以转换造成异常不报错,所以一般会使用dynamic_cast来进行类型转换。可以看到转换后就可以对cat的独有成员函数进行调用了。
typeid
来说说typeid(),可以传入地址/引用/常数,typeid会返回一个常量对象的引用,这个常量对象是一个标准库类型 type_info类型。下面代码举个例子:
void main() {
int a;
const type_info &inf0 = typeid(a);
cout << inf0.name() << endl; //输出: int
const type_info &inf1 = typeid(1.2);
cout << inf1.name() << endl; //输出: double
animal *a0;
const type_info &inf2 = typeid(a0);
cout << inf2.name() << endl; //输出: class animal *
animal *a1 = new cat;
const type_info &inf3 = typeid(a1);
cout << inf3.name() << endl; //输出: class animal *
const type_info &inf4 = typeid(*a1);
cout << inf4.name() << endl; //输出: class cat
system("pause");
}
typeid可以传入多种不同类型的参数,我估计是运用了函数重载的功能。那为什么自定义的类型也可以识别出来呢?比如上面的cat,animal类。原因如下:
上面说的写了animal *a = new cat;这样的代码的时候,会有一个隐藏的指针生成,指向等号左边的类型的虚函数表,就是cat类的虚函数表。虚函数表的第一个指针指向一个type_inf的常量对象,里面就记录了一些类的相关信息。所以用typeid()可以将类的信息提取出来:const type_info &inf4 = typeid(*a1);
RTTI
RTTI,全程run time type idefinitication,就是指程序运行时动态的识别对象的类型。也就是通过typeid()可以做到。
以上就是对多态学习的一些总结笔记,可以有些是我自己理解的不一定对,但是理论上行得通,暂且这样认为。