【讨论C++继承】

讨论C++继承

  • 继承
    • 定义
    • 继承方式和访问限定符
  • 基类和派生类的赋值转换
  • 继承中的作用域
  • 派生类的默认成员函数
  • 继承和友元
  • 继承和静态成员
  • 菱形继承
    • 虚拟继承

继承是面向对象程序设计中,使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承

定义

class Person {
public:void print() {cout << "这是一个人类" << endl;}
protected:string name;int age;
};class Student : public Person{
private:int stuId;
};int main() {Student s;Person p;s.print();p.print();return 0;
}

继承打印结果

上述代码,Person是基类,Student是派生类。

使用:来实现继承。

继承定义

继承方式和访问限定符

继承方式

访问限定符

基类/继承方式public继承protected继承private继承
public成员派生类的public成员派生类的protected成员派生类的private成员
protected成员派生类的protected成员派生类的protected成员派生类的private成员
private成员在派生类中不可见在派生类中不可见在派生类中不可见
  1. 基类使用private修饰的成员在派生类中无论以什么方式继承,都不可见。不可见是指还是会继承,只是不能直接使用,因为private修饰的成员在类外不可使用。
  2. protected修饰的成员不可在类外使用,但是存在继承关系的,派生类可以直接访问基类的成员。
  3. 使用关键字class时,默认的继承方式是private,使用struct时,默认的继承方式是public

基类和派生类的赋值转换

派生类对象可以直接赋值给基类的对象、指针、引用。

基类的对象不能赋值给派生类对象。

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用,但必须是基类的指针指向派生类的对象时才安全。

赋值转换

class Person {
public:void print() {cout << "这是一个人类" << endl;}
protected:string name;int age;
};class Student : public Person{
private:int stuId;
};int main() {Student s;Person* ps = &s;Person& rps = s;Person p = s;return 0;
}
  • 派生类对象赋值给基类对象,基类对象只能访问基类拥有的成员变量,不能访问派生类特有的成员变量。
  • 派生类赋值给基类对象时,不产生临时变量。

继承中的作用域

  1. 在继承体系中,基类和派生类的作用域是独立的。
  2. 当基类和派生类中有同名成员时,派生类成员将屏蔽基类同名成员的直接访问,称为隐藏或重定义。
  3. 成员函数只需要函数名相同即可达成隐藏。
class Person {
protected:string name = "张三";int age = 10;
};class Student : public Person{
public:void print() {cout << "姓名:" << name << endl;cout << "年龄:" << age << endl;cout << "学号:" << stuId << endl;}
private:int stuId;int age;
};int main() {Student s;s.print();return 0;
}

作用域结果

  • 上述代码,Student类中的agePerson类中的age构成隐藏。Student对象使用自己类域中的age成员变量,因此是随机值。
  • 要想使用Person类中的age,需要Person::age
class Student : public Person{
public:void print() {cout << "姓名:" << name << endl;cout << "年龄:" << Person::age << endl;cout << "学号:" << stuId << endl;}
private:int stuId;int age;
};

作用域结果2

派生类的默认成员函数

  1. 派生类构造时必须调用基类的构造函数初始化基类的部分成员,如果基类没有默认构造函数,必须在派生类构造函数初始化列表中显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须调用基类的operator=完成基类的赋值。
  4. 派生类的析构函数会在调用完成后自动调用基类的析构函数清理基类成员。
  5. 派生类对象初始化先调用基类构造函数再调用派生类构造函数。
  6. 派生类对象析构时先调用派生类析构函数再调用基类析构函数。
class Person {
public:Person(const char *name = "张三"): _name(name) {cout << "Person(name)" << 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;
};class Student : public Person {
public:Student(const char *name, int stuId = 110): Person(name), _stdId(stuId) {cout << "Student(name, stuId)" << endl;}Student(const Student &s): Person(s), _stdId(s._stdId) {cout << "Student(const Student &s)" << endl;}Student& operator=(const Student& s) {cout << "Person& operator=(const Person& p)" << endl;if(this != &s) {Person::operator=(s);_stdId = s._stdId;}return *this;}~Student() {cout << "~Student()" << endl;}private:int _stdId;
};int main() {Student s1("李四", 111);cout << "====================" << endl;Student s2(s1);cout << "====================" << endl;Student s3("王五", 112);s2 = s3;cout << "====================" << endl;return 0;
}
  • 根据规则,派生类构造前应完成基类构造,因此在创建派生类对象,一定会初始化基类,析构时先析构派生类,再析构基类。

验证结果

继承和友元

友元关系不能被继承。

class Student;
class Person {
public:friend void print(const Person& p, const Student& s);
public:Person(const char *name = "张三"): _name(name) {cout << "Person(name)" << 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;
};class Student : public Person {
public:Student(const char *name, int stuId = 110): Person(name), _stdId(stuId) {cout << "Student(name, stuId)" << endl;}Student(const Student &s): Person(s), _stdId(s._stdId) {cout << "Student(const Student &s)" << endl;}Student& operator=(const Student& s) {cout << "Person& operator=(const Person& p)" << endl;if(this != &s) {Person::operator=(s);_stdId = s._stdId;}return *this;}~Student() {cout << "~Student()" << endl;}protected:int _stdId;
};void print(const Person& p, const Student& s) {cout << p._name << endl;cout << s._name << s._stdId << endl;
}int main() {Student s("李四", 111);print(s, s);return 0;
}
  • 上述代码会报错,error: '_stdId' is a protected member of 'Student'。证明友元函数没有被继承,因为Student类使用privateprotected修饰的成员变量不能在类外使用。

继承和静态成员

基类定义的static静态成员,整个继承体系中只有一个这样的成员。

class Person {
public:Person() {++n;}
public:static int n;
};
int Person::n = 0;
class Student : public Person {
};int main() {Student s;cout << Person::n << endl;s.n = 10;cout << Person::n << endl;return 0;
}

验证结果

菱形继承

