3. C++ 继承与派生详解

C++ 继承与派生详解

继承和派生的概念

派生:通过特殊化已有的类来建立新类的过程,叫做“类的派生”, 原有的类叫做”基类”,新建立的类叫做“派生类”。

继承:类的继承是指派生类继承基类的数据成员和成员函数。继承用来表示类属关系,不能将继承理解为构成关系。

继承派生的作用

  • 增加新的成员(数据成员和成员函数)

  • 重新定义已有的成员函数

  • 改变基类成员的访问权限

单一继承

代码格式:

class 派生类名: 访问控制 基类名 {         
private: 成员声明列表         
protected: 成员声明列表         
public: 成员声明列表 
};

“冒号”表示新类是哪个基类的派生类;“访问控制”指继承方式。

三个方式:public、protected、private

派生类的构造函数和析构函数

// 基类
class Point {    int x;    int y;        
public:    Point(int a, int b) {        x = a;        y = b;        cout << "init Point" << endl;    }    void showPoint() {        cout << "x = " << x << ", y = " << y << endl;    }    ~Point() {        cout << "delete Point" << endl;    }
};// 派生类
class Rect: public Point {    int w;    int h;        
public:    // 调用基类的构造函数对基类成员进行初始化    Rect(int a, int b, int c, int d):Point(a, b) {        w = c;        h = d;        cout << "init Rect" << endl;    }    void showRect() {        cout << "w = " << w << ", h = " << h << endl;    }    ~Rect() {        cout << "delete Rect" << endl;    }
};int main() {   Rect r(3, 4, 5, 6);    r.showPoint();    r.showRect();        /*输出结果     init Point   // 当定义一个派生类的对象时, 首先调用基类的构造函数, 完成对基类成员的初始化     init Rect    // 然后执行派生类的构造函数, 完成对派生类成员的初始化     x = 3, y = 4 // 调用基类成员函数showPoint();     w = 5, h = 6 // 调用派生类成员函数showRect();     delete Rect  // 构造函数的执行顺序和构造函数的执行顺序相反, 首先调用派生类的析构函数     delete Point // 其次调用基类的析构函数     */
}

类的保护成员

如果希望Rect中的showRect()函数可以一次显示x、y、w、h。我们直接修改showRect()函数是不行的。

void showRect() {  cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
}

报错 error: ‘x’ is a private member of ‘Point’ ‘y’ is a private member of ‘Point’

x, y为Point类的私有成员,公有派生时,在Rect类中是不可访问的。

我们还需要将基类Point中的两个成员声明为protected的属性。像这样:

// 基类
class Point {    
// 公有数据成员    
protected:    int x;    int y;        
public:    Point(int a, int b) {        x = a;        y = b;        cout << "init Point" << endl;    }    void showPoint() {        cout << "x = " << x << ", y = " << y << endl;    }
};// 派生类
class Rect: public Point {    int w;    int h;        
public:    // 调用基类的构造函数对基类成员进行初始化    Rect(int a, int b, int c, int d):Point(a, b) {        w = c;        h = d;        cout << "init Rect" << endl;    }    /** 公有派生, Point类中的受保护数据成员, 在Rect类中也是受保护的, 所以可以访问  // 而通过公有继承的基类私有的成员, 在派生类中是不可被访问的    void showRect() {        cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;    }*/
};int main() {    Rect r(3, 4, 5, 6);    r.showPoint();    r.showRect();
}

访问权限和赋值兼容规则

在根类中,对于成员的访问级别有三种:public、protected、private

在派生类中,对于成员的访问级别有四种:public(公有)、protected(受保护)、private(私有)、inaccessible(不可访问)

(1)公有派生和赋值兼容规则

公有派生

基类成员的访问权限在派生类中基本保持不变。

  • 基类的公有成员在派生类中仍然是公有的

  • 基类的保护成员在派生类中仍然是受保护的

  • 基类的不可访问的成员在派生类中仍然是不可访问的;

  • 基类的私有成员在派生类中变成了不可访问的;

***总结:***在公有派生的情况下,通过派生类自己的成员函数可以访问继承过来的公有和保护成员, 但是不能访问继承来的私有成员, 因为继承过程的私有成员,变成了第四个级别,不可访问的。

赋值兼容规则:

在公有派生的情况下, 一个派生类的对象可以作为基类的对象来使用的情况。

(2)“is-a”和”has-a“的区别

继承和派生 is-a

比如一个Person类,派生出一个Student类,我们可以说Student就是Person,也就是 Student is-a Person,而反过来则不行。

一个类用另一个类的对象作为自己的数据成员或者成员函数的参数 has-a。像这样:

// 地址类
class Address {};
class PhoneNumber {};
// 职工类
class Worker {    String name;    Address address;    PhoneNumber voiceNumber;
};

表示一个Worker对象有一个名字,一个地址,一个电话号码,has-a的关系,包含的关系。

(3)私有派生

通过私有派生,基类的私有和不可访问成员在派生类中是不可访问的,而公有和保护成员这里就成了派生类的私有成员

因为私有派生不利于进一步派生, 因而实际中私有派生用得并不多。

(4)保护派生保护派生使原来的权限都降一级使用

即private变为不可访问,protected变为private,public变为protected

限制了数据成员和成员函数的访问权限,因此在实际中保护派生用得也不多。

比如:我们在上个例子中,Rect类保护派生于Point,则在Test类中Point::show();就可以使用啦!

多重继承

一个类从多个基类派生

代码格式:

class 派生类名: 访问控制 基类名1, 访问控制 基类名2, … {   // 定义派生类自己的成员
}

像这样:

