| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> 【C++】继承 -> 正文阅读 |
|
[C++知识库]【C++】继承 |
文章目录一、什么是继承1.概念继承(inheritance)机制是面向对象程序设计中使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生的新类,称派生类(或子类),被继承的类称基类(或父类)。 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。之前接触的复用都是函数复用,继承是类设计层次的复用。 下面先看一段代码来理解继承的作用,这一部分主要是看看继承到底有什么用,具体细节都会在后面讲到。 代码如下:
通过调试可以看到,s和t对象中都含有Person的成员变量,这是从Person处继承来的。 调试结果如下: 继承继承的是成员,也就是说包括成员变量和成员函数,上图通过调试说明继承到了成员变量,下图通过运行程序说明继承到了成员函数。 运行结果如下: 2.定义(1)格式继承的定义方式如下,可参考前文代码。 (2)继承关系和访问限定符继承关系和访问限定符均有三种,分别是public、protected、private,继承方式是在继承时指明的,访问限定符就是类内成员的访问方式。 (3)继承后基类成员的访问权限各种继承方式和类成员访问权限组合后情况如下: 总结:
二、基类和派生类赋值转换1.对象代码如下:
子类赋值给父类可以正常编译,父类赋值给子类编译报错。 编译结果如下: 这里需要提到一个“切片”的规则,如下图所示: 由于子类的成员数量一定大于等于父类的成员数量,所以当一个子类赋值给父类时,子类就可以将属于与父类的成员赋值给父类,多余的成员由于父类中不含有,所以不用管。也就是将父类的成员切片出来赋值给父类。 如上图,父类Person对象中有_name和_age两个成员,子类Student对象赋值时将自己含有的这两个成员切片赋值给父类对象。 2.指针和引用指针和引用实际仍遵循上述规则,实际上与对象和对象间的赋值差别不大,具体可见下图。 三、继承中的作用域基类和派生类都是独立的作用域,在不同作用域内可以定义同名的变量、函数而不会发生冲突,所以在子类访问这些同名的内容时就需要注意。 1.同名成员变量如果子类和父类中有同名成员,子类成员将屏蔽对父类同名成员的直接访问,这种情况叫隐藏(但在子类成员函数中,可以使用父类::父类成员来显式地进行访问)。 下面代码中,在父类和子类中定义同名的_name成员变量,观察在子类中访问时访问的是哪个。 代码如下:
如下图所示,在子类中直接访问时访问的是子类中的_name,如果想要访问父类的_name就需要再前面加上域作用限定符,指定访问Person类内的_name。 运行结果如下: 下面仅在Print函数添加一行通过域作用限定符访问父类_name的代码,可以看到确实访问到了父类的成员变量。 运行结果如下: 2.同名成员函数要注意的是,在父类和子类内同名的成员函数并不构成函数重载,因为函数重载的前提是两个函数在同一作用域。 成员函数的隐藏,只需要函数名相同就构成隐藏,对参数列表没有要求。 代码如下:
运行结果如下: 四、派生类的默认成员函数如果对默认成员函数有问题,可前往【C++】类和对象2(this指针、默认成员函数、构造函数)和【C++】类和对象3(析构、拷贝构造、赋值运算符重载、const成员函数) 为逻辑清晰,下面每一部分只给出对应部分的成员函数代码,省略其它成员函数。 1.构造函数派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函 下面的代码中Person有默认构造函数,则在子类的构造函数中,会先调用父类的默认构造函数完成父类成员的初始化,然后在调用子类的构造函数初始化子类的成员。 代码如下:
下面的代码中,父类没有默认构造函数(简单说就是不传参无法初始化),则需要在子类的构造函数中显式调用父类构造函数完成父类成员的初始化。 代码如下:
2.拷贝构造拷贝构造的逻辑和构造函数基本相同,需要注意的就是在子类中调用父类的拷贝构造时,直接传入子类对象即可,父类的拷贝构造会通过“切片”拿到父类的那一部分。 代码如下:
3.赋值运算符重载子类的operator=必须要显式调用父类的operator=完成父类的赋值。 代码如下:
4.析构函数析构函数在这里很奇怪,希望读者能耐心地跟着代码和讲解看下去。 首先,根据前面的逻辑,子类的析构函数只需要先调用父类的析构函数,然后在做子类析构函数该做的事即可,这样不难写出如下代码。 代码如下:
然后会发现上面的代码编译不通过,如下图: 编译如下: 于是修改代码如下: 代码如下:
从打印结果看到,父类的析构函数被调用了两次,这又是为什么呢? 运行如下: 由于栈的特性,构造函数调用时先调父类再调子类,所以析构时先析构子类,再析构父类,编译器为了能保证这个特性,默认在子类析构完成后调用父类的析构函数,所以不需要在子类的析构函数中显式地调用父类的析构函数。 代码如下:
运行如下: 总结:派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。 五、友元和静态成员友元关系无法继承,如果需要友元关系需要在父类和子类中都声明。 父类的静态成员在整个类体系中都只有一个,无论下面有多少层继承关系。 这两点了解一下即可。 六、菱形继承1.继承类型单继承:一个子类只有一个直接父类的继承关系。 多继承:一个子类有两个或以上直接父类的继承关系。 菱形继承:单继承和多继承组合后的一种特殊情况。 2.菱形继承菱形继承会有数据二义性的问题,以下面的代码为例进行说明,该代码的继承关系同上。
这里代码编写好后,如下图编译器会提示name不明确,因为它可能是Student类继承的name,也可能是Teacher类继承的name,而实际上只需要一个name即足够记录,所以有代码冗余和二义性的问题。 通过调试也可以看到数据的冗余。 调试如下: 要想没有歧义,只能如下编写,但下面的代码显然很冗余,这也是菱形继承的问题所在。 代码如下:
3.虚继承这一问题需要通过虚继承来解决,在继承方式前加上virtual。 代码如下:
这样一来,main函数通过三种方式修改name,最后的结果都是同一个name被修改,这样就不会出现数据冗余和二义性的问题。 运行如下: 4.关于多继承多继承是C++复杂的一个体现。有了多继承,就存在菱形继承,为了解决菱形继承,又出现了菱形虚拟继承,其底层实现又很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在 感谢阅读,如有错误请批评指正 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 21:20:07- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |