多态
一、初识多态
概念
概念:去完成某个行为,当不同的对象去完成时会产生出不同的状态。
eg:
买车票:普通成年人买票时,是全价票;学生买票时,是半价票;
“登场”
1>. 多态的构成条件
- 多态是不同继承关系的类对象,去调用同一函数,产生不同的行为。
- 在继承的基础上,需要:
- 必须通过基类的指针或者引用调用虚函数。
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 (覆盖)。
eg: “见见猪跑”
2>. 虚函数
虚函数:被virtual修饰的类成员函数
3>. 虚函数重写(覆盖)
虚函数重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型,函数名,参数类型完全相同)。称子类的虚函数重写了父类的虚函数
注意: 重写基类虚函数时,派生类的虚函数可以不加virtual关键字。
eg:
4>. 虚函数重写的两个例外
虚函数重写的要求是派生类虚函数与基类虚函数的返回值类型,函数名,参数类型完全相同(三同)。例外的原因就是不满足三同
1. 协变 一 基类和派生类虚函数返回值类型不同
协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同。
满足协变的条件:返回值类型可以不同,但是返回值必须是父子关系的指针或引用。
test code:
class A
{};
class B : public A
{};
class Person
{
public:
virtual A* f()
{
return new A;
}
};
class Student : public Person
{
public:
//注意在重写的地方,返回值类型虽然可以不同,但是必须是父子关系,而且同指针或同引用。不能出现基类虚函数返回值类型是父类引用,而派生类重写的虚函数返回类型是派生类的指针
virtual B* f()
{
return new B;
}
};
2. 析构函数重写(基类和派生类析构函数名不同)
基类的析构函数为虚函数,则派生类析构函数只要定义,无论是否加virtual关键字,都构成重写。虽然表象函数名不同,但是编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
test code:
class Person
{
public:
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
public:
virtual ~Student()
{
cout << "~Student()" << endl;
}
};
//只有派生类Student的析构函数重写了Person的析构函数,
//这时delete调用析构时,才能构成多态,保证正确调用析构函数
int main()
{
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2;
return 0;
}
//output:
//~Person()
//~Student()
//~Person()
小结
重载、覆盖(重写)、隐藏(重定义)的对比:
二、延伸知识
1>. C++11 override和final
- override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写报错
test code:
class Car
{
public:
virtual void Drive()
{}
};
class Benz : public Car
{
public:
virtual void Drive() override //ok 完成了重写
{
cout << "Benz-舒适" << endl;
}
};
- final:修饰虚函数,表示该虚函数不能再被重写
test code:
class Car
{
public:
virtual void Drive() final
{}
};
class Benz : public Car
{
public:
virtual void Drive() //error 原因:final禁止了重写
{
cout << "Benz-舒适" << endl;
}
};
拓展一最终类
当我们想设计不想被继承的类时,有两种方法
方法1 一一 对应C++98
eg1: 隐藏构造函数,当想要创建A对象时,定义一个静态的成员函数
class A
{
public:
static A CreateObj()
{
return A();
}
private:
A()
{}
};
class B : public A
{};
int main()
{
//B bb; //err
A::CreateObj();
return 0;
}
eg2:隐藏析构函数,当想要创建A对象new一个,释放时定义一个静态的destructor,即可
class A
{
public:
private:
~A()
{}
};
class B : public A
{};
int main()
{
//B bb; //err
A* p = new A;
return 0;
}
方法2 一一 对应C++11
eg:被final修饰的类,被称为最终类,不能被继承
class A final
{
public:
private:
};
class B : public A
{};
2>. 抽象类
概念
- 纯虚函数:虚函数的后面写上 = 0
- 抽象类(接口类):包含纯虚函数的类。
抽象类不能实例化出对象,派生类继承后也不能,只有重写纯虚函数,派生类才可以实例化对象。规范了派生类必须重写。
test code:
class Car
{
public:
virtual void Drive() = 0;
};
class Benz : public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
class BMW : public Car
{
public:
virtual void Drive()
{
cout << "BMW-好操控" << endl;
}
};
int main()
{
Car* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();
return 0;
}
接口继承和实现继承
- 实现继承:普通函数继承。派生类继承基类函数,可以使用,继承的是函数实现。
- 接口继承:虚函数的继承。派生类继承的是虚函数的接口,目的是为了重写,达成多态,继承的是接口。
注意:不实现多态就不要把函数定义成虚函数
三、原理
基于vs2019进行模型分析
1>. 虚函数表(也称虚表)
引入
test code:
//计算Base对象的大小
class Base
{
public:
virtual void Func()
{
cout << "Func()" << endl;
}
private:
int _b = 1;
};
int main()
{
Base b;
cout << sizeof(b) << endl;
return 0;
}
//output: 8
代码分析:
分析虚表
test code:
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
通过上面的测试代码,发现一下六点:
2>. 多态的原理
上面分析了很久的虚表,以对虚表的介绍为基础,来分析多态的原理。
test code:
class Person
{
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
void Func(Person* p)
{
p->BuyTicket();
}
int main()
{
Person ps;
Func(&ps);
ps.BuyTicket();
Student st;
Func(&st);
return 0;
}
达到多态,有两个条件:一是虚函数覆盖,一个是对象的指针或引用调用虚函数。
通过下面汇编代码的分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象中去找的。不满足多态的函数调用是编译时确认好的。
3>. 拓展 一 静态绑定和动态绑定
- 静态绑定(前期绑定):在程序编译期间确定了程序的行为,也称静态多态。 eg:函数重载
- 动态绑定(后期绑定):是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称动态多态
上一个汇编代码的例子,就很好的解释了静态绑定和动态绑定
四、单继承和多继承的虚函数表
1>. 单继承中的虚函数表
test code:
class Base
{
public:
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private:
int _b;
};
class Derive : public Base
{
public:
virtual void func1()
{
cout << "Derive::func1()" << endl;
}
virtual void func3()
{
cout << "Derive::func3()" << endl;
}
virtual void func4()
{
cout << "Derive::func4()" << endl;
}
private:
int _d;
};
int main()
{
Base b;
Derive d;
return 0;
}
观察下图中监视窗口,发现派生类的虚函数func3和func4看不见。 原因:编译器的监视窗口隐藏了这个两个函数。
在分析虚表这一小节内容时,在第四小点说到,虚表本质是一个存虚函数指针的指针数组,一般情况这个数组最后放一个nullptr,通过监视窗口查看不了派生类对象d的虚表,下面我们借用nullptr的帮助,使用代码打印出虚表中的函数
PrintVTable_code:打印虚表的代码
注意:这个打印虚表的代码经常崩溃,编译器对虚表的处理不干净,虚表最后没有放nullptr的指针,导致越界。我们只需要清理解决方案,重新编译即可。
typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr VTable[])
{
//依次取虚表中的虚函数指针打印并调用。
cout << "虚表地址>" << VTable << endl;
for (size_t i = 0; VTable[i] != nullptr; i++)
{
printf("第%d个虚函数地址:0X%p,->", i, VTable[i]);
VFPtr f = VTable[i];
f(); //调用方便看存的是那个函数
}
cout << endl;
}
注意:传参调用PrintVTable的思路
int main()
{
Base b;
Derive d;
//思路:取b、d对象的头4个字节,就是虚表的指针。
//以b对象讲解
//1.先取b的地址,强转成int*的指针
//2.再解引用取值,就取到b对象头4个字节的值,也就是指向虚表的指针
//3.再强转成VFPtr*,因为虚表就是一个存VFPtr类型(虚函数指针类型)的数组
//4.虚表指针传递给PrintVTable进行打印虚表
VFPtr* vTable_b = (VFPtr*)(*(int*)&b);
PrintVTable(vTable_b);
VFPtr* vTable_d = (VFPtr*)(*(int*)&d);
PrintVTable(vTable_d);
return 0;
}
上面代码打印出虚表中虚函数的结果分析:
2>. 多继承中的虚函数表
1. 多继承
test code:
class Base1
{
public:
virtual void func1()
{
cout << "Base1::func1()" << endl;
}
virtual void func2()
{
cout << "Base1::func2()" << endl;
}
private:
int _b1 = 1;
};
class Base2
{
public:
virtual void func1()
{
cout << "Base2::func1()" << endl;
}
virtual void func2()
{
cout << "Base2::func2()" << endl;
}
private:
int _b2 = 1;
};
class Derive : public Base1, public Base2
{
public:
virtual void func1()
{
cout << "Derive::func1()" << endl;
}
virtual void func3()
{
cout << "Derive::func3()" << endl;
}
private:
int _d = 2;
};
typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{
//依次取虚表中的虚函数指针打印并调用。
cout << "虚表地址>" << vTable << endl;
for (size_t i = 0; vTable[i] != nullptr; i++)
{
printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);
VFPtr f = vTable[i];
f(); //调用方便看存的是那个函数
}
cout << endl;
}
int main()
{
Derive d;
VFPtr* vTable_b1 = (VFPtr*)(*(int*)&d);
PrintVTable(vTable_b1);
//(char*)&d 这里一定要注意强转,否则+1,就是加一个Derive的大小
VFPtr* vTable_b2 = (VFPtr*)(*(int*)((char*)&d + sizeof(Base1)));
PrintVTable(vTable_b2);
return 0;
}
多继承测试代码展开分析:
2. 菱形继承
test code:
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
int _a = 0;
};
class B : public A
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
int _b = 0;
};
class C : public A
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
virtual void fun2()
{
cout << "C::fun2()" << endl;
}
int _c = 0;
};
class D : public B, public C
{
public:
virtual void fun2()
{
cout << "D::fun2()" << endl;
}
virtual void fun3()
{
cout << "D::fun3()" << endl;
}
};
typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{
//依次取虚表中的虚函数指针打印并调用。
cout << "虚表地址>" << vTable << endl;
for (size_t i = 0; vTable[i] != nullptr; i++)
{
printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);
VFPtr f = vTable[i];
f(); //调用方便看存的是那个函数
}
cout << endl;
}
int main()
{
D d;
VFPtr* vTable_d = (VFPtr*)(*(int*)&d);
PrintVTable(vTable_d);
C* ptr1 = &d;
VFPtr* vTable_c = (VFPtr*)(*(int*)ptr1);
PrintVTable(vTable_c);
return 0;
}
菱形继承测试代码展开分析:(菱形继承和多继承没有什么大的区别)
3. 菱形虚拟继承
- 只有A类有虚函数
test code:
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
public:
int _a = 0;
};
class B : virtual public A
{
public:
int _b = 0;
};
class C : virtual public A
{
public:
int _c = 0;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
代码内存分析:
- B和C完成对A的虚函数重写
test code:
class A
{
public:
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
public:
int _a = 0;
};
class B : virtual public A
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
public:
int _b = 0;
};
class C : virtual public A
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
public:
int _c = 0;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
运行结果:
编译报错
原因:因为整个对象只有A一张虚表是共享的,B要重写,C也要重写。不明确到底重写谁的。
解决办法:让D重写即可。当然B和C的重写并不是没有意义,如果定义B和C类型的对象,单独使用还是有意义的。
- 只有A类有虚函数 + B和C有单独的虚函数
test code:
class A
{
public:
virtual void func1()
{
cout << "A::func1" << endl;
}
public:
int _a;
};
class B : virtual public A
{
public:
virtual void func1()
{
cout << "B::func1" << endl;
}
virtual void func2()
{
cout << "B::func2" << endl;
}
public:
int _b;
};
class C : virtual public A
{
public:
virtual void func1()
{
cout << "C::func1" << endl;
}
virtual void func2()
{
cout << "C::func2" << endl;
}
public:
int _c;
};
class D : public B, public C
{
public:
virtual void func1()
{
cout << "D::func1" << endl;
}
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
代码内存分析:
- 如果在第三点的基础上,D类也有自己的虚函数,那么将放哪里?
test code:
class A
{
public:
virtual void func1()
{
cout << "A::func1" << endl;
}
public:
int _a;
};
class B : virtual public A
{
public:
virtual void func1()
{
cout << "B::func1" << endl;
}
virtual void func2()
{
cout << "B::func2" << endl;
}
public:
int _b;
};
class C : virtual public A
{
public:
virtual void func1()
{
cout << "C::func1" << endl;
}
virtual void func2()
{
cout << "C::func2" << endl;
}
public:
int _c;
};
class D : public B, public C
{
public:
virtual void func1()
{
cout << "D::func1" << endl;
}
virtual void func3()
{
cout << "D::func3" << endl;
}
public:
int _d;
};
typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{
//依次取虚表中的虚函数指针打印并调用。
cout << "虚表地址>" << vTable << endl;
for (size_t i = 0; vTable[i] != nullptr; i++)
{
printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);
VFPtr f = vTable[i];
f(); //调用方便看存的是那个函数
}
cout << endl;
}
int main()
{
D d;
VFPtr* vTable_d = (VFPtr*)(*(int*)&d); //B的虚表
PrintVTable(vTable_d);
C* ptr1 = &d;
VFPtr* vTable_c = (VFPtr*)(*(int*)ptr1); //C的虚表
PrintVTable(vTable_c);
A* ptr2 = &d;
VFPtr* vTable_a = (VFPtr*)(*(int*)ptr2); //A的虚表
PrintVTable(vTable_a);
return 0;
}
运行结果:由结果得到D类自己的虚函数放在第一张虚表中