【C++】继承(二)深入理解继承:派生类默认成员函数与友元、静态成员的奥秘

目录

  • 派生类的默认成员函数
    • ①派生类的构造函数
    • ②派生类的拷贝构造函数
    • ③派生类的赋值构造
    • ④派生类的析构函数
  • 继承与友元
  • 继承与静态成员

前言

我们在上一章讲解了: 继承三部曲,本篇基于上次的基础继续深入了解继承的相关知识,欢迎大家和我一起学习继承

派生类的默认成员函数

在这里插入图片描述
6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

①派生类的构造函数

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

1.1、有默认构造的情况:

class Person
{
public:Person(const char* name="hhh"):_name(name){cout << "Person() " << endl;}
protected:string _name;
};
class Student :public Person
{
protected:int _stuid;
};
int main()
{Student s;return 0;
}

在有默认构造的情况下,Student s,创建的派生类s对象会自动调用自己的默认构造,它里面的内置类型_stuid不做处理,但是继承父类里面的_name会被当做一个Person类的对象,也就是自定义类型成员,_name会调用Person的默认构造来初始化自己

1.2、没有默认构造的情况:

这个基类我们没有写无参默认构造,但是我们写了带参的默认构造,所以编译器不会为我们生成默认无参的构造函数

class Person
{
public:Person(const char* name):_name(name){cout << "Person() " << endl;}
protected:string _name;
};

在派生类中如何初始化成员?
下面是❌示范

class Student:public Person
{
public:Student(int stuid=1001,const char* name="peter"):_name(name)//这里这样写会直接报错,有红色波浪线的那种,只是这里看不出来,_stuid(stuid){cout << "Student()" << endl;}
protected:int _stuid;
};

我们不能够直接拿父类的成员出来单独进行初始化,父类的初始化要看作是一个整体,可以理解为父类的成员是隐藏在子类中的自定义类型成员,然而在初始化时,自定义类型需要走它的默认构造,即使我们不在初始化列表显示写,它也会走初始化列表,然而这里我们没有默认的Person构造函数,所以我们需要显示调用这个构造,我们看下面的正确写法

✔写法
我们显示调用Person类的构造来初始化从Person那边继承过来的成员变量就行了,这里就充分体现了父类的初始化要看成是一个整体

class Student:public Person
{
public:Student(int stuid=1001,const  char* name="peter"):Person(name)//如果这个构造函数有多个参数,那我们就传多个参数,看具体构造函数来传参,_stuid(stuid){cout << "Student()" << endl;}
protected://Person _p;//父类的成员就好似这样,需要我们走Person的构造,不能单独初始化里面的成员int _stuid;
};

当然除了上面这种写法我们还可以去父类自己写一个无参默认构造,这里我就不做演示了,如果不会可以评论,我再进行补充✍

总结:

派生类的初始化=父类+自己(内置类型和自定义类型),父类调用父类的构造函数初始化自己(这里体现了复用),在派生类中,要把父类成员当成一个整体的自定义类型成员,子类的其他成员和以前一样(对内置类型不做处理,对自定义类型去调它的默认构造)

形象的理解一下:父类是一个整体的概念

class BB
{
public:BB(int num,const char* name):_p(name)//会在初始化列表调用Person的构造函数来初始name,_num(num){}
private:Person _p;//这里显示有Person的对象int _num;
};

②派生类的拷贝构造函数

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

以下面这个父类来举例:

class Person
{
public:Person(const char* name = "hhh"):_name(name){cout << "Person() " << endl;}Person(const Person& p)//拷贝构造:_name(p._name){}
protected:string _name;
};

2.1、子类中不显示写拷贝构造,就使用编译器默认生成的拷贝构造

class Student :public Person
{
public:Student(int num=1001,const char* name="peter"):_num(num),Person(name){}
protected:int _num;
};
int main()
{Student s(1002, "okk");Student s1(s);return 0;
}

在实现用s拷贝s1时,派生类的拷贝构造和上面我们所说的默认构造有异曲同工之妙,他们都把父类成员当成一个整体的自定义类型成员,在走拷贝构造时,会去调用自定义类的拷贝构造