  • 单继承:一个派生类只有一个直接基类。Java只支持单继承。

单继承

  • 多继承:一个派生类继承于多个基类。C++支持多继承。

多继承

  • 菱形继承:它是多继承的一个特殊情况。

菱形继承

菱形继承所带来的问题是,最初继承的类内成员会存在两份,即数据冗余和二义性问题。

class A {
public:int _a;
};class B : public A{
public:int _b;
};class C : public A {
public:int _c;
};class D : public B, public C {
public:int _d;
};int main() {D d;return 0;
}

调试结果

  • 想要直接通过d对象去修改_a是不被允许的,因为编译器不知道想要修改的是B类和C类中哪个类的_a
  • 只能通过d.B::a = 10这种指定类域的方式来修改或赋值。这种方式可以解决二义性问题,但无法解决数据冗余问题。

虚拟继承

  • 使用virtual关键字建立虚拟继承,可以解决数据冗余和二义性问题。
  • 当不使用虚拟继承时,内存空间是这样的。
class A {
public:int _a;
};class B : public A{
public:int _b;
};class C : public A {
public:int _c;
};class D : public B, public C {
public:int _d;
};int main() {D d;d.B::_a = 1;d.C::_a = 9;d._b = 2;d._c = 3;d._d = 4;return 0;
}

内存图

  • 使用虚拟继承,_a就只存在一份,可以直接赋值。
class A {
public:int _a;
};class B : virtual public A{
public:int _b;
};class C : virtual public A {
public:int _c;
};class D : public B, public C {
public:int _d;
};int main() {D d;d.B::_a = 1;d.C::_a = 9;d._b = 2;d._c = 3;d._d = 4;return 0;
}

内存结果

偏移

