C++:多态

 一、多态是什么

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了
Person。Person对象买票全价,Student对象买票半价。

在继承中构成多态的两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

也就是说,父类的指针或者引用可以指向父类,也可以指向子类,指向父类时调用父类的虚函数,指向子类时调用子类的虚函数,如果调用的不是虚函数,则会调用父类的成员函数

这里是使用虚函数的简单例子、

#include<iostream>
using namespace std;
class person
{
public:
	virtual void buyticket()
	{
		cout << "买票,全价" << endl;
	}
};
class student :public person
{
public:
	virtual void buyticket()
	{
		cout << "买票,半价" << endl;
	}
};
int main()
{
	person p;
	p.buyticket();
	student s;
	s.buyticket();
	person& p1 = s;
	p1.buyticket();
	return 0;
}

 运行结果

p1是s中的person切片的引用,但是它的输出结果仍然是半价,这就是多态的结果

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。 

但是有两个特殊情况

1.派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。 

class A{};
class B : public A {};

class Person {
public:
virtual A* f() {return new A;}//基类返回基类
};
class Student : public Person {
public:
virtual B* f() {return new B;}//派生类返回派生类
};

 2. 析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor。

为什么需要重写析构函数,即为什么需要将析构函数处理成虚函数?

这里是一个例子

class A
{

public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	int* a;
};
class B :public A
{
public:
	B()
		:b(nullptr)
	{
		cout << "B()" << endl;
		b = new int[10];
	}
	~B()
	{
		cout << "~B()" << endl;
		delete[]b;
	}
public:
	int* b;
};
int main()
{
	A* a = new B;
	delete a;
	return 0;
}

运行结果

很明显只是将基类析构了,派生类中new出来的数组没有处理。因为a是A类的指针,调用delete时会调用A类切片的析构,B的部分没有处理,那么只要我们将析构函数处理成虚函数就可以 

class A
{

public:
	A()
	{
		cout << "A()" << endl;
	}
	virtual ~A()
	{
		cout << "~A()" << endl;
	}
	int* a;
};
class B :public A
{
public:
	B()
		:b(nullptr)
	{
		cout << "B()" << endl;
		b = new int[10];
	}
	virtual ~B()
	{
		cout << "~B()" << endl;
		delete[]b;
	}
public:
	int* b;
};
int main()
{
	A* a = new B;
	delete a;
	return 0;
}

 这里加上了virtual,再次运行

因为a指向的是派生类,且析构函数是虚函数,所以调用派生类的析构函数

二、重载、重定义(隐藏)、重写(覆盖)的对比

 其中重载是最容易区分的,其次,只要意识到不构成重写就构成重定义,就很容易区分这三者

class person
{
public:
	virtual void buyticket()
	{
		cout << "买票,全价" << endl;
	}
};
class student :public person
{
public:
	virtual void buyticket(int)
	{
		cout << "买票,半价" << endl;
	}
};

这里的buyticket函数,参数不同,所以不构成重写,所以构成重定义,父类指针调父类函数,子类指针调子类函数

三、抽象类

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

 接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实
现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成
多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

四、多态的原理 

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

 这里的运行结果

明明只有一个int类型成员变量,为什么占用8个字节呢? 

通过监视窗口,我们发现这里实例化出来的对象多了一个_vfptr指针

 

我们对上面的代码进行改进

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

 让Derive继承Base,并重写Func1函数

打开监视窗口

 

很明显,d中Base的虚表和b中虚表的Fun1不一样了,而Fun2还是一样的,原因是Derive对Func1进行了重写(覆盖),而没有重写Func2,而Func3不是虚函数,所以没有存放在虚表

那么现在多态的原理呼之欲出了,当基类指针或引用指向基类时,在虚表中找到的是基类的虚函数,即没有被重写的虚函数;当基类指针或引用指向派生类时,在虚表中找到的是派生类的虚函数,即重写后的虚函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

迷梦y

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

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

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

打赏作者

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

抵扣说明:

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

余额充值