// 基类A, 也叫根类
class A {    int a;        
public:    void setA(int x) {        a = x;    }        void showA() {        cout << "a = " << a << endl;    }
};// 基类B, 也叫根类
class B {    int b;        
public:    void setB(int x) {       b = x;   }  void showB() {        cout << "b = " << b << endl;    }
};// 多重继承, 公有继承自类A, 私有继承自类B
class C: public A, private B {    int c;        
public:    void setC(int x, int y) {        c = x;        setB(y);    }        void showC() {        showB();        cout << "c = " << c << endl;    }
};int main() {    C c;    c.setA(53);     // 调用基类setA()函数    c.showA();      // 调用基类showA()函数        c.setC(55, 58); // 调用派生类C的setC()函数    c.showC();      // 调用派生类C的showC()函数        // 派生类C私有继承自基类B, 所以基类B中私有成员b, 在派生类C中不可访问, // 基类B中公有成员setB(), showB()在派生类C中变私有. 在main()函数中不可访问    }

二义性及其支配规则

对基类成员的访问必须是无二义性的,如果一个表达式的含义可以解释为可以访问多个基类中的成员,则这种对基类成员的访问就是不确定的,称这种访问具有二义性。

作用域分辨符和成员名限定

代码格式:

类名::标识符

:: 为作用域分辨符,"类名"可以是任一基类或派生类名,“标识符”是该类中声明的任一成员名,像这样:

// 基类A, 也叫根类
class A {   
public:    void func() {        cout << "A func" << endl;    }
};// 基类B, 也叫根类
class B {    
public:    void func() {        cout << "B func" << endl;    }        void gunc() {        cout << "B gunc" << endl;    }};// 多重继承
class C: public A, public B {    
public:    void gunc() {        cout << "C gunc" << endl;    }        void hunc() {        /**         这里就具有二义性, 它即可以访问A类中的func(), 也可以访问类B中的func()         */        //func(); // error: Member 'func' found in multiple base classes of different types    }        void hunc1() {        A::func();    }        void hunc2() {        B::func();    }
};int main() {    C c;    //c.func(); //具有二义性   c.A::func();    c.B::func();    c.B::gunc();    c.C::gunc();       c.gunc();   c.hunc1();    c.hunc2();        /** 输出结果     A func     B func     B gunc     C gunc          C gunc // 如果基类中的名字在派生类中再次声明, 则基类中的名字就被隐藏. 如果我们想要访问被隐藏的基类中的成员则使用作用域分辨符B::gunc();     A func     B func     */
}

派生类支配基类的同名函数

如果派生类定义了一个同基类成员函数同名的新成员函数(具有相同参数表的成员函数),派生类的新成员函数就覆盖了基类的同名成员函数。

在这里,直接使用成员名只能访问派生类中的成员函数,使用作用域运算符,才能访问基类的同名成员函数。

派生类中的成员函数名支配基类中的同名的成员函数名,这称为名字支配规则。

如果一个名字支配另一个名字,则二者之间不存在二义性,当选择该名字时,使用支配者的名字。

例如上个例子中

c.gunc() // 输出”C gunc”, 基类B中的gunc成员函数被支配了
c.B::gunc(); // 加上作用域分辨符, 来使用被支配的成员

总结

C++中的多重继承可能更灵活, 并且支持三种派生方式。

继承和组合
  • 组合为“has-a”关系,即整体和部分的关系。比如类X作为类Y的成员,则X是部分,Y是整体。
    • 不破坏封装、整体类与局部类松耦合、彼此相对独立。具有较好的扩展性。
    • 创建整体类时,需要创建所有局部类的对象。
  • 继承为“is-a”关系,即特殊和一般的关系。比如类X被类Y继承,则X是一般,Y是特殊。
    • 破坏封装、子类与父类紧密耦合、子类依赖父类的实现、缺乏独立性。
    • 子类可以自动继承父类的接口。

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

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

相关文章

直播预告|小白开箱: 云数据库在五朵云上的评测

3 月 7 日&#xff0c;周四晚上 19:00-20:30 由明说三人行组织&#xff0c;邀请了 NineData 国际总经理(GM) Ni Demai、云猿生数据 CTO &#xff06; 联合创始人子嘉&#xff0c;和《明说三人行》创始人 &主持人明叔&#xff0c;共同围绕《小白开箱: 云数据库在五朵云上的评…

官网正在被哪些产品蚕食,定制网站又被哪些建站产品挤占。

2023-12-09 16:22贝格前端工场 官网建设是一个被大多数人看衰的市场&#xff0c;本文来理性分析下&#xff0c;谁在蚕食这个市场&#xff0c;谁又在挤占这个产品生存空间&#xff0c;欢迎大家评论&#xff0c;探讨。 网站正在被以下产品形式取代&#xff1a; 1. 移动应用&…

揭秘Web缓存:提升网站性能与用户体验

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

金融数据采集与风险管理:Open-Spider工具的应用与实践

一、项目介绍 在当今快速发展的金融行业中&#xff0c;新的金融产品和服务层出不穷&#xff0c;为银行业务带来了巨大的机遇和挑战。为了帮助银行员工更好地应对这些挑战&#xff0c;我们曾成功实施了一个创新的项目&#xff0c;该项目采用了先进的爬虫技术&#xff0c;通过ope…

七彩虹@电脑cpu频率上不去问题@控制中心性能模式cpu频率上不去@代理服务器超时@账户同步设置失败

文章目录 windows电脑cpu频率上不去新电脑的系统时间问题系统时间不准造成的具体问题举例代理超时vscode同步请求失败自动校准时间 windows电脑cpu频率上不去 问题描述,标压处理器的笔记本,cpu频率上不去 如果cpu没问题的话,就应该是系统限制了功耗导致的有的笔记本有控制中心…

动手学深度学习PyTorch版

基本的数据操作 import torch # 创建一个行向量&#xff0c;默认为从0开始的12个整数 # n维数组也称为张量 x torch.arange(12) x # 张量的形状 x.shape# 张量的大小,张量所有元素的个数 x.numel()#修改张量的形状 x x.reshape(3,4)#生成形状为3*4的两个向量&#xff0c;向…

深度学习与人类的智能交互:迈向自然与高效的人机新纪元

引言 随着科技的飞速发展&#xff0c;深度学习作为人工智能领域的一颗璀璨明珠&#xff0c;正日益展现出其在模拟人类认知和感知过程中的强大能力。本文旨在探讨深度学习如何日益逼近人类智能的边界&#xff0c;并通过模拟人类的感知系统&#xff0c;使机器能更深入地理解和解…

20240308-1-校招前端面试常见问题CSS

校招前端面试常见问题【3】——CSS 1、盒模型 Q&#xff1a;请简述一下 CSS 盒模型&#xff1f; W3C 模式&#xff1a;盒子宽widthpaddingbordermargin 怪异模式&#xff1a;盒子宽widthmargin Q&#xff1a;inline、block、inline-block 元素的区别&#xff1f; inline&am…

设计模式大题做题记录

设计模式大题 09年 上半年&#xff1a; 09年下半年 10年上半年 10年下半年 11年上半年 11年下半年 12年上半年 12年下半年 13年上半年 13年下半年

程序设计天梯赛:千手观音

题目大意&#xff1a;给n个字符串&#xff0c;字符串表达的是一种特殊的数字&#xff0c;例如说as.asf.wad&#xff0c;就是三位数&#xff0c;从左到右分别为高位到地位。n个字符串按照递增序列给出&#xff0c;递增的比较规则和数字相同。要求求出来这些特殊数字的相对大小&a…

Springboot教程(六)——异常处理

拦截异常 在Spring Boot中&#xff0c;我们可以将异常统一放在全局处理类来处理&#xff0c;创建一个全局异常处理类需要用到ControllerAdvice和ExceptionHandler注解。 ControllerAdvice类似一个增强版的Controller&#xff0c;用于标注类&#xff0c;表示该类声明了整个项目…

嵌入式学习-FreeRTOS-Day3

嵌入式学习-FreeRTOS-Day3 一、思维导图 二、 1.FreeRTOS任务的调度算法及实现 默认是抢占式调度时间片轮询 1.抢占式调度&#xff1a;任务优先级高的可以打断任务优先级低的执行&#xff08;适用于不同优先级&#xff09; 2.时间片轮转&#xff1a;每一个任务拥有相同的时…

使用 Cypress 进行可视化回归测试:一种务实的方法

每次组件库 Picasso 发布新版本时&#xff0c;都会更新所有的前端应用程序&#xff0c;让绝大部分新功能能与整个平台的设计保持一致。上个月&#xff0c;推出了 Toptal Talent Portal 的 Picasso 更新&#xff0c;这是我们的用户用来找工作和与客户互动的平台。 已知了这个版本…

软考70-上午题-【面向对象技术2-UML】-UML中的图1

一、图的定义 图是一组元素的图形表示&#xff0c;大多数情况下把图画成顶点、弧的联通图。 顶点&#xff1a;代表事物&#xff1b; 弧&#xff1a;代表关系。 可以从不同的角度画图&#xff0c;UML提供了13种图&#xff1a;&#xff08;只看9种&#xff09; 类图&#xff…

数据结构实现

目录 一、线性表中顺序表的实现&#xff1a; 二、线性表的链式存储&#xff08;链表-带头节点&#xff09; 三、习题练习&#xff1a; 四、栈&#xff08;stack&#xff09; 五、循环队列 1.数组形式&#xff1a; 2.链表形式&#xff1a; 3.习题练习 六、二叉树 1.…

226. 翻转二叉树

代码实现&#xff1a; 方法1&#xff1a;先序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/// 交换左右子树 void swap(struct TreeNode *root) {struct TreeNode *l root…

代码随想录算法训练营第day11|20. 有效的括号、 1047. 删除字符串中的所有相邻重复项、 150. 逆波兰表达式求值

目录 a.20. 有效的括号 b.1047. 删除字符串中的所有相邻重复项 c.150. 逆波兰表达式求值 a.20. 有效的括号 题目链接 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足…

Linux grep

文章目录 1. 基本用法2.字符转义3.二进制文件查找4.打印目标字段的附近行4. 多条件过滤5. 目录中过滤——用于在文件夹中筛选/排除指定后缀文件6.反向过滤——用于筛选7.只输出匹配内容——用于统计8. 筛选出包含字段的文件9.正则匹配10.管道和grep11.grep和wc/uniq/sort的合用…

B端系统优化,可不是换个颜色和图标,看看与大厂系统的差距。

Hi&#xff0c;我是贝格前端工场&#xff0c;优化升级各类管理系统的界面和体验&#xff0c;是我们核心业务之一&#xff0c;欢迎老铁们评论点赞互动&#xff0c;有需求可以私信我们 一、不要被流于表面的需求描述迷惑。 很多人找我们优化系统界面&#xff0c;对需求总是轻描淡…

小白跟做江科大51单片机之AD/DA

1.看原理图找接口 2.看时序图编写读取数据代码 XPT2046.c代码 #include <REGX52.H> //引脚定义 sbit XPY2046_DINP3^4; sbit XPY2046_CSP3^5; sbit XPY2046_DCLKP3^6; sbit XPY2046_DOUTP3^7; unsigned int XPT2046_ReadAD(unsigned char Command) { unsigned char …