c++阶梯之类与对象(中)

目录

1.类的6个默认成员函数

2. 构造函数 

2.1 构造函数概念的引出 

2.2 构造函数的特性

3. 析构函数

3.1 析构函数的概念 

3.2 特性

未使用构造与析构的版本 

使用了构造与析构函数的版本

4. 拷贝构造函数 

4.1 拷贝构造函数的概念 

4.2 特性 

结语


本节我们来认识一些类的默认成员函数。

1.类的6个默认成员函数

如果一个类中什么都不写,我们简称它为空类

但空类中真的什么都没有吗?

不是这样的,任何类在我们什么都不写的情况下,编译器会自动生成六个默认成员函数。

默认成员函数:当用户没有显式定义时,编译器自动生成的成员函数。

2. 构造函数 

构造函数是六个默认成员函数中最为重要的成员函数。

2.1 构造函数概念的引出 

为什么要有构造函数呢?

我们来看看这段示例代码:

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void showinfo(){cout << _year << "/" << _month << "/" << _day  << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Init(2024, 2, 1);d1.showinfo();Date d2;d2.Init(1997, 1, 1);d2.showinfo();return 0;
}

对于Date类,创建对象时我们可以调用公有方法Init() 来初始化对象,但我们每次创建对象时都需要调用它,会显得有一些麻烦,那么有没有一种方法,在我们创建对象的同时就能完成对他的初始化呢?

答案是有的。构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

2.2 构造函数的特性

构造函数是特殊的成员函数,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。

我们将上面的Date类代码进行改造:

class Date
{
public:Date()//无参构造函数{}Date(int year, int month, int day)//带参构造函数{_year = year;_month = month;_day = day;}void showinfo(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;//调用无参构造函数d1.showinfo();Date d2(1997, 1, 1);//调用带参构造函数d2.showinfo();Date d3();return 0;
}

在这里我们看到,d3的用法是调用无参函数,调用无参函数是不能在对象后加括号的。 

当我们调用无参构造函数时,发现输出的成员变量都是随机值,我们接着往下看。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。

我们将显式定义的构造函数全部注释,然后再调用无参默认构造,结果如下:

6. 关于编译器生成的默认成员函数,很多朋友会有疑惑:不实现构造函数的情况下,编译器会
生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默
认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类
型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看
下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员
函数。
 

class Time
{
public:Time(){_hour = 1;_minute = 30;_second = 47;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:void showinfo(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;Time _t;
};int main()
{Date d1;d1.showinfo();return 0;
}

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值


 使用如下:

class Time
{
public:Time(){_minute = 30;_second = 47;}
private:int _hour=1;int _minute;int _second;
};
class Date
{
public:void showinfo(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year=1997;int _month=10;int _day=9;Time _t;
};int main()
{Date d1;d1.showinfo();return 0;
}

 7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数,所以默认构造函数有三种。(不需要传参就可以调用的,都可以称为默认构造函数。)

 下面这段代码就无法正常执行。

class Date
{
public:Date(){}Date(int year=2020, int month=1, int day=1){this->_year = year;this->_month = month;this->_day = day;}void showinfo(){cout << _year << "/" << _month << "/" << _day  << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;return 0;
}

 

 因为全缺省构造函数与无参构造函数都属于默认构造函数,调用不明确。默认构造函数只能有一个。

3. 析构函数

3.1 析构函数的概念 

学习了上面的构造函数,我们知道了对象是如何创建的,那么对象是如何销毁的呢?

简单来说,对象通过调用析构函数清空对象的内容,然后由编译器销毁空间。

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

3.2 特性

析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4. 对象生命周期结束时,编译系统自动调用析构函数。

这里我们借用c++实现Stack的部分代码做实例。如果想看完整代码的同学可以点击链接查看。

目录九:c++实现类封装Stack

未使用构造与析构的版本 

typedef int DataType;
class Stack
{
public://初始化void STInit(){_a = (DataType*)malloc(sizeof(DataType) * 4);if (_a == NULL){perror("malloc fail");return;}_size = 0;_capacity = 4;}void STPush(DataType x){_a[_size] = x;_size++;}//销毁void STDestory(){if (_a == NULL)return;free(_a);_a = NULL;_size = 0;_capacity = 0;}private:DataType* _a;int _size;int _capacity;
};int main()
{Stack ST;//创建对象ST.STInit();//初始化对象ST.STPush(1);//压栈ST.STPush(2);ST.STDestory();//清空对象return 0;
}

使用了构造与析构函数的版本

typedef int DataType;
class Stack
{
public://初始化Stack(){_a = (DataType*)malloc(sizeof(DataType) * 4);if (_a == NULL){perror("malloc fail");return;}_size = 0;_capacity = 4;}void STPush(DataType x){_a[_size] = x;_size++;}//销毁~Stack(){if (_a == NULL)return;free(_a);_a = NULL;_size = 0;_capacity = 0;}private:DataType* _a;int _size;int _capacity;
};int main()
{Stack ST;//创建对象,同时系统自动调用构造函数进行初始化ST.STPush(1);//压栈ST.STPush(2);return 0;//对象生命周期结束,系统自动调用析构函数清空对象内容。
}

我们看这段代码:

class Time
{
public:Time(){cout << "Time()" << endl;}~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 2020, int month = 1, int day = 1){cout << "Date()" << endl;}~Date(){cout << "~Date()" << endl;}
private:int _year;int _month;int _day;Time _t;
};int main()
{Date d1;return 0;
}

在这段代码里有两个类,Date类的成员变量中有Time类的对象,对于两个类的构造与析构我们都只是做了打印函数名的操作。

 我们看到,只是创建了一个Date类的d1对象,但却调用了两个类的构造与析构,根据打印内容,我们可以明晰函数的调用顺序。那么,为什么会出现上面这个结果呢?

内置类型成员的销毁不需要资源清理,而销毁类中的自定义类型变量时,需要调用其析构函数进行清理。

因为Time类的对象是Date类的成员变量,因此必须先创建Time类的对象,才能创建Date类对象。同时必须先调用Date类的析构函数,然后调用Date类中自定义类型的析构函数。

也可以这样理解:

//在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,
//_day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对// 所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:
main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date
类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部
调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析
构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

 6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

有兴趣的同学可以研究一下不同存储区对象的析构。

4. 拷贝构造函数 

4.1 拷贝构造函数的概念 

在现实生活中,我们见过两个一模一样的人,并称其为双胞胎。

那么在创建对象时,能不能创建一个与已存在对象一模一样的新对象呢?

在之前的学习中,我们可以用拷贝来实现,在c++中同样可以。

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

4.2 特性 

拷贝构造函数也是特殊的成员函数,其特征如下:

1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

 下面这段代码是正确示范:

class Date
{
public:Date(int year = 2020, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d)//只是拷贝,不能修改原对象的内容{_year = d._year;_month = d._month;_day = d._day;}void showinfo(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year=1997;int _month=10;int _day=9;
};int main()
{Date d1(2022, 9, 13);Date d2(d1);d2.showinfo();return 0;
}

关于第二点,为什么传值方式会无穷递归呢?

这是因为形参就是原对象的拷贝,需要调用拷贝函数,但拷贝函数需要传入对象的形参,因此构成了无限递归拷贝的现象。 

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

class Date
{
public:Date(int year = 2020, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void showinfo(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year=1997;int _month=10;int _day=9;
};int main()
{Date d1(2022, 9, 13);Date d2(d1);d2.showinfo();return 0;
}

这段代码里我们并没有显式实现拷贝构造函数,但结果依旧。

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的

既然编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
我们来看看Stack类是怎么做的

typedef int DataType;
class Stack
{
public://初始化Stack(){_a = (DataType*)malloc(sizeof(DataType) * 4);if (_a == NULL){perror("malloc fail");return;}_size = 0;_capacity = 4;}void STPush(DataType x){_a[_size] = x;_size++;}//销毁~Stack(){if (_a == NULL)return;free(_a);_a = NULL;_size = 0;_capacity = 0;}private:DataType* _a;int _size;int _capacity;
};int main()
{Stack ST;ST.STPush(1);ST.STPush(2);ST.STPush(3);Stack st(ST);return 0;
}

在这段代码里,我们并没有显式实现Stack类的拷贝构造,上面的Date类同样没有,但他们的结果却截然不同。

很明显,这样做是错的。那这是为什么呢?

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

 

5. 拷贝构造函数典型调用场景:
使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象

小建议:为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

下期再见!

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

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

相关文章

使用 PyTorch 构建 NLP 聊天机器人

一、说明 聊天机器人提供自动对话&#xff0c;可以帮助用户完成任务或寻求信息。随着深度学习的最新进展&#xff0c;聊天机器人正变得越来越具有对话性和实用性。这个全面的教程将利用 PyTorch 和 Python 从头开始构建聊天机器人&#xff0c;涵盖模型架构、数据准备、训练循环…

AIGC技术讲解以及应用的落地

简介 近期&#xff0c;火爆的“AI绘画”、图片转AI图&#xff0c;智能聊天软件ChatGPT&#xff0c;引起了人们广泛关注。人工智能潜力再次被证明&#xff0c;而这三个概念均来自同一个领域&#xff1a;AIGC。AIGC到底是什么&#xff1f;为什么如此引人关注&#xff1f;AIGC能产…

Linux ---- Shell编程之免交互

一、Here Document 多行重定向 1、Here Document定义 使用I/O重定向的方式将命令列表提供给交互式程序标准输入的一种替代品Here Document 是标准输 入的一种替代品&#xff0c;可以帮助脚本开发人员不必使用临时文件来构建输入信息&#xff0c;而是直接就地生产出一个文件…

(15)求两个整数的平均值

文章目录 每日一言题目解题思路代码结语 每日一言 现在&#xff0c;我怕的并不是那艰苦严峻的生活&#xff0c;而是不能再学习和认识我迫切想了解的世界。对我来说&#xff0c;不学习&#xff0c;毋宁死。——罗蒙诺索夫 题目 输入两个整数m和n&#xff0c;写一个函数average…

GrayLog踩坑历险记

背景 GrayLog作为ELK的替代产品&#xff0c;是新生代的日志采集框架。在一个采集节点日志的需求中&#xff0c;因为节点很多&#xff0c;产生的日志也很多&#xff0c;因此尝试了使用GrayLog进行日志的采集。下面记录一下使用GrayLog中遇到的坑和解决方案。 一、部署与启动 …

(十三)Java开发扩展之软件包与安装——JDK和MySQL

文章目录 1、RPM1.1、什么是RPM&#xff1f;1.2、RPM包的名称格式1.2.1、RPM查询命令1.2.2、RPM卸载命令1.2.3、RPM安装命令 2、YUM2.1、什么是YUM?2.2、yum安装程序命令 3、安装JDK4、安装MySQL 1、RPM 1.1、什么是RPM&#xff1f; RPM&#xff08;RedHat Package Manager&a…

《学成在线》微服务实战项目实操笔记系列(P1~P49)【上】

《学成在线》项目实操笔记系列【上】&#xff0c;跟视频的每一P对应&#xff0c;全系列12万字&#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳&#xff0c;参考这篇&#xff0c;相信会带给你极大启发。同时也欢迎大家提问与讨论&#xff0c;我会尽力帮大家解…

阿里面试:Seata如何实现RC?保证事务的隔离性?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; Seata 如何实现 RC &#xff1f;保证事务的隔离性&#xff1…

Transformer实战-系列教程3:Vision Transformer 源码解读1

&#x1f6a9;&#x1f6a9;&#x1f6a9;Transformer实战-系列教程总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 Vision Transformer 源码解读1 Vision Transformer 源码解读2 Vision Transformer 源码解读3 Vis…

kubesphere部署k8s-v1.23.10

功能&#xff1a; &#x1f578; 部署 Kubernetes 集群 &#x1f517; Kubernetes 多集群管理 &#x1f916; Kubernetes DevOps &#x1f50e; 云原生可观测性 &#x1f9e9; 基于 Istio 的微服务治理 &#x1f4bb; 应用商店 &#x1f4a1; Kubernetes 边缘节点管理 &#x1…

latex论文写作遇到的问题

图一&#xff1a; 图二&#xff1a; 图三&#xff1a; 使用模版的时候将图一转为图二&#xff1a;在.tex文件开头导言部分加上&#xff1a; \usepackage{titletoc} \titlecontents{section}[0pt]{\addvspace{1.5pt}\filright\bf}{\contentspush{第\thecontentslabel\ 章\qu…

2024.2.4 awd总结

防御阶段 感觉打了几次awd&#xff0c;前面阶段还算比较熟练 1.ssh连接 靶机登录 修改密码 [root8 ~]# passwd Changing password for user root. New password: Retype new password: 2.xftp连接 备份网站源码 我觉得这步还是非常重要的&#xff0c;万一后面被删站。。…

【幻兽帕鲁】如何快速部署私人服务器

看了许多关于如何部署服务器的&#xff0c;大部分都是要买阿里云或者腾讯云的服务器并且至少四核以上才能保证流畅运行。 但是对于想搭建私服但又没有技术的小白&#xff0c;确实是有点难度了。购买云服务器后还要配置服务器&#xff0c;配置OpenVPN、PalServer&#xff0c;doc…

解锁亚马逊测评防关联新技术:亚马逊鲲鹏系统

在亚马逊测评的过程中&#xff0c;一直以来都存在着一些技术难题&#xff0c;特别是在模拟买家行为时需要考虑诸多因素&#xff0c;包括关键词搜索、IP地址切换以及防关联等。然而&#xff0c;最新的技术突破&#xff0c;亚马逊鲲鹏系统正是为了解决这些问题而诞生的。 首先&am…

No matching client found for package name ‘com.unity3d.player‘

2024年2月5日更新 下面的一系列操作最终可能都无用&#xff0c;大致这问题出现原因是我在Unity采用了Android方式接入Firebase&#xff0c;而Android接入实际上和Unity接入方式有配置上的不一样&#xff0c;我就是多做了几步操作如下。https://firebase.google.com/docs/androi…

【Java】Redis入门

1. Redis入门 1.1 Redis简介 Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件。 官网&#xff1a;https://redis.io 中文网&#xff1a;https://www.redis.net.cn/ key-value结构存储&#xff1a; 主要特点&#xff1a; 基于内…

docker复习笔记01(小滴课堂)安装+部署mysql

查看内核版本。 关闭防火墙&#xff1a; 查看docker版本&#xff1a; 下载阿里yum源&#xff1a; 再看一下yum版本都有哪些&#xff1a; 我们可以看的docker-ce了。 安装它&#xff1a; 设置docker服务开机启动&#xff1a; 更新日志文件&#xff1a; 启动docker&#xff1a; …

CSS写渐变边框线条

box-sizing: border-box; border-top: 1px solid; border-image: linear-gradient(to right, red, blue) 1;

STM32F407移植OpenHarmony笔记9

继上一篇笔记&#xff0c;已经完成liteos内核的基本功能适配。 今天尝试启动OHOS和XTS兼容性测试。 如何启动OHOS&#xff1f; OHOS系统初始化接口是OHOS_SystemInit(void)&#xff0c;在内核初始化完成后&#xff0c;就能调用。 extern void OHOS_SystemInit(void); OHOS_Sys…

JupyterLab 更换内核 使用 conda 虚拟环境

未有conda虚拟环境default先创建环境 conda create -n default python3.8 ipykernel已有conda虚拟环境default激活后安装ipykernel conda activate defaultpip install ipykernel将虚拟环境写入 jupyter notebook 的 kernel 中 python -m ipykernel install --user --name 虚…