C++进阶篇(一):继承——权限、切片、默认成员函数与菱形继承(附完整代码仓库)
0.1 概述序言这里是白杨。入门篇十八篇走完string / vector / list / stack / queue / 模板进阶篇正式开张——第一站继承。上一篇模板进阶的结尾卖过关子stack既像 vector 的一种、又有一个 vector两种关系都对这层模糊地带今天终于能说透了——一种是is-a继承一种是has-a组合。这篇要过四件事继承怎么写、三种继承方式下权限怎么变九宫格表派生类对象和基类之间的互相转换什么是对象切片派生类的默认成员函数构造 / 析构的调用顺序 同名隐藏的坑多继承的终极 BOSS菱形继承与虚继承老规矩跑得通的结果才写进来环境见下完整代码文末自取也可以直接去仓库gitee.com/BaiYang_SQ/inherit-sim。实测环境w64devkit g 15.2.0-stdc11 -O2 -Wall -Wextra VS2022教学简化版。文中代码均为本人手写并实测官方文档cppreference·类继承。让我们开始吧。一、为什么要有继承从复制粘贴说起没有继承的时候写 Student 和 Teacher 是这样的姓名、年龄、电话这些公共信息两个类里一人抄一份。三五个类还行三十个类就是灾难。继承干的事把公共部分提到基类里派生类只管自己特有的部分。namespacebasic{classPerson{public:voididentity(){coutidentity(): _name / _ageendl;}protected:string _name张三;// 姓名protected派生类里能直接访问string _address;// 地址string _tel;// 电话private:int_age18;// 年龄private派生类里看得见摸不着};classStudent:publicPerson{public:voidstudy(){coutstudy(): _name 正在学习endl;// protected 成员直接可用// cout _age endl; // 编译报错private 成员在派生类中不可见}protected:int_stuid2026;// 学号};classTeacher:publicPerson{public:voidteaching(){coutteaching(): _name 正在授课endl;}protected:string _title助教;// 职称};}定义格式拆开看就三个词class 派生类 : 继承方式 基类比如class Student : public Person。实测输出identity(): 张三 / 18 study(): 张三 正在学习 identity(): 张三 / 18 teaching(): 张三 正在授课派生类对象可以直接调用基类的 public 成员identityprotected 成员可以在派生类内部直接用_name而 private 的 _age 在派生类里不可见。【一句话记】继承 类设计层次的代码复用表达的是is-a是一种关系Student 是一种 Person。二、三种继承方式权限九宫格继承方式有三种public/protected/private写法上只差这一个词基类成员的访问权限也有三种组合起来就是经典的九宫格基类成员 继承方式public 继承protected 继承private 继承public 成员publicprotectedprivateprotected 成员protectedprotectedprivateprivate 成员不可见不可见不可见九宫格对照见上一节图片的右侧下面三点比死记表格更管用。三条要点派生类里的访问权限 Min(成员在基类的权限, 继承方式)——public protected private谁更小听谁的private 成员是不可见而不是不存在它照样被继承、躺在对象里只是派生类的语法层面不能碰class默认 private 继承、struct默认 public 继承——所以写继承一定显式写出 public别踩默认值。【必背】实践里 99% 用 public 继承protected / private 继承知道有这回事就行。三、基类和派生类对象之间的转换切片是重点派生类对象里内含一个基类子对象——这是所有转换规则的根。四条规则派生类对象可以赋值给基类的指针 / 引用Person* pp sobj;、Person rp sobj;——指针和引用只是绑到基类那半派生类对象可以赋值给基类对象Person pobj sobj;——这其实调用了基类的拷贝构造只拷基类部分派生类部分被切掉这就是切片反过来不行基类对象不能赋值给派生类对象直接编译报错基类指针 / 引用强转回派生类需要它真的指向派生类对象才安全dynamic_cast属于 RTTI后面章节细讲。conv::Studentsobj(jack,18);conv::Person*ppsobj;// 指针绑到基类子对象conv::Personrpsobj;// 引用同理conv::Person pobjsobj;// 切片调基类拷贝构造_No 没了// sobj pobj; // 编译报错基类对象不能赋值给派生类对象实测输出指针看基类部分: jack | 引用看基类部分: jack 切片后 pobj: jack_No 被切掉了 sobj 不受影响: jack / _No18【易错】切片不报错、不崩溃就是悄悄地丢数据——函数参数如果按基类对象而不是引用/指针传传进去的派生对象就被切了。这是继承里最隐蔽的坑之一。四、作用域与隐藏基类和派生类都有独立的作用域。一旦出现同名派生类的同名成员会屏蔽基类成员的直接访问——这叫隐藏想访问基类版本得写基类::成员成员函数的隐藏只看函数名不看参数列表不是重载实际开发里继承体系中最好别定义同名成员。namespacehide{classPerson{protected:string _name小李子;int_num111;// 身份证号};classStudent:publicPerson{public:voidPrint(){cout 姓名:_nameendl;cout 身份证号:Person::_numendl;// 指定基类作用域cout 学号:_numendl;// 默认访问自己的}protected:int_num999;// 学号同名 → 隐藏基类的 _num};classA{public:voidfun(){coutA::fun()endl;}};classB:publicA{public:voidfun(inti){coutB::fun(int i): iendl;}};}实测输出姓名:小李子 身份证号:111 学号:999 B::fun(int i): 10 A::fun()Student::_num999和Person::_num111是两个独立的数据B::fun(int)一出现基类A::fun()就被隐藏了——b.fun()编译不过必须写b.A::fun()。【易错】两个小测验自行默答A和B里两个 func 是什么关系——隐藏不是重载重载要求同作用域b.fun()能编译吗——不能基类版本被隐藏后找不到。五、派生类的默认成员函数默认的意思你不写编译器帮你生成。放到派生类里这套生成规则有讲究派生类的构造函数必须调用基类构造初始化基类那部分成员基类没有默认构造时必须在初始化列表显式调用派生类的拷贝构造必须调用基类拷贝构造派生类的operator必须调用基类的operator——注意它隐藏了基类的operator要写Person::operator(s)显式调用否则函数发生了无限递归派生类的析构函数在被调用完成后会自动调用基类析构顺序构造先基类、后派生顺序析构先派生、后基类像剥洋葱谁构造得早谁析构得晚析构函数名会被编译器统一处理成destructor()所以基类析构不加 virtual 时派生类和基类析构构成的是隐藏关系多态章节会再展开这条。解释一下3当代码这样写的时候Studentoperator(constStudents){if(this!s){operator(s);_nums._num;_addrsss._addrss;}return*t;}尝试运行如图我们来解释一下 子类的operator隐藏了父类的operator 导致的在子类的operator中调用operator调用的实际上还是子类的而不是父类的。解决办法加一个显示指定Person::operator(s);namespacedm{classPerson{public:Person(constchar*namepeter):_name(name){cout Person(const char*) 构造_nameendl;}Person(constPersonp):_name(p._name){cout Person(const Person) 拷贝构造endl;}Personoperator(constPersonp){cout Person::operator(const Person) 赋值endl;if(this!p)_namep._name;return*this;}~Person(){cout ~Person() 析构endl;}protected:string _name;};classStudent:publicPerson{public:Student(constchar*name,intnum):Person(name),_num(num){cout Student(const char*, int) 构造endl;}Student(constStudents):Person(s),_num(s._num){cout Student(const Student) 拷贝构造endl;}Studentoperator(constStudents){cout Student::operator(const Student) 赋值endl;if(this!s){Person::operator(s);// 隐藏了基类的必须显式调_nums._num;}return*this;}~Student(){cout ~Student() 析构endl;}protected:int_num;};}实测输出构造 s1 → 拷贝 s2 → 构造 s3 → 赋值 s1s3 → 依次析构--- 构造 s1 --- Person(const char*) 构造jack Student(const char*, int) 构造 --- 拷贝构造 s2(s1) --- Person(const Person) 拷贝构造 Student(const Student) 拷贝构造 --- 赋值 s1 s3 --- Student::operator(const Student) 赋值 Person::operator(const Person) 赋值 --- 离开作用域析构顺序 派生类先、基类后 --- ~Student() 析构 ~Person() 析构 ×3每个对象都是先派生后基类【必背】构造函数基类 → 派生析构函数派生 → 基类。operator 记得显式调基类的。顺带一个不能被继承的类怎么写C98 写法把基类构造函数设为私有——派生类必须调用基类构造看不见就调不了自然生不出对象C11 写法class Base final {};——final 修饰的类直接禁止被继承。实测见仓库取消注释class Derive : public Base就会编译报错。六、友元与静态成员友元不能被继承基类的友元访问不了派生类的私有 / 保护成员反过来也一样。想让Display(p, s)同时看懂 Person 和 Student 的内部得让两个类都把 Display 设为友元所以代码里有个class Student;的前置声明——Person 要认识 Student 这个名字。classStudent;// 前置声明classPerson{public:friendvoidDisplay(constPersonp,constStudents);protected:string _namepeter;};classStudent:publicPerson{public:friendvoidDisplay(constPersonp,constStudents);// 再声明一次protected:int_stuNum20260001;};静态成员则相反整个继承体系里只有一份。实测输出地址对比最有说服力Display: peter / 20260001 非静态成员 _name 的地址不同: 0x2a325ff710 vs 0x2a325ff760派生类各自继承一份 静态成员 _count 的地址: 0x7ff65c0f9030 vs 0x7ff65c0f9030两个地址一模一样全体系共用一份 两个类域都能访问: 0 / 0【一句话记】友元不能继承要重新声明静态成员不用继承全体系就一份Person::_count和Student::_count是同一个。七、多继承、菱形继承与虚继承一个派生类只有一个直接基类叫单继承有两个或以上叫多继承。多继承的对象内存模型先继承的基类排在前面派生类自己的成员放最后。菱形继承是多继承的特殊情况——Assistant 同时是 Student 和 Teacher而两位又都继承自 PersonclassPerson{public:string _name;};classStudent:publicPerson{protected:int_num;};classTeacher:publicPerson{protected:int_id;};classAssistant:publicStudent,publicTeacher{protected:string _majorCourse;};这种结构带来两个经典问题数据冗余Assistant 里躺着两份Person 的成员两份 _name二义性a._name peter;直接编译报错error C2385: 对_name的访问不明确写a.Student::_name/a.Teacher::_name能编译但只是指明用哪一份冗余的问题一点没解决。实测x64非虚菱形Student::_name xxxStudent 那份 非虚菱形Teacher::_name yyyTeacher 那份两份数据冗余 sizeof(非虚 Assistant) 112 字节虚继承在继承方式前面加virtual——class Student : virtual public Person。效果是一份 Person 只保留唯一一份二义性和冗余一起消失虚菱形av._name 王五最派生类 AssistantV 执行的 PersonV(王五) 生效 sizeof(虚 AssistantV) 96 字节注意上面那行王五虚基类由最派生类初始化。构造AssistantV(张三, 李四, 王五)时StudentV / TeacherV 里传的名字都会被忽略只有最派生类 AssistantV 初始化列表里的PersonV(name3)生效——所以三个名字里王五赢。最后补一道经典选择题指针偏移指针偏移实测Base1* p1 Derive* p3偏移 0 字节Base2* p2 偏移 4 字节 课本 7.3 选择题答案 Cp1 p3 ! p2——先继承的基类排在前面【面试】虚继承解决了什么问题代价是什么——解决数据冗余和二义性代价是多出虚基表指针本实测里 112 → 96省下一份 Person 的成员同时多了两个 vbptr 的开销、对象布局变复杂、访问还多一次间接。所以多继承可以设计但别设计出菱形继承——后来的 Java 干脆不支持多继承从语言层面躲开了这个坑。标准库自己也只在 IO 家族iostream那棵菱形树里用过这一招。八、继承 vs 组合is-a 和 has-apublic 继承 is-a每个派生类对象都是一个基类对象组合 has-aB 里放了一个 A 对象B 就有一个 A。两者的工程差异对比继承白箱复用组合黑箱复用关系is-ahas-a内部细节基类对派生类可见破坏封装只看接口内部不可见封装好耦合强基类一变派生类跟着受影响弱依赖接口// Tire 和 Car 更像 has-a车有四条轮胎classCar{/* ... */Tire _t1,_t2,_t3,_t4;};// BMW 和 Car 更像 is-a宝马是一种车classBMW:publicCar{public:voidDrive(){cout好开-操控endl;}};还记得 stack 和 vector 吗——两种关系都说得通。这种情况怎么办优先组合templateclassTclassstack2// 组合版有一个 vector{vectorT_v;// has-apublic:voidpush(constTx){_v.push_back(x);}// ...};实测输出好开-操控、组合版 stack2: 8。【一句话记】is-a 用继承has-a 用组合两种都符合时优先组合耦合低、封装好。例外只有两个实现多态必须继承实在没有组合方案时才用继承。九、速记卡考前扫一眼面试不卡壳十、自测三连先自己默答再看答案对照派生类里成员的访问权限怎么算private 成员到底有没有被继承答案 Min(成员在基类的权限, 继承方式)按 public protected private 取更低的那个。private 成员被继承了、存在于对象里但派生类的语法层面不可见不能直接访问可以通过基类的 public/protected 接口间接使用。什么是对象切片为什么派生类对象给基类指针/引用赋值不算切片答案派生类对象赋值给基类对象Person pobj sobj;时调用的是基类拷贝构造只拷基类部分派生类成员被丢掉——这就是切片。指针/引用只是绑到对象的基类子对象上对象本身没被复制或裁掉所以不算切片。菱形继承有什么问题虚继承怎么解决虚基类由谁初始化答案问题数据冗余多份虚基类成员 二义性不指定类域没法访问。解法继承时写virtual public虚继承保留唯一一份基类成员。虚基类由最派生类的构造函数初始化中间类初始化列表里对虚基类的传参会被忽略。附录 A完整代码仓库完整工程单文件版全部实验一次跑完在 Giteegitee.com/BaiYang_SQ/inherit-sim。仓库文件说明inherit_sim.cpp单文件全部实验章节 ↔ 函数名对照见仓库 README 导览表output.txt全部实验的真实运行输出含 sizeof / 地址 / 偏移实测不用装编译器build.batWindows 一键编译 运行 刷新 output.txtREADME.md一分钟上手 代码导览面试前手写练手照这个骨架压考点完整版见仓库// 继承·考点骨架完整可跑版见 Gitee 仓库#includeiostream#includestringusingnamespacestd;classPerson{public:Person(conststringnamepeter):_name(name){}string _name;};classStudent:publicPerson{public:Student(conststringname,intno):Person(name),_No(no){}int_No;};// 隐藏同名就隐藏函数只看名字classA{public:voidfun(){coutA::funendl;}};classB:publicA{public:voidfun(inti){coutB::fun iendl;}};intmain(){Students(jack,18);Person*pps;// OK绑基类部分Person ps;// 切片_No 被切掉coutpp-_name / p._nameendl;B b;b.fun(10);b.A::fun();// 被隐藏的基类版本要指定类域// Student s2 p; // 编译报错基类对象不能赋给派生类return0;}好了C 进阶篇第一篇到此完结下一篇继续进阶主线——多态预告虚函数、重写、虚函数表、动态绑定到底怎么发生。如果对你有帮助不要忘记点赞三连一波哦我是白杨我们下期见。

相关新闻

深入剖析AUTOSAR数据一致性:从操作系统原语、RTE机制到多核IOC与无锁架构

深入剖析AUTOSAR数据一致性:从操作系统原语、RTE机制到多核IOC与无锁架构

目录 第一章 汽车软件并发危机:为什么数据一致性关乎功能安全? 1.1 四大典型并发冲突场景 1.2 数据撕裂与乱序执行的本质 第二章 单核防御前哨:中断屏蔽原语与执行代价 2.1 三大中断屏蔽接口谱系 2.2 中断屏蔽的执行代价:不可控的中断抖动 第三章 调度层优雅解法:A…

2026/10/17 2:44:39 阅读更多 →
开源项目第236期:PentAGI — 全自主渗透测试多智能体系统,自带 20+ 专业工具箱

开源项目第236期:PentAGI — 全自主渗透测试多智能体系统,自带 20+ 专业工具箱

引言PentAGI Penetration testing Artificial General Intelligence这是「每日一个开源项目」系列的第 236 篇。今天的项目是 PentAGI —— 一个开源、可自托管的全自主渗透测试多智能体系统,由安全工程公司 VXControl 开发,28,770 颗 Star,…

2026/10/17 2:44:39 阅读更多 →
短效代理:高频网络任务的“临时通行证”

短效代理:高频网络任务的“临时通行证”

“短效代理是有效时长较短、会频繁自动更换的网络代理资源,单IP存活时间通常在几十秒到几十分钟之间,也可按请求次数或固定会话周期切换。与长效静态代理不同,它通过高频轮换让每次请求尽量来自不同出口,降低被目标网站识别、限流…

2026/10/17 2:43:39 阅读更多 →

最新新闻

AVPlayer打造资讯App级列表视频流:UITableView自动播放与Cell复用实践

AVPlayer打造资讯App级列表视频流:UITableView自动播放与Cell复用实践

简介:面向苹果设备开发者的列表视频播放功能示例,使用Objective-C编写,模仿网易视频与今日头条的交互体验,能够解决列表滚动预览、点击全屏与自动暂停等常见需求。工程覆盖UITableView/UICollectionView上的视频滚动播放、AVFound…

2026/10/17 3:23:48 阅读更多 →
STM32硬件IIC驱动QMC5883L磁力计与航向角校准实战

STM32硬件IIC驱动QMC5883L磁力计与航向角校准实战

简介:面向STM32硬件IIC应用的QMC5883L磁力计驱动文件,为嵌入式开发人员提供一套可直接移植到HAL库工程的完整驱动方案,集中解决方向检测、姿态测量、电子罗盘等场景下的磁场数据读取问题,核心价值在于降低IIC外设使用门槛。压缩包…

2026/10/17 3:23:48 阅读更多 →
rsync + 定时任务实战:打造每日自动同步归档系统

rsync + 定时任务实战:打造每日自动同步归档系统

前两篇笔记都记在本地草稿里,一篇是 Linux 基础命令的整理,一篇是 vim 操作的练习过程,都还属于刚摸到门的状态。这一篇我决定换一种学法:不再按知识点一条条看下去,而是挑一个实际要解决的问题,把之前零零…

2026/10/17 3:23:48 阅读更多 →
STM32硬件IIC驱动QMC5883L磁力计:从寄存器配置到航向校准

STM32硬件IIC驱动QMC5883L磁力计:从寄存器配置到航向校准

简介:这是一份面向STM32开发者的QMC5883L磁力计驱动文件,基于硬件IIC接口与HAL库实现,解决了设备初始化、寄存器读写、三轴磁场数据获取等实际问题,适用于无人机定姿、机器人导航、电子罗盘等需要方向感知的场景。资源压缩包内共2…

2026/10/17 3:23:48 阅读更多 →
MySQL删除操作:DELETE、TRUNCATE、DROP到底怎么选?

MySQL删除操作:DELETE、TRUNCATE、DROP到底怎么选?

我最早接触这三个命令时,也和很多人一样,靠一张对比表背下了“delete删行、truncate清表、drop删表”,觉得自己已经会了。直到后来真在某项目的线上库出过一次事故——有人想清理一张订单流水表的部分历史数据,手滑执行成TRUNCATE…

2026/10/17 3:23:48 阅读更多 →
LassoCV实战指南:告别手动调参,用交叉验证自动选择最优alpha

LassoCV实战指南:告别手动调参,用交叉验证自动选择最优alpha

很多人在项目里一上来就Lasso(alpha0.01),看着 CV 分数不错就交付了。但我可以负责任地讲,手动试 Lasso 的正则化参数,十次有八次会在换数据集、换特征尺度之后翻车。alpha 这个参数对 Lasso 来说就是“稀疏度旋钮”,拧小了特征没…

2026/10/17 3:22:48 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →