c++类继承

一、继承的规则

(1)基类成员在派生类中的访问权限不得高于继承方式中指定的权限。例如,当继承方式为protected时,那么基类成员在派生类中的访问权限最高也为protected,高于protected会降级为protected,但低于protected不会升级。当继承方式为public时,继承方式则保持不变;

(2)继承方式中的public、protected、private是用来指定基类成员在派生类中最高访问权限的;

(3)不管继承方式是什么,基类中的private成员在派生类中始终不能使用(不能在派生类的成员函数中访问和使用);

(4)如果希望基类的成员既不向外暴漏(不能通过对象访问),还能在派生类中使用,那么只能声明为protected;

在这里插入图片描述

(5)由于private和protected继承方式会改变基类成员在派生类中的访问权限,导致继承关系复杂,所以在实际开发中,一般使用public。
(6)在派生类中,可以通过基类的公有成员函数间接访问基类的私有成员;
(7)使用using关键字可以改变基类成员在派生类中的访问权限。

继承访问权限:

#include <iostream>

class A
{
public:
    int m_A = 5;
protected:
    int m_B = 10;
private:
    int m_C = 15;
};

class B : public A 
{
public:
    int getB()
    {
        return m_B;
    }
};

int main()
{
    B b;
    b.m_A = 5;          // 没问题
    //b.m_B = 10;       // 不能访问,m_B是受保护成员变量
    //b.m_C = 15;       // 不能访问,m_C是私有变量,派生类不能访问
    b.getB();           // 可以通过成员函数访问m_B
}

使用using改变继承属性:

#include <iostream>

class A
{
public:
    int m_A = 5;
protected:
    int m_B = 10;
private:
    int m_C = 15;
};

class B : public A 
{
public:
    using A::m_B;

    int getA()
    {
        return m_A;
    }
private:
    using A::m_A;
};


int main()
{
    B b;
    // b.m_A = 5;            // 不能访问,使用using m_A变成了私有成员属性
    b.m_B = 10;              // 没问题,使用using,m_B变成了public
    //b.m_C = 15;            // 不能访问,m_C是私有变量,派生类不能访问
    b.getA();               // 可以通过成员函数访问m_A
}

二、类的对象模型

(1)创建派生类对象时,先调用基类构造函数,再调用派生类构造函数;
(2)销毁派生类对象时,先调用派生类析构函数,再调用基类析构函数;
(3)创建派生类对象时只会申请一次内存,派生类对象包含了基类对象的内存空间,this指针相同的;
(4)创建派生类对象时,先初始化基类对象,再初始化派生类对象;
(5)对派生类对象用sizeof得到的是基类所有成员(包括私有成员)+派生类对象所有成员的大小;

#include <iostream>
using namespace std;

void* operator new(size_t size)
{
    void* ptr = malloc(size);   // 申请内存
    cout << "申请到的内存地址是:" << ptr << " size大小:" << size << endl;
    return ptr;
}

void operator delete(void* ptr)
{
    if (ptr == nullptr) return;
    free(ptr);                  // 释放内存
    cout << "释放了内存。" << endl;
}

class A
{
public:
    int m_A = 5;
protected:
    int m_B = 10;
private:
    int m_C = 15;
public:
    A()
    {
        cout << "A中this指针是:" << this << endl;
        cout << "A中m_A指针是:" << &m_A << endl;
        cout << "A中m_B指针是:" << &m_B << endl;
        cout << "A中m_C指针是:" << &m_C << endl;

    }
};

class B : public A
{
public:
    int m_D = 30;
    B()
    {
        cout << "A中this指针是:" << this << endl;
        cout << "A中m_A指针是:" << &m_A << endl;
        cout << "A中m_B指针是:" << &m_B << endl;
        //cout << "A中m_C指针是:" << &m_C << endl;  // 私有无法访问
        cout << "A中m_D指针是:" << &m_D << endl;
    }
};

int main()
{
    cout << "基类占用内存的大小是:" << sizeof(A) << endl;
    cout << "派生类占用内存的大小是:" << sizeof(B) << endl;
    B* b = new B;
    delete b;
    
}

打印输出:

基类占用内存的大小是:12
派生类占用内存的大小是:16
申请到的内存地址是:00000168C69232B0 size大小:16
A中this指针是:00000168C69232B0
A中m_A指针是:00000168C69232B0
A中m_B指针是:00000168C69232B4
A中m_C指针是:00000168C69232B8
A中this指针是:00000168C69232B0
A中m_A指针是:00000168C69232B0
A中m_B指针是:00000168C69232B4
A中m_D指针是:00000168C69232BC
释放了内存。

