多态的定义和原理
大约 4 分钟
多态的定义和原理
多态的定义
多态性(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接口不用修改,方便许多
