【C++ 进阶】继承(上):解锁代码复用的核心密码,体会代码复用的魅力!

【C++ 进阶】继承(上):解锁代码复用的核心密码,体会代码复用的魅力!
前言:C++的三大核心特性是封装、继承和多态。在前文中,我们已经通过类和对象讲解了封装特性。接下来,本文将深入探讨C++继承机制的奥秘。
在这里插入图片描述
🌟 专注用图文结合拆解难点+代码落地知识,让技术学习从「难懂」变“一看就会”!
🏠 个人主页MSTcheng · ZEEKLOG
💻 代码仓库MSTcheng · Gitee📚 精选专栏 :📖 :《C语言》🧩 :《数据结构》💡 :《C++由浅入深》💬 座右铭 :“路虽远行则将至,事虽难做则必成!”

文章目录

一、继承的概念及定义

1.1继承的概念

继承面向对象编程(OOP)中的核心机制之一,允许一个类(子类/派生类)基于另一个类(父类/基类)来构建子类自动获得父类的属性和方法,并可扩展或修改这些功能。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们触的函数层次的复⽤,继承是类设计层次的复⽤。

举个例子:

classStudent{public:// 身份验证voididentity(){}// 学习voidstudy(){ cout << _name <<":正在学习"<< endl;}protected: string _name ="张三";// 姓名 string _address;// 地址 string _tel;// 电话int _age =18;// 年龄int _stuid;// 学号};classTeacher{public:// 省份验证voididentity(){}// 授课voidteaching(){ cout << _name <<":正在授课"<< endl;}protected: string _name ="李四";// 姓名int _age =25;// 年龄 string _address;// 地址 string _tel;// 电话 string _title;// 职称};

通过比较教师类和学生类可以发现,它们具有多个相同的特性:

共有成员变量: 姓名、年龄、电话、地址
共有成员函数: 身份验证

而它们的独有特性分别是:

教师类: 职称(成员变量)、授课(成员函数)
学生类: 学号(成员变量)、学习(成员函数)

既然存在这些共性,我们可以将共有属性提取到一个基类中,然后让教师类和学生类通过继承来复用这些属性。

1.2继承的定义

下面我们就来定义一个基类,然后让教师类和学生类来复用这些属性:

//定义一个person类定义共同特性classPerson{public://身份验证voididentity(){ cout <<"void identity()"<< _name << endl;}protected: string _name ="张三";// 姓名 string _address;// 地址 string _tel;// 电话int _age;// 年龄};//定义一个student类继承person类classStudent:publicPerson{public:voidprint(){ _name ="李四"; _age =18; cout <<"姓名:"<< _name << endl; cout <<"学号:"<< _stuid << endl;}// 学习voidstudy(){ cout << _name <<":正在学习"<< endl;}protected:int _stuid=241610101;// 学号};//定义一个teacher类继承person类classTeacher:publicPerson{public:voidprint(){ _name ="王五"; _age =25; cout <<"姓名:"<< _name << endl; cout <<"职称:"<< _title << endl;}// 授课voidteaching(){ cout << _name <<":正在授课中"<< endl;}protected: string _title="老师";// 职称};intmain(){ Student s; s.print(); s.study(); Teacher t; t.print(); t.teaching();return0;}

我们看到student类和teacher类的姓名都继承至person类,学号,职称,还有学习和授课(成员函数)是自身特有的,这样就实现了代码的复用。

通过上面的代码我们可以看到继承的定义格式:

在这里插入图片描述


Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。

在这里插入图片描述

1.3继承方式与访问方式的组合

以下是C++中基类成员在不同继承方式下于派生类中的访问权限表格:

基类成员访问权限 \ 继承方式public 继承protected 继承private 继承
基类public 成员派生类 public 成员派生类protected 成员派生类private 成员
基类protected 成员派生类 protected 成员派生类 protected 成员派生类 private 成员
基类private 成员在派生类中不可见在派生类中不可见在派生类中不可见

对于上面的这个表格有以下几点注意事项:

1.基类的私有成员无论以何种方式继承在派生类中均不可访问。这里的不可访问性是指,虽然 基类的私有成员确实存在于派生类对象中,但从语法层面限制了派生类对象(无论在类内部还是外部)都无法直接访问这些成员。
2.基类的私有成员在派生类中不可访问。若希望基类成员类外不可直接访问,但在派生类中可访问,则应将其定义为protected。由此可见,保护成员访问修饰符是专门为继承机制而设计的。
3.通过上面的表格我们会发现:基类的私有成员在派生类都是不可见基类的其他成员在派生类的访问⽅式 == Min(成员在基类的访问限定符,继承⽅式),且遵循给public >protected > private。

例如:
public继承

  • 父类的public成员,在派生类中任为public成员。
  • 父类的protect成员,在派生类任然为protect成员。
  • 父类的private成员在派生类不可见。

protect继承

  • 父类的public成员,在派生类中任为protect成员。
  • 父类的protect成员,在派生类任然为protect成员。
  • 父类的private成员在派生类不可见。

private继承以此类推

我们会发现,派生类对于基类的访问,要取决于继承方式和基类访问限定最小的那个!

4.在C++中,class默认采用private继承方式,而struct默认采用public继承方式。但为了代码清晰性,建议显式指定继承方式。

1.4继承类模板

#include<iostream>#include<vector>usingnamespace std;//继承/组合namespace my_stack {//=========================//类模板:stack<T>//继承自std::vector<T>的模板//使用vector的特定来模拟栈//=========================template<classT>classstack:public std::vector<T>{//不写public公有 默认就是私有 私有的话类外面访问不到public:voidpush(const T& x){//C++中栈的底层是使用vector来实现的 所以是栈继承了vector//当基类(父类)是模板时,派生类(子类)在使用父类的函数时就要指定类域//否则就会编译报错说push_back找不到标识符// 因为stack<int>实例化时,也实例化vector<int>了// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到//push_back(x);vector<T>::push_back(x);}voidpop(){vector<T>::pop_back();}const T&top(){returnvector<T>::back();}boolempty(){returnvector<T>::empty();}};}intmain(){ bit::stack<int> st; st.push(1); st.push(2); st.push(3);while(!st.empty()){ cout << st.top()<<" "; st.pop();}return0;}

二、基类和派生类对象的赋值转换

1、通常情况下我们把⼀个类型的对象赋值给另⼀个类型的指针或者引⽤时,存在类型转换,中间会产⽣临时对象,所以需要加const,如:

int a=1;constdouble& b=a;//不加const编译报错!

而在public继承中存在一个特殊处理:派生类对象可以直接赋值给基类指针或引用,且无需添加const限定。 此时指针或引用绑定的是派生类对象中基类部分(如下图所示)。这意味着基类指针或引用既可以指向基类对象,也可以指向派生类对象。

在这里插入图片描述


2、派生类对象赋值给基类对象是通过基类的拷贝构造函数或赋值运算符完成的(这两个函数的细节将在后续小节详细讲解)。这个过程类似于将派生类特有的成员部分"切除"因此也被称为对象切割或切片,如下图所示。

在这里插入图片描述


下面来看代码:

#include<iostream>usingnamespace std;// 基类classBase{public:int base_data;};// 派生类classDerived:publicBase{public:int derived_data;};intmain(){ Derived d; d.base_data =10; d.derived_data =20; Base b; b = d;//发生切片 cout << b.base_data << endl;//基类b只能看到base_data这一成员!!return0;}

切片的关键点
派生类对象
d 赋值给基类对象b 时,编译器仅拷贝 Base 部分的成员(base_data),而 Derived 特有的成员(derived_data)会被丢弃这就是对象切片的本质。

通过指针或引用可以避免切片:

Base& ref = d;// 通过引用访问,保留派生类完整性 Base* ptr =&d;// 通过指针访问,保留派生类完整性
此时的refptr引用/指向的就是整个派生类对象的基类部分,至于派生类特有的部分虽然看不到,但它还是存在派生类对象中,并没有发生切片!。

举个例子:

classPerson{public:voidDisplay(){ cout <<"姓名"<< _name << endl; cout <<"年龄"<< _age << endl;}protected: string _name;// 姓名 string _sex;// 性别int _age;// 年龄};classStudent:publicPerson{public:voidstudy(){ _name ="张三"; cout << _name <<"正在学习"<< endl;}public:int _No;// 学号};intmain(){ Student sobj;//基类指针/引用指向/引用派生类对象 Person* p=&sobj;//基类指针指向 student Person& rp = sobj;//引用绑定到 student//=========基类的指针访问派生类========= p->Display();//✅只能访问派生类中基类部分的成员//p->stduy();//❌语法上看不到派生类特有的部分//=========基类引用访问派生类=========== rp.Display();//✅同上//rp.study();//❌//==============赋值问题==============// 派生类对象可以赋值给基类的对象是通过调用//Person pobj = sobj;//2.基类对象不能赋值给派生类对象,这里会编译报错//sobj = pobj;return0;}

注意:子类对象能赋值给父类对象,但父类对象不能赋值给子类对象!

三、继承中的作用域

谈到作用域,作用域机制主要用于解决命名冲突问题,在继承场景中也不例外。当基类和派生类出现同名变量或函数时,究竟会优先调用哪个?这就需要了解隐藏规则了。

3.1隐藏规则

  1. 在继承体系中基类和派生类都有独立的作用域
  2. 派生类基类存在同名成员时派生类的成员会优先被访问,从而屏蔽基类中的同名成员,这种现象称为"隐藏"(如需访问基类的同名成员,可通过"基类::成员名"的方式显式调用)。
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员
classPerson{protected: string _name ="张三";// 姓名int _age =20;// 年龄};classStudent:publicPerson{public:voidprint(){ cout <<"姓名:"<< _name << endl;//=================隐藏============== cout <<"年龄:"<< _age << endl;//这里的_age与派生类的_age就构成了隐藏关系 默认访问的是派生类的 cout <<"年龄:"<< Person::_age << endl;//想要访问基类的_age 指定类域!!}protected:int _stuid =241610101;// 学号int _age =18;};intmain(){ Student t; t.print();return0;}

3.2继承作用域的两道笔试题

在这里插入图片描述
第一个问题:显然派生类B与基类A出现了同名函数,那么他们之间就构成隐藏关系!⚠️区别于函数重载:函数重载是在同一作用域下的同名函数(参数不同)才构成重载!而在继承中基类派生类都有自己独立的作用域所以不可能构成重载!。

第二个问题:在main函数中调用了两个fun函数,区别就是一个有参一个无参,有参的fun函数肯定调用的是派生类的fun函数 (未指定默认调用派生类的)。而无参的fun函数既可以是派生类的fun也可以是基类的fun(函数名相同构成隐藏),编译器不知道是哪个fun函数所以就会报编译错误!

常见的一些报错的原因:

阶段核心错误类型典型表现/原因
编译期语法错误缺少分号、括号不匹配
类型不匹配变量/返回值类型不符
符号未定义/冲突变量未声明、宏冲突
依赖缺失缺少头文件/库
运行时内存访问错误空指针、数组越界
运算错误除零
资源耗尽堆栈溢出、文件句柄耗尽
逻辑错误算法实现错误
输入/数据问题非法输入未校验、文件操作失败
其他内存泄漏未释放动态内存
精度/编码问题浮点误差、字符编码错误
超时/硬件问题操作超时、硬件故障

四、总结

本文系统讲解了继承的核心概念,首先明确定义了继承的基本原理,接着详细分析了不同继承方式与访问权限的组合应用。随后深入探讨了基类与派生类对象间的赋值转换问题,最后解析了继承体系中的作用域规则,并通过两道典型笔试题进行实战演练。
由于篇幅问题,后续的继承相关的内容将在下一篇文章介绍!

Read more

将现有 REST API 转换为 MCP Server工具 -higress

将现有 REST API 转换为 MCP Server工具 -higress

Higress 是一款云原生 API 网关,集成了流量网关、微服务网关、安全网关和 AI 网关的功能。 它基于 Istio 和 Envoy 开发,支持使用 Go/Rust/JS 等语言编写 Wasm 插件。 提供了数十个通用插件和开箱即用的控制台。 Higress AI 网关支持多种 AI 服务提供商,如 OpenAI、DeepSeek、通义千问等,并具备令牌限流、消费者鉴权、WAF 防护、语义缓存等功能。 MCP Server 插件配置 higress 功能说明 * mcp-server 插件基于 Model Context Protocol (MCP),专为 AI 助手设计,

By Ne0inhk
MCP 工具速成:npx vs. uvx 全流程安装指南

MCP 工具速成:npx vs. uvx 全流程安装指南

在现代 AI 开发中,Model Context Protocol(MCP)允许通过外部进程扩展模型能力,而 npx(Node.js 生态)和 uvx(Python 生态)则是两种即装即用的客户端工具,帮助你快速下载并运行 MCP 服务器或工具包,无需全局安装。本文将从原理和对比入手,提供面向 Windows、macOS、Linux 的详细安装、验证及使用示例,确保你能在本地或 CI/CD 流程中无缝集成 MCP 服务器。 1. 工具简介 1.1 npx(Node.js/npm) npx 是 npm CLI(≥v5.2.0)

By Ne0inhk
解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程

解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程

文章目录 * 解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程 * 引言:技术融合的奇妙开篇 * 认识主角:Dify、MCP 与 MySQL * (一)Dify:大语言模型应用开发利器 * (二)MCP:连接的桥梁 * (三)MySQL:经典数据库 * 准备工作:搭建融合舞台 * (一)环境搭建 * (二)安装与配置 Dify * (三)安装与配置 MySQL * 关键步骤:Dify 与 MySQL 的牵手过程 * (一)安装必要插件 * (二)配置 MCP SSE * (三)创建 Dify 工作流 * (四)配置 Agent 策略 * (五)搭建MCP

By Ne0inhk
如何在Cursor中使用MCP服务

如何在Cursor中使用MCP服务

前言 随着AI编程助手的普及,越来越多开发者选择在Cursor等智能IDE中进行高效开发。Cursor不仅支持代码补全、智能搜索,还能通过MCP(Multi-Cloud Platform)服务,轻松调用如高德地图API、数据库等多种外部服务,实现数据采集、处理和自动化办公。 本文以“北京一日游自动化攻略”为例,详细讲解如何在 Cursor 中使用 MCP 服务,完成数据采集、数据库操作、文件生成和前端页面展示的全流程。 学习视频:cursor中使用MCP服务 一、什么是MCP服务? MCP(Multi-Cloud Platform)是Cursor内置的多云服务接口,支持调用地图、数据库、文件系统等多种API。通过MCP,开发者无需手动写HTTP请求或繁琐配置,只需在对话中描述需求,AI助手即可自动调用相关服务,极大提升开发效率。 二、环境准备 2.1 cursor Cursor重置机器码-解决Too many free trials. 2.

By Ne0inhk