(6)在C++中,不同继承方式的访问权限只是语法上的处理;
(7)对派生类对象用memset()清空基类私有成员;
(8)用指针可以访问到基类中的私有成员(没有内存对齐,没有占位符)

#include <iostream>
using namespace std;

void* operator new(size_t size)
{
    void* ptr = malloc(size);   // 申请内存
    cout << "申请到的内存地址是:" << ptr << " size大小:" << size << endl;
    return ptr;
}

void operator delete(void* ptr)
{
    if (ptr == nullptr) return;
    free(ptr);                  // 释放内存
    cout << "释放了内存。" << endl;
}

class A
{
public:
    int m_A = 5;
protected:
    int m_B = 10;
private:
    int m_C = 15;
public:
    A()
    {
        cout << "A中this指针是:" << this << endl;
        cout << "A中m_A指针是:" << &m_A << endl;
        cout << "A中m_B指针是:" << &m_B << endl;
        cout << "A中m_C指针是:" << &m_C << endl;
    }
    void funcA()
    {
        cout << "m_A=" << m_A << ",m_B=" << m_B << ",m_C=" << m_C << endl;
    }
};

class B : public A
{
public:
    int m_D = 30;
    B()
    {
        cout << "A中this指针是:" << this << endl;
        cout << "A中m_A指针是:" << &m_A << endl;
        cout << "A中m_B指针是:" << &m_B << endl;
        //cout << "A中m_C指针是:" << &m_C << endl;  // 私有无法访问
        cout << "A中m_D指针是:" << &m_D << endl;
    }
    void funcB()
    {
        cout << "m_D=" << m_D << endl;
    }
};

int main()
{
    cout << "基类占用内存的大小是:" << sizeof(A) << endl;
    cout << "派生类占用内存的大小是:" << sizeof(B) << endl;
    B* b = new B;
    b->funcA();  b->funcB();
    //memset(b, 0, sizeof(B));        // m_A m_B m_C m_D都被清零
    *((int*)b + 2) = 100;
    b->funcA();  b->funcB();
    delete b;
    
}

打印输出:

基类占用内存的大小是:12
派生类占用内存的大小是:16
申请到的内存地址是:00000240B7313490 size大小:16
A中this指针是:00000240B7313490
A中m_A指针是:00000240B7313490
A中m_B指针是:00000240B7313494
A中m_C指针是:00000240B7313498
A中this指针是:00000240B7313490
A中m_A指针是:00000240B7313490
A中m_B指针是:00000240B7313494
A中m_D指针是:00000240B731349C
m_A=5,m_B=10,m_C=15
m_D=30
m_A=5,m_B=10,m_C=100
m_D=30
释放了内存。

三、如何构造基类

(1)创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数;
(2)如果没有指定基类构造函数,将使用基类的默认构造函数;
(3)可以用初始化列表指明要使用的基类构造函数;
(4)基类构造函数负责初始化被继承的数据成员,派生类构造函数主要用于初始化新增的数据成员;
(5)派生类的构造函数总是调用一个基类构造函数,包括拷贝构造函数。

#include <iostream>
using namespace std;

class A
{
public:
    int m_A;
private:
    int m_B;
public:
    A():m_A(0), m_B(0) {
        cout << "调用基类默认构造函数A()" << endl;
    }
    A(int a, int b) :m_A(a), m_B(b) {
        cout << "调用基类构造函数A(int a, int b)" << endl;
    }
    A(const A& a) :m_A(a.m_A), m_B(a.m_B) {
        cout << "调用基类拷贝构造函数A(int a, int b)" << endl;
    }
    void funcA()
    {
        cout << "m_A=" << m_A << ",m_B=" << m_B << endl;
    }
};

class B : public A
{
public:
    int m_C = 30;
    B():m_C(0) {
        cout << "调用基类默认构造函数B()" << endl;
    }
    B(int a, int b, int c) : A(a, b), m_C(c){
        cout << "调用基类构造函数B(int a, int b, int c)" << endl;
    }
    B(const A& a, int c) :A(a), m_C(c) {
        cout << "调用基类拷贝构造函数B(const A& a, int c)" << endl;
    }
    void funcB() {
        cout << "m_C=" << m_C << endl;
    }
};

int main()
{
    B b1;                   // 调用基类默认构造函数
    b1.funcA(); b1.funcB();

    B b2(1, 2, 3);          // 将调用基类两个参数的构造函数
    b2.funcA(); b2.funcB();

    A a(10, 20);
    B b3(a, 30);
    b3.funcA(); b3.funcB(); // 调用基类构造函数  
}

