文章目录
1. 多态的感念
多态就是多种形态.具体就是去完成某个行为,当不同的对象去完成时会产生不同的状态或结果.
2. 多态的定义与实现
2.1 多态的构成条件
多态的两种条件:
- 虚函数的重写. ----要求三同(函数名,参数类型,返回值).
- 父类的指针或者引用去调用.
2.2 虚函数
使用
virtual
修饰的成员函数就是虚函数.
class Person
{
public:
virtual void BuyTicket(){ cout << "全价" << endl; };
};
class Student : public Person
{
public:
virtual void BuyTicket(){ cout << "半价" << endl; };
};
虚函数重写
- 虚函数(父类该接口是虚函数)子类可以不加修饰,则成了接口继承,子类又重写了这个接口(重写实现.).
class Person
{
public:
virtual void BuyTicket(){ cout << "全价" << endl; };
};
class Student : public Person
{
public:
void BuyTicket(){ cout << "半价" << endl; };
};
- 协变(父类与子类的返回值不同): 返回值可以不同,必须是父子关系指针或者引用
class Person
{
public:
virtual Person* BuyTicket(){ cout << "全价" << endl; return this; };
};
class Student : public Person
{
public:
virtual Student* BuyTicket(){ cout << "半价" << endl; return this; };
};
注意:协变, 返回值不要求单是本身父子关系的指针或引用,只需满足父子类关系的指针或引用即可.
2.3 c++11:final和override
2.3.1 final
修饰虚函数,表示该虚函数不能被重写
class A
{
virtual void Derive() final {}
};
2.3.2 override
检查是否完成重写,不是就报错.
class B: public A
{
virtual void Derive() override {}
};
2.4 重载,重写,隐藏
重载:
- 两个函数在同一作用域,函数名相同,参数不同.
重写:
- 两个函数分别在父类和子类的作用域.
- 函数名/参数/返回值都必须相同(协边除外)
- 两个函数必须是虚函数
隐藏(重定义):
- 两个函数分别在父类和子类的作用域
- 函数名相同
- 两个父类和子类的同名函数不构成重写就是重定义
3. 抽象类
在虚函数的后面写上=0,则这个函数就是纯虚函数,包含纯虚函数的类就是抽象类,抽象类特点就是不能实例化对象.
纯虚函数:强制了子类必须重写,否则子类还是抽象类.
抽象类: 一个类型在现实中没有对应的实体,就可以定义为抽象类.
4. 多态的原理
4.1 虚函数表
父类对象的虚表存的是父类的虚函数 ,子类对象的虚表存的是子类的虚函数以及继承来没有重写的虚函数和以重写的虚函数.
虚函数表:本质是一个虚函数表指针数组,(vs编译器中在这个数组最后放了一个nullptr).
_vfptr
指针 : 所指向的空间即是虚函数表.
虚表是在什么阶段生成的? – 编译
对象中的虚表指针什么时候初始化的? – 构造函数的初始化列表阶段.
虚表存在那里? --代码段(常量区)
4.2 多继承虚表
子类实现的虚函数存放在那个表呢? --第一个虚表.
class Person1
{
public:
virtual void Print()
{
cout << "Person1" << endl;
}
virtual void test1()
{
cout << "Person1:test1()" << endl;
}
};
class Person2
{
public:
virtual void Print()
{
cout << "Person2" << endl;
}
virtual void test2()
{
cout << "Person2:test2()" << endl;
}
};
class Student : public Person1,public Person2
{
public:
virtual void Print()
{
cout << "student" << endl;
}
virtual void test3()
{
cout << "studnet:test3()" << endl;
}
};
可以看到第一个虚表中,存放这子类的虚函数.
若子类重写了两个毫无关系的父类拥有的相同的虚函数(如上述代码中的
Print()
函数) ,则子类的两个虚表都拥后该虚函数,但维一区别的是调用第一个虚表的该虚函数是直接调用,而第二个虚函数则是经过编译器一层封装,最终还是调用第一个虚表(期间会修正this
指针指向第一个虚表)的该函数.
5. 动态绑定和静态绑定
静态多态 : 在编译时完成,例:函数重载.
动态多态 : 在运行时完成.
题外:打印虚表程序
typedef void(*VF_PTR)(); //声明一个函数指针
void PrintVFTable(VF_PTR table[]) //虚表打印函数
{
for(int i = 0; table[i] != nullptr ; ++i)
{
printf("[%d]:%p\n",i,table[i]);
}
}
int main()
{
A a;
B b;
PrintVFTable((VF_PTR)(*(int*)&a));//第一种方法,读取对象头4个字节适合32位系统下.
PrintVFTable((*(VF_PTR**)&b));//第二种方法,读取对象头4/8个字节,自适应系统.
}