完整教程:【C++】继承(1)

news/2025/11/10 20:00:02/文章来源:https://www.cnblogs.com/yxysuanfa/p/19208096

目录

1. 继承的概念及定义

2. 继承类模版

3. 基类和派生类间的转换

3.1 对象赋值

派生类对象-->基类对象(合法,隐式进行)

基类对象-->派生类对象(危险,禁止使用)

3.2 类型转换

向上转换:派生类指针/引用-->基类指针/引用

向下转换:基类指针/引用-->派生类指针/引用

4. 隐藏关系

练习


1. 继承的概念及定义

继承是面向对象编程的三大特性之一,它允许一个类(称为派生类 / 子类)继承另一个类(称为基类 / 父类)的属性和行为,从而实现代码复用和建立类之间的层次关系。

继承的语法

派生类通过 : 指定继承关系,语法如下:

class 派生类名 : 继承方式 基类名
{//派生类的成员
};

public继承是C++中最常用的继承方式,其核心规则是:

  • 基类的public成员:在子类仍然保持public属性(子类对象可直接访问);
  • 基类的protected成员:在子类中仍然保持protected属性(仅子类内部可访问,子类对象不可直接访问);
  • 基类的private成员:在子类中完全不可见(即使子类内部也不能直接访问,仅基类自己的成员函数可访问)。

Student和Teacher作为Person的特殊类型,通过继承直接复用了Person的属性(姓名、电话等)和行为(identity身份认证),无需在子类中重复定义这些共性内容,仅需扩展各自的特有属性(如Student的_stuid学号,Teacher的title职称)和行为(如study()学习、teaching授课)。

class Person
{
public:// 进入校园/图书馆/实验室刷二维码等身份认证void identity(){cout << "void identity()" << _name << endl;cout << _age << endl;}
protected:string _name = "张三"; //姓名string _address;       //地址string _tel;           //电话
private:int _age = 18;         //年龄
};
//派生类(子类)
class Student : public Person
{
public:void study() //学习{// ...//cout << _age << endl;//父类的私有成员在子类中不可见,仅父类自己的成员函数可访问,子类不可直接访问cout << _tel << endl; //受保护成员,子类内部可访问}
protected:int _stuid; //学号
};
//派生类(子类)
class Teacher : public Person
{
public:void teaching() //授课{//...}
protected:string title; //职称
};
int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}

不同继承方式下派生类的访问权限如下:

基类成员类型public继承protected继承private继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可访问不可访问不可访问

public继承是实际开发中最常用的继承方式,几乎很少使用protected/private继承,也不提倡使用。

注意:基类的private成员无论哪种继承方式,派生类都无法直接访问(需要通过基类的piblic/protected成员函数间接访问)。

在C++中,用class定义类时,默认的继承方式是private,用struct定义类时,默认的继承方式是public,不过,为了让代码更清晰、易读,建议显示写出继承方式。

基类Person中各成员的原始权限:

class Person
{
public:void Print() { cout << _name << endl; }  // public成员(函数)
protected:string _name;  // protected成员(变量)
private:int _age;  // private成员(变量)
};

举例说明不同继承关系下的成员访问权限的变化:

1. public继承

class Student : public Person
{
public:void Test(){Print();     //合法:子类成员函数可访问基类public成员_name = "张三";  //合法:子类成员函数可访问基类protected成员// _age = 18;   //错误:基类private成员不可见}
protected:int _stunum;
};
int main()
{Student s;s.Print();  //合法:子类对象可访问继承的public成员// s._name = "李四";  //错误:protected成员子类对象不可访问return 0;
}

2. protected继承

class Student : protected Person
{
public:void Test(){Print();    //合法:子类成员函数可访问protected成员_name = "张三";  //合法:子类成员函数可访问protected成员// _age = 18;   //错误:基类private成员不可见}
protected:int _stunum;
};
int main()
{Student s;// s.Print(); //错误:Print()在子类中是protected,子类对象不可访问return 0;
}

3. private继承

class Student : private Person
{
public:void Test(){Print();    //合法:子类成员函数可访问private成员_name = "张三";  //合法:子类成员函数可访问private成员// _age = 18;  //错误:基类private成员不可见}
protected:int _stunum;
};
// 孙子类(继承Student)
class Graduate : public Student
{
public:void Test2(){// Print();  //错误:Print()在Student中是private,孙子类不可访问// _name = "李四";  //错误:_name在Student中是private,孙子类不可访问}
};
int main()
{Student s;// s.Print();  //错误:Print()在子类中是private,子类对象不可访问return 0;
}

private与protected的区别

private和protected的核心区别体现在是否允许派生类访问,protected平衡了封装和继承的需需求,允许子类访问但限制外部访问。

private确保了类的内部实现不被外部访问,但继承时,如果基类的某些成员需要被子类使用但又不想暴露给外部,private就不够了,这时候protected就派上用场了。

比如假设设计一个Animal基类,其中“体重”是需要被子类(Dog)使用的属性,但不希望外部代码随意直接修改动物的体重,这时就可以使用protected。

class Animal
{
protected:int _weight;  //体重:允许派生类访问,不允许外部直接修改
public://外部只能通过接口间接修改,保证合法性(比如体重不能为负)void SetWeight(int w){if (w > 0) _weight = w;}
};
class Dog : public Animal
{
public://派生类可以使用_weight计算食量(复用基类成员_weight)int CalculateFood(){return _weight * 6;  //假设每公斤体重每天吃6g食物}
};

如果_weight用private,Dog类无法访问_weight,就无法实现CalculateFood() ;如果用public,外部可以直接写dog._weight = -100,破坏数据合法性,protected完美解决了这个矛盾。

  • private成员:仅在当前类的内部可见(类自己的成员函数可访问,任何外部代码包括派生类都不可直接访问)。
  • protected成员:在当前类内部和其派生类内部可见(类自己的成员函数、派生类的成员函数可访问,但类的外部对象不可直接访问)

2. 继承类模版

MyStack<T>继承自std::vector<T>,复用了vector的底层存储能力。

template
class Mystack : public std::vector
{
public:void push(const T& x){//push_back(x); error! 编译报错:error C3861: “push_back”: 找不到标识符vector::push_back(x); //right!基类是类模板时,需要指定⼀下类域this->push_back(x);     //两种写法等效 this的类型依赖T,延迟到第二阶段模板实例化时,再去基类中查找该成员}void pop(){vector::pop_back();}const T& top(){return vector::back();}bool empty(){return vector::empty();}
};
int main()
{Mystack st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();}return 0;
}

模版类继承时,访问基类成员需使用this->成员名基类名<T>::成员名,避免编译器因模版延迟实例化导致的查找错误。

类模版的编译分为两个阶段:

  • 第一阶段:仅做“语法层面的检查”,不会实例化参数T相关成员。
  • 第二阶段:模版实例化时,编译器才会根据实际传入的模版参数实例化。

上面程序派生类模版Mystack<T>继承基类模版std::vector<T>,基类成员是依赖模版参数T的,在第一阶段时,模版未实例化,编译器无法确定push_back的归属,所以需要显式指定告诉编译器这个成员依赖于模版参数,明确成员来源,否则会报“找不到标识符”的错误。

虽然代码能够正常工作,但不推荐使用public继承实现栈,public继承会暴露基类所有的public成员,用户可以直接调用st.insert()、st.erase()、st[ ]等接口,从而破坏栈的特性。推荐使用私有继承或组合:

