C++类与对象笔记十四:多态一:多态概念、虚函数、动态多态、静态多态、重写虚函数、里氏转换法

多态是面向对象的三大特性之一:封装、继承、多态。

多态分为两类:

  • 静态多态:函数重载运算符重载属于静态多态,复用函数名。
  • 动态多态:派生类虚函数实现运行时多态。

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

  • 静态多态的函数地址 绑定 —— 编译阶段确定函数地址。
  • 动态多态的函数地址 绑定 —— 运行阶段确定函数地址。


#include
using namespace std;

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

void doSpeak(Animal& animal)   // Animal& animal = cat;
{
	animal.Speak();
}
void test01()
{
	Cat cat;
	doSpeak(cat);  // 输出:"动物会说话"  相当于把cat传递给参数Animal& animal。
}
int main()
{
	test01();
	system("pause");
	return 0;
}

输出:不是“小猫在说话”。

输出:"动物会说话" 

当我们通过doSpeak调用时:将cat传递给Animal& animal;

等价于:

Animal& animal = cat;

C++中允许父子之间的类型转换。不需要做强制类型转换。

父类的引用或者指针可以直接指向子类对象

为什么输出不是我们期望的cat呢?

void doSpeak(Animal& animal)   // Animal& animal = cat;
{
	animal.Speak();
}

原因就是现在函数的地址早绑定:在编译阶段就能确定函数的地址:他绑定的地址是animal中的Speak。而不是运行时传参传入的子类Cat中的Speak。所以调用这个函数,不论我们传入任何Animal子类的动物,都不会变,都是“动物会说话”。

为了达到我们:输入任一动物,就输出这一动物的Speak。就需要在运行时动态多态。也就是说,让函数地址 晚 绑定

如果想要执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,也就是地址晚绑定。

如何实现?

在父类Speak返回类型之前加上关键字:virtual;此时这个函数被称为虚函数。此时的代码就可以实现动态多态效果。

#include
using namespace std;

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

class Dog :public Animal
{
public:
	void Speak()
	{
		cout << "小狗在说话" << endl;
	}
};
void doSpeak(Animal& animal)   // Animal& animal = cat;
{
	animal.Speak();
}
void test01()
{
	Cat cat;
	doSpeak(cat);  // 输出:"动物会说话"  相当于把cat传递给参数Animal& animal。
	Dog dog;
	doSpeak(dog);
}
int main()
{
	test01();
	system("pause");
	return 0;
}


动态多态满足条件:

1、有继承关系。

2、子类重写父类的虚函数。(类似C#中的override)。子类中的virtual可写可不写

3、父类中虚函数:virtual

重写:函数返回值类型、函数名、参数列表 完全相同。



动态多态使用条件

父类的指针或者引用指向子类的对象。里氏转换法。

Animal& animal = cat;


总结:

子类通过重写父类虚函数,来实现动态多态。调用时,父类的指针或者引用指向子类的对象

C#中设计里氏转换法则:可以参照。

  • 子类可以赋值给父类
  • 如果 一个父类装的是子类的对象,那么父类可以直接强转成子类。
  • 父类数组可以装多种子类。

你可能感兴趣的:(C++,c++,多态,面向对象,虚函数,动态多态)