C++ 多态
一、多态性
1、多态性的定义
“多态(polymorphism)”指的是同一名字的事物可以完成不同的功能
2、多态性的分类及实现方式
多态分为两种:
1)、编译时的多态性(静态多态):通过函数重载和函数模板实现
2)、运行时的多态性(动态多态):通过虚函数实现
3、动态多态的作用
没有实现多态的时候,基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数,当实现多态之后,基类指针就既可以访问派生类的成员变量也可以访问派生类的成员函数
示例:
#include <iostream>
using namespace std;
//基类
class Base
{
protected:
int m_a;
public:
Base(int a)
{
m_a = a;
}
void display()
{
cout << "m_a = " << m_a << " 这是基类的display() " << endl;
}
};
//派生类
class Derive: public Base
{
protected:
int m_b;
public:
Derive(int a, int b) : Base(a)
{
m_b = b;
}
void display()
{
cout << "m_b = " << m_b << " 这是派生类的display()" << endl;
}
};
int main()
{
Base *p = new Base(1);
p->display();
p = new Derive(3, 2);
p->display();
return 0;
}
运行结果:
m_a = 1 这是基类的display()
m_a = 3 这是基类的display()
上述示例中,如果指针指向了派生类对象,那么就应该使用派生类的成员变量和成员函数,但是运行结果却告诉证明事实并不是如此,当基类指针 p 指向派生类Derive的对象时,虽然使用了 Derive的成员变量,但是却没有使用它的成员函数,导致输出结果不符合预期。
为此,如果要实现基类指针指向基类对象时调用基类方法、基类指针指向派生类对象时调用派生类方法的功能,这就需要多态性来实现,而多态性是由虚函数来实现的。
在上面示例中,只需将基类中的display()声明为虚函数便可实现多态
#include <iostream>
using namespace std;
//基类
class Base
{
protected:
int m_a;
public:
Base(int a)
{
m_a = a;
}
virtual void display()
{
cout << "m_a = " << m_a << " 这是基类的display() " << endl;
}
};
//派生类
class Derive: public Base
{
protected:
int m_b;
public:
Derive(int a, int b) : Base(a)
{
m_b = b;
}
void display()
{
cout << "m_b = " << m_b << " 这是派生类的display()" << endl;
}
};
int main()
{
Base *p = new Base(1);
p->display();
p = new Derive(3, 2);
p->display();
return 0;
}
运行结果:
m_a = 1 这是基类的display()
m_b = 2 这是派生类的display()
借助引用也可以实现多态,不过指针可以随时改变指向,而引用只能指代固定的对象
#include <iostream>
using namespace std;
//基类
class Base
{
protected:
int m_a;
public:
Base(int a)
{
m_a = a;
}
virtual void display()
{
cout << "m_a = " << m_a << " 这是基类的display() " << endl;
}
};
//派生类
class Derive: public Base
{
protected:
int m_b;
public:
Derive(int a, int b) : Base(a)
{
m_b = b;
}
void display()
{
cout << "m_b = " << m_b << " 这是派生类的display()" << endl;
}
};
int main()
{
Base b(1);
Derive d(3, 2);
Base &rb = b;
Base &rd = d;
rb.display();
rd.display();
return 0;
}
运行结果:
m_a = 1 这是基类的display()
m_b = 2 这是派生类的display()
4、虚函数的注意事项
1)、 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加;
2)、 将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数;
3) 、当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数;
4)、 只有派生类的虚函数覆盖基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数);
5)、 构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义;
6) 、析构函数可以声明为虚函数,而且有时候必须要声明为虚函数
5、构成多态的条件
1)、必须存在继承关系;
2)、继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同);
3)、存在基类的指针,通过该指针调用虚函数
6、什么时候声明虚函数
首先看成员函数所在的类是否会作为基类;
然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数
二、动态绑定和静态绑定
1、函数名联编
程序调用函数时,编译器将源代码中的函数调用解释为执行特定的函数代码块称之为函数名联编
2、静态绑定
静态绑定也叫静态联编或早期联编,是指在编译阶段进行的函数名联编。也就是说如果有多个重载函数,在编译阶段就确定了要执行哪个函数
3、动态绑定
动态绑定也叫动态联编或晚期联编,是指编译器在运行阶段进行的函数名联编。当有虚函数存在时,无法在编译阶段确定执行哪个函数,因为编译器无法确定用户会选择哪种类型的对象,所以只能在程序运行来确定执行哪个函数
4、静态联编与动态联编的比较
1)、默认联编方式:静态联编是默认的联编方式
2)、效率比较:静态联编的效率高于动态联编,因为动态联编需要追踪基类指针或引用指向的对象类型
5、需要动态联编的情况
1)、类不会用作基类
2)、派生类不重新定义基类的任何方法
三、多态实现的机制
多态是借助虚函数实现的,多态的实现机制其实就是虚函数的工作机制
虚函数的工作原理
编译器会为每个对象添加一个隐藏成员,这个成员保存了一个指向函数地址数组的指针,这个函数地址数组称之为虚函数表(virtual function table, vtbl)。虚函数表中存储了为类对象声明的虚函数的地址。
例如,基类对象包含一个指向基类中所有虚函数地址表的指针,派生类对象包含一个指向派生类中所有虚函数地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址,若派生类没有提供虚函数的定义,则保存函数原始版本的地址
四、多态的优缺点
1、优点
1)、提高了代码的可扩展性
2)、多态可以增加灵活性,使用基类指针使用基类方法和派生类方法
2、缺点
1)、不能使用子类的特有属性和行为
2)、在内存和执行速度方面有一定的成本