概述
多态的概念:通俗讲就是多种形态,具体来说就是当需要完成某个行为时,由不同的对象去完成会产生不同的结果。
比如乘车买票,普通人必须买全票,学生可以买半票。
实现
在C++中实现多态需要满足的条件
1、前提:发生继承
2、父类中要有虚函数
3、调用虚函数的类型必须是指针或者引用
4、虚函数需要在子类中被重写
5、一般是通过父类的指针或者引用来调用虚函数 (可以将子类对象的地址或引用赋值给父类的指针或引用—切片)
代码示例:
#include<iostream>
using namespace std;
class Person {
public:
virtual void BuyTicket()
{
cout << "全票" << endl;
}
};
class Student :public Person {
public:
void BuyTicket()
{
cout << "半票" << endl;
}
};
void test()
{
Person p;
Person* p1 = &p;
Person& p2 = p;
//p.BuyTicket(); //用对象来调用虚函数时,不会产生多态
p1->BuyTicket();
p2.BuyTicket();
Student s;
p1 = &s; //切片
Person& p3 = s; //切片
p1->BuyTicket();
p3.BuyTicket();
}
int main()
{
test();
return 0;
}
运行结果:
虚函数重写
在继承体系中,父类定义了一个虚函数,在子类中重新定义了一个函数名
、参数列表
、返回值
和父类虚函数完全相同
的函数,这叫做虚函数的重写。
注意:子类重写父类虚函数时可以不加virtual关键字
重写的特例:
协变:重写的虚函数返回值可以和父类虚函数不同,但必须是具有继承关系的指针或者引用
#include<iostream>
using namespace std;
class Person {
public:
virtual void BuyTicket()
{
cout << "全票" << endl;
}
virtual Person* RedPapper()
{
cout << "100块" << endl;
return new Person;
}
};
class Student :public Person {
public:
//重写:函数名,参数列表、返回值与父类虚函数完全相同
void BuyTicket()
{
cout << "半票" << endl;
}
//协变:返回值为具有继承关系的类指针或引用
Student* RedPapper()
{
cout << "1毛" << endl;
return new Student;
}
};
void test()
{
Person p;
Person* p1 = &p;
Person& p2 = p;
//p.BuyTicket(); //用对象来调用虚函数时,不会产生多态
p1->BuyTicket();
p2.BuyTicket();
p1->RedPapper();
p2.RedPapper();
Student s;
p1 = &s;
Person& p3 = s;
p1->BuyTicket();
p3.BuyTicket();
p1->RedPapper();
p3.RedPapper();
}
int main()
{
test();
return 0;
}
运行结果:
协变除了上述定义方法外,其返回值还可以是自定义的具有继承关系的类引用或指针,例如:
#include<iostream>
using namespace std;
class A{};
class B :public A {};
class Person {
public:
virtual A* RedPapper()
{
cout << "100块" << endl;
return new A;
}
};
class Student :public Person {
public:
//协变:返回值为具有继承关系的指针或引用
B* RedPapper()
{
cout << "1毛" << endl;
return new B;
}
};
void test()
{
Person p;
Person* rp=&p;
rp->RedPapper();
rp = (Person*)new Student;
rp->RedPapper();
}
int main()
{
test();
return 0;
}
运行结果:
override关键字 final关键字
override关键字的作用:
用于修饰子类中的函数
,自动检查这个函数是否重写父类的虚函数,如果没有重写则报错,即强制重写。
//强制重写
virtual void fun() override
{}
final关键字的作用:
1、修饰类
:表示该类无法被继承
//表示类A不能被继承
class A final
{};
2、修饰父类虚函数
:表示该虚函数不能被重写
//无法被重写
virtual void fun() final
{}
抽象类
纯虚函数:在虚函数后面写上=0,表示该函数是一个纯虚函数,例如:
virtual void fun() =0;
抽象类:包含纯虚函数的类叫做抽象类(也叫接口类)。
抽象类不能实例化出对象。
派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
如果不实现多态,不要把函数定义成虚函数。
虚函数表
阅读代码:
显然,类A中只有一个int型的数据成员,那为什么这里对象a却占了8个字节的空间呢?
调试:
通过调试我们发先对象a中多了一个名为_vfptr
的指针,对象中的这个指针我们叫做虚函数表指针
(v代表virtual,f代表function),虚函数表指针指向的就是该类的虚函数表。
一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表
中,虚函数表也简称虚表。
虚函数表其实是一个指针数组。
下面再深入理解一下:
#include<iostream>
using namespace std;
class A {
public:
A() {}
virtual void fun1() {}
virtual void fun2() {}
void fun3() {}
int _a;
};
class B :public A {
public:
void fun1(){}
int _b;
};
int main()
{
A a;
B b;
cout << sizeof(a) << endl;
return 0;
}
说明:
1、派生类对象b中也有一个虚表指针,这个虚表指针中也有一部分继承自基类
2、基类a对象和派生类b对象虚表是不一样的,这里我们发现fun1完成了重写,所以b的虚表中存的是重写的B::fun1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。
3、fun2继承下来后是虚函数,所以放进了虚表,fun3也继承下来了,但不是虚函数,所以不会放进虚表。
4、虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr。
5、 总结一下派生类的虚表生成:
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己新增的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。