C++进阶----多态
目录
- 引言
- 1.多态的概念
- 2.多态的定义及实现
- 2.1 多态的构成条件
- 2.2虚函数
- 2.3 虚函数的重写
- 2.4 关键字override和final
- 2.5 重载、覆盖(重写)、隐藏对比
- 3.抽象类
- 3.1 抽象类概念
- 4.多态的原理
- 4.1 虚函数表
- 4.2虚函数表的底层
- 4.3多态的原理
- 4.4 动态绑定和静态绑定
- 5.单继承和多继承关系中的虚函数表
- 5.1 单继承中的虚函数表
- 5.2 多继承中的虚函数表
- 6.总结
引言
C++的三大的特性:封装、继承、多态
多态是一种面向对象编程的特性,它允许不同类的对象对同一消息做出响应。
在C++中,多态主要通过虚函数和抽象类来实现。虚函数允许派生类重写基类的方法,而抽象类则可以定义一个接口,强制派生类实现特定的方法。这样,基类指针或引用可以指向派生类对象,从而在运行时实现多态行为。
1.多态的概念
1.1概念
多态(Polymorphism)是面向对象编程(OOP)的一个基本概念,它允许同一个接口接受不同的数据类型。(简单来说就是去完成某个行为,当不同的对象去完成时会产生出不同的状态,比如火车票,成人买成人全价票,儿童买半票,学生买票享有学生的专属折扣票军人买票可以优先买票)
2.多态的定义及实现
2.1 多态的构成条件
多态是在不同继承关系的类对象,在这个前提下构成多态的俩个条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数
2.2虚函数
虚函数:被virtual修饰的类成员函数称为虚函数,无法通过自己本身来实例化
class Person{
public:virtual void BuyTicket(){cout<<"买票-全价"<<endl;}
};
2.3 虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数.
#include<iostream>
using namespace std;
class father
{
public:virtual void Buysth(){ cout << "dad will take you shoping" << endl; }
};class son:public father
{
public:virtual void Buysth(){ cout << "I will go shoping by meself" << endl; }
};void fun(father& f)
{f.Buysth();
}int main()
{father f;son s;f.Buysth();s.Buysth();fun(f);fun(s);//s的切片return 0;
}
虚函数重写的俩个例
- 协变(基类与派生类虚函数返回值类型不同)
基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变
- 析构函数的重写
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor
2.4 关键字override和final
- find
关键字override
用于显式指定派生类函数覆盖了基类的虚函数;final
关键字则用于阻止一个虚函数在其派生类中被覆盖,或者阻止一个类被继承。
class D{
public:
virtual void d() final{}//基类
}
- override
override
关键字主要用于检查派生类的函数是否真正的重写了基类的虚函数,如果没有则会报错。调用覆盖的函数时,会调用派生类的版本,而不是基类的版本。
class D{
public:
virtual void d ()voerride{}//派生类
}
2.5 重载、覆盖(重写)、隐藏对比
3.抽象类
3.1 抽象类概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
抽象类是一个特殊的类,它是为了表示一种抽象的概念而设立的,不能被实例化。抽象类通常作为一种基本形态,由它派生出各种具体类。抽象类至少包含一个纯虚函数。
class father
{
public:virtual void Buysth() =0 { cout << "dad will take you shoping" << endl; }
};class son:public father
{
public:virtual void Buysth(){ cout << "I will go shoping by meself" << endl; }
};
4.多态的原理
4.1 虚函数表
虚函数表是C++中的一个概念,用于支持多态性。每个具有虚函数的对象都有一个指向虚函数表的指针,虚函数表中包含了对象的虚函数的地址。
class father
{
public:virtual void Buysth() { cout << "dad will take you shoping" << endl; }
private:int _x;
};
class A
{
public:void a(){}
private:int _x;
};
int main()
{father f;A a;return 0;
}
可以看出存在虚函数的类中,大小和普通类的大小是不一样的。
除了_x成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function
4.2虚函数表的底层
class Base
{
public:virtual void fun1() { cout << "fun1()" << endl; }virtual void fun2() { cout << "fun2()" << endl; }
private:int _B = 0;
};
class A:public Base
{
public:void fun1() { cout << "A::fun1()" << endl; }
private:int _a = 1;
};class B :public Base
{
public:void fun1() { cout << "B::fun1()" << endl; }void fun2() { cout << "B::fun2()" << endl; }
private:int _b = 2;
};int main()
{Base base;A a;B b;return 0;
}
可以看出每个实例类的虚函数表的地址和所包括的虚函数都不一样,构成重写的会生成新的虚函数,而没有构成重写的会继承基类的虚函数
4.3多态的原理
多态的实现原理基于两个关键概念:虚函数表和虚函数表指针。每个多态类都有一张虚函数表,这张表中存储了该类的虚函数地址。每个多态类的对象都有一个虚函数表指针,该指针指向该类的虚函数表。当我们通过基类的指针调用虚函数时,程序会根据这个指针指向的虚函数表中的地址去调用对应的函数,从而实现了多态。
通过调用基类的引用和指针,派生类的指针和引用,来区别不继承类和基类的虚函数调用。满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。
4.4 动态绑定和静态绑定
- 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,
比如:函数重载
- 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
5.单继承和多继承关系中的虚函数表
5.1 单继承中的虚函数表
在4.2中的虚函数表底层中就是常见的单继承
5.2 多继承中的虚函数表
class Base
{
public:virtual void fun1() { cout << "fun1()" << endl; }virtual void fun2() { cout << "fun2()" << endl; }
private:int _B = 0;
};class More
{
public:virtual void fun1(){}virtual void fun2(){}
private:int _M = 0;
};class A:public Base,public More
{
public:void fun1() { cout << "A::fun1()" << endl; }
private:int _a = 1;
};int main()
{Base base;A a;return 0;
}
可以看出,多继承虚函数的派生类也会有多应多个的虚函数表(虚函数指针表)
6.总结
在C++中,多态性是一种允许不同类型的对象对同一消息做出不同响应的特性。通过虚函数和继承,我们可以实现运行时多态。运行时多态是面向对象编程的三大特性之一,它极大地增强了程序的可扩展性。但是,我们也要注意虚函数会带来一些性能开销,因此在使用时需要权衡利弊。