【C++面向对象】--- 继承 的奥秘(下篇)

news2024/12/25 0:02:10

个人主页:平行线也会相交💪
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创
收录于专栏【C++之路】💌
本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍
希望我们一起努力、成长,共同进步。🍓
在这里插入图片描述

目录

  • 一、作用域
    • 出个小题
    • 小总结
  • 二、派生类的默认成员函数
    • 构造函数
    • 拷贝构造函数
    • 赋值运算符重载
    • 析构函数
    • 小总结
  • 三、继承与友元
  • 四、继承和静态成员

一、作用域

接下来对C++继承体系中的作用域展开分析。

在C++继承体系中,子类和父类有各自的作用域,所以子类和父类可以定义同名的成员

请看针对不同作用域的举例:

局部域和当前类域在这里插入图片描述
这里有个小概念:
隐藏/重定义子类和父类有同名成员时,子类的成员隐藏了父类的成员。(如上左图所示)

指定当前的父域:
在这里插入图片描述

作用域当然也对成员函数起作用,请看:
在这里插入图片描述

出个小题

类B和类A中的fun()函数有什么关系。

class A
{
public:
	void fun()
	{
		cout << "fun()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		cout << "fun(int i)" << endl;
	}
};

B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。并不会构成函数重载(因为函数重载针对的是不同的作用域)
在这里插入图片描述

小总结

  • 在继承体系中基类和派生类都有独立的作用域
  • 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
    也叫重定义。(在子类成员函数中,可以使用基类::基类成员进行显示访问,举个例子就比如说:B b; b.A::fun();
  • 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  • 但是其实在实际中在继承体系里面最好不要定义同名的成员(省的给自己添麻烦)。

二、派生类的默认成员函数

再来回顾一下C++中的6个默认成员函数:构造函数、析构函数、拷贝构造函数、赋值运算符重载、取地址及const取地址运算符重载。

构造函数

class Person
{
public:
    Person(const char* name = "peter")
        : _name(name)
    {
        cout << "Person()" << endl;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name; // 姓名
};
class Student : public Person
{
public:
    Student(const char* name = "李四",int id = 0)
        :_id(0)
    {}
protected:
    int _id;
};
int main()
{
    Student s;
    return 0;
}

运行结果如下:
在这里插入图片描述
上述代码中我们并没有定义Person类对象,但是却调用了Person类中的默认构造函数,为什么呢?

因为C++规定了派生类必须调用父类的成员函数来初始化父类的成员变量。
在这里插入图片描述
这里是在初始化列表来调用父类中的默认成员函数的。

在来看下面的情况,请看:
在这里插入图片描述
解释在创建Student对象时,先调用Person类的构造函数来初始化Person类的成员变量_name,然后再调用Student类的构造函数来初始化Student类的成员变量_id。
所以这里是Person类中的成员函数先进行初始化,然后再对Student中的成员进行初始化。即派生类的构造函数在执行之前,基类的构造函数必须首先完成。

重点:通过使用初始化列表,并在其中调用基类的构造函数来初始化基类的成员变量,可以确保在派生类的构造函数中正确初始化基类的数据成员这是由于派生类的构造函数在执行之前,基类的构造函数必须首先完成。

拷贝构造函数

class Person
{
public:
    Person(const char* name = "peter")
        : _name(name)
    {
        cout << "Person()" << endl;
    }
    Person(const Person& p)
        : _name(p._name)
    {
        cout << "Person(const Person& p)" << endl;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name; // 姓名
};
class Student : public Person
{
public:
    //构造函数
    Student(const char* name = "李四",int id = 0)
        :_id(0)
        ,Person(name)
    {}

    //拷贝构造函数
    Student(const Student& s)
        :Person(s)
        , _id(s._id)
    {}
protected:
    int _id;
};
int main()
{
    Student s1;
    Student s2(s1);
    return 0;
}

运行结果如下:在这里插入图片描述

如果我们去掉基类拷贝构造函数中的Person(s)会怎样呢(即没有显式调用基类中的拷贝构造函数)?

解析:去掉Person(s)将导致基类Person的成员变量_name不会被复制,而是会调用基类中的默认构造函数,而倘若此时基类也没有提供默认构造函数的话就会直接报错。
在这里插入图片描述
所以,我们应该显式调用拷贝构造函数。如下:

//拷贝构造函数
Student(const Student& s)
    :Person(s)//这里要显式调用拷贝构造函数,否则会调用基类中的默认构造函数
    , _id(s._id)
{}

一句话总结派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

赋值运算符重载

//父类赋值运算符重载
Person& operator=(const Person& p)
{
    cout << "Person operator=(const Person& p)" << endl;
    if (this != &p)
        _name = p._name;

    return *this;
}
//子类赋值运算符重载
Student& operator=(const Student& s)
{
    cout << "Student& operator= (const Student& s)" << endl;
    if (this != &s)
    {
        Person::operator=(s);
        _id = s._id;
    }
    return *this;
}

运行结果如下:
在这里插入图片描述
在这里插入图片描述
这里有的小伙伴看到Student s2 = s1;可能会产生疑惑,为什么这里不调用赋值运算符重载函数。
解答

因为在语句Student s2 = s1;中,发生的是对象的初始化,而不是赋值操作
当使用Student s2 = s1;来初始化一个已存在的对象s2时,会调用拷贝构造函数而不是赋值运算符重载函数。拷贝构造函数用来创建一个新对象,并将其内容初始化为另一个同类型对象的副本。
如果要调用赋值运算符重载函数,需要使用赋值操作符=来对已存在的对象进行赋值,例如s2 = s1;。这样才会调用赋值运算符重载函数,将s1的值赋给s2。

析构函数

//父类析构函数
~Person()
{
    cout << "~Person()" << endl;
}
//子类析构函数
~Student()
{
    cout << "~Student()" << endl;
}

在C++中,无法显式调用父类的析构函数。当一个派生类对象被销毁时,首先会自动调用派生类的析构函数,然后再自动调用基类的析构函数(即按照先父后子的顺序来完成对对象的析构)
如果要显式调用是没有办法保证先子后父进行析构的。

小总结

  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
    的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  • 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  • 派生类的operator=必须要调用基类的operator=完成基类的复制。
  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。即按照先清理派生类对象,再清理基类对象的顺序
  • 派生类对象初始化先调用基类构造再调派生类构造;同时派生类对象初始化先调用基类构造再调派生类构造。

三、继承与友元

友元关系不能继承,即基类友元不能访问子类私有和保护成员,基类的友元只能访问基类的成员而不能访问派生类的成员。

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
	friend void Display(const Person& p, const Student& s);
protected:
	int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}
int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

在这里插入图片描述

解释:Person类和Student类互相引用对方作为友元函数,因此需要先进行一次前向声明(即开头的class Student;。这样可以确保在实际定义这两个类的成员函数之前,编译器已经知道这两个类的存在。

四、继承和静态成员

class Person
{
public:
	Person() { ++_count; }
//protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
class Graduate : public Student
{
protected:
 string _seminarCourse; // 研究科目
};
int main()
{
	Person p;
	Student s;
	cout << &p._name << endl;
	cout << &s._name << endl;
	
	cout << &p._count << endl;
	cout << &s._count << endl;
	cout << &Person::_count << endl;
	cout << &Student::_count << endl;
}

运行结果如下:
在这里插入图片描述
静态成员变量是一种属于类而不是类的实例的变量。它在所有类的实例之间共享,并且在整个程序的生命周期中只存在一个副本。静态成员变量是在类定义外部进行初始化的

静态成员变量适用于在类的多个实例之间共享数据,并且可以通过类名直接访问,而无需实例化类对象。它们在数据共享和数据统计方面非常有用。需要注意的是,静态成员变量仅属于类,而不属于类的任何特定实例。

静态成员变量的访问方式:静态成员变量可以使用类名::成员变量名的方式进行访问(即类名::成员变量名),例如Person::_count

下面请看下面代码,要统计Person类及其Person派生类对象总共创建了多少个

class Person
{
public:
	Person() { ++_count; }
//protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
class Graduate : public Student
{
protected:
 string _seminarCourse; // 研究科目
};
int main()
{
	Person p;
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << Person::_count << endl;
}

运行结果:Person类及其派生类对象总共创建了4个对象

解释:在代码中,将_count定义为静态成员变量是为了在整个类层级中共享同一个计数变量。当创建派生类对象时,构造函数会依次调用每个类的构造函数,包括父类的构造函数。所以在父类的构造函数中进行++_count操作,可以确保每个派生类对象的创建都能正确地增加计数。

好了,本文到这里就结束了,希望对大家学习C++继承体系有所帮助。
再见啦,友友们!!!

在这里插入图片描述

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

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

相关文章

自动化测试用例设计实例

在编写用例之间&#xff0c;笔者再次强调几点编写自动化测试用例的原则&#xff1a; 1、一个脚本是一个完整的场景&#xff0c;从用户登陆操作到用户退出系统关闭浏览器。 2、一个脚本脚本只验证一个功能点&#xff0c;不要试图用户登陆系统后把所有的功能都进行验证再退出系统…

CAS 的执行流程 ?CAS 中 ABA 问题如何解决 ?CAS 在 Java 中有哪些实现类 ?

目录 1. CAS 的执行流程 2. CAS 中的 ABA 问题 3. 如何解决 CAS 中的 ABA 问题 4.CAS 在Java 中的实现类有哪些 1. CAS 的执行流程 CAS 比较并替换的大致流程是这样的&#xff1a; 它有三个操作单位&#xff1a;V&#xff08;内存值&#xff09;&#xff0c;A&#xff08;…

【C++】做一个飞机空战小游戏(八)——生成敌方炮弹(rand()和srand()函数应用)

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…

Iceberg 学习笔记

本博客对应于 B 站尚硅谷教学视频 尚硅谷数据湖Iceberg实战教程&#xff08;尚硅谷&Apache Iceberg官方联合推出&#xff09;&#xff0c;为视频对应笔记的相关整理。 1. Iceberg简介 1.1 概述 为了解决数据存储和计算引擎之间的适配的问题&#xff0c;Netflix 开发了 I…

【算法题】螺旋矩阵IV (求解n阶折线蛇形矩阵)

一、问题的提出 n阶折线蛇形矩阵的特点是按照图1所示的方式排列元素。n阶蛇形矩阵是指矩阵的大小为nn&#xff0c;其中n为正整数。 题目背景 一个 n 行 n 列的螺旋矩阵可由如图1所示的方法生成&#xff0c;观察图片&#xff0c;找出填数规律。填数规则为从 1 开始填到 nn。 …

如何使用AIGC人工智能辅助开发?

文章目录 引言AIGC辅助开发的应用场景代码生成图像识别与生成自然语言处理视频剪辑与生成 AIGC辅助开发的实现步骤数据准备模型选择模型训练结果评估与优化应用开发 AIGC辅助开发的优势与局限优势局限 未来展望总结 &#x1f389;欢迎来到AIGC人工智能专栏~如何使用AIGC人工智能…

计算机网络----CRC冗余码的运算

目录 1. 冗余码的介绍及原理2. CRC检验编码的例子3. 小练习 1. 冗余码的介绍及原理 冗余码是用于在数据链路层的通信链路和传输数据过程中可能会出错的一种检错编码方法&#xff08;检错码&#xff09;。原理&#xff1a;发送发把数据划分为组&#xff0c;设每组K个比特&#…

【Python】解决“Tk_GetPixmap: Error from CreateDIBSection”闪退问题

解决Python使用Tkinter的Notebook切换标签时出现的“Tk_GetPixmap: Error from CreateDIBSection 操作成功完成”闪退问题 零、问题描述 在使用Tkinter的Notebook控件时&#xff0c;对其标签进行切换&#xff0c;发现切换不了&#xff0c;一切换就报如下图错误&#xff1a; …

Python学习笔记_基础篇(五)_数据类型之字典

一.基本数据类型 整数&#xff1a;int 字符串&#xff1a;str(注&#xff1a;\t等于一个tab键) 布尔值&#xff1a; bool 列表&#xff1a;list 列表用[] 元祖&#xff1a;tuple 元祖用&#xff08;&#xff09; 字典&#xff1a;dict 注&#xff1a;所有的数据类型都存在想对…

3. 爬取自己CSDN博客列表(分页查询)(网站反爬虫策略,需要在代码中添加合适的请求头User-Agent,否则response返回空)

文章目录 步骤打开谷歌浏览器输入网址按F12进入调试界面点击网络&#xff0c;清除历史消息按F5刷新页面找到接口&#xff08;community/home-api/v1/get-business-list&#xff09;接口解读 撰写代码获取博客列表先明确返回信息格式json字段解读 Apipost测试接口编写python代码…

每天一道leetcode:646. 最长数对链(动态规划中等)

今日份题目&#xff1a; 给你一个由 n 个数对组成的数对数组 pairs &#xff0c;其中 pairs[i] [lefti, righti] 且 lefti < righti 。 现在&#xff0c;我们定义一种 跟随 关系&#xff0c;当且仅当 b < c 时&#xff0c;数对 p2 [c, d] 才可以跟在 p1 [a, b] 后面…

JMeter 特殊组件-逻辑控制器与BeanShell PreProcessor 使用示例

文章目录 前言JMeter 特殊组件-逻辑控制器与BeanShell PreProcessor 使用示例1. 逻辑控制器使用1.1. While Controller 使用示例1.2. 如果&#xff08;If&#xff09;控制器 使用示例 2. BeanShell PreProcessor 使用示例 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞…

视频云存储平台EasyCVR视频汇聚接入AI算法接口,如何在检测中对视频流画框?

视频集中存储EasyCVR安防监控视频汇聚平台基于云边端智能协同架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;平台可支持多协议接入&#xff0c;包括市场主流标准协议与厂家私有协议及SDK&#xff0c;如&#xff1a;国标GB28181、RTMP、RTSP/Onvif、海康Ehome…

Matlab中图例的位置(图例放在图的上方、下方、左方、右方、图外面)等

一、图例默认位置 默认的位置在NorthEast r 10; a 0; b 0; t0:0.1:2.1*pi; xar*cos(t); ybr*sin(t); A1plot(x,y,r,linewidth,4);%圆 hold on axis equal A2plot([0 0],[1 10],b,linewidth,4);%直线 legend([A1,A2],圆形,line)二、通过Location对legend的位置进行改变 变…

国标GB28181视频平台EasyGBS国标视频平台接入大量通道,创建角色未响应的问题解决方案

国标GB28181协议视频平台EasyGBS是基于国标GB28181协议的视频云服务平台&#xff0c;支持多路设备同时接入&#xff0c;并对多平台、多终端分发出RTSP、RTMP、FLV、HLS、WebRTC等格式的视频流。平台可提供视频监控直播、云端录像、云存储、检索回放、智能告警、语音对讲、平台级…

生成IOS app专用密码教程

转载&#xff1a;生成IOS app专用密码教程 APP专用密码app-specific password是专门用于上传ipa文件的一种密码&#xff0c;是一种苹果的安全机制&#xff01; 现在苹果开发者账号开启了双重认证&#xff0c;提交ipa文件时候都需要这个密码&#xff01; 一.注册AppID账号 1…

Linux 路由三大件

对于 Linux 网络&#xff0c;好奇心强的同学一定思考过两个问题&#xff1a; 当我们发出一个包的时候&#xff0c;Linux 是如何决策该从哪个网卡&#xff08;假设有多个网卡&#xff09;、哪个下一跳发出这个包&#xff0c;用什么 IP 作为 source......当 Linux 收到一个包时&a…

云服务 Ubuntu 20.04 版本 使用 Nginx 部署静态网页

所需操作&#xff1a; 1.安装Nginx 2.修改配置文件 3.测试、重启 Nginx 4.内部修改防火墙 5.配置解析 6.测试是否部署成功 1.安装Nginx // 未使用 root 账号 apt-get update // 更新apt-get install nginx // 安装 nginx 1.1.测试是否安装没问题 在网页上输入云服务的公网…

链表之第一回

欢迎来到我的&#xff1a;世界 收录专栏&#xff1a;链表 希望作者的文章对你有所帮助&#xff0c;有不足的地方还请指正&#xff0c;大家一起学习交流 ! 目录 前言第一题&#xff1a;删除链表的倒数第n个节点第二题&#xff1a;链表的中间结点第三题&#xff1a;合并两个排序…

Apache Dubbo 云原生可观测性的探索与实践

作者&#xff1a;宋小生 - 平安壹钱包中间件资深工程师 Dubbo3 可观测能力速览 Apache Dubbo3 在云原生可观测性方面完成重磅升级&#xff0c;使用 Dubbo3 最新版本&#xff0c;你只需要引入 dubbo-spring-boot-observability-starter 依赖&#xff0c;微服务集群即原生具备以…