  • clang编译器通过BC的两个指针,指向一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存在偏移量。通过偏移量可以找到A

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

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

相关文章

【ONLYOFFICE】| 桌面编辑器从0-1使用初体验

目录 一. &#x1f981; 写在前面二. &#x1f981; 在线使用感受2.1 创建 ONLYOFFICE 账号2.2 编辑pdf文档2.3 pdf直接创建表格 三. &#x1f981; 写在最后 一. &#x1f981; 写在前面 所谓桌面编辑器就是一种用于编辑文本、图像、视频等多种自媒体的软件工具&#xff0c;具…

算法训练营day24--93.复原IP地址 +78.子集 +90.子集II

一、93.复原IP地址 题目链接&#xff1a;https://leetcode.cn/problems/restore-ip-addresses/ 文章讲解&#xff1a;https://programmercarl.com/0093.%E5%A4%8D%E5%8E%9FIP%E5%9C%B0%E5%9D%80.html 视频讲解&#xff1a;https://www.bilibili.com/video/BV1fA4y1o715 1.1 初…

d3dcompiler_47.dll缺失怎么修复?d3dcompiler_47.dll修复使用说明

d3dcompiler_47.dll是一个重要的系统文件&#xff0c;属于MicrosoftWindows操作系统中Direct3D的一部分&#xff0c;它主要负责处理在Windows上运行的应用程序和游戏中的3D图形编程。这个DLL文件是“DirectX”的一项组成部分&#xff0c;DirectX是一套核心技术&#xff0c;用于…

13-Django项目--文件上传

目录 前端展示 路由: 数据库字段: 函数视图: 前端展示 {% extends "index/index.html" %}{% block content %}<div class"container"><input type"button" id"btnAdd" value"上传荣耀" class"btn btn-succ…

Oracle 集群的守护进程

ohas&#xff1a;主要用于守护cluster ware进程&#xff0c;在单节点建立集群的时候&#xff0c;没有crs&#xff0c;只有ohas、cluster ware GPnP&#xff1a;管理clusterware的配置信息&#xff0c;放在本地磁盘上 crs&#xff1a;管理clusterware中的资源&#xff0c;数据库…

成功解决ES高亮内容引起的字段显示不一致问题

在处理搜索引擎&#xff08;如Elasticsearch&#xff09;结果时&#xff0c;常见需求之一是对用户搜索的关键词进行高亮显示&#xff0c;这有助于用户快速识别搜索结果为何与其查询相关。但在实际应用中&#xff0c;如果处理不当&#xff0c;直接使用高亮片段可能会导致原始数据…

A股站不稳3000点让人稀罕不已啊

今天的A股&#xff0c;让人稀罕不已&#xff0c;你知道是为什么吗&#xff1f;盘面出现2个重要信号&#xff0c;一起来看看&#xff1a; 1、今天两市冲了下3000点&#xff0c;第一个主题炒作的热点终于出现了&#xff0c;税改方向的行情发酵&#xff0c;并带动着其他改革相关方…

echarts的折线图实现部分虚线部分实线

场景&#xff1a; 折线图一般都是实线为准&#xff0c;但是由于最后一个数据是预测。所以想要实现最后一段为虚线。 效果图&#xff1a; 具体实现&#xff1a; series:[{name: "销售总金额",type: "line",smooth: true,barWidth: 10,stack: Total,itemSty…

Ubuntu下反弹shell的思考

目录 Ubuntu的命令执行环境 bash (Bourne Again SHell): sh (Bourne SHell): dash (Debian Almquist SHell): 它们之间的关系&#xff1a; 可能遇到的问题 一、脚本权限问题 二、命令执行环境(shell解释器)问题 如何解决&#xff1f; 1.修改/bin/sh软连接的指向为bas…

ESP32CAM物联网教学01

ESP32CAM物联网教学01 拍照 视频 这么小的一个开发板都带上摄像头了&#xff0c;能拍照&#xff1f;能视频吗&#xff1f;现在就跟着我做起来。 初识ESP32CAM 我们到淘宝搜索“ESP32Cam”&#xff0c;就能买到这样一块开发板。 ESP32Cam是双核处理器&#xff0c;提供WIFI和…

Cyuyanzhong的内存函数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、memcpy函数的使用与模拟实现二、memmove函数的使用和模拟实现三、memset函数与memcmp函数的使用&#xff08;一&#xff09;、memset函数&#xff08;内存块…

Linux shell编程学习笔记59: ps 获取系统进程信息,类似于Windows系统中的tasklist 命令

0 前言 系统进程信息是电脑网络信息安全检查中的一块重要内容&#xff0c;对于使用Linux和基于Linux作为操作系统的电脑来说&#xff0c;可以使用ps命令。 1 ps命令 的功能、格式和选项说明 1.1 ps命令 的功能 Linux 中的ps&#xff08;意为&#xff1a;process status&…

Chrome导出cookie的实战教程

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

leetcode刷题:vector刷题

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;leetcode刷题 1.只出现一次的数字 这道题很简单&#xff0c;我们只需要遍历一次数组即可通过异或运算实现。(一个数与自身异或结果为0&#xff0c;任何数与0异或还是它本身) class Solut…

WPF UI 3D 基本概念 点线三角面 相机对象 材质对象与贴图 3D地球 光源 变形处理 动作交互 辅助交互插件 系列三

WPF UI交互专题 平面图形 Path Drawing 绘图 渐变 Brush 矩阵 Transform 变形 阴影效果 模糊效果 自定义灰度去色效果 系列二-CSDN博客 1软件中的3D基本概念 WPF 中 3D 功能的设计初衷并非提供功能齐全的游戏开发平台。 WPF 中的 3D 图形内容封装在 Viewport3D 元素中&#x…

WPF自定义模板--Button

属性&#xff1a; TemplateBinding&#xff1a;用于在ControlTemplate中绑定到控件的属性&#xff0c;例如Background、BorderBrush等。TargetType&#xff1a;指定该模板应用于哪种控件类型。在这个例子中&#xff0c;是Button。 标准的控件模板代码&#xff1a; <Style…

借助 Aspose.Words,在 C# 中将 Word 转换为 Excel

有时我们会遇到需要将 Word 文档&#xff08;DOC 或 DOCX&#xff09;转换为 Excel 文档的任务。例如&#xff0c;这对于数据分析和报告很有用&#xff0c;或者如果您收到了任何文本数据并想将其转换为表格格式&#xff08;XLS 或 XLSX&#xff09;以便进一步工作。在本文中&am…

IAR工程目录移动报错(改变文件目录结构)

刚开始用IAR&#xff0c;记录一下。 工作中使用华大单片机&#xff0c;例程的文件目录结构太复杂了想精简一点。 1.如果原本的C文件相对工程文件&#xff08;.eww文件&#xff09;路径变化了&#xff0c;需要先打开工程&#xff0c;再将所有的.c文件右键Add添加进工程&#xf…

Day50

Spring AOP 概念 AOP全称为Aspect Oriented Programming&#xff0c;表示面向切面编程。切面指的是将那些与业务无关&#xff0c;但业务模块都需要使用的功能封装起来的技术。 AOP基本术语 **连接点&#xff08;Joinpoint&#xff09;&#xff1a;**连接点就是被拦截到的程序执…

第三篇——始计篇:诡计,就是引诱对方犯错误

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 这一篇讲解了诡计&#xff0c;对于诡计的解释和定位&#xff1b;我们更应…