一、概念
1、什么是多态?
同一种事务,在不同场景下的多种形态。
2、分类:
A、静态多态(静态链编译,静态绑定,早绑定):静态多态是编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对 应的函数就调用该函数,否则出现编译错误 。【编译器在编译期间来确定程序的行为(确定具体调用哪个函数)】【a、函数重载;b、泛型编程】
B、动态多态(静态链编,动态绑定,晚绑定):【在程序运行时确定程序的具体行为】
(1)条件:a、基类中必须包含虚函数,在派生类中必须对基类的虚函数进行重写;b、必须通过基类指针或引用调用虚函数
(1B)基类中必须包含虚函数,并且派生类一定要对基类中的虚函数进行重写
重写:a)、基类中将被重写的函数必须是虚函数;b)、基类和派生类中虚函数的原型(返回值类型 函数名字(参数列表)要保持一致(完全相同,但是协变和析构函数例外)。 c)基类中虚函数和派生类虚函数的访问限定符可以不同(一般基类中的限定符为公有的,因为基类的虚函数要在类外进行调用)
协变(基类里的虚函数返回基类对象的指针或引用,派生类中虚函数返回派生类对象的指针或者引用)
析构函数:基类和派生类中函数的名字不同,一般建议把基类中的函数给成虚函数
3、代码实现多态
4、动态多态的调用原理:口述加例子
1‘、
class Base
{
public:
virtual void Test1(){
cout << "Base:Test1()" << endl;
}
};
//重写:派生类和基类中虚函数原型要保持一致
class Derived : public Base
{
public:
virtual void Test1(){
cout << "Derived::Test1()" << endl;
}
};
void TestVirtualFunc(Base& b)
{
b.Test1();
}
int main()
{
Base b;
Derived d;
TestVirtualFunc(b);//引用的是基类中的对象,调用基类中的函数
TestVirtualFunc(d);//引用的是派生类对用,调用派生类中的
system("pause");
return 0;
}
2’、下述代码会产生问题:
using namespace std;
class Base
{
public:
virtual void Test1(){
cout << "Base:Test1()" << endl;
}
virtual void Test2(){
cout << "Base::Test2()" << endl;
}
void Test3(){
cout << "Base::Test3()" << endl;
}
virtual void Test4(int){
cout << "Base::Test4()" << endl;
}
};//重写:派生类和基类中虚函数原型要保持一致
class Derived : public Base
{
public:
virtual void Test1(){
cout << "Derived::Test1()" << endl;
}
void Test2(){
cout << "Derived::Test2()" << endl;
}
virtual void Test3(){
cout << "Derived::Test3()" << endl;
}
virtual void Test4(char){
cout << "Derived::Test4()" << endl;
}//参数类型不同,基类中为int,派生类中为char
};
void TestVirtualFunc(Base& b)
{
b.Test1();
b.Test2();
b.Test3();
b.Test4(12);
b.Test5('a');
}
int main()
{
Base b;
Derived d;
TestVirtualFunc(b);
TestVirtualFunc(d);
system("pause");
return 0;
}
(b)、以下会发生重写虚函数返回值类型存在差异的错误:
class Base
{
public:
virtual int Test5(){
cout << "Base::Test5()" << endl;
return 0;
}
};
//重写:派生类和基类中虚函数原型要保持一致
class Derived : public Base
{
public:
virtual char Test5(){
cout << "Derived::Test5()" << endl;
return 0;
}
};
但是协变不会产生错误,如下述代码:
class Base
{
public:
virtual Base* Test5(){
cout << "Base::Test5()" << endl;
return 0;
}
};
//重写:派生类和基类中虚函数原型要保持一致
class Derived : public Base
{
public:
virtual Derived* Test5(){
cout << "Derived::Test5()" << endl;
return 0;
}
};
3‘、如下,析构函数调用派生类中的对象,释放的是基类中的指针,指向派生类中的对象,基类与派生类中都为虚函数,所以发生了重写。
class Base
{
public:
virtual Base* Test5(){
cout << "Base::Test5()" << endl;
return 0;
}
virtual ~Base()
{
cout << "Base:~Base()" << endl;
}
};
//重写:派生类和基类中虚函数原型要保持一致
class Derived : public Base
{
public:
virtual Derived* Test5(){
cout << "Derived::Test5()" << endl;
return 0;
}
virtual ~Derived()
{
cout << "Derived:~Derived()" << endl;
}
};
int main()
{
Base *pb = new Derived;
delete pb;
system("pause");
return 0;
}
因为pb是派生类中的对象,调用派生类中的对象。
(B2)通过基类对象的指针或者引用调用虚函数
小结:若要构成重写,把普通的函数给成虚函数,基类和派生类中的原型要保持一致。但是协变和析构函数例外
3、模拟实现一个多态例子:根据指针(引用)指向的对象来确定具体调用哪个类的虚函数
二、
1、
(1)、访问限定符不同的含义是基类中的成员函数必须是公有的,因为指针或者引用要通过基类中的对象来调用,派生类中的限定符可以不是公有的。
class Base
{
public:
virtual void Test1(){
cout << "Base:Test1()" << endl;
}
virtual void Test2(){
cout << "Base::Test2()" << endl;
}
};
//重写:派生类和基类中虚函数原型要保持一致
class Derived : public Base
{
protected:
virtual void Test1(){
cout << "Derived::Test1()" << endl;
}
void Test2(){
cout << "Derived::Test2()" << endl;
}
};
void TestVirtualFunc(Base* b)
{
b->Test1();
b->Test2();
}
int main()
{
Base b;
Derived d;
TestVirtualFunc(&b);
TestVirtualFunc(&d);
system("pause");
return 0;
}
通过派生类的对象调用函数,则是不正确的,以下测试代码会产生错误:
void TestVirtualFunc(Base* b)
{
Derived d;
d.Test2();
}
但是通过基类中的对象调用就不会产生错误,原因是编译期间,编译器只会把b当成基类的指针对待(编译期间不管实际的类型,只看静态的类型(在声明变量时给的类型)(动态类型:变量实际给定的对象指定的类型))
(2)同名隐藏:基类与派生类有同名的成员,就会发生同名隐藏。派生类中调用同名的对象优先调用派生类的。在基类和派生类中只要不构成重写就是重定义。
问题:
a、什么是函数重载,同名隐藏,重写?
b、哪些函数不能定义为虚函数?
3、抽象类:在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。【方法只给出来,不实现】
(1)抽象类不能实例化对象,下述代码会发生问题:
class WC;
class Person
{
public:
virtual void GoToWC(WC &c);
};
int main()
{
Person p;
return 0;
}
但是可以定义指针,以下代码就正确了
int main()
{
Person *p;
return 0;
}
(2)纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
class WC;
class Person
{
public:
virtual void GoToWC(WC &c);
};
class Man :public Person
{
public:
//虚函数
virtual void GoToWC(WC &c)
{
//操作
}
};
int main()
{
Person *p;
//如果派生类不是一个虚函数,则也是一个抽象对象,实例化后编译时会发生问题,所以必须定义虚函数
Man m;
return 0;
}
(3)
class Base
{
public:
void Test1()
{
cout << "Base::Test1()" << endl;
}
int _b;
};
int main()
{
cout << sizeof(Base) << endl;
system("pause");
return 0;
}
如果给成一个虚函数,则会多出4个字节:
public:
virtual void Test1()
{
cout << "Base::Test1()" << endl;
}
int _b;
};
(3)
class Base
{
public:
virtual void Test1()
{
cout << "Base::Test1()" << endl;
}
int _b;
};
int main()
{
cout << sizeof(Base) << endl;
Base b;
b._b = 1;
system("pause");
return 0;
}
(4)Base类对象大小:成员大小总和+4字节(多出来的4个字节是一个地址,该地址指向一个空间,空间中存放虚函数的地址即虚函数的表格(虚表)
a、
class Base
{
public:
virtual void Test1()
{
cout << "Base::Test1()" << endl;
}
virtual void Test2()
{
cout << "Base::Test2()" << endl;
}
virtual void Test3()
{
cout << "Base::test3()" << endl;
}
int _b;
};
typedef void (*PVTF)();
void PrintVTP(Base &b)
{
PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
{
(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
++pVTF;
}
cout << endl;
}
class Derived :public Base
{
public:
int _d;
};
int main()
{
cout << sizeof(Base) << endl;
cout << sizeof(Derived) << endl;
Base b;
b._b = 1;
Derived d;
d._b = 1;
d._d = 2;
PrintVTP(b);
PrintVTP(d);
system("pause");
return 0;
}
基类中的虚表:将类中的虚函数按照在类中声明的先后次序添加到虚函数表中
b、单继承派生类的虚表:将基类虚函数表中的内容拷贝一份,
class Base
{
public:
virtual void Test1()
{
cout << "Base::Test1()" << endl;
}
virtual void Test2()
{
cout << "Base::Test2()" << endl;
}
virtual void Test3()
{
cout << "Base::test3()" << endl;
}
int _b;
};
typedef void (*PVTF)();
void PrintVTP(Base &b,const string &str)
{
PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
cout << str << endl;//引用之前先打印str
while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
{
(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
++pVTF;
}
cout << endl;
}
class Derived :public Base
{
public:
int _d;
};
int main()
{
cout << sizeof(Base) << endl;
cout << sizeof(Derived) << endl;
Base b;
b._b = 1;
Derived d;
d._b = 1;
d._d = 2;
PrintVTP(b,"Base VTF:");
PrintVTP(d,"Derived VTF:");
system("pause");
return 0;
}
重写部分代码(派生类):
class Derived :public Base
{
public:
virtual void Test2()
{
cout << "Derived::Test2()" << endl;
}
int _d;
};
说明:单继承派生类的虚表:将基类虚函数表中的内容拷贝一份,一旦对派生类进行改写,编译器用改写后相同偏移量的位置把基类的虚函数替换成派生类自己的虚函数。
c、按照派生类特有虚函数的声明次序将其增加到虚函数表的最后。
# include<iostream>
# include<stdio.h>
# include<stdlib.h>
# include<string>
using namespace std;
class Base
{
public:
virtual void Test1()
{
cout << "Base::Test1()" << endl;
}
virtual void Test2()
{
cout << "Base::Test2()" << endl;
}
virtual void Test3()
{
cout << "Base::test3()" << endl;
}
int _b;
};
typedef void(*PVTF)();
void PrintVTP(Base &b, const string &str)
{
PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
cout << str << endl;//引用之前先打印str
while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
{
(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
++pVTF;
}
cout << endl;
}
class Derived :public Base
{
public:
virtual void Test4()
{
cout << "Derived::Test4()" << endl;
}
virtual void Test2()
{
cout << "Derived::Test2()" << endl;
}
virtual void Test3()
{
cout << "Derived::Test3()" << endl;
}
virtual void Test5()
{
cout << "Derived::Test5()" << endl;
}
int _d;
};
int main()
{
cout << sizeof(Base) << endl;
cout << sizeof(Derived) << endl;
Base b;
b._b = 1;
Derived d;
d._b = 1;
d._d = 2;
PrintVTP(b, "Base VTF:");
PrintVTP(d, "Derived VTF:");
system("pause");
return 0;
}
(5)
虚函数的调用原理:a、从对象的前4个字节取虚表的地址;b、传递this指针;c、从虚表中取对应的虚函数:虚表的地址+偏移量;d、执行当前虚函数。
void TestFunc(Base &pb)
{
pb.Test1();
pb.Test2();
pb.Test3();
}
int main()
{
Base b;
TestFunc(b);
Derived d;
TestFunc(d);
system("pause");
return 0;
}
执行以上代码,首先查看是不是基类里面的虚函数的指针,如果是、则调用虚函数
如果传递基类的对象,到基类的虚表中查找对应的虚函数;如果查找的是派生类的对象,到派生类的虚表中查找对应的虚函数。
void TestFunc(Base &pb)
{
pb.Test1();
pb.Test2();
pb.Test3();
}
int main()
{
Base b;
b.Test1();
Derived d;
system("pause");
return 0;
}
执行以上代码,则不用查看是不是基类里面的虚函数和指针,因为是一个成员函数,不会按照多态的方式进行调用。
(7)带有虚函数的多继承场景下派生类的对象模型及派生类的虚表
a、
class B1
{
public:
virtual void Test1()
{
cout << "B1::Test1()" << endl;
}
virtual void Test2()
{
cout << "B1::Test2()" << endl;
}
int _b1;
};
class B2
{
public:
virtual void Test3()
{
cout << "B1::Test3()" << endl;
}
virtual void Test4()
{
cout << "B1::Test4()" << endl;
}
int _b2;
};
class Derived :public B1, public B2
{
public:
int _d;
};
typedef void(*PVTF)();
void PrintVTP(B1 &b, const string &str)
{
PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
cout << str << endl;//引用之前先打印str
while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
{
(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
++pVTF;
}
cout << endl;
}
void PrintVTP(B2 &b, const string &str)
{
PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
cout << str << endl;//引用之前先打印str
while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
{
(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
++pVTF;
}
cout << endl;
}
int main()
{
cout << sizeof(Derived) << endl;
Derived d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
system("pause");
return 0;
}
b、进行重写之后:把相同偏移量位置的虚函数替换成派生类自己的虚函数
class Derived :public B1, public B2
{
public:
virtual void Test2()
{
cout << "Dereved::Test2()"<<endl;
}
virtual void Test3()
{
cout << "Dereved::Test3()" << endl;
}
int _d;
};
c、增加派生类自己的虚函数(只有两个虚表指针,不可能将其重新放到一个虚表里面),将其放到第一张虚表的后面,因为如果从第二张虚表开始取,需要将其偏移一个位置才能取,这样的方式不方便。将派生类新增加的虚函数放置到第一张虚表的最后。
class Derived :public B1, public B2
{
public:
virtual void Test5()
{
cout << "Dereved::Test5()" << endl;
}
virtual void Test2()
{
cout << "Dereved::Test2()"<<endl;
}
virtual void Test3()
{
cout << "Dereved::Test3()" << endl;
}
virtual void Test6()
{
cout << "Dereved::Test6()" << endl;
}
int _d;
};
(8)菱形继承
class B
{
public:
virtual void Test1()
{
cout << "B::Test1()" << endl;
}
virtual void Test2()
{
cout << "B::Test2()" << endl;
}
int _b;
};
class c1 :public B
{
public:
virtual void Test1()
{
cout << "C1::Test()" << endl;
}
virtual void Test2()
{
cout << "C1::Test3()" << endl;
}
int _c1;
};
class c2 :public B
{
public:
virtual void Test2()
{
cout << "C2::Test2()" << endl;
}
virtual void Test4()
{
cout << "C2::Test4()" << endl;
}
int _c2;
};
class Derived :public c1, public c2
{
public:
virtual void Test1()
{
cout << "Derived::Test1()" << endl;
}
virtual void Test3()
{
cout << "Derived::Test3()" << endl;
}
virtual void Test4()
{
cout << "Derived::Test4()" << endl;
}
virtual void Test5()
{
cout << "Derived::Test5()" << endl;
}
int _d = 0;
};
typedef void(*PVTF)();
void PrintVTP(c1 &b, const string &str)
{
PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
cout << str << endl;//引用之前先打印str
while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
{
(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
++pVTF;
}
cout << endl;
}
void PrintVTP(c2 &b, const string &str)
{
PVTF *pVTF = (PVTF*)(*(int *)&b);//指向表格第一个元素的地址
cout << str << endl;//引用之前先打印str
while (*pVTF)//因为表格最后的位置为0,为0则不进入,完成循环
{
(*pVTF)();//(*pVTF)拿到空间中的内容//(*pVTF)()调用这个函数
++pVTF;
}
cout << endl;
}
int main()
{
cout << sizeof(Derived) << endl;
Derived d;
d.c1::_b = 1;
d._c1 = 2;
d.c2::_b = 3;
d._c2 = 2;
d._d = 5;
c1& cc1 = d;
PrintVTP(cc1, "Derived VTF--->cc1");
c1& cc2 = d;
PrintVTP(cc2, "Derived VTF--->cc2");
system("pause");
return 0;
}
对多态理解的总结: