C++核心编程之类和对象---C++面向对象的三大特性--继承

目录

一、继承

1. 继承的概念

2. 继承的定义

3. 类与类之间的关系

4. 继承的两类关系

二、继承方式的基本语法

总而言之,父类的私有内容,子类是访问不到的。

三、继承中的对象模型

父类中的私有属性被编译器隐藏,访问不到,但被继承下去了

用开发人员命令提示符查看对象模型

四、继承中的构造和析构的顺序

五、继承中同名成员的处理

六、继承同名静态成员 的处理方式

七、多继承语法

八、菱形继承


一、继承

1. 继承的概念

利用已有的数据类型来定义新的数据类型通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。

继承是面向对象三大特性之一。

2. 继承的定义

在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。

3. 类与类之间的关系

我们发现,定义这些类的时候,下级别的成员除了拥有上一级的共性,还有自己的特性。

这个时候我们就可以考虑利用继承得到技术,减少重复代码。

4. 继承的两类关系

具体化

类的层次通常反映了客观世界中某种真实的模型。在这种情况下,不难看出:基类是对若干个派生类的抽象,而派生类是基类的具体化。基类抽取了它的派生类的公共特征,而派生类通过增加行为将抽象类变为某种有用的类型。

延续化

先定义一个抽象基类,该基类中有些操作并未实现。然后定义非抽象的派生类,实现抽象基类中定义的操作。例如,虚函数就属此类情况。这时,派生类是抽象的基类的实现,即可看成是基类定义的延续。这也是派生类的一种常用方法。

派生类

在多继承时,一个派生类有多于一个的基类,这时派生类将是所有基类行为的组合。

派生类将其本身与基类区别开来的方法是添加数据成员和成员函数。因此,继承的机制将使得在创建新类时,只需说明新类与已有类的区别,从而大量原有的程序代码都可以复用,所以有人称类是“可复用的软件构件”。

总结:

继承的好处:可以减少重复的代码量

语法:

class 子类:继承方式  父类

class A :  public B;

A  类称为子类  或者  派生类

B  类称为父类  或者  基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员

从基类继承过来的表现其共性,而新增的成员体现了其个性。

二、继承方式的基本语法

class 子类 : 继承方式 父类

 继承的方式一共有三种:

  1.  公共继承 : 除了私有内容,父类的内容到子类都可以访问得到
  2.  保护继承 : 除了私有内容,父类的公共的和保护的内容都变为保护的内容
  3.  私有继承 : 除了私有内容,父类的公共的和保护的内容都变为私有的内容

总而言之,父类的私有内容,子类是访问不到的。

示例:

#include<iostream>
using namespace std;
// 继承方式
// 公共继承
// 父类
class Base1 {
public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

// 子类

class Son1 :public Base1 {

public:

    void func()

    {

        m_A = 10;  // 父类中的公共权限成员到子类依然是公共权限

        m_B = 20;  // 父类中的保护权限成员到子类依然是保护权限

        // m_C = 30;  // 父类中的私有权限成员到子类访问不到

    }

};

void test01()

{

    Son1 s1;

    s1.m_A = 100;   // 创建一个儿子类,访问父亲类中的公共权限是可以访问到的

    // s1.m_B = 200;   // 在Son类中,m_B是 保护权限 在类外是访问不到的

}

// 保护继承

class Base2 {

public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

// 子类

class Son2 :protected Base2 {

public:

    void func()

    {

        m_A = 90;  // 父类中公共成员到子类中变为保护权限,在类内才能访问

        m_B = 80;  // 原本的保护权限的成员到子类中依然是保护权限

        // m_C = 70;  // 不管是哪种方式继承,父类中的私有属性的内容都访问不到

    }
    
};

void test02()

{

    Son2 s2;

    // s2.m_A = 1000;  // 报错,父类中公共成员,继承到子类的时候已经变为保护权限,在类外访问不到

    // s2.m_B = 2000;  // 报错,保护权限在类外访问不到

}

// 私有继承

class Base3 {

public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

// 子类

class Son3 :private Base3 {

public:

    void func()

