c++多态 ,虚函数,虚函数表,虚析构函数,纯虚函数,抽象类,接口类,运行时类型识别
一。
虚函数(子类有和父类同名的成员函数,在父类函数前面加virtual,子类函数前面可加可不加,最好加)
使父类指针指向子类对象后可以调用子类的成员(同名的那个)通过父类指针可以操作子类对象
不加virtual,则父类指针调用的是父类的成员函数
函数的覆盖和隐藏 加virtual是覆盖,不加是隐藏。
隐藏是对于子类对象调用函数,这个函数子类父类都有,同名,此时父类的被隐藏。
覆盖是对于父类指针指向子类对象的情况(如下),不加virtual,这个指针就访问不了子类里同名的函数。加了就叫覆盖。
// 通过父类对象实例化狗类
Animal *p = new Dog();
虚函数原理:类的成员里有一个虚函数表指针(有虚函数的类里面才有),用虚函数表指针找到虚函数表,虚函数表里面有函数指针(指向函数的地址),如果不加virtual,子类里这个函数指针地址和父类这个同名函数的地址一样,调用的就是父类的成员函数。如果加了virtual,这个函数指针会变,指向另一个地址,调用的时候就会调用子类的成员函数
上述虚函数用于父类指针指向子类对象时,由于都是父类指针,所以虚函数表指针指的地址一样,里面父类和子类同名的函数指针只有一个,指的是父类的。
每个类只有一份虚函数表,所有该类的对象共用同一张虚函数表
二。
虚析构函数(在析构函数前面加virtual)
子类对象通过父类指针申请内存,delete父类指针后子类的对象没有自动delete,造成内存泄漏
People *p = new Soldier
这样,new的时候会先调用子类构造函数,父类构造函数,delete p 之后,只调用父类的析构函数
原理:理论前提:执行完子类的析构函数就会执行父类析构函数。
加virtual会把析构函数虚函数表里的析构函数指针指向的地址变得和父类的析构函数地址不同。
三。
纯虚函数
在类里面这样声明:
virtual void function() = 0;
定义纯虚函数目的
就是因为不知道这个函数具体要干什么,所以空着让子类去完成。如图形类有个求面积函数,子类有三角形,圆形,只有在子类里面才能去具体实现这个求面积函数,所以在父类里面定义成纯虚函数
四。
抽象类:含有纯虚函数的类
抽象类不能实例化对象
抽象类的子类必须把继承来的所有纯虚函数具体实现了,才能实例化对象,要不然子类还是抽象类
五。
接口类
类中没有数据成员,只有成员函数,且都是纯虚函数
class Flyable//接口类
{
public:
virtual void fly();
}
class Plane:public Flyable
{
//这里实现fly()函数
}
FlyableMatch(Flyable *a, Flyable *b)//这两个参数用接口类,实际使用的时候传进来的是继承这个接口的类,这样可以确保有这个接口类里面的函数
{
a->fly();
b->fly();
}
int main()
{
Plane p1;
Plane p2;
FlyableMatch(&p1,&p2);
return 0;
}
接口类可以隐藏数据结构--------给别人一个自己写的库调用时,要给.h、 lib、dll三个文件(编译好的库),或者 .h 和.cpp(未编译的库),当别人看到.h 文件时,会看到所有成员,包括私有的。用接口类技术可以隐藏这些东西,给别人提供接口类就行了,而不用给自己的类。
自己的类继承了接口类,并实现了里面的虚函数和虚成员。
用接口类的人定义一个接口类的对象,并用这个对象可以访问子类的成员(接口类里面的虚成员和虚函数)。
六。RTTI 运行时类型识别
例:判断如果传入的obj是Brid类型,就执行brid类型的foraging觅食函数。如果是别的类型,就干别的(代码里没体现)
dynamic_cast<>( )
obj本来是Flayable * 类型的对象,现在转换成Brid *类型
typeid()
例1:
int i = 0;
cout<<typeid(i).name()<<endl;
输出结果:int
例2:指针
int *i ;
cout<<typeid(*i).name()<<endl;
cout<<typeid(i).name()<<endl;
输出结果:int
int *
例3:
int换成类类型也行
Brid i;
打印出class Brid