广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++多态的实现与原理及抽象类实例分析
  • 847
分享到

C++多态的实现与原理及抽象类实例分析

2023-06-29 08:06:51 847人浏览 安东尼
摘要

这篇文章主要讲解了“c++多态的实现与原理及抽象类实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++多态的实现与原理及抽象类实例分析”吧!多态的概念多态: 从字面意思来看,就是事物

这篇文章主要讲解了“c++多态的实现与原理及抽象类实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++多态的实现与原理及抽象类实例分析”吧!

多态的概念

多态: 从字面意思来看,就是事物的多种形态。用C++的语言说就是不同的对象去完成同一个行为会产生不同的效果。

虚函数

虚函数: 被virtual关键字修饰的类成员函数叫做虚函数。

实例演示: 看一下代码,其中BuyTicket成员函数被virtual关键字修饰

class Person{public:// 虚函数virtual void BuyTicket(){cout << "买票全价" << endl;}};

多态构成的条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。

继承中构成多态有两个条件:

  • 必须有基类的指针或引用调用

  • 被调用的函数必须是虚函数,其派生类必须对基类的虚函数进行重写

虚函数的重写是什么?

虚函数的重写(覆盖): 派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。(重写是对函数体进行重写)

实例演示:

class Person{public:virtual void BuyTicket(){cout << "买票全价" << endl;}};class Student : public Person{public:virtual void BuyTicket() // 这里也可以不写virtual,因为基类的虚函数属性已经被保留下来了,这里只是完成虚函数的重写{cout << "买票半价" << endl;}};

虚函数重写的两个例外:

协变:基类和派生类的虚函数的返回类型不同

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(也就是基类虚函数的返回类型和派生类的虚函数的返回类型是父子类型的指针或引用)

// 协变  返回值类型不同,但它们之间是父子或父父关系  返回类型是指针或者引用// 基类虚函数   返回类型  是  基类的指针或者引用  // 派生类虚函数 返回类型  是  基类或派生类的返回类型是基类的指针或引用class A {};class B : public A {};class Person {public:virtual A* f() { return new A; }};class Student : public Person {public:virtual A* f() { return new B; }};

析构函数的重写 基类与派生类的析构函数的函数名不同
我在上一篇博客中说到过,基类和派生类的析构函数的函数名会被编译器统一处理成destructor,所以只要基类的析构函数加了关键字virtual,就会和派生类的析构函数构成重写。

我们再回到多态构成的两个条件中,完成基类虚函数的重写我已经介绍了,还有一个必须由基类的指针或引用调用的条件,这个应该很好理解吧。下面举个例子:     实例演示:

class Person{public:virtual void BuyTicket(){cout << "买票全价" << endl;}};class Student : public Person{public:virtual void BuyTicket() // 这里也可以不写virtual,因为基类的虚函数属性已经被保留下来了,这里只是完成虚函数的重写{cout << "买票半价" << endl;}};void Func1(Person& p) { p.BuyTicket(); }void Func2(Person* p) { p->BuyTicket(); }void Func3(Person p) { p.BuyTicket(); }int main(){Person p;Student s;// 满足多态的条件:与类型无关,父类指针指向的是谁就调用谁的成员函数// 不满足多态的条件:与类型有关,类型是谁就调用谁的成员函数cout << "基类的引用调用:" << endl;Func1(p);Func1(s);cout << "基类的指针调用:" << endl;Func2(&p);Func2(&s);cout << "基类的对象调用:" << endl;Func3(p);Func3(s);return 0;}

代码运行结果:

C++多态的实现与原理及抽象类实例分析

总结

  • 满足多态的条件:成员函数调用与对象类型无关,指向那个对象就调用哪个的虚函数

  • 不满足多态的条件:成员函数的调用与对象类型有关,是哪个对象类型就调用哪个对象的虚函数。

思考: 析构函数是否要加virtual?     答案是需要的。先给大家看一个例子:

class Person{public: ~Person(){cout << "~Person()" << endl;}};class Student: public Person{public:~Student(){cout << "~Student()" << endl;}};int main(){Person* p = new Person;Person* ps = new Student;// 不加virtual,不构成多态,父类指针只会根据类型去调用对于的析构函数// 加了virtual,构成多态,父类指针会根据指向的对象去调用他的析构函数delete p;delete ps;return 0;}

下面分别是基类析构函数不加virtual和加virtual的代码运行结果:

C++多态的实现与原理及抽象类实例分析

C++多态的实现与原理及抽象类实例分析

可以看出,不加virtual关键字时,第二个对象delete时没有调用子类的析构函数清理释放空间。为什么呢?因为不加virtual关键字时,两个析构函数不构成多态,所以调用析构函数时是与类型有关的,因为都是都是父类类型,所以只会调用父类的析构函数。加了virtual关键字时,因为两个析构函数被编译器处理成同名函数了,所以完成了虚函数的重写,且是父类指针调用,所以此时两个析构函数构成多态,所以调用析构函数时是与类型无关的,因为父类指针指向的是子类对象,所以会调用子类的析构函数,子类调用完自己的析构函数又会自动调用父类的析构函数来完成对父类资源的清理。     所以总的来看,基类的析构函数是要加virtual的。

C++11override和final

final: 修饰虚函数,表示该虚函数不可以被重写(还可以修饰类,表示该类不可以被继承)

实例演示:

class Car{public:// final  表示该虚函数不能被重写  也可以修饰类,表示该类不可以被继承virtual void Drive() final {}};class Benz :public Car{public:virtual void Drive() { cout << "Benz-舒适" << endl; }};

编译器检查结果: 由于dirve字母编写错误,所以编译器检查出没有重写基类的虚函数

C++多态的实现与原理及抽象类实例分析

overide: 检查派生类虚函数是否重写了基类的某个虚函数         实例演示:

class Car{public:// final  表示该虚函数不能被重写  也可以修饰类,表示该类不可以被继承virtual void Drive() final {}};class Benz :public Car{public:virtual void Drive() { cout << "Benz-舒适" << endl; }};

编译器检查结果:

C++多态的实现与原理及抽象类实例分析

重载、重写和重定义(隐藏)

名称作用域函数名其他
重载两个函数在同一作用域相同参数类型不同
重写两个函数分别再基类和派生类的作用域相同函数返回类型和参数类型一样
重定义(隐藏)两个函数分别再基类和派生类的作用域相同两个基类和派生类的同名函数不是构成重写就是重定义

抽象类

概念: 在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化象纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

总结出几个特点:

  • 虚函数后面加上=0

  • 不能实例化出对象

  • 派生类如果不重写基类的纯虚函数那么它也是抽象类,不能实例化出对象

  • 抽象类严格限制派生类必须重写基类的纯虚函数

  • 体现了接口继承

实例演示:

class Car{public:virtual void Drive() = 0;};class Benz : public Car{public:virtual void Drive(){cout << "Benz" << endl;}};class BMW : public Car{public:virtual void Drive () override{cout << "BMW" << endl;}};int main(){Car* pBenZ = new Benz;pBenZ->Drive();Car* pBMW = new BMW;pBMW->Drive();delete pBenZ;delete pBMW;return 0;}

代码运行结果:

C++多态的实现与原理及抽象类实例分析

抽象类的意义?

  • 强制子类完成父类虚函数的重写

  • 表示该类是抽象类,没有实体(例如:花、车和人等)

接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

多态的原理

虚函数表

概念: 一个含有虚函数的类中至少有一个虚函数指针,这个指针指向了一张表——虚函数表(简称虚表),这张表中存放了这个类中所有的虚函数的地址。

计算一下下面这个类的大小:

class Base{public:virtual void func1() {}virtual void func2() {}public:int _a;};int main(){cout << sizeof(Base) << endl;return 0;}

代码运行结果如下:

C++多态的实现与原理及抽象类实例分析

这个类中存放了一个虚表指针和一个成员变量,所以总大小就是8。给大家看一下它的类对象模型:

C++多态的实现与原理及抽象类实例分析

实例演示:

class Person{public:virtual void BuyTicket(){cout << "买票全价" << endl;}virtual void func(){cout << "func()" << endl;}int _p = 1;};class Student : public Person{public:virtual void BuyTicket() // 这里也可以不写virtual,因为基类的虚函数属性已经被保留下来了,这里只是完成虚函数的重写{cout << "买票半价" << endl;}int _s = 1;};int main(){Person p;Student s;return 0;}

类对象模型如下:

C++多态的实现与原理及抽象类实例分析

可以看出,两个虚函数地址是不一样的,其实子类会先把父类的虚表拷贝一份下来,如果子类重写了虚函数,那么子类的虚函数的地址将会覆盖虚表中的地址,如果没有重写,那么将不覆盖。

总结几点:

