c++多态

目录

前言

实际场景

多态的定义

构成多态的规则

规则1:

规则2:

特殊情况

第一种:协变

第二种:析构函数的重写 

第三种:普通继承与接口继承

重写(覆盖)、重定义(隐藏)的关系

c++11的override和final

final

override

抽象类

概念

意义

多态的原理

虚函数表指针

多态的原理

关于虚表的几个常见问题

补充概念:动态绑定与静态绑定

多继承关系的虚函数表 


前言

在看多态之前,建议先看看往期关于继承的讲解,因为多态这里需要用到继承,而继承也是实现多态的基础

实际场景

1、小明和他父亲今年过年打算回老家,于是它们两个都用各自的身份证去app上买票,但它们两个买票的价格不同,小明父亲是全价买票,但小明作为一个学生买票是半价。那么卖票的app是怎么分辨它们各自的身份买票的价格呢?要实现这个逻辑我们可以使用多态的方式

 2、张三的女朋友小美是一个资深的支付宝用户,有一天,支付宝推出了一个活动“扫码抢红包”,小美扫了几毛钱,抱着尝试的心态,小美叫张三也来试试,但张三没有用过支付宝。于是小美就帮他注册了一个新的支付宝账号并且绑定实名。当张三去参加这个扫码抢红包的活动时,居然扫到了20块钱,小美很好奇为什么会这样,于是她又叫了几个朋友去参加这个活动。最终发现一个规律:为了吸引用户,越不经常用支付宝的人扫到的红包越多。那么支付宝是如何实现这个逻辑的呢?其实这个逻辑也可以用多态的方式完成

多态的定义

所谓多态,就是在不同继承关系的类对象,去调用同一个函数,产生了不同的行为。

例如学生继承了人,人买票全价,学生买票半价

如下代码实现的就是多态

class Person
{
public:
	virtual void BuyTicket()
	{
		std::cout << "买票-全价" << std::endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		std::cout << "买票-半价" << std::endl;
	}
};
void func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person p;
	Student s;
	func(p);
	func(s);
	//运行结果:
	//买票-全价
	//买票-半价
	return 0;
}

上述代码,实现了多态调用,即传给func的对象类型是Person,就调用Person的买票函数,传给func的对象类型是Student,就调用Student的买票函数。

(上述代码在构成多态的规则章节会一直用)

我们已经看到了多态的简单场景,那么怎么样才能实现多态呢?

很显然,要构成多态是需要一些规则的,我们接下来就是要详细了解这些规则,以及一些特殊的情况

构成多态的规则

规则1:

构成多态的两个函数必须满足虚函数重写规则

 首先,我们需要先了解一下什么是虚函数

虚函数:被virtual修饰的函数就是虚函数

重写:就是在一个父子继承类中的同名函数的返回值和参数类型都相同,总结一下就是三同(返回值、参数类型、名字),并且满足重写的两个函数必须是虚函数,例如上面的代码实现中,Person类和Student类的Buyticket函数就满足重写,并且我们称子类的虚函数重写了父类的虚函数

规则2:

必须通过基类的指针或者引用调用虚函数

也就是说在上面的代码实现中,如果func函数的形参不是Person&或Person*,那么就无法实现多态调用

上述,我们说的两个规则都是普遍情况,但有时候会出现特殊情况,也就是不满足上述两个规则但也构成多态的情况

特殊情况

第一种:协变

协变说的是在有些情况下,虚函数的返回值不同也能构成多态

但协变也有要求,就是虚函数的返回值必须是父子类关系的指针或者引用,如下代码

class Person
{
public:
	virtual Person& BuyTicket()
	{
		std::cout << "买票-全价" << std::endl;
		return *this;
	}
};

class Student : public Person
{
public:
	virtual Student& BuyTicket()
	{
		std::cout << "买票-半价" << std::endl;
		return *this;
	}
};
void func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person p;
	Student s;
	func(p);
	func(s);
	//运行结果:
	//买票-全价
	//买票-半价
	return 0;
}

 上述代码演示的协变是父子类关系的引用,而父子类关系的指针同样也可以

第二种:析构函数的重写 

析构函数的重写说的是两个类的析构函数名字不相同,但它们可以构成重写关系

如下代码A类和B类的析构函数构成重写

class A
{
public:
	virtual ~A()
	{
		std::cout << "~A()" << std::endl;
	}
};

class B : public A
{
public:
	virtual ~B()
	{
		std::cout << "~B()" << std::endl;
	}
};

 对于析构函数的重写这种特殊情况,其实是有意义的,如下场景中就一定要用到多态,而多态就要满足虚函数的重写

class A
{
public:
	~A()
	{
		std::cout << "~A()" << std::endl;
	}
};

class B : public A
{
public:
	~B()
	{
		std::cout << "~B()" << std::endl;
	}
};

int main()
{
	A* pA = new A;
	delete pA;
	A* pB = new B;
	delete pB;
    //运行结果:
    //~A()
    //~A()
	return 0;
}

我们可以看到,在上述代码中,pA指向的对象只调用一次A类的析构是没问题的,但pB指向的对象是一个B类型,正常来说是要先调用一次B类析构,再调用一次A类析构,但上述代码调用析构函数的时候只调用了一次A类的析构函数,B类的析构函数并没有调用,这是由于上述代码是普通调用,而不是多态调用,对于普通调用是根据指针的类型来调用析构的,多态调用是根据指针指向的类型,上述场景就

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值