打印输出:

调用基类默认构造函数A()
调用基类默认构造函数B()
m_A=0,m_B=0
m_C=0
调用基类构造函数A(int a, int b)
调用基类构造函数B(int a, int b, int c)
m_A=1,m_B=2
m_C=3
调用基类构造函数A(int a, int b)
调用基类拷贝构造函数A(int a, int b)
调用基类拷贝构造函数B(const A& a, int c)
m_A=10,m_B=20
m_C=30

四、名字遮蔽与类作用域

(1)如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,通过派生类对象或者在派生类的成员函数中使用该成员时,将使用派生类新增的成员,而不是基类的;
(2)基类的成员函数和派生类的成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数;
(3)类是一种作用域,每个类都有它自己的作用域,在这个作用域之内定义成员;
(4)在类的作用域之外,普通的成员只能通过对象(可以是对象本身,也可以是对象指针或对象引用)来进行访问,静态成员可以通过对象访问,也可以通过类访问;
(5)在成员前面加类名和域解析符可以访问对象的成员;
(6)如果不存在继承关系,域名和域解析符可以省略不写;
(7)当存在继承关系时,基类的作用嵌套派生类的作用域中,如果成员在派生类的作用域已经找到,就不会在基类作用域中继续查找,如果没有找到,则继续在鸡类作用域中查找。

五、继承的特殊关系

(1)如果继承方式是公有的,派生类对象可以使用基类成员;
(2)可以把派生类对象赋值给基类对象(包括私有成员),但是会舍弃非基类的成员;
(3)基类指针可以在不进行显示转换的情况下指向派生类对象;
(4)基类引用可以在不进行显示转换的情况下引用派生类对象。

注意:
(1)基类指针或引用只能调用基类的方法,不能调用派生类的方法;
(2)可以用派生类构造基类;
(3)如果函数的型参是基类,实参可以用派生类;
(4)C++要求指针和引用类型与赋给的类型匹配,这一规则对继承来说是例外。但是,这种例外只是单向的,不可以将基类对象和地址赋给派生类引用和指针(没有价值,没有讨论的必要)。

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

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

相关文章

阿里云部署的幻兽帕鲁服务器,为什么没有一键更新游戏服务端、导入存档、可视化的游戏配置

如果有的朋友发现自己用阿里云部署的幻兽帕鲁服务器&#xff0c;找不到一键更新游戏服务端、一键导入存档、以及可视化的游戏配置。其实答案很简单&#xff0c;因为你的“阿里云计算巢”版本需要更新。你只需要更新到最新版&#xff0c;就可以拥有这些便捷的功能了。 具体的操…

【备战蓝桥杯】——循环结构终篇

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-yl4Tqejg4LkjZLAM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

一文辨析清楚LORA、Prompt Tuning、P-Tuning、Adapter 、Prefix等大模型微调方法

本文探讨了大模型微调的核心概念和方法&#xff0c;详细介绍了如LoRA、Adapter Tuning、Prefix Tuning等多种微调策略。每种方法的原理、优势及适用场景都有详尽阐述&#xff0c;大家可以根据不同的应用需求和计算资源&#xff0c;选择到最合适自己的微调途径。 希望本文能对想…

Linux下grep命令详解

grep #文件内容过滤显示 #在指定的普通文件中查找并显示含有指定字符串的行&#xff0c;也可与管道符一起使用格式&#xff1a; grep-参数 查找条件 文件名 参数&#xff1a; 示例&#xff1a; [rootnode1 ~]# grep -n "root" /etc/passwd # -n&a…

LangChain结合通义千问的自建知识库

LangChain结合通义千问的自建知识库 在使用了通义千问API了之后&#xff0c;下一步就是构建知识库文档&#xff0c;使用了比较有名的LangChian&#xff0c;最后成果将自己的txt生成了知识向量库&#xff0c;最后我还把自己的论文生成了一个知识向量库&#xff0c;然后问他我的…

Java 基于 SpringBoot+Vue 的前后端分离的火车订票管理系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Cambalache in Ubuntu

文章目录 前言apt install flatpak这很ok后记 前言 gtkmm4相比gtkmm3有很多改革, 代码也干净了许多, 但在windows上开发 有ui设计器那自然方便很多, 但glade又不支持gtkmm4, windows上装Cambalache很是困难. 各种问题都找不到答案.于是 我用VMware虚拟机Ubuntu20.xx安装Cambal…