  • 子类对象由两部分构成,一部分是父类继承下来的成员,虚表指针指向的虚表有父类的虚函数,也有子类新增的虚函数

  • 子类完成父类虚函数的重写其实是对继承下来的虚表的中重写了的虚函数进行覆盖,把地址更换了,语法层是称为覆盖

  • 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr

  • 虚表生成的过程:先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后

下面我们来讨论一下虚表存放的位置和虚表指针存放的位置

虚表指针肯定是存在类中的,从上面的类对象模型中可以看出。其次虚表存放的是虚函数的地址,这些虚函数和普通函数一样,都会被编译器编译成指令,然后放进代码段。虚表也是存在代码段的,因为同类型的对象共用一张虚表。下面带大家验证一下(环境:vs2019)

验证代码:

class Base{public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }virtual void func3() { cout << "Base::func3" << endl; }void func() {}int b = 0;};class Derive :public Base{public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func2() { cout << "Derive::func2" << endl; }virtual void func4() { cout << "Derive::func4" << endl; }virtual void func5() { cout << "Derive::func5" << endl; }int d = 0;};void func() {}int globalVar = 10;int main(){Base b;Derive d;const char* pChar = "hello";int c = 1;static int s = 20;int* p = new int;const int i = 10;printf("栈变量:%p\n", &c);printf("虚表指针:%p\n", (int*)&b);printf("对象成员:%p\n", ((int*)&b + 1));printf("堆变量:%p\n", p);printf("代码段常量:%p\n", pChar);printf("普通函数地址:%p\n", func);printf("成员函数地址:%p\n", &Base::func);printf("虚函数:%p\n", &Base::func1);printf("虚函数表:%p\n", *(int*)&b);printf("数据段:%p\n", &s);printf("数据段:%p\n", &globalVar);delete p;return 0;}

代码运行结果如下:

C++多态的实现与原理及抽象类实例分析

容易看出,代码段常量存放的地址和虚表存放的地址很接近,和数据段的地址也很接近,所以可以猜测虚表存放在数据段或代码段,更可能是在代码段。

原理

多态是在运行时到指向的对象中的虚表中查找要调用的虚函数的地址,然后进行调用。

总结:

  • 多态满足的两个条件:一个是虚函数的覆盖,一个是对象的指针和引用调用

  • 满足多态后,函数的调用不是编译时确认的,而是在运行时确认的。

C++多态的实现与原理及抽象类实例分析

动态绑定和静态绑定

  • 静态绑定: 发生在编译时,也就是早期绑定,就是我们之前说过的函数重载就是属于静态绑定,也称静态多态。