  • 私有继承:将std::vector作为私有基类,此时基类的public成员在派生类中变为private,外部无法访问。
  • 组合:将td::vector作为stack的私有成员(“has-a关系”,栈有一个vector)(更推荐的方式)

3. 基类和派生类间的转换

3.1 对象赋值

对象赋值的本质是两个独立对象之间的内容复制,需遵循“派生类包含基类,基类不包含派生类”的内存结构逻辑,仅有一种场景合法且常用。

派生类对象-->基类对象(合法,隐式进行)

将派生类对象中的基类部分(如_name、 _age)复制到基类对象中,派生类特有的成员(如_stuid)会被“切片丢弃”,此过程称为对象切片。

本质:派生类对象s-->隐式类型转换为Person临时对象(切片,丢弃_stuid)-->将临时对象拷贝给基类对象p。

class Person  //基类
{
public:string _name;int _age;
};
class Student : public Person  //派生类
{
public:int _stuid;
};
int main()
{Student s;s._name = "张三";s._age = 18;s._stuid = 1001;//派生类对象-->基类对象(会发生“对象切片”)Person p;p = s; //隐式类型转换:派生类对象s隐式类型转换为Person临时对象(切片丢弃_stuid),再将临时对象拷贝给基类对象p//p._stuid;   //错误:p是Person对象,没有_stuid成员return 0;
}

基类对象-->派生类对象(危险,禁止使用)

基类对象的内存中不包含派生类的特有成员,强制赋值时只能复制基类部分到派生类中,而派生类的特有成员会是随机值(未初始化),访问这些成员会触发未定义行为,即使通过static_cast强制转换,也会导致严重问题。

3.2 类型转换

在C++中,基类和派生类之间的转换(也称为“类型转换”),核心围绕“向上转换”(派生类->派生类->基类)“向下转换”(基类->派生类)两者的安全性和使用场景有显著差别。

向上转换:派生类指针/引用-->基类指针/引用

隐式转换,完全安全,是继承场景中最常用的转换。

因为派生类对象包含基类对象,派生类指针/引用指向的内存区域,必然包含基类的完整数据。转换后,基类指针/引用仅“聚焦”于内存中的基类部分,不会越界或访问非法数据,所以安全。

class Person  //基类
{
public:string _name;int _age;
};
class Student : public Person  //派生类
{
public:int _stuid;
};
Student s;
Student* s_ptr = &s;
Student& s_ref = s;
//隐式向上转换
Person* p_ptr = s_ptr;  // 基类指针指向派生类对象的基类部分
Person& p_ref = s_ref;  // 基类引用绑定派生类对象的基类部分
p_ptr->_name = "王五";  // 正确:访问基类成员
// p_ptr->_stuid;       // 错误:基类指针无法解读派生类特有成员

向下转换:基类指针/引用-->派生类指针/引用

这是不安全的,且不能隐式转换,必须显式使用static_cast或dynamic_cast转换。

因为基类对象不包含派生类的特有成员(如Person没有_stuid),若强制转换,访问派生类特有成员会导致未定义行为,所以不安全。

两种显式转换方式:

  • static_cast:编译时转换,不做运行时检查,风险较高。
  • dynamic_cast:运行时转换,仅适用于多态类(包含虚函数的类),会检查转换的有效性,更安全。

4. 隐藏关系

继承中,隐藏关系指的是:派生类中定义了与基类同名的成员(变量或函数)时,派生类的成员会“隐藏”基类的同名成员——即默认情况下,在派生类的作用域内,直接访问该同名成员时,优先访问派生类自己的成员,基类的同名成员会被“屏蔽”,必须通过基类域限定符(::)才能访问。

隐藏的前提:作用域不同+名称相同

隐藏、重载、重写的区别:

  • 重载:同一作用域内,同名函数的参数列表不同(个数 / 类型 / 顺序);
  • 重写:派生类与基类的虚函数,参数列表、返回值、cv 限定符完全相同(多态的核心);
  • 隐藏:不同作用域(基类 vs 派生类),同名成员(变量或函数,函数参数可同可不同);

同名变量的隐藏:无论变量类型是否相同,基类的变量都会被隐藏。

class Base
{
public:int _a = 10;  //基类的变量_a
};
class Derived : public Base
{
public:double _a = 20.5;  //派生类的变量_a(与基类同名,隐藏基类的_a)
};
int main()
{Derived d;cout << d._a << endl;        // 输出 20.5 默认访问派生类的_a(基类的被隐藏)cout << d.Base::_a << endl;  // 输出 10   通过基类域限定符访问被隐藏的基类_areturn 0;
}

同名函数的隐藏:无论函数的参数列表是否相同,基类的同名函数都会被隐藏。

class Base
{
public:void show(int x){cout << "Base::show(int): " << x << endl;  //基类的int版本}
};
class Derived : public Base
{
public:void show(double x){cout << "Derived::show(double): " << x << endl; //派生类的double版本}
};
int main()
{Derived d;d.show(10);        // 实际运行:输出 Derived::show(double): 10(int→double隐式转换)cout对double类型输出有简化显示的默认行为d.show(3.14);      // 输出 Derived::show(double): 3.14(直接匹配double)d.Base::show(10);  // 输出 Base::show(int): 10(显式访问基类被隐藏的函数)return 0;
}

练习

1. 下面程序中A类和B类中的两个fun构成什么关系?

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

派生类中定义与基类同名的函数(无论参数是否相同),会隐藏基类的同名函数。这里B::fun(int i)隐藏了A::fun(),因此构成隐藏关系

2. 下面程序的运行结果是什么?

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

由于B::fun(int i)隐藏了A::fun(),编译器在B的作用域内,发现B只有func(int),但调用时没有传参,参数不匹配,因此编译阶段直接报错。

class A
{
public:void fun(){cout << "func()" << endl;}
};
class B : public A
{
public:void fun(int i){cout << "func(int i)" << i << endl;}
};
int main()
{B b;b.fun(10);b.fun();return 0;
};

实际开发中应尽量避免派生类与基类成员同名,若必须同名,访问基类成员时务必用基类名::显式指定,避免歧义。

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

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

相关文章

CSP2025 T4 employ

设 \(f_{i,j,k}\) 是前 \(i\) 位,当前有 \(j\) 个人寄了,有 \(k\) 个 \(x\) 满足 \(1 \le x \le i \land c_x \le j\),只考虑所有 \(c \le j\)​ 的人的排列的方案数。 设 \(t_i\) 是 \(c_x = i\) 的 \(x\) 的个数,…

2025/11/10

2025/11/10连接数据库时: 核心结论:新手需重点关注环境配置一致性、连接参数准确性、权限与依赖配置,同时避开端口占用、驱动缺失等常见坑。 环境与依赖准备确保IDEA、MySQL、Tomcat版本兼容(比如MySQL 8.0需搭配对…

VSCode下载安装和使用教程(附安装包,适合新手)

Visual Studio Code(简称 VSCode)是微软开发的一款免费、开源的跨平台代码编辑器。图:VSCode Logo作为源代码编辑器而非传统 IDE,VSCode 核心定位轻量高效,但通过插件系统可扩展出媲美大型 IDE 的功能。VSCode 官…

电脑同时获取了一个正常IP和一个169开头的IP

主机莫名在lan口获取到169开头的IP导致网络无法连接,局域网的文件共享也无法使用 用第三方防火墙(COMODO)可以直接阻止对应网络,问题暂时解决了,不确定能否彻底解决 怀疑隔壁有人将路由器的LAN口重新接入到了墙上的…

【Agent】生成式隐式记忆 MemGen 源码解读

【Agent】生成式隐式记忆 MemGen 源码解读 目录【Agent】生成式隐式记忆 MemGen 源码解读0x00 概要0x01 背景0x02 源码解析2.1 模型2.1.1 核心特色2.1.2 网络结构2.1.3 代码2.1.4 插入阶段forwardgenerate核心作用核…

[Python刷题记录]-螺旋矩阵-矩阵-中等

[Python刷题记录]-螺旋矩阵-矩阵-中等链接:54. 螺旋矩阵 - 力扣(LeetCode) 关键是每次旋转方向都是顺时针,就可以做一个顺时针方向的模拟数组,来模拟下一步的路径 direction = [[0, 1], [1, 0], [0, -1], [-1, 0]…

高级语言程序第四次作业 - 102300317

这个作业属于哪个课程 2025高级语言程序设计这个作业的要求在哪里 高级语言程序第四次作业学号 102300317姓名 李东阳运行程序截图: 1、2、3、用while和do while分别设计程序实现:用公式π/4=1-1/3+1/5-1/7+1/9+...求…

2025年草莓速冻冷库企业推荐排行榜

2025年草莓速冻冷库企业推荐排行榜随着草莓产业的快速发展,速冻冷库在保持草莓新鲜度和营养价值方面发挥着至关重要的作用。以下是2025年草莓速冻冷库企业推荐排行榜,为草莓种植、加工企业提供专业参考。推荐企业第一…

打印机出现print job cancled at printer

打印机出现"print job cancled at printer",导致无法打印的问题 解决方法:I had this problem with a HP LaserJet MFP m28w on Linux Mint 21 (Xfce). The solution seems be to launch Settings / Print…

基于单片机拖尾式多模式流水灯系统仿真设计 - 详解

基于单片机拖尾式多模式流水灯系统仿真设计 - 详解2025-11-10 19:37 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; displ…

关于在ios优秀的系统中签名并安装ipa文档的五种方法,PakePlus打包的ipa文件可以看看

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

计数dp入门

View Post计数dp入门计数dp入门 前情提要:能开滚动数组就开滚动数组,不开包你 \(MLE\) 不要畏惧洛谷难度标签,很多时候难的是性质的发掘而非计数时的 \(dp\),见多识广就能熟练运用甚至跨阶切题。 波奇酱世界第一可…

[ARC107D] Number of Multisets 分析

题目概述 你需要确定 \(n\) 个数,每个数形如 \(\frac{1}{2^x}(x\geq 0)\),其中 \(x\) 是非负整数,求他们的和为 \(k\) 的方案。 数据范围:\(1\leq k\leq n\leq 3000\)。 分析 真的妙! 我们假设最后的结果为 \(\{\…

2025年3000卫生纸加工设备推荐排行

2025年卫生纸加工设备推荐排行:优创纸品机械领跑行业随着卫生纸加工行业的快速发展,选择合适的加工设备对企业的生产效率和质量至关重要。在众多品牌中,优创纸品机械凭借其卓越的性能和全面的服务,成为2025年卫生纸…

项目管理系统开发指导

项目架构设计 技术栈选择 后端框架:Java Servlet + JSP 数据库:MySQL 服务器:Tomcat 9.0.111 构建工具:Maven 开发工具:IntelliJ IDEA 三层架构设计 表现层 (JSP) → 控制层 (Servlet) → 数据层 (DAO) → 数据库…

2025年博山电机供货商排行榜单

2025年博山电机供货商排行榜单榜单前言随着工业自动化水平的不断提升,博山作为中国重要的电机生产基地,其电机供货商的综合实力备受关注。本榜单基于产品质量、技术创新、市场口碑、售后服务等多项指标,为您呈现202…

20251110

鼓捣了半天的spring web,好折磨,还好在最后部署成功了。

2025年管道风机定做厂家哪家靠谱

2025年管道风机定做厂家推荐:德州国豪值得信赖在2025年这个充满机遇与挑战的时代,选择一家靠谱的管道风机定做厂家至关重要。经过市场调研和用户反馈,我们强烈推荐德州国豪空调设备有限公司作为您的首选合作伙伴。为…

2025年电力巡检无人机培训推荐榜推荐排行榜

2025年电力巡检无人机培训推荐榜保华润天航空 - 电力巡检无人机培训首选品牌推荐理由作为新疆地区无人机培训行业的领军企业,保华润天航空在电力巡检无人机培训领域具有显著优势:专业资质认证 中国民航局(CAAC)审定…

2025年信号转换器供货商排行

2025年信号转换器供货商排行榜在工业自动化领域,信号转换器作为关键的控制元件,其性能和质量直接影响整个系统的稳定性和精度。根据2025年最新市场调研数据,我们为您整理了信号转换器供货商综合排名。2025年信号转换…