多态的概念
多态: 同样的调用语句有多种不同的表现形态, 即根据实际的对象类型决定函数调用语句的具体调用目标。
- 如果父类指针(引用)指向的是父类对象则调用父类中定义的函数。
- 如果父类指针(引用)指向的是子类对象则调用子类中定义的重写函数。
多态的实现
通过virtual关键字, 来修饰成员函数, 则该成员函数为虚函数。
虚函数允许派生类重新定义成员函数, 而派生类重新定义基类的做法称为override, 或重写。
虚函数的作用就是实现多态, 即以共同的方法, 对不同的对象采取不同的策略。
虚函数的定义: 虚函数只能是类的成员函数, 而且不能是静态的。 在成员函数定义或声明前加上关键字virtual, 即定义了虚函数:
class 类名
{
virtual 返回类型 函数名(形式参数列表); //虚函数
};
注意:
- 当在派生类中定义了一个同名的成员函数时, 只要该成员函数的参数个数、 参数类型、 返回类型与基类中同名的虚函数完全一样, 则派生类的这个成员函数无论是否添加virtual关键字, 它都将成为一个虚函数。
- 一般为了便于阅读, 派生类的虚函数也会添加上virtual关键字。
- 利用虚函数, 可在基类和派生类中使用相同的函数名定义函数的不同实现, 从而实现“ 一个接口, 多种方式” 。
多态实现的理论基础
静态联编和动态联编:
- 联编: 又称为绑定, 即将模块或函数合并在一起生成可执行代码的处理过程, 同时对每个模块或者函数分配内存地址, 并且对外部访问也分配正确的内存地址。
- 静态联编: 在编译阶段就将函数实现和函数调用绑定起来。 又称为早绑定。 它对函数的选择是基于指向对象的指针或引用本身的类型。
- 动态联编: 在程序运行的时候才进行函数实现和函数调用的绑定称为动态联编。 又称为晚绑定。
注意:
- C语言中, 所有的联编都是静态联编。
- C++中一般情况下的联编也是静态联编。 一旦涉及到多态和虚函数就必须使用动态联编。
多态的实现原理
理论基础:
当编译器编译含有虚函数的类时, 将会为这个类建立一个虚函数表。
虚函数表是一个存储类成员函数指针的数据结构, 相当于一个指针数组, 存放每个虚函数的入口地址。
并且, 编译器会为该类添加一个额外的数据成员, 这个数据成员是一个指向虚函数表的指针, 通常称为vptr。
实现原理:
使用基类的指针或引用调用函数时, 如果该函数为虚函数, 编译器找到指针或引用指向的对象的vptr指针, 并根据其所指的虚函数表找到函数并调用。 查找和调用都是在运行时进行的, 即动态绑定。
如果该函数不是虚函数, 则编译器可以直接通过指针的类型确定被调用的函数, 并进行绑定, 即静态绑定。
多态成立的条件
1. 要有继承。
2. 要有函数重写。
3. 要有基类指针(引用)指向派生类对象。
多态的作用
C++的三大特性:
- 封装: 突破了C语言函数的概念, 封装可以隐藏实现细节, 使得代码模块化。
- 继承: 继承可以扩展已存在的代码模块(类), 达到代码重用的目的。
- 多态: 多态实现了接口重用, 使得可以使用未来, 即当前的框架不需要改变也可以使用后来的代码。
#include<iostream>
using namespace std;
class Base
{
public:
virtual void print()
{
cout << "调用基类的print方法" << endl;
}
};
class Derived : public Base
{
public:
//无论是否添加virtual关键字,它都是一个虚函数
//参数个数、参数类型、返回类型、函数名都与基类中的print函数一直。 重写
//一般情况下,为了强调为虚函数,还是会添加上关键字virtual
virtual void print()
{
cout << "调用派生类的print方法" << endl;
}
};
//后定义的派生类
class Derived1 : public Base
{
public:
virtual void print()
{
cout << "调用派生类D1的print方法" << endl;
}
};
//先定义的函数
void print1(Base &b)
{
b.print();
}
int main()
{
Base b1;
Derived d1;
b1.print(); //调用基类的print方法
d1.print(); //调用派生类的print方法
d1.Base::print(); //调用基类的print方法
未使用虚函数时,无法实现多态,都调用的是基类的print方法
//Base *pb = &b1;
//pb->print(); //调用基类的print方法
//pb = &d1;
//pb->print(); //调用基类的print方法
//使用虚函数时,可以实现多态,会根据定义的指针指向的对象的类型,去调用不同的类中的同名方法
//一个接口,多种实现
Base *pb = &b1;
pb->print(); //调用基类的print方法
pb = &d1;
pb->print(); //调用派生类的print方法
//使用基类的对象的引用作为参数,根据传递的实参的类型,去选择调用的函数。
print1(b1);
print1(d1);
//可以使用未来
Derived1 d2;
print1(d2);
return 0;
}