  • 动图绑定: 发生在运行时,也就是后期绑定,多态就是发生在运行时,也称动态多态。

单继承和多继承的虚表

单继承的虚表

先看下面的代码(单继承)

class Base{public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }virtual void func3() { cout << "Base::func3" << endl; }void func() {}int b = 0;};class Derive :public Base{public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func2() { cout << "Derive::func2" << endl; }virtual void func4() { cout << "Derive::func4" << endl; }virtual void func5() { cout << "Derive::func5" << endl; }int d = 0;};

观察它的类对象模型:

C++多态的实现与原理及抽象类实例分析

在上面的类对象模型中,派生类中只可以看见func1和func2,后面两个函数看不见,这是因为编译器把这两个新增的虚函数给隐藏了,为了我们能够更好的观察,我们可以通过写代码来看。         先定义一个函数指针:

typedef void(*VF_PTR)(); // 给函数指针typedef

下面是打印虚表的代码:

void PrintVFTable(VF_PTR* pTable){for (size_t i = 0; pTable[i] != nullptr; ++i){printf("vfTable[%d]:%p->", i, pTable[i]);VF_PTR f = pTable[i];f();// 通过函数地址调用函数}cout << endl;}

下面我们只需要通过传虚表地址的方式来调用函数打印虚表,虚表地址如何获取呢?从上面的类对象模型可以知道,类对象的前四个地址存放的是虚表指针,虚表指针也就是虚表的指针,所以我们要获取类对象的前四个字节。下面是获取方法:

(VF_PTR*)*(int*)&b;

先将类对象的地址取出,然后强转为整形,解引用就会按照四个字节来获取内容,这四个字节的内容是虚表指针,其实也是虚表的地址,我们可以把这个整形强转为函数地址的类型就可以了。

打印虚表:

int main(){Base b;Derive d;PrintVFTable((VF_PTR*)*(int*)&b);PrintVFTable((VF_PTR*)*(int*)&d);return 0;}

打印结果如下:

C++多态的实现与原理及抽象类实例分析

可以看出派生类对象中新增的虚函数会按照虚函数函数次序声明放在虚表的最后。

多继承的虚函数表

看下面代码(多继承)

class Base1 {public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }private:int b1;};class Base2 {public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() { cout << "Base2::func2" << endl; }private:int b2 = 1;};class Derive : public Base1 , public Base2 {public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }private:int d1 = 1;};

类对象模型如下:

C++多态的实现与原理及抽象类实例分析

为了更好地观察,我们还是通过打印虚表来观察:

int main(){Derive d;cout << sizeof(Derive) << endl;cout << "Base1的虚表:" << endl;PrintVFTable((VF_PTR*)*(int*)&d);cout << "Base2的虚表:" << endl;PrintVFTable((VF_PTR*)*(int*)((char*)&d+sizeof(Base1)));cout << "Derive的成员变量d:" << endl;//PrintVFTable((VF_PTR*)*(int*)((char*)&d + sizeof(Base1) + sizeof(Base2)));cout << *(int*)((char*)&d + sizeof(Base1) + sizeof(Base2)) << endl;return 0;}

打印结果如下:

C++多态的实现与原理及抽象类实例分析

可以看出,派生类新增的虚函数放在了第一个继承的对象的虚表中最后了。

感谢各位的阅读,以上就是“C++多态的实现与原理及抽象类实例分析”的内容了,经过本文的学习后,相信大家对C++多态的实现与原理及抽象类实例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: C++多态的实现与原理及抽象类实例分析

本文链接: https://www.lsjlt.com/news/323661.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++多态的实现与原理及抽象类实例分析
    这篇文章主要讲解了“C++多态的实现与原理及抽象类实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++多态的实现与原理及抽象类实例分析”吧!多态的概念多态: 从字面意思来看,就是事物...
    99+
    2023-06-29
  • C++数据结构分析多态的实现与原理及抽象类
    目录多态的概念虚函数多态构成的条件C++11override和final重载、重写和重定义(隐藏)抽象类多态的原理虚函数表原理单继承和多继承的虚表单继承的虚表多继承的虚函数表几个值得...
    99+
    2022-11-13
  • C#中的WebRequest与WebResponse抽象类、DNS静态类、Ping类实例分析
    今天小编给大家分享一下C#中的WebRequest与WebResponse抽象类、DNS静态类、Ping类实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后...
    99+
    2023-06-30
  • C/C++多态原理实例分析
    本篇内容介绍了“C/C++多态原理实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!多态面向对象编程有三大特性:继承、封装和多态。其中,...
    99+
    2023-07-02
  • C++ 超详细分析多态的原理与实现
    目录多态的定义及实现多态的构成条件虚函数重写C++11的override和final抽象类多态的原理虚函数表动态绑定与静态绑定单继承和多继承关系的虚函数表单继承中的虚函数表多继承中的...
    99+
    2022-11-13
  • Java的单例模式与final及抽象类和接口实例分析
    这篇文章主要介绍“Java的单例模式与final及抽象类和接口实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java的单例模式与final及抽象类和接口实例分析”文章能帮助大家解决问题。1....
    99+
    2023-06-30
  • C++中的类与对象实例分析
    今天小编给大家分享一下C++中的类与对象实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。初始化列表引论//初始化列表的...
    99+
    2023-06-29
  • Java语言中的抽象类与继承实例代码分析
    这篇文章主要介绍“Java语言中的抽象类与继承实例代码分析”,在日常操作中,相信很多人在Java语言中的抽象类与继承实例代码分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java语言中的抽象类与继承实例代...
    99+
    2023-07-04
  • 关于虚函数实现多态的原理及分析
    目录1、C++中如何实现多态2、虚函数实现多态的原理2.1 单类继承2.2 多类继承示例总结1、C++中如何实现多态 基类中先声明一个虚函数至少有一个继承该基类的子类 2、虚函数实现...
    99+
    2023-02-05
    虚函数 虚函数实现多态 多态的原理
  • 【C++】多态的实现及其底层原理
    个人主页:🍝在肯德基吃麻辣烫 我的gitee:gitee仓库 分享一句喜欢的话:热烈的火焰,冰封在最沉默的火山深处。 文章目录 前言一、什么是多态?二、多态的构成条件2.1什么是虚函数?2.2虚函数的重...
    99+
    2023-08-17
    c++ 开发语言 多态
  • Java抽象类、继承及多态和适配器的实现代码
    Java继承 方法重写是Java语言多态的特性,必须满足以下条件 在子类中,方法名称与父类方法名称完全相同 方法的参数个数和类型完全相同,返回类型完全相同 ...
    99+
    2022-11-12
  • c++与python实现二分查找的原理及实现
    目录1、时间复杂度与优缺点2、python实现3、C++实现在计算机中,数据的查找方式与其存储方式关系密切。试想一下,如果图书馆中书籍杂乱无章的存放,那么要想找到心仪的书籍将会非常困...
    99+
    2022-11-13
  • C++多态性的实现及常见问题分析
    C++多态性的实现及常见问题分析引言:多态性是面向对象编程语言的一个重要特性,在C++中也得到了广泛应用。多态性允许不同类型的对象以相同的方式进行处理,提高了代码的灵活性和可维护性。本文将介绍C++中多态性的实现方式,并分析常见的多态性问题...
    99+
    2023-10-22
    实现 C++关键词:多态性 常见问题分析
  • C++类的对象作类成员调用构造、析构函数及静态成员实例分析
    这篇文章主要介绍了C++类的对象作类成员调用构造、析构函数及静态成员实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++类的对象作类成员调用构造、析构函数及静态成员实例分析文章都会有所收获,下面我们一起...
    99+
    2023-06-30
  • C++中的多态问题—理解虚函数表及多态实现原理
    目录一、多态的概念概念构成条件二、虚函数的重写重写的定义重写的特殊情况override和final关键字区分重写、重载、重定义抽象类的概念三、多态的实现原理父类对象模型补充:生成默认...
    99+
    2023-02-05
    C++中的多态 C++虚函数表 C++多态实现原理
  • 链表原理及java实现的示例分析
    这篇文章主要介绍了链表原理及java实现的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一:单向链表基本介绍链表是一种数据结构,和数组同级。比如,Java中我们使用的...
    99+
    2023-05-30
    java
  • 详解Java面向对象之多态的原理与实现
    目录何为多态代码实现多态理解何为多态 定义: 多态是指不同的子类在继承父类后分别都重写覆盖了父类的方法,即父类同一个方法,在继承的子类中表现出不同的形式。系统在运行时(而非编译时),...
    99+
    2022-11-13
  • 基于Apache组件分析对象池原理的实现案例分析
    目录一、设计与原理1、基础案例2、接口设计1.1 PooledObjectFactory 接口1.2 ObjectPool 接口1.3 PooledObject 接口3、运行原理二、...
    99+
    2022-11-13
  • Python实现的堆排序算法原理与用法实例分析
    本文实例讲述了Python实现的堆排序算法。分享给大家供大家参考,具体如下: 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的...
    99+
    2022-06-04
    算法 实例 原理
  • Java面向对象之多态的原理是什么与怎么实现
    本文小编为大家详细介绍“Java面向对象之多态的原理是什么与怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java面向对象之多态的原理是什么与怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。何为多...
    99+
    2023-06-30
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作