一般来说,在其他语言中,多态就是父类指针指向了子类对象
但是对于c++来说,对象不一定是在堆区,还有可能在栈区,那么这时候的多态会是什么样的呢?
多态和虚函数
c++中的多态分为 静态多态 和 动态多态
静态多态:实质上就是函数重载
一般说的多态实质上指的是 动态多态
先来看一个例子
class Father{
public:
void who(){
cout << "father" << endl;
}
};
class Son:public Father{
void who(){
cout << "son" << endl;
}
};
void say(Father& obj){
obj.who();
}
int main(int argc, const char * argv[]) {
Son son;
say(son);
}
输出为:father
因为普通成员函数的调用和全局函数实质上没有上面区别,在编译的时候就已经决定调用Fahter的say函数,称之为静态绑定。
如果想要输出为:son,就要使用动态绑定,将who声明为 virtual
class Father{
public:
virtual void who(){
cout << "father" << endl;
}
};
class Son:public Father{
public:
void who(){
cout << "son" << endl;
}
};
void say(Father& obj){
obj.who();
}
int main(int argc, const char * argv[]) {
Son son;
say(son);
}
为什么会出现这样的情况呢?
- 如果who没有加virtual,Father对象的内存大小为1个字节。
- 如果加了virtual,Father对象的大小变为4个字节,因为对象内部多了一个 vfptr 指针,指向该类对应的虚函数表。
- Son继承于Father,因此也继承了父类的 vfptr
指针和虚函数表,由于是继承的父类的vfptr,所以一开始子类的vfptr指向的是父类的虚函数表(指针内容相同),但在对象的构造函数中,会将vfptr指向子类自己的虚函数表。 - 如果没有重写父类的 say 函数,子类虚函数表中say和父类虚函数表中的say是一样的。
- 如果重写了父类的say函数,就会用重写的say函数替换虚函数表中的say函数。
虚函数表是动态联编的,运行的时候,才会通过虚函数指针找到虚函数表中对应的函数。
抽象类和纯虚函数
class Father{
//这样声明了一个纯虚函数,也就是只有函数声明,没有函数实现的函数
//一旦一个类中有纯虚函数,这个类就不能被实例化,这种类就是c++中的抽象类
virtual void func() = 0;
}
class Son:Father{
//子类继承了抽象类,就必须实现抽象类的纯虚函数
virtual void func(){
return 0;
}
}
虚析构和纯虚析构函数
虚析构函数用于解决多态时,子类释放不干净的情况
在使用多态的时候,由于使用了父类指针指向了子类对象,因此在调用析构函数的时候,只会调用父类的析构函数。这个时候就需要将析构函数转化为虚函数。
class Father{
virtual ~Father(){};
}
class Child{
~Child(){};
}
int main(){
Father* c = new Child; // 多态
delete c; // 因为Father的析构函数是虚函数,因此c释放的时候会同时调用子类和父类的析构函数
}
纯虚析构函数
类内声明,类外实现加粗样式
class Father{
virtual ~Father(){} = 0;
}
Father::~Father(){};
有纯虚析构函数的类,也是虚类,因此不能实例化。
析构函数和纯虚析构函数其实差不多,都要实现,只是一个在类内,一个在类外,另外,有纯虚析构函数的类是虚类,不能被实例化。
向上类型转换和向下类型转换
将子类指针或引用转化为父类是安全的
Son* s = new Son;
Father* f = (Father*)s;
因为父类指针的寻址范围总是小于子类(多态就是这种情况)
但是反过来就不安全了
不安全
Father* f = new Father;
Son* s = (Father*)f;
如果将父类指针强转为子类,子类指针的寻址范围会超出父类的范围