2.2、假如派生类需要写拷贝构造完成一些深拷贝,那我们要显示的写出拷贝构造,要怎么写父类的那一块呢?

class Student :public Person
{
public:Student(const Student& s):_num(s._num),Person(s)//显示调用基类的拷贝构造函数,用s来初始化Person部分 {}Student(int num=1001,const char* name="peter"):_num(num),Person(name){}
protected:int _num;
};

另外这里我们要知道,当一个派生类(如Student)的对象被创建时,其基类(如Person)的部分会首先被初始化。这是对象构造过程的一部分,它确保基类部分在派生类部分之前处于有效状态

③派生类的赋值构造

派生类的operator=必须要调用基类的operator=完成基类的复制

基类

class Person
{
public:Person(const char* name = "hhh"):_name(name){cout << "Person() " << endl;}Person(const Person& p):_name(p._name){}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}protected:string _name;
};

子类

class Student :public Person
{
public:Student(const Student& s):_num(s._num),Person(s){}Student(int num=1001,const char* name="peter"):_num(num),Person(name){}Student& operator=(const Student& s){if (this != &s){//operator=(s);//在子类中这样调用父类中的赋值构造是不对的,他们函数名相同,会隐藏掉父类的operator=函数//这里如果这样写,会一直反复调用子类中的operator=,这样会栈溢出//如果想调到父类的operator=函数可以显示调用:Person::operator=(s);Person::operator=(s);_num = s._num;}return *this;}
protected:int _num;
};

④派生类的析构函数

派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

class Person
{
public:Person(const char* name = "hhh"):_name(name){cout << "Person() " << endl;}Person(const Person& p):_name(p._name){}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 Student& s):_num(s._num),Person(s){}Student(int num=1001,const char* name="peter"):_num(num),Person(name){}Student& operator=(const Student& s){if (this != &s){Person::operator=(s);_num = s._num;}return *this;}~Student(){//~Person();//这里不能直接访问父类的析构函数,因为后续多态的需要,析构函数名字会被统一处理成destructor//所以这里析构也是被隐藏了,根本找不到这个析构名,所以直接报红色波浪线了//如果你想访问也可以,就显示调用他就好了Person::~Person();cout << "~Student()" << endl;}
Student s(1002, "okk");

如果我在子类析构函数中显示调用父类的析构函数,就会出问题1
在这里插入图片描述
这里我们父类的构造只构造了一次,却析构了两次,这样会造成不可预料的问题,所以我们就不该显示的写父类的析构函数

问题2:

~Student()
{Person::~Person();cout<<_name<<endl;//这里是父类的成员cout<<"~Student()"<<endl;
}

还有就是,如果我们先析构了父类,但是我们还需要用到父类的成员就会出现访问不到的情况,或者是其他不可预料的问题,在继承机制中,子类的析构函数通常会自动调用其父类的析构函数,所以父类的析构不需要我们显示写,不要画蛇添足

派生类对象析构清理先调用派生类析构再调基类的析构,要保证这个原则,所以我们不能显示调用父类的析构,将上面的子类析构函数改成:

~Student()
{cout<<"~Student()"<<endl;
}

总结:
派生类对象在初始化时:先父后子(如果你不信,可以调试看一下)
派生类对象在析构时:先子后父(这个就是继承机制的原因了)

继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;//cout << s._stuNum << endl;Display()函数是父类的友元,并不是子类的友元,不能访问子类的私有或者保护
}

如果你需要访问子类和父类的私有成员和保护成员,那你可以让这个函数即是父类的友元,也是子类的友元,一个函数可以同时是多个类的友元

继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例

注意:静态成员是属于类本身的,而不是类的实例(对象)的

class Person
{
public:Person() { ++_count; }
protected:string _name; // 姓名
public:static int _count; // 统计人的个数。---统计Person及其Person对象一共产生了多少个
};
int Person::_count = 0;
//注意静态成员要在外面定义,定义的时候才会为他开空间
//由于静态成员是属于类本身的,而不是类的任何实例,所以它们需要有一个唯一的存储空间
//类外的定义确保了这一点class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
int main()
{//_count 静态成员只有一份,当前类和它的派生类共用一个//父类静态成员属于当前类,也属于当前类的所有派生类cout << &(Person::_count) << endl;cout << &(Student::_count) << endl;cout << &(Graduate::_count) << endl;return 0;
}

由于Student类继承了Person类,所以他可以使用这个静态_count成员,至于Graduate 类他继承的是Student类,但是Student类继承了Person类,所以Graduate也可以使用_count成员,上面分别是从这三个类中找到_count对象并取出它的地址,打印出来我们会发现这是同一个地址,这就更验证了 父类静态成员属于当前类,也属于当前类的所有派生类

有了这个特性之后,我们可以用他来求父类在一个程序中总共创建了多少个对象,在构造函数里面加上_count++就可以统计出该程序从运行到结束一共创建了多少个对象,如果只想知道现在还存在的对象一共有多少个,就可以在析构函数里面写上_count--


本篇暂且先到这里,我们下篇见✋

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

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

相关文章

探索Python中的随机数生成与统计分析

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、随机数的魅力与实用性 1. 随机数生成基础 2. 批量生成随机数 二、随机数的高级应用&a…

windows中每日定时执行python脚本,解决问题

由于需要一个每天定时执行的任务&#xff0c;所以需要定时启动&#xff0c;网上看了很多方法&#xff0c;感觉不能在python脚本种写个while true 定时执行&#xff0c;占资源不说还不可靠。 最后考虑通过系统工具定时启动&#xff0c;发现linux中有crontab&#xff0c;windows…

前端 CSS 经典:水波进度样式

前言&#xff1a;简单实现水波进度样式&#xff0c;简单好看。 效果图&#xff1a; 代码实现&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"utf-8" /><meta http-equiv"X-UA-Compatible" cont…

git:Unable to negotiate问题解决

场景说明&#xff1a; 安装了Gitblit(自架的代码仓库服务)发现部分电脑无法推代码&#xff0c;报错误如下&#xff1a; Unable to negotiate with **** port 22: no matching host key type found. Their offer: ssh-rsa 并排队了账户权限问题。 解决方案&#xff1a; 1.打开问…

5.28 学习总结

一.CSS学习(一) 一、CSS简介 1、什么是CSS CSS&#xff1a;Cascading Style Sheet 层叠样式表是一组样式设置的规则&#xff0c;用于控制页面的外观样式 2、为什么使用CSS 实现内容与样式的分离&#xff0c;便于团队开发样式复用&#xff0c;便于网站的后期维护页面的精确…

iptables练习题

目录 练习题1. 显示当前的iptables规则2. 允许所有来自192.168.1.0/24的TCP流量到本机的22端口&#xff08;SSH&#xff09;3. 禁止所有来自10.0.0.0/8的ICMP流量4. 允许所有出站流量5. 拒绝所有来自外部的HTTP流量&#xff08;80端口&#xff0c;tcp协议&#xff09;6. 删除IN…

数据恢复与取证软件: WinHex 与 X-Ways Forensics 不同许可证功能区别

天津鸿萌科贸发展有限公司从事数据安全业务20余年&#xff0c;在数据恢复、数据取证、数据备份等领域有丰富的案例经验、专业技术及良好的行业口碑。同时&#xff0c;公司面向取证机构及数据恢复公司&#xff0c;提供数据恢复实验室建设方案&#xff0c;包含数据恢复硬件设备及…

期货学习笔记-斐波那契学习1

斐波那契数列介绍 斐波那契数列是1、1、2、3、5、8、13、21、34、55、89…据说这是数学家莱昂纳多 斐波那契研究兔子繁殖时发现的一个神奇数列&#xff0c;似乎大自然在按照这个数列进行演化&#xff0c;一个斐波那契数字是由该数列相邻的前两个数字相加得到的 在斐波那契交易…

基于STM32实现智能交通灯控制系统

目录 引言环境准备智能交通灯控制系统基础代码示例&#xff1a;实现智能交通灯控制系统 GPIO控制交通灯定时器配置与使用红外传感器检测车辆用户界面与显示应用场景&#xff1a;城市交通管理与自动化控制问题解决方案与优化收尾与总结 1. 引言 本教程将详细介绍如何在STM32嵌…

14.Redis之JAVASpring客户端

1.引入依赖 此时就会引入操作 redis 的依赖了~~ 2.yml配置 spring:redis:host: 127.0.0.1port: 8888 3.准备 前面使用 jedis,是通过 Jedis 对象里的各种方法来操作 redis 的.此处Spring 中则是通过 StringRedisTemplate 来操作 redis .最原始提供的类是 RedisTemplateStrin…

洛谷 P1346电车

题目来源于&#xff1a;洛谷 解题思路&#xff1a; 可以把一个路口看作一张图中的一个点&#xff0c;轨道是图中的边&#xff08;注意&#xff1a;这是有向图&#xff09;&#xff0c;每一条边的权值就是这个边所联通的点是否需要按按钮&#xff08;需要按按钮就是1&#xff0…

关于NLTK

一、NLTK简介 下图来自NLTK官网&#xff1a;https://www.nltk.org/index.html NLTK&#xff0c;全称为Natural Language Toolkit&#xff0c;是一个用于处理和分析自然语言文本的Python库。它提供了一系列丰富的工具和资源&#xff0c;包括词汇资源&#xff08;如WordNet&am…

蓝桥楼赛第30期-Python-第三天赛题 提取电影信息题解

楼赛 第30期 Python 模块大比拼 提取电影信息 介绍 JSON&#xff08;JavaScript Object Notation, /ˈdʒeɪsən/&#xff09;是一种轻量级的数据交换格式&#xff0c;最初是作为 JavaScript 的子集被发明的&#xff0c;但目前已独立于编程语言之外&#xff0c;成为了通用的…

常见开源蜜罐系统

蜜罐系统&#xff08;Honeypot&#xff09;在信息安全领域中是一种被广泛使用的技术&#xff0c;旨在吸引和诱导黑客入侵&#xff0c;从而获取和分析攻击者的行为和手段。以下是一些常见的蜜罐系统的介绍&#xff1a; HFish开源蜜罐系统 特点&#xff1a; 多功能&#xff1a;支…

Scrapy框架简单介绍及Scrapy项目编写详细步骤(Scrapy框架爬取豆瓣网站示例)

引言 Scrapy是一个用Python编写的开源、功能强大的网络爬虫框架&#xff0c;专为网页抓取和数据提取设计。它允许开发者高效地从网站上抓取所需的数据&#xff0c;并通过一系列可扩展和可配置的组件来处理这些数据。Scrapy框架的核心组成部分包括&#xff1a; Scrapy Engine&…

H4022 12V24V36V40V4A同步降压芯片 Buck-DCDC 高效率95%

H4022 40V4A同步降压芯片是一款Buck-DCDC转换器&#xff0c;其高效率、高稳定性。以下是对该产品的详细分析&#xff1a; 一、产品优势 高效率&#xff1a;H4022的转换效率高达95%&#xff0c;这主要得益于其同步降压技术。同步降压技术相较于传统的异步降压技术&#xff0c;能…

macOS上用Qt creator编译并跑shotcut

1 简介 Shotcut是一个开源的跨平台的视频编辑软件&#xff0c;支持WIN/MACOS/LINUX等平台&#xff0c;由于该项目的编译较为麻烦&#xff0c;踩坑几许&#xff0c;因此写此文章记录完整编译构建过程&#xff0c;后续按此法编译&#xff0c;可减少走弯路&#xff0c;提高生产力。…

【NumPy】全面解析NumPy的bitwise_xor函数:高效按位异或操作指南

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

民国漫画杂志《时代漫画》第29期.PDF

时代漫画29.PDF: https://url03.ctfile.com/f/1779803-1248635405-bf3c87?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

代码文本编辑器-小白教程(Sublime text, Notepad++ Acode下载安装与使用)

代码文本编辑器-小白教程&#xff08;Sublime text, Notepad Acode下载安装与使用&#xff09; 1. Windows平台和Linux平台1.1 Sublime text1.2 Notepad 2. 安卓平台 Acode参考资料 1. Windows平台和Linux平台 1.1 Sublime text 一、安装教程 1、打开Sublime Text官网下载安…