首先多态可以理解为:同一事件的不同形态
例如:买票这一事件,普通人买票就是全价票,学生买票是半价票,军人买票是优先买票。
多态条件
- 前提:继承
- 有虚函数
- 子类重写父类的虚函数
- 调用此函数的类型是基类的指针、引用
class Person
{
public:
//虚函数:virtual + 正常的函数定义
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
//一般基类的析构函数都定义为虚函数,避免内存泄漏的问题
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
public:
//虚函数重写:子类定义了一个和父类接口完全相同的函数
//重写的虚函数:函数名,参数列表,返回值类型和父类对应的函数完全一致
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
//即使前面不加virtual 也会有虚函数属性
//都是虚函数,在底层和父类析构函数名相同,构成虚函数重写,构成多态
~Student()
{
delete[100] ptr;
cout << "~Student()" << endl;
}
protected:
char* ptr = new char[100];
};
//多态:看对象
void fun(Person& p)
{
p.BuyTicket();
}
//非多态:看类型
void fun2(Person p)
{
p.BuyTicket();
}
void test()
{
Person p;
Student s;
fun(p);
fun(s);
//构成多态,指向子类对象,先调用子类的析构,再调用父类析构
Person* p1 = new Student;
delete p1;
Student* p2 = new Student;
delete p2;
}
int main()
{
test();
return 0;
}
协变:返回值类型可以不同,但是必须是有继承关系的指针/引用
class A
{};
class B: public A
{};
class Person
{
public:
//虚函数:virtual + 正常的函数定义
//协变:返回值类型可以不同,但是必须是有继承关系的指针/引用
virtual A* BuyTicket()
{
cout << "买票-全价" << endl;
return new A;
}
};
class Student : public Person
{
public:
//虚函数重写:子类定义了一个和父类接口完全相同的函数
//重写的虚函数:函数名,参数列表,返回值类型和父类对应的函数完全一致
virtual B* BuyTicket()
{
cout << "买票-半价" << endl;
return new B;
}
};
final 在父类中使用:表示父类的虚函数不能再被子类重写
虚函数+final:此函数不能被子类重写
class A
{
//子类不能再对该函数进行重写
virtual void fun() final
{
cout << "A::fun()"<<endl;
}
}
override 通常在子类中使用:表示会自动检查必须重写父类的一个函数
虚函数+override:强制重写父类的一个虚函数
class A
{
public:
virtual void fun()
{
cout << "A::fun()"<<endl;
}
}
class B:public A
{
public:
virtual void fun() override
{
cout<<"B::fun()"<<endl;
}
}
接口继承和实现继承:
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
多态原理
虚函数表
- 只要类中有虚幻书,类对象中都会有一个虚表指针成员:_vfptr :二级指针–>函数指针指针
- 虚表是虚函数指针数组,需表中不存放普通函数的指针
- 子类会继承父类的虚表
- 子类的需表中,如果有重写的虚函数,则对应的函数指针也会被子类的虚函数指针覆盖(地址的覆盖)
- 虚表没有存放在对象中,虚表一般存放在代码段
获取虚表地址
//定义vfptr为函数指针类型
typedef void(*vfptr)();
//vfTable代表虚表指针,指向虚表的首元素地址,为二级指针
//虚表内存放的都是虚函数指针
void printVTable(vfptr vfTable[])
{
cout << "虚表地址:"<< vfTable << endl;
//定义一个二级指针fptr也指向虚表首元素地址
vfptr* fptr = vfTable;
//做解引用,拿到一个虚函数指针
while(*fptr != nullptr)
{
//虚函数指针(),虚函数指针指向的函数开始执行
(*fptr)();
//往下移动,指向下一个虚函数指针
++fptr;
}
}
void test()
{
Person p;
cout << "Person vfptr: ";
vfptr* vftable = (vfptr*)(*(int*)&p);
printVtable(vftable);
}
多继承:
- 子类中虚表的个数和直接父类的个数一致
- 子类新定义的虚函数,其虚函数指针存放在第一个直接父类的虚表中
重载、覆盖(重写)、隐藏(重定义)的对比
重载
- 两个函数在同一作用域
- 函数名/参数相同
重写(覆盖)
- 两个函数分别在基类和派生类的作用域
- 函数名/参数/返回值都必须相同(协变例外)
- 两个函数必须是虚函数
重定义(隐藏)
- 两个函数分别在基类和派生类的作用域
- 函数名相同
- 两个基类和派生类的同名函数不构成重写就是重定义