C++集群聊天服务器 网络模块+业务模块+CMake构建项目 笔记 (上)

跟着施磊老师做C项目&#xff0c;施磊老师_腾讯课堂 (qq.com) 一、网络模块ChatServer chatserver.hpp #ifndef CHATSERVER_H #define CHATSERVER_H#include <muduo/net/TcpServer.h> #include <muduo/net/EventLoop.h> using namespace muduo; using namespace …

5分钟快速掌握 XML (Extensible Markup Language)

背景 在Java开发的过程中&#xff0c;我们经常需要和配置文件打交道&#xff0c;其中接触最多的就是XML。从最初学习 JavaWeb 时在 Tomcat 中配置servlet&#xff0c;到后来接触Spring框架并在XML中编写各种配置&#xff0c;XML一直是不可或缺的一部分。然而&#xff0c;XML的…

在Vue中如何构建复杂表单?

概述 很有可能&#xff0c;在我们的软件工程旅程中&#xff0c;我们至少要构建一次复杂的表单。本文将介绍如何创建一个复杂的表单&#xff0c;该表单可以使用一些Vue特性(如v-for和v-model)逐步增强。它还提供了一些基本的Vue核心功能的复习&#xff0c;这些功能将在您日常使…

MySQL中去除重复(十一)

MySQL中去除重复(十一) 一、相同的行 我们要去除相同行要使用DISTINCT关键字 SELECT DISTINCT 列名 FROM 表名; distinct 是针对查询的结果集合进行去重而不是针对某一行或者某一列。 二、查询中的行选择 用 WHERE 子句限制从查询返回的行。一个 WHERE 子句包含一个 必须满…

【Matplotlib】figure方法之图形的保存

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;matplotlib &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

在flutter中集成Excel导入和导出

flutter中集成Excel导入和导出功能 1、需要的依赖 在pubspec.yaml #excel导出syncfusion_flutter_xlsio: ^24.1.45open_file: ^3.0.1#导入excelflutter_excel: ^1.0.1#选择文件的依赖file_picker: ^6.1.1&#xff08;1&#xff09;依赖说明 在测试时&#xff0c;我们在使用导…

Faster-Whisper 实时识别电脑语音转文本

Faster-Whisper 实时识别电脑语音转文本 前言项目搭建环境安装Faster-Whisper下载模型编写测试代码运行测试代码实时转写脚本实时转写WebSocket服务器模式 参考 前言 以前做的智能对话软件接的Baidu API&#xff0c;想换成本地的&#xff0c;就搭一套Faster-Whisper吧。 下面是…

25考研|660/880/1000/1800全年带刷计划

作为一个参加过两次研究生考试的老学姐&#xff0c;我觉得考研数学的难度完全取决于你自己 我自己就是一个很好的例子 21年数学题目是公认的简单&#xff0c;那一年考130的很多&#xff0c;但是我那一年只考了87分。但是22年又都说是有史以来最难的一年&#xff0c;和20年的难度…

centos 7 部署若依前后端分离项目

目录 一、新建数据库 二、修改需求配置 1.修改数据库连接 2.修改Redis连接信息 3.文件路径 4.日志存储路径调整 三、编译后端项目 四、编译前端项目 1.上传项目 2.安装依赖 3.构建生产环境 五、项目部署 1.创建目录 2.后端文件上传 3. 前端文件上传 六、服务启…

Linux信号详解~

目录 前言 一、初识信号 二、信号的概念 三、信号的发送与捕捉 3.1 信号的发送 3.1.1 kill 命令 3.1.2 kill 函数 3.1.3 raise函数 3.1.4 abort函数 3.2 信号的捕捉 3.2.1 signal函数 3.2.2 sigaction函数 3.2.3 图示 四、信号的产生 4.1 硬件异常产生信号 4.2 …

C++输出地址

下面是一段输出地址的程序。 #include <bits/stdc.h> using namespace std;int main() {int s;cout << &s;//原地址return 0; }假如有一个人&#xff08;的朋友&#xff09;后来了&#xff0c;他也想住进的房间&#xff0c;我们可以这样&#xff1a; #includ…

OfficeWeb365 Readfile 任意文件读取漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

windows下安装go

下载golang Go 官网下载地址&#xff1a; https://golang.org/dl/ Go 官方镜像站&#xff08;推荐&#xff09;&#xff1a; https://golang.google.cn/dl/ 选择安装包 验证有没有安装成功 查看 go 环境 说明 &#xff1a; Go1.11 版本之后无需手动配置环境变量&#xff0c…