C++多态

**多态解决的问题:**当不同的对象去完成时会产生出不同的状态
例:当普通人去买票是原价,而学生买票是半价
我们定义一个Person类,再定义一个Student类继承Person类

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;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

这时,Func(ps)输出的是"买票-全价",而Func(st)输出的是"买票-半价"。

多态需要满足的条件:

  1. 必须通过基类的指针或者引用调用虚函数
    Func函数中形参是Person(父类)的引用
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
    父类和子类的BuyTicket函数都加了virtual进行修饰,为虚函数
    虚函数重写的两个例外:
    (1)协变:(类与子类虚函数返回值类型不同)
    派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
    (2)析构函数的重写(基类与派生类析构函数的名字不同)
    如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,因为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
    两个关键字:
    1.final:修饰虚函数,表示该虚函数不能再被重写
class Car
{
public:
 virtual void Drive() final {}
};
class Benz :public Car
{
public:
 virtual void Drive() {cout << "Benz-舒适" << endl;}
};

2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

class Car{
public:
 virtual void Drive(){}
};
class Benz :public Car {
public:
 virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

纯虚函数
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:
virtual void Drive() = 0;
};

class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
//子类中Drive函数必须重写

多态原理(虚函数表)
虚函数表也称虚表,一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数
的地址要被放到虚函数表中

class Base
{
public:
 virtual void Func1()
 {
 cout << "Func1()" << endl;
 }
private:
 int _b = 1;
};

上面代码中,sizeof(Base)为8(64位平台是16)
因为Func1是虚函数, 除了_b成员,还多一个__vfptr放在对象的前面(有些平台可能会放到对象的最后面,这个跟平台有关),所以大小为8或16,__vfptr这个指针我们叫做虚函数表指针,指向虚函数表
在这里插入图片描述
修改以上代码
1.增加一个派生类Derive去继承Base
2.Derive中重写Func1
3.Base再增加一个虚函数Func2和一个普通函数Func3

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;
}

查看对象模型
在这里插入图片描述
1.可以看到对象b和d中都有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员、虚表指针,另一部分是自己的成员。
2.父类的虚表与子类的虚表不一样,子类重写了父类的Func1函数。Func2也继承了,放进了虚表,但是没有重写。Func3也继承了,但是不是虚函数,所以不用放进虚表。
3.虚函数表本质是一个函数指针数组,里面存放的是虚函数指针,数组最后面放了一个nullptr。
4.要注意,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。千万不要以为,虚函数存放在虚表,虚表存放在对象中
总结:首先子类继承父类的成员和函数。
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后

我们再回顾这段代码,以及为什么实现多态要满足两个条件

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;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

查看对象模型
在这里插入图片描述
p指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket
p指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket
这样就实现了多态
那为什么必须要是对象的指针或引用调用虚函数,因为满足多态以后的函数调用,不是在编译时确定的,是运行
起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的

静态多态:静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,
比如:函数重载
动态多态:动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体
行为,调用具体的函数,也称为动态多态。

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力码代码的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值