c++ 多态 学习总结1 多态的基本语法和多态的原理

一、多态的基本语法

多态分为两类:

静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名

动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

静态多态的函数地址早绑定 - 编译阶段确定函数地址

动态多态的函数地址晚绑定 - 运行阶段确定函数地址

这篇文章我主要说的是动态的多态!

举个例子:

#include<iostream>
using namespace std;
class Animal
{
public:
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};
class Cat:public Animal
{
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
void doSpeak(Animal &a)
{
	a.speak();
}
int main()
{
	Cat c;
	doSpeak(c);
	return 0;
}

运行结果:

我们给doSpeak函数传入的是一个Cat的实例对象,虽然doSpeak在定义的时候要求传入一个Animal类的对象引用,但c++中父类的引用是可以指向对象的。我们传入Cat的对象肯定想获得

“小猫在说话”的运行结果,但事实上结果是“动物在说话”,如何解决这个问题呢。

多态的基本语法:

多态的满足条件:

1.Animal类和Cat类有继承关系(满足)

2.子类重写父类中的虚函数(上述代码不满足)

根据“2.子类重写父类中的虚函数”这一原则我们试试新的代码

#include<iostream>
using namespace std;
class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
class Cat:public Animal
{
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
void doSpeak(Animal &a)
{
	a.speak();
}
int main()
{
	Cat c;
	doSpeak(c);
	return 0;
}

对比两端代码,我只是在父类Animal的speak()函数前加了一个virtual

(在子类Cat类中void speak()函数前的virtual可以加可以不加)

多态的使用条件:

发生函数重写

发生函数重写的条件:

父类指针或引用指向子类对象

例子中我们在定义doSpeak的时候要求传入一个Animal类的对象的引用(Animal &a),但实际上我们传入的是他的子类Cat类的对象c,这样不太明显,我改一下代码给大家看看:

改法1:父类的引用指向子类对象

#include<iostream>
using namespace std;
class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
class Cat:public Animal
{
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
void doSpeak(Animal &a)
{
	a.speak();
}
int main()
{
	Cat c;
	Animal& a = c;
	doSpeak(a);
	return 0;
}

我添加了一行Animal &a=c,并且doSpeak函数传入的是父类Animal的引用a不再是子类Cat的对象c。

改法2:父类的指针指向子类

#include<iostream>
using namespace std;
class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
class Cat:public Animal
{
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
void doSpeak(Animal *a)
{
	a->speak();
}
int main()
{
	Animal *animal=new Cat;
	doSpeak(animal);
	return 0;
}

可以看 main函数我更改为: Animal *animal=new Cat(父类Animal类的指针指向子类Cat);函数doSpeak也更改为传入一个Animal类的指针。

二、多态的原理(动态多态)

之前说过,动态多态的函数地址晚绑定,函数重写是实现晚绑定的条件,通过函数重写来实现动态多态。因此我这里主要说说函数重写的原理

函数重写指的是子类重写父类的函数,重写与重载不同的是,子类的函数和父类函数完全一样(函数返回值类型、函数名、函数的形参列表全部相同)

如:

class Animal
{
public:
     void speak()
	{
		cout << "动物在说话" << endl;
	}
};
class Cat:public Animal
{
	 void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

父类Animal类和子类Cat类的函数完全一样。

这时我们只要在父类Animal类要重写的函数(void speak())前加上virtual将它变为虚函数即可

(子类Cat类对应的重写函数(void speak())前可以加virtual也可以不加)。

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};
class Cat:public Animal
{
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

函数重写父类虚函数的原理

在未将Animal类的speak函数变为虚函数前,我们知道Animal类的大小为1字节,因为成员函数并未存储在Animal类中(详细可见另一个博主的博客C++中类所占的内存大小以及成员函数的存储位置_SOC罗三炮的博客-CSDN博客_类成员函数存储在哪),当我们把Animal类的speak函数变为虚函数后,Animal类的大小变为4个字节,是因为Animal类中多了一个vfptr:

 这个指针指向一个vftabel

表中记录的Animal类的虚函数地址。
因此Animal类的内部结构为:

而我们知道,Cat类是Animal的子类,理论上Cat继承了Animal类的所有属性,

因此Cat类的内部结构为:

重点来了!:

当我们满足条件:

父类指针或引用指向子类对象时,就会发生子类重写父类的虚函数。

此时Cat的内部的虚函数表就会发生变化,Animal的speak函数地址会被Cat中的speak函数地址覆盖掉,即用&Cat::speak覆盖掉&Animal::speak。

因此我们在程序运行时,会执行子类Cat类中的speak函数。

但是要注意的是,子类重写父类虚函数时,父类的虚函数表是不会变的,子类只会重写自己类内的虚函数表。

这里我再补充一下当父类的引用指向子类对象时:

如:

void speak (Animal &a)

Cat c;

Animal &a= c;

speak(c);

传入的虽然是Animal类的引用,但这个引用指向子类Cat类的对象c,因此编译器会从Cat类的虚函数表中去找speak函数的地址,调用Cat类的speak函数。

当父类的引用指向子类对象时:

如:

void speak (Animal *a)

Animal *animal=new Cat;

speak(animal);

传入的虽然是Animal类的指针,但这个指针指向的地址是子类Cat类的地址,因此编译器会从Cat类的虚函数表中去找speak函数的地址,调用Cat类的speak函数。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值