同名成员到底调用谁?C++ 隐藏规则你真的会吗?

同名成员到底调用谁?C++ 隐藏规则你真的会吗?

欢迎来到 s a y − f a l l 的文章 欢迎来到say-fall的文章 欢迎来到say−fall的文章

在这里插入图片描述

🌈say-fall:个人主页🚀专栏:《手把手教你学会C++》 | 《C语言从零开始到精通》 | 《数据结构与算法》 | 《小游戏与项目》💪格言:做好你自己,才能吸引更多人,与他们共赢,这才是最好的成长方式。


前言:

对于c++来说,有三大核心特性,是面向对象编程(OOP)的经典三要素:封装、继承、多态。这三个特性是 C++ 区别于纯面向过程语言(如 C)的核心,也是理解 C++ 面向对象思想的关键。之前利用类和对象的思想和STL中的适配器:queue和stack了解过封装,本篇文章就详细介绍一下继承这个特性

文章目录


正文:

一、 什么是继承?

继承(inheritance) 机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类( 基类 )特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称 派⽣类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复⽤( 模板 ),继承是类设计层次的复⽤。

下面我们设计了两个类:Student和Teacher,Student和Teacher都有姓名/地址/电话/年龄等成员变量,都有identity⾝份认证的成员函数,设计到两个类⾥⾯就是冗余的。当然他们也有⼀些不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣的独有成员函数是学习,⽼师的独有成员函数是授课。

