跳至主要內容

多态的定义和原理

张威大约 4 分钟c/c++多态

多态的定义和原理

多态的定义

多态性(polymorphism)是面向对象设计语言的基本特征之一。仅仅是将数据和函数捆绑在一起,进行类的封装,使用一些简单的继承,还不能算是真正应用了面向对象的设计思想。多态性是面向对象的精髓。多态性可以简单地概括为“一个接口,多种方法”。

通常是指对于

为什么用多态

封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类)。它们的目的都是为了代码重用。而多态除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性

如果项目耦合度很高的情况下,维护代码时修改一个地方会牵连到很多地方,会无休止的增加开发成本。而降低耦合度,可以保证程序的扩展性。而多态对代码具有很好的可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。

多态的分类

  • 编译时多态:也称为静态多态,我们之前学习过的函数重载、运算符重载模板(函数模板、类模板)(模板的实例化发生在编译阶段)就是采用的静态多态,C++编译器根据传递给函数的参数函数名决定具体要使用哪一个函数,又称为先期联编(early binding)。
  • 运行时多态在继承结构中,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数),基类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法,称为动态多态
    • 必须在程序运行时完成选择,因此编译器必须提供这么一套称为“动态联编”(dynamic binding)的机制,也叫晚期联编(late binding)。多态底层是通过来实现的,C++通过来实现动态绑定/动态联编。
    • 基类中给所有派生类,让派生类进行重写,然后就可以使用多态了

案例1.静多态:函数重载

bool compare(int, int){}
bool compare(double, double){}

compare(10, 20); //call compare_int_int,在编译阶段就确定好调用的函数版本
compare(10.5, 20.5);//call compare_double_double

案例2.静多态:模板,模板的实例化发生在编译阶段

template<typename T>
bool compare(T a, Tb){}

compare(10, 20); //=>int 实例化一个compare<int>
compare(10.5, 20.5); //=>double 实例化一个compare<double>

案例3.动多态:实现一个动物类

//动物基类
class Animal
{
public:
	Animal(string name):_name(name){}
	virtual void bark(){}
protected:
	string _name;
};

//动物实体类
class Cat : public Animal
{
public:
	Cat(string name):Animal(name){}
	void bark()
	{
		cout << _name << "bark:miao miao!" << endl;
	}
};

class Dog : public Animal
{
public:
	Dog(string name):Animal(name){}
	void bark()
	{
		cout << _name << "bark:wang wang!" << endl;
	}
};

class Pig : public Animal
{
	public:
	Pig(string name):Animal(name){}
	void bark()
	{
		cout << _name << "bark:heng heng!" << endl;
	}
};

void bark(Cat &cat)
{
	cat.bark();
}

void bark(Dog &dog)
{
	dog.bark();
}

void bark(Pig &pig)
{
	pig.bark();
}

int main()
{
	Cat cat("小猫");
	Dog dog("小狗");
	Pig pig("小猪");

	bark(cat);
	bark(dog);
	bark(pig);

	return 0;
}

此时发生动态绑定但是这个bark()接口不太好,我们若添加更多的新的动物,派生类对象越多,bark()方法还需要继续增加。相应的实体类若删除,其对应接口也要删除,达不到高内聚低耦合。

那么用什么类型能将上面的对象都能接受呢? 我们使用统一的基类类型来接受派生类的对象

void bark(Animal *p)
{
	p->bark();
}

Cat,Dog,Pig是从Animla继承而来,基类指针就可以指向派生类对象,都可以调用。由于Animal::bark()执行动态绑定,先访问指针指向的对象的前四个字节,基类指针指向了三个不同的对象,每一次访问的就是这三个不同对象的虚函数表,取用它们重写的bark()

p->cat cat vftable &Cat::bark();
p->dog dog vftable &Dog::bark();
p->Pig pig vftable &Pig::bark();

此时再增加新的派生类型,API接口不用修改,方便许多