多态
一、概念
同一事物在不同的场景下具有不同的形态
二、分类
- 静态多态(函数重载、泛型编程):编译器在编译期间完成,编译器根据函数实参的类型(可能会进行隐式类型转换), 可推断出要调用哪个函数,如果有对应的函数就调用该函数,否则编译出错。
- 动态多态(虚函数):在运行期间进行
三、动态多态的条件
- 基类中必须包含虚函数,并且派生类一定要对基类中的虚函数进行重写。
- 通过基类对象的指针或引用去调用虚函数。
四、继承体系同名成员函数的关系:
- 函数重载:在同一作用域,函数名相同,参数列表不同(参数个数、类型或次序),返回值可以不同。
- 函数重写:在不同的作用域(分别在基类和派生类),函数名相同,参数相同,返回值相同(协变和析构函数除外),基类函数必须有virtual关键字,派生类可加可不加但尽量加上,访问修饰符可以不同。
- 协变:两个函数都是虚函数,基类函数返回基类对象的指针或引用;派生类函数返回派生类对象的指针或引用。
- 析构函数:两个类的析构函数返回值不同。
- 重定义(同名隐藏):在不同的作用域中(分别在基类和派生类),只要函数名相同(和返回值类型、参数都无关),在基类和派生类中只要不构成重写就是重定义。
注:通过基类对象去调用,一定调用的是基类的;通过派生类对象去调用,一定调用的是派生类的。
五、多态的调用原理之单继承
class Base
{
public:
Base()
{}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest12()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Base::FunTest3()" << endl;
}
int _b;
};
int main()
{
Base b;
b._b = 10;
return 0;
}
- 如没有给出构造函数,则系统必会自动合成,因为在构造函数中要进行:“将虚表的地址放到对象空间的前4个字节中去”。
如果给了,系统会将其改写,给构造函数体内部插入一条:“将虚表的地址放到对象空间的前4个字节中去”。 类里面有多少个虚函数,虚表指针所指向的虚函数表里面就有多少个虚函数。
基类:
class Base
{
public:
Base()
{}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest12()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Base::FunTest3()" << endl;
}
int _b;
};
typedef void (*PVFT)();//函数指针
#include <string> //在C++中有很多头文件不需要加.h
void PrintfVFT(Base& b,const string& str)//打印虚表(只需将对象前4个字节的内容取到)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*(int*)(&b));
while (*pVFT)//类似于数组的处理方法
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
int main()
{
Base b;
PrintfVFT(b,"Base VFT:");
return 0;
}
// 打印结果:
Base VFT:
Base::FunTest1()
Base::FunTest2()
Base::FunTest3()
虚函数表中虚函数的次序 = 在类中虚函数的声明次序
派生类:
class Base
{
public:
Base()
{}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Base::FunTest3()" << endl;
}
int _b;
};
class Derived :public Base
{
public:
virtual void FunTest5() //派生类自己的
{
cout << "Derived::FunTest5()" << endl;
}
virtual void FunTest2() //将基类的虚函数2在派生类中改写了
{
cout << "Derived::FunTest2()" << endl;
}
virtual void FunTest3() //将基类的虚函数3在派生类中也改写了
{
cout << "Derived::FunTest3()" << endl;
}
virtual void FunTest4() //派生类自己的
{
cout << "Derived::FunTest4()" << endl;
}
int _d;
};
typedef void(*PVFT)(); //函数指针
#include <string> //在C++中有很多头文件不需要加.h
void PrintfVFT(Base& b, const string& str)//打印虚表(只需将对象前4个字节的内容取到)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*(int*)(&b));
while (*pVFT)//类似于数组的处理方法
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
int main()
{
Base b;
PrintfVFT(b, "Base VFT:");
Derived d1;
Derived d2;
PrintfVFT(d1, "Derived VFT:");
return 0;
}
// 打印结果:
Base VFT :
Base::FunTest1()
Base::FunTest2()
Base::FunTest3()
Derived VFT :
Base::FunTest1()
Derived::FunTest2()
Derived::FunTest3()
Derived::FunTest5() //因为5是在4的前面声明的
Derived::FunTest4()
由上可知:
1. 派生类和基类所指向的虚表不一样,所以对象空间的前4个字节的内容不同。
2. 派生类的虚表是:先将基类的虚表拷贝下来,再看派生类中将基类的哪些虚函数重写了,在相应的位置变成派生类的;若在派生类中出现基类中没有的虚函数则按其出现的先后次序放在后面。
3. 若将派生类中的虚函数屏蔽掉,则打印结果:
Base VFT :
Base::FunTest1()
Base::FunTest2()
Base::FunTest3()
Derived VFT :
Base::FunTest1()
Base::FunTest2()
Base::FunTest3()
派生类的虚函数表只是将基类的虚函数表拷贝下来了。
4.对象d1和对象b的前4个字节的内容是不一样的,而对象d1和对象d2的前4个字节的内容是一样的。
所以说,同一个类的所有对象实例共用同一个虚表。
通过基类的指针或引用去调用虚函数,传递不同的对象看结果:
class Base
{
public:
Base()
{}
virtual void FunTest1()
{
cout << "Base::FunTest1()" << endl;
}
virtual void FunTest2()
{
cout << "Base::FunTest2()" << endl;
}
virtual void FunTest3()
{
cout << "Base::FunTest3()" << endl;
}
int _b;
};
class Derived :public Base
{
public:
virtual void FunTest5() //派生类自己的
{
cout << "Derived::FunTest5()" << endl;
}
virtual void FunTest2() //将基类的虚函数2在派生类中改写了
{
cout << "Derived::FunTest2()" << endl;
}
virtual void FunTest3() //将基类的虚函数3在派生类中也改写了
{
cout << "Derived::FunTest3()" << endl;
}
virtual void FunTest4() //派生类自己的
{
cout << "Derived::FunTest4()" << endl;
}
int _d;
};
typedef void(*PVFT)(); //函数指针
#include <string> //在C++中有很多头文件不需要加.h
void PrintfVFT(Base& b, const string& str) //打印虚表(只需将对象前4个字节的内容取到)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*(int*)(&b));
while (*pVFT) //类似于数组的处理方法
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
void TestDynamic(Base& b) //测试多态
{
b.FunTest1();
b.FunTest2();
b.FunTest3();
}
int main()
{
Base b;
PrintfVFT(b, "Base VFT:");
Derived d;
PrintfVFT(d, "Derived VFT:");
Base* pb = (Base*)&d;
pb->FunTest2(); //此处调用的是派生类的虚函数
TestDynamic(b);
TestDynamic(d);
return 0;
}
结果:
1. 通过基类的指针或引用来调用虚函数,传递对象不同时,就去调用相应的虚函数。
打印结果:
Base VFT :
Base::FunTest1()
Base::FunTest2()
Base::FunTest3()
Derived VFT :
Base::FunTest1()
Derived::FunTest2()
Derived::FunTest3()
Derived::FunTest5()
Derived::FunTest4()
Base::FunTest1()
Base::FunTest2()
Base::FunTest3()
Base::FunTest1()
Derived::FunTest2()
Derived::FunTest3()
2.若将基类中虚函数2改成普通函数,则打印结果:
Base VFT :
Base::FunTest1()
Base::FunTest3()
Derived VFT :
Base::FunTest1()
Derived::FunTest3()
Derived::FunTest5()
Derived::FunTest2()
Derived::FunTest4()
Base::FunTest1()
Base::FunTest2() // 在程序的编译阶段进行
Base::FunTest3()
Base::FunTest1()
Base::FunTest2() // 在程序的编译阶段进行
Derived::FunTest3()
注:
- 若被调用的函数不是虚函数,则在程序的编译阶段进行,只调用基类的;
- 若被调用的函数是虚函数,则在程序的运行阶段进行,看它传入的对象是谁,就在谁的虚函数表中去找对应的虚函数。
3.若是通过基类的指针指向派生类对象(根据赋值兼容规则),该指针指向派生类中基类的那部分, 若所调函数是虚函数且在派生类中被重写,则调用的虚函数是派生类的;若所调函数不是虚函数或者该函数在派生类中没有被重写,则调用的函数是基类的。
4.若是以void TestDynamic(Base b)方式去调用函数,则不论所传对象是基类还是派生类,结果都调用的是基类的。
Base::FunTest1()
Base::FunTest2()
Base::FunTest3()
Base::FunTest1()
Base::FunTest2()
Base::FunTest3()