classStudent{public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 voididentity(){// ...}// 学习 voidstudy(){// ...}protected: string _name ="peter";// 姓名  string _address;// 地址  string _tel;// 电话 int _age =18;// 年龄 int _stuid;// 学号 };classTeacher{public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 voididentity(){// ...}// 授课 voidteaching(){//...}protected: string _name ="张三";// 姓名 int _age =18;// 年龄  string _address;// 地址  string _tel;// 电话  string _title;// 职称 };intmain(){return0;}

显然,有大量的重复代码出现,我们将这些重复代码,或者说公共成员放入一个person类中,用继承的方法来处理,就不需要重复定义了

classPerson{public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证 voididentity(){ cout <<"void identity()"<< _name << endl;}protected: string _name ="张三";// 姓名 int _age =18;// 年龄  string _address;// 地址  string _tel;// 电话 };classStudent:publicPerson{public:voidstudy(){ cout <<"void study()"<< endl;}protected:int _stuid;};classTeacher:publicPerson{public:voidtesching(){ cout <<"void tesching()"<< endl;}protected: string _title;};intmain(){ Student s; Teacher t; s.identity(); t.identity();return0;}

二、 继承的定义

定义的格式

下⾯我们看到Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。(因为翻译的原因,所以既叫基类/派⽣类,也叫⽗类/⼦类)

在这里插入图片描述


其实子类和父类比较好理解,那么继承方式是什么呢?

在这里插入图片描述


在这里插入图片描述


继承方式如图,和访问限定符有点类似,都有三种,下面我们来看一下继承基类访问方式的变化:

在这里插入图片描述
  1. 看起来花里胡哨的,其实规则蛮简单的:
    public > protect > private 选其中小一点的
  2. 还有一些我们都知道的规则:使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显⽰的写出继承⽅式。
  3. 在实际运⽤中⼀般使⽤都是public继承,⼏乎很少使⽤protetced/private继承,也不提倡使⽤protetced/private继承,因为protetced/private继承下来的成员都只能在派⽣类的类⾥⾯使⽤,实际中扩展维护性不强。

以上我们继承的Person是一个普通的类,继承不只能继承普通类,还能继承类模板

继承类模板

namespace say_fall {//template<class T>//class vector//{};// stack和vector的关系,既符合is-a,也符合has-a template<classT>classstack:public std::vector<T>{public:voidpush(const T& x){// 基类是类模板时,需要指定⼀下类域, // 否则编译报错:error C3861: “push_back”: 找不到标识符 // 因为stack<int>实例化时,也实例化vector<int>了 // 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到 vector<T>::push_back(x);//push_back(x);---->没有实例化,所以找不到}voidpop(){vector<T>::pop_back();}const T&top(){returnvector<T>::back();}boolempty(){returnvector<T>::empty();}};}intmain(){ say_fall::stack<int> st; st.push(1); st.push(2); st.push(3);while(!st.empty()){ cout << st.top()<<" "; st.pop();}return0;}

注意到,这里用继承实现了一个stack类,而不是之前适配器的方法:用函数封装,这里就要解释一下,原来适配器实现用的是组合的方法,还可以用继承实现。

三、 基类和派生类之间的转换

继承就像是私有制极其严格的父子关系一样,有这么几条规则:

  1. public继承的派⽣类对象 可以赋值给 基类的指针 / 基类的引⽤。这⾥有个形象的说法叫切⽚或者切割。寓意把派⽣类中基类那部分切出来,基类指针或引⽤指向的是派⽣类中切出来的基类那部分。
  2. 基类对象不能赋值给派⽣类对象。

基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型,可以使⽤RTTI(Run-Time Type Information)的dynamic_cast 来进⾏识别后进⾏安全转换。

在这里插入图片描述
  • 类比:父亲给孩子的财产父亲是有权使用的,而孩子不能直接使用父亲的财产,必须经过父亲允许才可以
classPerson{protected: string _name;// 姓名  string _sex;// 性别 int _age;// 年龄 };classStudent:publicPerson{public:int _No;// 学号 };intmain(){ Student sobj;// 1.派生类对象可以赋值给基类的指针/引用 Person* pp =&sobj; Person& rp = sobj;// 派生类对象可以赋值给基类的对象是通过调用基类的拷贝构造完成的  Person pobj = sobj;//2.基类对象不能赋值给派生类对象,这里会编译报错  sobj = pobj;//报错:没有与这些操作数匹配的 "=" 运算符return0;}

四、 继承的作用域

隐藏规则与重载比较

重载规则:

在同一作用域下的同名函数,在参数不同的情况下构成函数重载。

隐藏规则
  1. 在继承体系中基类和派⽣类都有 独⽴的作⽤域
  2. 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。
    (在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系⾥⾯最好不要定义同名的成员。
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆 classPerson{protected: string _name ="小李子";// 姓名 int _num =111;// ⾝份证号 };classStudent:publicPerson{public:voidPrint(){ cout <<" 姓名:"<< _name << endl; cout <<" ⾝份证号:"<< Person::_num << endl; cout <<" 学号:"<< _num << endl;}protected:int _num =999;// 学号 };intmain(){ Student s1; s1.Print();return0;};

相关选择题

classA{public:voidfun(){ cout <<"func()"<< endl;}};classB:publicA{public:voidfun(int i){ cout <<"func(int i)"<< i << endl;}};intmain(){ B b; b.fun(10); b.fun();return0;};
1. A和B类中的两个func构成什么关系()

A. 重载 B. 隐藏 C.没关系

  • 答案是 B ,回顾重载,其定义是一个作用域中的同名函数,而继承出来的子类和父类实际上是在两个作用域的,所以是隐藏
2. 上⾯程序的编译运⾏结果是什么()

A. 编译报错 B. 运⾏报错 C. 正常运⾏

  • 首先我们直到调用的func()是构成隐藏的,在调用b.fun();时候,却没有传入参数,很显然是编译错误。

五、 派生类默认成员函数

在这里插入图片描述


自己手动写一个类的过程中,我们了解过这6个默认成员函数,也就是即使我们自己不实现,编译器也会自己实现的,而继承的类中会自动生成吗?生成的规则又是什么样子的?

4个常⻅默认成员函数
在这里插入图片描述


这里说的四个默认构造函数是指构造、析构、赋值重载,拷贝构造:

  1. 派⽣类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。如果基类没有默认的构造函数,则必须在派⽣类构造函数的初始化列表阶段显⽰调⽤。
  2. 派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化。
  3. 派⽣类的operator=必须要调⽤基类的operator=完成基类的复制。需要注意的是派⽣类的operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域
  4. 派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。因为这样才能保证派⽣类对象先清理派⽣类成员再清理基类成员的顺序。
  5. 派⽣类对象初始化先调⽤基类构造再调派⽣类构造。
  6. 派⽣类对象析构清理先调⽤派⽣类析构再调基类的析构。
  7. 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系。
classPerson{public:Person(constchar* name ="peter"):_name(name){ cout <<"Person()"<< endl;}Person(const Person& p):_name(p._name){ cout <<"Person(const Person& p)"<< endl;} Person&operator=(const Person& p){ cout <<"Person operator=(const Person& p)"<< endl;if(this!=&p) _name = p._name;return*this;}~Person(){ cout <<"~Person()"<< endl;}protected: string _name;// 姓名 };classStudent:publicPerson{public:Student(constchar* name,int num):Person(name)//用父类的构造,_num(num){ cout <<"Student()"<< endl;}//一般来说构造函数都要自己实现Student(const Student& s):Person(s)//用父类的,_num(s._num){ cout <<"Student(const Student& s)"<< endl;}//拷贝构造则要看类型,内置类型不需要自己实现,而有资源释放的则必须需要自己实现 Student&operator=(const Student& s){ cout <<"Student& operator= (const Student& s)"<< endl;if(this!=&s){// 构成隐藏,所以需要显⽰调⽤  Person::operator=(s);//operator=(s); 会触发递归,因为调用的是自己的operator=() _num = s._num;}return*this;}//赋值重载和拷贝构造是一样的~Student(){ cout <<"~Student()"<< endl;}//析构同样也和拷贝构造一样//有关于析构:析构子类时候会连带父类一起析构,所以父类不需要显式析构protected:int _num;//学号 };intmain(){ Student s1("jack",18); Student s2(s1); Student s3("rose",17); s1 = s3;return0;}

  • 本节完…

Read more

【GitHub项目推荐--TypeTale(字字动画):免费AIGC视频创作工具】非开源

简介 TypeTale (字字动画)是一款专为内容创作者打造的完全免费的AIGC创作软件,主要用于小说推文、AI短剧、AI电影制作。它集成了多种AI能力,提供从文案处理到视频生成的全链路创作支持,承诺现有功能与基础功能永久免费。 🔗 GitHub地址 : https://github.com/TypeTale/TypeTale 🎬 核心价值 : AIGC视频生成 · 小说推文 · AI短剧 · 完全免费 · 中文优化 项目背景 : * 内容创作 :短视频内容创作需求增长 * AIGC技术 :AI生成内容技术成熟 * 成本控制 :降低视频制作成本需求 * 中文优化 :中文内容创作工具需求 * 开源生态 :开源创作工具生态 项目特色 : * 🆓 完全免费 :永久免费使用 * 🇨🇳 中文优化 :专为中文优化 * 🤖 AI集成 :多AI能力集成 * 🎬 视频生成 :全链路视频生成 * 🔧 易用性 :简单易用界面 技术亮点 : * 多模型支持 :支持多种AI模型 * ComfyUI集成 :深度ComfyUI集成 * 工作流系统

By Ne0inhk
【毕业论文没思路?paperxm智能写作一键解决!】

【毕业论文没思路?paperxm智能写作一键解决!】

毕业论文写作困境的常见原因 缺乏明确的研究方向或选题,导致难以展开论文框架。文献综述不足,无法有效支撑论点,造成写作停滞。时间管理不当,临近截止日期时压力倍增。对学术写作规范不熟悉,格式和逻辑容易出错。 paperxm的链接我就给大家放着了哈:www.paperxm.com Paperxm智能写作工具的核心功能 基于自然语言处理技术,自动生成论文大纲和初稿,提供选题建议。内置文献检索模块,快速匹配相关学术资源,辅助文献综述。支持多语言写作,满足不同学科领域的表达需求。实时语法检查和格式修正,确保论文符合学术规范。 如何使用Paperxm 它从开题报告、文献综述、论文写作还有仿写、到最后的答辩PPT都能帮我直接解决! 优势一:节省时间 使用paperxm写论文,可以让我告别繁琐的资料搜集和整理工作。paperxm能迅速从海量资料中筛选出有用的信息,为我的论文提供有力支持。这样一来,你就有更多的时间去思考、创新,提高论文质量。 优势二:提高写作质量 paperxm具有强大的语言处理能力,可以帮助我优化句子结构、纠正语法错误,甚至提供写作建议。这意味着,我的论文在pape

By Ne0inhk
2026最新AI聚合系统(渐进式AIGC系统):nano-banana-2第二代绘画、VEO3/VEO3.1、Sora-2视频生成大模型私有化独立系统+扣子工作流Agent智能体

2026最新AI聚合系统(渐进式AIGC系统):nano-banana-2第二代绘画、VEO3/VEO3.1、Sora-2视频生成大模型私有化独立系统+扣子工作流Agent智能体

SparkAi系统:渐进式AIGC系统,一款基于OpenAi/ChatGPT、GPT-5.2/GPT-5、最新旗舰大模型Claude-opus-4-6、nano-banana-2第二代绘画大模型、Gemini-3.1-pro、DeepSeek、Sora-2、VEO3.1、Agent智能体 扣子(coze)插件、工作流、函数、知识库 等AI大模型能力开发的一站式AI系统;支持「🤖AI聊天」、「🎨专业AI绘画」、「🧠AI智能体」、「🪟Agent应用」、「🎬AI视频生成」等,支持独立私有部署!提供面向个人用户 (ToC)、开发者 (ToD)、企业 (ToB)的全面解决方案。 一、SparkAi系统/官网 最新旗舰大模型Claude-opus-4-6、GPT-5.3-Codex、GPT-5.2、GPT-5-PRO、gpt-image-1.5绘画大模型、超强生图

By Ne0inhk
昇腾NPU运行Llama模型全攻略:环境搭建、性能测试、问题解决一网打尽

昇腾NPU运行Llama模型全攻略:环境搭建、性能测试、问题解决一网打尽

背景 最近几年,AI 大模型火得一塌糊涂,特别是像 Llama 这样的开源模型,几乎成了每个技术团队都在讨论的热点。不过,这些"巨无霸"模型虽然能力超强,但对硬件的要求也高得吓人。这时候,华为的昇腾 NPU 就派上用场了。 说实话,昇腾 NPU 在 AI 计算这块确实有两把刷子。它专门为神经网络计算设计,不仅算力强劲,功耗控制得也不错,最关键的是灵活性很好,可以根据不同场景进行裁剪。所以,用它来跑大模型推理,理论上应该是个不错的选择。 为什么偏偏选了 Llama 来测试? 说到 Llama,这玩意儿现在可是开源界的"网红"。Meta 把它完全开源出来,社区生态搞得风生水起,各种优化和适配层出不穷。 其实选择 Llama 做测试,主要有这么几个考虑:

By Ne0inhk