    {

        m_A = 100; // 父类的公共成员到子类中变为 私有成员

        m_B = 200; // 父类的保护成员到子类中变为 私有成员

        //m_C = 300; // 不管是哪种方式继承,父类中的私有属性的内容都访问不到

    }

};

class GrandSon3 :public Son3

{

public:

    void func()

    {

        // m_A = 1000;// 到了Son3中, m_A变为私有属性,即使是儿子,也访问不到

        // m_B = 2000; //与上一个相同,都是访问不到的

    }

};

void test03()

{

    Son3 s3;

    // s3.m_A = 1000;

    // s3.m_B = 2000; // 子类的成员都变为私有成员类外访问不到

}



int main()

{

    test01();

    test02();

    test03();

    return 0;

}

运行结果:

三、继承中的对象模型

??:从父类继承过来的成员,哪些属于子类对象中?

父类中所有非静态成员属性都会被子类继承下去

父类中的私有属性被编译器隐藏,访问不到,但被继承下去了

示例:

#include<iostream>

using namespace std;

// 继承中的对象模型

class Base

{

public:

    int a;

protected:

    int b;

private:

    int c;

};

 

class Son : public Base

{

public:

    int d;

};

int main()

{

    // 父类中所有非静态成员属性都会被子类继承下去

    // 父类的私有成员属性,是被编译器隐藏了,因此访问不到,但是确实被继承了

    cout<<"儿子类的大小:"<<sizeof(Son)<<endl;// 16

    return 0;

}

运行结果:

用开发人员命令提示符查看对象模型

进入文件所在盘符,cd跳转进入文件路径下,dir查看文件中的内容

输入:cl  /d1  reportSingleClassLayout类名  cpp的文件(可以输入前几个字符后tab键自动填充)

四、继承中的构造和析构的顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构的顺序是谁先谁后?

先构造父类再构造子类,先析构子类再析构父类(没有父类直接声明子类的时候,也会先构造父类)

示例:

#include<iostream>

using namespace std;

class Base{

public:

    Base()

    {

        cout<<"Base的构造函数."<<endl;

    }

    ~Base()

    {

        cout<<"Base的析构函数."<<endl;

    }

};

class Son :public Base{

public:

    Son()

    {

        cout<<"Son的构造函数."<<endl;

    }

    ~Son()

    {

        cout<<"Son的析构函数."<<endl;

    }

};

void test01()

{

    //Base b;

    Son s; // 创建子类的时候,也会先创建父类

}

int main()

{

    test01();

    return 0;

}

运行结果:

五、继承中同名成员的处理

问题:当子类与父类出现同名成员,如何通过子类对象,访问到子类或父类中同名的数据?

  1. 访问子类同名成员,直接访问即可
  2. 访问父类的同名成员,需要加作用域

示例:

#include<iostream>

using namespace std;

class Base{

public:

    Base()

    {

        m_A = 100;

    }

    void func()

    {

        cout<<"Base - func()函数的调用 "<<endl;

    }

    int m_A;

};

class Son :public Base

{

public:

    Son()

    {

        m_A =200;

    }

    void func()

    {

        cout<<"Son - func()函数的调用 "<<endl;

    }

    int m_A;

};

 

// 同名成员的处理方式

void test01()

{

    Son s;

    cout<<"m_A = "<<s.m_A<<endl;

    // 如果通过子类的对象 访问到父类的同名成员,需要加父类的作用域

    cout<<"Base 下的 m_A = "<<s.Base::m_A<<endl;// 加父类的作用域

}

 

// 同名函数的处理方式

void test02()

{

    Son s1;

    s1.func(); // 直接调用 调用的是子类的函数

    s1.Base::func(); // 加作用域 调用父类的函数调用

 

    // s.func(100);

    // s.Base::func(100);

   // 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名函数(重载的)

    // 如果想要访问到父类中隐藏的同名成员函数,需要加作用域

}

int main()

{

    test01();

    test02();

    return 0;

}

运行结果:

总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类同名函数

六、继承同名静态成员 的处理方式

问题:

继承中同名的静态成员在子类对象上如何进行访问?

  • 访问子类同名成员,直接访问即可
  • 访问父类的同名成员,需要加作用域

总结:

