C++:继承与多态详解

news2026/4/27 4:51:58
文章目录1. 继承1.1 继承的概念1.2 继承方式1.3 基类和派生类的转换1.4 继承中的作用域1.5 类可以不被继承吗1.6 基类包含static函数1.7 多继承与菱形继承问题1.7 虚继承2. 多态2.1 多态的构成条件2.2 虚函数2.2.1 虚函数的重写/覆盖2.3 析构函数的重写2.4 override 和 final 关键字2.5 重载/重写/隐藏2.6 纯虚函数和抽象类2.7 虚函数表2.8 多态原理2.9 动态绑定与静态绑定1. 继承1.1 继承的概念核心定义继承是一个类派生类 / 子类复用、扩展另一个类基类 / 父类的成员变量和成员函数的机制。也就是一个类可以使用另一个类的成员变量和函数。举个例子#includeiostream#includestringusing namespace std;class person{protected:string name;intage;public:person(string name,intage):name(name),age(age){}stringwname(){returnthis-name;}intwage(){returnage;}};class student:public person//这里表示继承{protected:string id;public:student(string name,intage,string id):person(name,age),//调用父类构造id(id){}stringwid(){returnid;}};intmain(){studenta(赛罗,20000,16班);couta.wid()endl;couta.wname()endl;couta.wage()岁endl;}在这个代码中student就是子类派生类public就是继承方式person就是父类基类。1.2 继承方式类成员/继承方式public继承protected继承private继承基类的 public 成员派生类的 public 成员派生类的 protected 成员派生类的 private 成员基类的 protected 成员派生类的 protected 成员派生类的 protected 成员派生类的 private 成员基类的 private 成员在派生类中不可见在派生类中不可见在派生类中不可见简而言之private成员在子类中不可见无法访问但确实是被继承了所以我们一般用protected,protected 就是为了继承而设计的访问权限,外部不可见但子类可见public与protected唯一的区别就是继承后的protected成员对外依旧是隐藏的工程实践中public 继承是主流选择protected/private继承因会隐藏基类接口、丧失is-a语义与多态性扩展性差故极少使用且不推荐。1.3 基类和派生类的转换public继承的派生类对象 可以赋值给 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中基类那部分切出来基类指针或引用指向的是派生类中切出来的基类那部分。基类对象不能赋值给派生类对象。基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。三种情况Person*ptr;// 基类指针只能“看见”Person部分Student s;// 派生类对象完整的学生ptrs;// ✅ 合法指针指向s的Person部分ptr-show();//假设父类子类都有个void show(),这里只能调用Person的成员函数访问不到Student的内容这里就是把子类的show()给切出来了。基类对象不能赋值给派生类对象。Person p;Student sp;// ❌ 编译报错Student s;// 真正的派生类对象Person*ps;// 基类指针指向派生类对象// 安全强转回 Student*Student*s1(Student*)p;s1-id2026001;// ✅ 能正常访问派生类成员如果基类指针不指向派生类对象就不行Person p;// 纯基类对象Person*ptrp;// 基类指针指向基类对象// 危险强转成 Student*Student*s2(Student*)ptr;s2-id2026001;// ❌ 访问非法内存程序崩溃1.4 继承中的作用域在继承体系中基类和派生类都有独立的作用域。派生类和基类中有同名成员派生类成员将屏蔽基类对同名成员的直接访问这种情况叫隐藏。在派生类成员函数中可以使用 基类::基类成员 显示访问需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。注意在实际中在继承体系里面最好不要定义同名的成员。class Person{protected:string _name赛罗;// 姓名int_num111;// ⾝份证号};class Student:public Person{public:voidPrint(){cout 姓名:_nameendl;cout 身份证号:Person::_numendl;cout 学号:_numendl;//这里两个_num就构成隐藏}protected:int_num999;// 学号};intmain(){Student s1;s1.Print();return0;};这里特别声明隐藏后的函数是找不到的一旦调用编译器找不到就会报错。1.5 类可以不被继承吗法1派生类的构造函数必须调用基类的构造函数来初始化基类部分。如果基类的构造函数是 private派生类就无法访问它也就无法实例化派生类对象从而实现 “不能被继承”。class NoInherit{private:// 私有化构造函数NoInherit(){}friend NoInheritCreateNoInherit();};// 友元函数可以调用私有构造用来创建对象NoInheritCreateNoInherit(){returnNoInherit();}// 尝试继承会编译报错class Derived:public NoInherit{// ❌ 无法访问基类的私有构造函数};intmain(){// 可以通过友元创建基类对象NoInherit objCreateNoInherit();// Derived d; // ❌ 编译失败return0;}法2final 关键字是 C11 新增的特性专门用来禁止类被继承语法更简洁是现代 C 中推荐的写法。// 用 final 修饰类禁止继承class NoInherit final{public:NoInherit()default;};// 尝试继承会直接编译报错// class Derived : public NoInherit { // ❌ 编译失败// };intmain(){NoInherit obj;// ✅ 可以正常创建对象return0;}外加友元关系不能被继承基类友元不能访问派生类私有和保护成员。1.6 基类包含static函数静态成员的本质是属于类本身而不是某个对象。基类定义了 static 成员后它就独立存在不随任何对象创建 / 销毁而改变派生类继承后只是获得了访问这个静态成员的权限并不会复制一份新的静态成员所以不管派生出多少个派生类整个体系里始终只有这一个静态成员实例1.7 多继承与菱形继承问题多继承顾名思义就是继承多个父类class Person{...};class Teacher{...};class Assistant:public Person,public Teacher{...};// 多继承内存模型特点继承顺序决定内存布局先继承的基类成员排在前面后继承的基类排在后面派生类自己的成员放在最后。菱形继承当两个派生类继承自同一个基类又有一个新类同时继承这两个派生类时就会形成菱形结构菱形继承的两大问题数据冗余Assistant 对象中会包含两份 Person 基类成员一份来自 Student一份来自 Teacher浪费内存。二义性访问 Person 的成员时编译器不知道该用哪一份副本直接访问会编译报错。建议强烈不建议设计菱形继承模型代码维护成本高容易出错。当然有解决方案————虚继承。1.7 虚继承本质虚继承的本质是让多个派生类共享同一份公共基类的成员而不是各自复制一份。当派生类声明为 virtual 继承公共基类时编译器会在派生类对象中通过虚基类指针指向一个共享的基类成员副本而不是复制多份。这样一来整个继承体系中公共基类的成员就只有一份实例彻底解决了数据冗余和二义性。#includeiostream#includestringusing namespace std;class Person{public:string _name;};// 虚继承class Student:virtual public Person{public:Student(){_name1;// 先赋值 1}};// 虚继承class Teacher:virtual public Person{public:Teacher(){_name2;// 后赋值 2}};// 多继承class Assistant:public Student,public Teacher{public:voidPrint(){cout_nameendl;}};intmain(){Assistant a;a.Print();// 输出多少// 你甚至可以从 Student 路径访问cout从Student访问a.Student::_nameendl;// 从 Teacher 路径访问cout从Teacher访问a.Teacher::_nameendl;return0;}结果2. 多态多态是什么简单说同一个行为不同对象做会有不同的表现。买票普通人全价、学生打折、军人优先动物叫猫 “喵”、狗 “汪汪”在C里多态分两种类型别名例子特点编译时多态静态多态函数重载、函数模板编译器在编译阶段就确定调用哪个函数运行时多态动态多态虚函数重写程序运行时根据对象的实际类型决定调用哪个函数2.1 多态的构成条件实现多态还有两个必须重要条件必须是基类的指针或者引用调用虚函数被调用的函数必须是虚函数并且完成了虚函数重写/覆盖。说明要实现多态效果第一必须是基类的指针或引用因为只有基类的指针或引用才能既指向基类对象又指向派生类对象第二派生类必须对基类的虚函数完成重写/覆盖重写或者覆盖了基类和派生类之间才能有不同的函数多态的不同形态效果才能达到。2.2 虚函数类成员函数前面加virtual修饰那么这个成员函数被称为虚函数。注意非成员函数不能加virtual修饰。class Person{public:virtualvoidBuyTicket(){cout买票-全价endl;}};2.2.1 虚函数的重写/覆盖虚函数的重写/覆盖派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)称派生类的虚函数重写了基类的虚函数。注意在重写基类虚函数时派生类的虚函数在不加virtual关键字时虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)但是该种写法不是很规范不建议这样使用不过在考试选择题中经常会故意买这个坑让你判断是否构成多态。#includeiostream#includestringusing namespace std;// 基类Personclass Person{public:// 加了 virtual这就是【虚函数】virtualvoidBuyTicket(){coutPerson全价买票endl;}};// 派生类Studentclass Student:public Person{public:// 函数名、参数、返回值 和 基类完全相同构成【虚函数重写】virtualvoidBuyTicket()override{// override 是C11的规范写法可选但强烈推荐coutStudent半价买票endl;}};// 派生类Soldierclass Soldier:public Person{public:// 同样重写了基类的虚函数virtualvoidBuyTicket()override{coutSoldier优先买票endl;}};// 多态的核心基类引用/指针接收不同对象voidDoBuy(Personp){p.BuyTicket();}intmain(){Person normal;Student stu;Soldier sol;DoBuy(normal);// 调用 Person::BuyTicket()DoBuy(stu);// 调用 Student::BuyTicket()DoBuy(sol);// 调用 Soldier::BuyTicket()return0;}2.3 析构函数的重写析构函数的特殊 “重写” 规则普通函数重写要求函数名、参数、返回值都必须完全一致。但析构函数不一样基类析构函数加了 virtual 后派生类的析构函数哪怕名字不同~A() vs ~B()也会被编译器特殊处理自动构成重写。原理编译器在编译时会把所有类的析构函数统一命名为 destructor所以它们能匹配上构成多态。为什么基类析构函数必须是虚函数关键考点举个例子当你用基类指针指向派生类对象然后 delete 它时不加 virtual只会调用基类的析构函数派生类的析构函数不会被执行 → 派生类中申请的资源比如动态内存无法释放 → 内存泄漏。加了 virtual触发多态会先调用派生类的析构函数再调用基类的析构函数 → 资源完全释放不会泄漏。#includeiostreamusing namespace std;class A{public:A(){pnewint;}// 关键加virtual析构函数成为虚函数virtual~A(){delete p;coutA::~A()endl;}private:int*p;};class B:public A{public:B(){qnewint;}// 派生类析构函数自动构成重写可加override规范写法~B()override{delete q;coutB::~B()endl;}private:int*q;};intmain(){A*pnewB();delete p;// 会先调用B::~B()再调用A::~A()资源完全释放return0;}2.4 override 和 final 关键字一、override 关键字给虚函数重写 “加个保险”作用明确标记派生类的函数是 “重写基类虚函数”让编译器帮你检查是否符合重写规则。核心价值解决手误导致的重写失败问题比如参数写错、返回值不匹配代码可读性更强一眼就能看出 “这是重写不是新函数”。class Base{public:virtualvoidfunc(intx){}};class Derive:public Base{public:// ✅ 正确写法用override标记重写virtualvoidfunc(intx)override{// 重写逻辑}// ❌ 错误示例参数写错编译器会直接报错// virtual void func(double x) override {}};二 final 关键字final在继承有禁止继承的功能在修饰虚函数有禁止重写的功能。基类的虚函数加了 final 后派生类不能再重写它。class Base{public:virtualvoidfunc()final{}// 禁止派生类重写};class Derive:public Base{public:// ❌ 编译报错无法重写final虚函数// virtual void func() override {}};2.5 重载/重写/隐藏2.6 纯虚函数和抽象类在虚函数的后面写上 0则这个函数为纯虚函数纯虚函数不需要定义实现(实现没啥意义因为要被派生类重写但是语法上可以实现)只要声明即可。包含纯虚函数的类叫做抽象类抽象类不能实例化出对象如果派生类继承后不重写纯虚函数那么派生类也是抽象类。纯虚函数某种程度上强制了派生类重写虚函数因为不重写实例化不出对象。virtualvoidFunc()0;// 纯虚函数核心作用强制派生类必须重写该函数否则派生类也会变成抽象类无法实例化。为多态提供一个统一的接口规范让不同派生类实现各自的逻辑。抽象类的规则定义包含至少一个纯虚函数的类就是抽象类。关键限制抽象类不能实例化对象无法用 new 或直接定义创建对象。如果派生类继承抽象类后没有重写所有纯虚函数那么派生类依然是抽象类同样不能实例化。#includeiostreamusing namespace std;// 抽象类包含纯虚函数class Animal{public:// 纯虚函数强制派生类实现“叫”这个行为virtualvoidSpeak()0;virtual~Animal()default;};// 派生类猫必须重写 Speakclass Cat:public Animal{public:voidSpeak()override{cout喵~endl;}};// 派生类狗必须重写 Speakclass Dog:public Animal{public:voidSpeak()override{cout汪汪endl;}};intmain(){// Animal a; // ❌ 抽象类不能实例化Animal*catnew Cat;Animal*dognew Dog;cat-Speak();// 输出喵~dog-Speak();// 输出汪汪delete cat;delete dog;return0;}2.7 虚函数表定义每个包含虚函数的类的对象都会额外包含一个隐藏的指针成员就是虚函数表指针。它指向这个对象所属类的虚函数表。32 位程序中占 4 字节64 位程序中占 8 字节。它通常在对象内存布局的最开头。那么虚函数表有什么用呢每个有虚函数的类会自动生成一张虚函数表 vtable表里存的是虚函数的地址每个对象内部隐藏一个 vfptr 虚表指针指向自己类的虚表继承 重写时子类会复制父类虚表只要子类重写了虚函数✅ 虚表中对应位置的函数地址直接替换成子类函数地址调用时通过对象的 vfptr 找到自己真实的虚表查表运行时拿到子类函数地址调用子类方法。#includeiostreamusing namespace std;// 父类class Person{public:// 加 virtual → 产生虚函数表virtualvoidbuy(){cout全价买票endl;}};// 子类class Student:public Person{public:// 重写虚函数子类虚表会替换该函数地址voidbuy()override{cout半价买票endl;}};intmain(){// 父类指针 指向 子类对象Person*pnew Student;// 靠【虚函数表】运行时找子类的函数p-buy();delete p;return0;}2.8 多态原理编译时生成虚函数表只要类中定义了虚函数编译器就会为该类生成一张虚函数表vtable表中存储了所有虚函数的地址。Person 类的虚表存储 Person::BuyTicket 的地址Student 类的虚表存储 Student::BuyTicket 的地址重写后会覆盖基类的地址运行时动态查表调用当通过基类指针 / 引用调用虚函数如 ptr-BuyTicket()时程序不会在编译阶段就绑定固定的函数地址而是通过对象内部的虚函数表指针vfptr找到该对象所属类的虚函数表。从虚函数表中取出对应虚函数的地址。调用该地址对应的函数。最终效果当 ptr 指向 Person 对象时查表调用 Person::BuyTicket当 ptr 指向 Student 对象时查表调用 Student::BuyTicket从而实现了同一个调用语句根据对象的实际类型自动执行不同的函数逻辑这就是多态的本质。子类的虚函数表 复制父类的虚表内容但是 一张全新的、独立的表子类对象的 vfptr 指向这张新表和父类指针不一样2.9 动态绑定与静态绑定对不满足多态条件(指针或者引用调用虚函数)的函数调用是在编译时绑定也就是编译时确定调用函数的地址叫做静态绑定。满足多态条件的函数调用是在运行时绑定也就是在运行时到指向对象的虚函数表中找到调用函数的地址也就做动态绑定。动态绑定的核心是当通过基类指针/引用调用虚函数时程序不会在编译期写死函数地址而是在运行时通过对象内部的虚表指针vptr找到其所属类的虚函数表vtable再根据函数在表中的索引取出对应的函数地址并调用从而实现“同一个调用语句根据对象真实类型自动执行不同版本函数”的多态效果。#includeiostreamusing namespace std;class Person{public:virtualvoidBuyTicket(){coutPerson买全价票endl;}};class Student:public Person{public:voidBuyTicket()override{coutStudent买半价票endl;}};// 动态绑定的调用场景voidFunc(Person*ptr){// 这句就是典型的动态绑定运行时查表调用ptr-BuyTicket();}intmain(){Person p;Student s;Func(p);// ptr指向Person对象Func(s);// ptr指向Student对象return0;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2558403.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…