  1.  访问子类同名成员,直接访问即可
  2.  访问父类的同名成员,需要加作用域
  3. 子类出现和父类同名的静态成员函数,也会隐藏父类中所有同名成员函数
  4. 如果想要访问父类中被隐藏的同名成员,也需要加作用域

示例:

#include<iostream>

using namespace std;

// 继承中同名静态成员处理

class Base

{

public:

    static int m_A; // 类内声明,类外初始化

 

    static void func()

    {

        cout<<"Base - static 函数的调用 "<<endl;

    }

};

int Base:: m_A = 100;

class Son : public Base

{

public:

    static int m_A;

 

    static void func()

    {

        cout<<"Son - static 函数的调用 "<<endl;

    }

};

int Son:: m_A = 200;

 

// 同名静态成员属性

void test01()

{

    // 1. 通过对象访问数据

    cout<<"通过对象访问"<<endl;

    Son s;

    cout<<"Son 下的 m_A = "<<s.m_A<<endl;

    cout<<"Base 下的 m_A = "<<s.Base::m_A<<endl<<endl;

   

    // 2. 通过类名访问

    cout<<"通过类名访问"<<endl;

    cout<<" Son 下的 m_A = "<<Son::m_A<<endl;

    // 第一个:: 代表通过类名方式访问 第二个::代表访问父类作用域下

    cout<<"Base 下的 m_A = "<<Son::Base::m_A<<endl<<endl;

}

// 同名静态成员函数

void test02()

{

    // 1. 通过对象来访问

    cout<<"通过对象访问"<<endl;

    Son s2;

    s2.func();

    s2.Base::func();

   

    cout<<endl;

    cout<<"通过类名访问"<<endl;

    // 2. 通过类名访问

    Son::func();

     // 第一个:: 代表通过类名方式访问 第二个::代表访问父类作用域下

    Son::Base::func();

 

    // 如果子类出现和父类同名静态成员函数,也会隐藏父类中所有同名函数成员

    // 如果想访问父类中被隐藏同名成员,需要加作用域

    // Son::Base::func(100);  即重载了func为有参构造

}

int main()

{

    test01();

    test02();

    return 0;
    
}

运行结果:

七、多继承语法

c++允许一个类继承多个类

语法:class 子类 : 继承方式 父类1,继承方式 父类2...

多继承可能会引发类中有同名成员出现,需要加作用域区分

总结:多继承中如果父类出现同名情况,子类使用的时候加作用域。

示例:

#include<iostream>

using namespace std;

// 多继承的语法

class Base1

{

public:

    Base1()

    {

        m_A = 100;

    }

    int m_A;

};

class Base2

{

public:

    Base2()

    {

        m_A = 200;

    }

    int m_A;

};

 

// 子类 需要继承Base1 和 Base2

class Son:public Base1,public Base2

{

public:

    Son()

    {

        m_C = 300;

        m_D = 400;

    }

    int m_C;

    int m_D;

};

void test01()

{

    Son s;

    cout<<"子类的大小(sizeof Son)"<<sizeof(s)<<endl;

    // 当两个杜父类中有成员名相同,需要加作用域

    cout<<"Base1 m_A = "<<s.Base1::m_A<<endl;

    cout<<"Base2 m_A = "<<s.Base2::m_A<<endl;

}

int main()

{

    test01();

    return 0;

}

运行结果:

通过命令提示符查看类的大小

 

八、菱形继承

概念:

两个派生类继承同一个基类

又有某个类同时继承这两个派生类

这种继承称为菱形继承,或者钻石继承

案例:

问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

示例:

#include<iostream>

using namespace std;

// 模拟菱形继承

// 动物类

class Animal

{

public:

    int m_age;

};

 

// 利用虚继承  解决菱形继承的问题

// 继承之前加关键字 virtual 变为虚继承

// Animal 变为虚基类

// 羊类

class sheep:virtual public Animal

{

 

};

 

// 驼类

class Tuo:virtual public Animal{};

 

// 羊驼类

class sheep_Tuo:public sheep,public Tuo{};

void test01()

{

    sheep_Tuo st;

 

    st.sheep::m_age = 18;

    st.Tuo::m_age = 28;

    // 当出现菱形继承的时候,有两个父类中有相同的数据,需要加作用域区分

    cout<<"st.sheep::m_age = "<<st.sheep::m_age<<endl;

    cout<<"st.Tuo::m_age = "<<st.Tuo::m_age<<endl;

 

    // 虚继承导致数据只有一份,不存在数据来源不明确的情况

    cout<<"st.m_age = "<<st.m_age<<endl; // 现在就可以访问到

    // 产生虚基类指针 vbptr

    // virtual  base    pointer  指向 vbtable

    // 这份数据有一份就可以,但是菱形继承导致数据有两份,浪费资源

}

int main()

{

    test01();

    return 0;

}

运行结果:

命令提示符查看

vbptr  虚基类指针 ---指向 vbtable --虚基类表

v -- virtual

b -- base

ptr  -- pointer

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用FreeBASIC设计8051单片机汇编编译器

在STC论坛上看到有人用C语言实现8051汇编编译器&#xff08;源码&#xff09;&#xff0c;好奇下&#xff0c;试着用FB写了一下。 基本原理就是通过分析汇编文件然后转换为机器码。以下是51汇编与机器码对应的表格&#xff08;数据来自网络&#xff0c;如果发现有误请联系QQ149…

记录:排查create_ap偶发无法开启自发AP的问题

背景说明&#xff1a; 系统&#xff1a;Xubuntu16.04&#xff1b;内核&#xff1a;4.14&#xff1b;无线网卡&#xff1a;EDIMAX EW-7822UAC 关于无线网卡的驱动安装和create_ap配置参考博文&#xff1a;Xubuntu16.04系统中使用EDIMAX EW-7822UAC无线网卡开启5G自发AP 目录 问题…

【Linux笔记】自定义一个简单的shell

一、命令行解释器shell的原理 我们已经知道Linux给我们提供了一系列由exec开头的系统调用接口&#xff0c;可以让我们在自己所写的程序中调用各种指令或者我们自己写的其他程序&#xff1a; 而我们的shell命令行解释器也是接收用户输入的指令&#xff0c;然后执行&#xff1a;…

Java 实现单链表

文章目录 节点类定义单链表类总结 单链表是一种常用的数据结构&#xff0c;它由若干个节点&#xff08;Node&#xff09;组成&#xff0c;每个节点包含两部分&#xff1a;一部分是数据域&#xff0c;用于存储数据&#xff1b;另一部分是指针域&#xff0c;用于指向下一个节点。…

spring常见漏洞(1)

简介 Spring的英文翻译为春天&#xff0c;可以说是给Java程序员带来了春天&#xff0c;因为它极大的简化了开发。得出一个公式&#xff1a;Spring 春天 Java程序员的春天 简化开发。最后的简化开发正是Spring框架带来的最大好处。 Spring是一个开放源代码的设计层面框架&a…

使用RNN完成IMDB电影评论情感分析

使用RNN完成IMDB电影评论情感分析 任务描述一、环境设置二、数据准备2.1 参数设置2.2 用padding的方式对齐数据2.3 用Dataset与DataLoader加载三、模型配置四、模型训练五、模型评估六、模型预测 任务描述 本示例教程演示如何在IMDB数据集上使用RNN网络完成文本分类的任务。IM…

react native中使用tailwind并配置自动补全

使用的第三方库是tailwind-react-native-classnames&#xff0c;同类的也有tailwind-rn&#xff0c;但是我更喜欢前者官方demo&#xff1a; import { View, Text } from react-native; import tw from twrnc;const MyComponent () > (<View style{twp-4 android:pt-2 b…

51-12 多模态论文串讲—BLIP 论文精读

视觉语言预训练VLP模型最近在各种多模态下游任务上获得了巨大的成功&#xff0c;目前还有两个主要局限性: (1) 模型角度: 大多数方法要么采用encoder模型&#xff0c;要么采用encoder-decoder模型。然而&#xff0c;基于编码器的模型不太容易直接转换到文本生成任务&#xff0…

爬虫之牛刀小试(四):爬取B站番剧的简介

今天爬取的是b站。 如何爬取b站中的番剧呢&#xff1f; 首先我们来到番剧索引中&#xff0c;随便点开一部动漫&#xff0c;检查代码。 每个作品对应一个链接: https://www.bilibili.com/bangumi/play/ss…&#xff08;ss后面的数字称为ss号&#xff09; 发现关于动漫的信息…

HTML--超链接

超链接 作用&#xff1a;点击跳转到相应位置 a标签 语法&#xff1a; <a href"链接地址">文本或图片</a>范例&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8"/><title>跳转到百度</t…

程序员如何弯道超车?周末有奇效

作为一名程序员&#xff0c;不断提升自己的技能和知识是至关重要的。然而&#xff0c;在繁忙的工作日常中&#xff0c;很难有足够的时间和精力来学习新技术或深入研究。因此&#xff0c;周末成为了一个理想的时机&#xff0c;可以专注于个人发展和技能提升。所以程序员如何利用…

部署Tomcat及其负载均衡

简介 在前面已经学习了Nginx服务器的安装配置&#xff0c;本章主要采用案例课的形式介绍Tomcat及NginxTomcat负载均衡群集。Tomcat案例首先介绍其应用场景&#xff0c;然后重点介绍Tomcat的安装配置&#xff0c;NginxTomcat负载均衡群集案例是应用于生产环境下的一套可靠的Web站…

006集 正则表达式 re 应用实例—python基础入门实例

正则表达式指预先定义好一个 “ 字符串模板 ” &#xff0c;通过这个 “ 字符串模 板” 可以匹配、查找和替换那些匹配 “ 字符串模板 ” 的字符串。 Python的中 re 模块&#xff0c;主要是用来处理正则表达式&#xff0c;还可以利用 re 模块通过正则表达式来进行网页数据的爬取…

NUS CS1101S:SICP JavaScript 描述:三、模块化、对象和状态

原文&#xff1a;3 Modularity, Objects, and State 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 变化中安宁 &#xff08;即使它在变化&#xff0c;它仍然保持不变。&#xff09; ——赫拉克利特 变化越大&#xff0c;越是相同。 ——阿方斯卡尔 前面的章节介绍了构…

C++学习笔记——友元、嵌套类、异常

目录 一、友元 一个使用友元的示例代码 输出结果 二、嵌套类 一个使用嵌套类的示例代码 输出结果 三、异常 一个使用异常处理的示例代码 输出结果 四、结论 五、使用它们的注意事项 上一篇文章链接&#xff1a; C中的继承和模板是非常强大和灵活的特性&#xff0c;它…

C++学习笔记——标准模板库

目录 一、简介 二、STL概述 2.1STL是什么&#xff1f; 2.2STL的优势 三、容器&#xff08;Containers&#xff09; 3.1序列式容器&#xff08;Sequence Containers&#xff09; 3.2关联式容器&#xff08;Associative Containers&#xff09; 3.3容器适配器&#xff08;…

轻松批量重命名不同路径文件:从1到N的单独编号指南

在处理大量文件时&#xff0c;有时要批量重命名这些文件。整理、归档或是进行其他类型的处理。但不同的文件可能位于不同的路径下&#xff0c;增加了批量重命名的复杂性。下面来看云炫文件管理器如何批量重命名不同路径下的文件&#xff0c;从1到N单独编号的方法。 不同路径文件…

考研经验总结——目录

文章目录 一、写作顺序二、个人情况说明三、读评论四、一些小牢骚五、一些注意事项&#xff08;持续更新&#xff09; 一、写作顺序 我将准备从三个阶段开始介绍吧 考研前考研中考研后&#xff08;也就是现在我的这种情况&#xff09; 考研前我会分为&#xff1a;数学、专业…

三轴加速度计LIS2DW12开发(3)----检测活动和静止状态

e2studio开发三轴加速度计LIS2DW12.3--检测活动和静止状态 概述视频教学样品申请源码下载新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函数原型回调函数user_uart_callback ()…

vue3+vite开发生产环境区分

.env.development VITE_APP_TITLE本地.env.production VITE_APP_TITLE生产-ts文件中应用 console.log(import.meta.env.VITE_APP_TITLE)在html中应用&#xff0c;需要安装 html 模板插件 pnpm add vite-plugin-html -Dvite.config.ts中 import { createHtmlPlugin } from v…