Cpp类和对象(中)(4)

文章目录

  • 前言
  • 一、类的六个默认成员函数
  • 二、构造函数
    • 构造函数的概念
    • 构造函数的特性
    • 构造函数的两种分类
    • 编译器默认生成构造函数意义及相关问题
    • C++11打的补丁
  • 三、析构函数
    • 析构函数的概念
    • 析构函数的特性
    • 验证是否会自动调用析构函数
    • 验证析构函数对于内置与自定义类型处理
    • 验证先定义后析构,后定义先析构
  • 四、拷贝构造函数
    • 拷贝构造函数的概念
    • 拷贝构造函数的特性
      • 拷贝构造函数为什么只有一个参数?
      • 为什么传值会引发无穷递归调用呢?
      • 什么是默认拷贝构造函数的浅拷贝,那何为深拷贝?
    • 拷贝构造函数的使用场景
  • 总结


前言

  来了来了,事先声明本篇文章量大且深
  不可垂头丧气,也不可掉以轻心
  冲锋!


一、类的六个默认成员函数

class Date {}; // 空类

如上,一个类中什么成员都没有,我们简称其为空类,可对于空类,并不是真的什么都没有,编译器会自动默认生成以下六个默认成员函数:
在这里插入图片描述

其实,这有点像我们之前的缺省,你若不写,编译器会帮你自动生成;反之你若是写了,编译器就不生成了

二、构造函数

构造函数的概念

 相信你一定写过以下代码:

Stack st1;
st1.Push(1);

 也就是,创建一个栈变量,然后直接压栈一个数,这在Cpp中确实没问题,但我们当初学C语言的时候,假若真这么做,早就出错了,原因就在于st1并未被初始化

 至于在Cpp中这么做就没问题,显然肯定是完成了初始化,可既然你没这么做,那肯定就是编译器做的,具体怎么做?靠得就是构造函数

生活没有那么一帆风顺,如果你这么觉得了,肯定是有人为你负重前行

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

这里的翻译有很大问题,其实它并不是用来构造的,叫是这么叫,但是你心里要把它当成“初始化函数”,其目的不是开辟空间创建对象,而是对象初始化

构造函数的特性

构造函数特性

  1. 函数名与类名相同
  2. 无返回值 -> 这里所说的构造函数无返回值是真的无返回值,而不是说返回值为void
  3. 对象实例化时,编译器自动调用对应的构造函数 -> 当你用类创建一个对象时,编译器会自动调用该类的构造函数对新创建的变量进行初始化
  4. 构造函数支持函数重载 -> 这意味着你可以有多种初始化对象的方式,编译器会根据你所传递的参数去调用对应的构造函数,也就是说构造函数有好几种分类,我们下边会接着讲
  5. 无参的构造函数、全缺省的构造函数以及我们不写编译器自动生成的构造函数都称为默认构造函数,并且默认构造函数只能有一个 -> 请注意!不是我们不写,编译器自动生成的构造函数才叫默认构造函数,事实上,更贴切的说法是不传参构造函数,你细品一下不传参的意思

构造函数的两种分类

 大体来说,构造函数一共有显式构造函数和默认构造函数,没那么玄乎,一个传参一个不传参而已,就是那么简单,我们来看以下代码:

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day;}
private:int _year;int _month;int _day;
};int main()
{Date d1; // 调用默认构造函数Date d2(2024, 9, 21); // 显式调用构造函数Date d3(); // errd1.Print();d2.Print();return 0;
}

 我们发现创建d1变量,因为没传参,调用默认构造函数,也就是全缺省,打印出来1900-1-1
 而d2是显式传参,调用传参构造函数,打印出来2024-9-21
 而d3很容易让人产生误解,实际上这会导致程序错误,这是由于编译器很难区分对象实例化是调用无参构造函数还是函数声明,所以我们要求对象实例化调用无参构造函数,不允许添加括号,d1才是调用无参构造函数的正确方法

 同时我们还看到,在这里有参和无参构造函数被放到一个函数里面,即全缺省函数,实际上我们鼓励这种做法,这太方便了,无参、缺省、有参都可以被这个函数所囊括

编译器默认生成构造函数意义及相关问题

 你可能会问,假如我们不写构造函数,编译器不是自己会生成一个吗,这是不是意味着我们可以对其放任不管?

先放结论,不能,基本上绝大多数的构造函数都需要我们自己显式呈现

 我把上面代码的构造函数注释,创建d4,Print()打印如下:
在这里插入图片描述

是随机值! 这就不得不提到编译器默认生成的构造函数对于内置/自定义类型处理方式了:

C/C++把类型分成 内置类型(基本类型) 和 自定义类型 。内置类型就是语言提供的数据类型(int/char/double ),自定义类型就是自己通过关键字定义的类型(struct /class/union),你先有这个概念

对于内置与自定义类型处理:

  1. 对内置类型不做处理
  2. 对自定义类型的成员,会去调用他们的默认构造(无参构造函数、全缺省构造函数、我们没有写编译器默认生成的构成函数)

不如我们来个具体例子吧,可以让你对这个类型处理方式有个深刻认识:

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};int main()
{Date d;return 0;
}

 打开监视,可以观察到 d 的三个内置类型都是随机值不做处理,而一个自定义类型 _t ,我们调用了它的构造函数Time(),从输出我们打印了 Time() 以及 _t 的三个成员变量都被初始化就可以看出来了
在这里插入图片描述

 这也说明了为什么绝大多数情况下我们还是要自己写构造函数,因为就算是自定义类型,套娃最后还是内置类型,而C++编译器自己默认生成的构造函数对内置类型是没有规定要不要处理的

但是存在即合理,编译器的默认构造函数也有应用场景,就是没有内置类型,成员变量无需赋值的时候,比如:

class MyQueue
{// ...
private:stack _phst;stack _popst;
}

C++11打的补丁

 前面说了,编译器自己生成的默认构造函数对成员变量不做处理,基于这个特性,C++11打了一个补丁,内置类型的成员变量在声明时可以给默认值,举例如下:
在这里插入图片描述

三、析构函数

  同样的,我们之前用C语言写栈的时候,经常忘记加上Destroy()函数来释放申请的资源,这很不好,会造成内存泄露

报错中止就像人得了急性病一样,这能治,就怕慢性病拖到无法挽回了才麻烦,这就是内存泄露的恐怖之处
就像图中所示,st1和d1都开在main函数的栈帧上,可st1申请了动态资源,这需要释放
在这里插入图片描述

析构函数的概念

  析构函数与构造函数功能相反,该函数任务并不是完成对象本身销毁(局部对象的销毁时由编译器完成),而是对象在销毁时自动调用析构函数,完成对象中资源的清理工作,作用于对象出了作用域的时候

析构函数的特性

  1. 析构函数的函数名是在类名前加上字符 ’ ~ ’ -> ~Date() {}
  2. 析构函数无参数,无返回值 -> 也是真的无返回值,而不是返回值为void
  3. 对象生命周期结束时,C++编译器会自动调用析构函数 -> 大大降低了C语言中栈空间忘记释放问题的发生,因为当栈对象生命周期结束时,C++编译器会自动调用析构函数对其栈空间进行释放
  4. 一个类有且只有一个析构函数 -> 若未显示定义系统会自动生成默认的析构函数,机制是编译器自动生成的析构函数对内置类型不做处理(交给操作系统)。对于自定义类型,编译器会再去调用它们自己的默认析构函数
  5. 先构造的后析构,后构造的先析构 -> 因为对象是定义在函数中的函数调用会建立栈帧,栈帧中的对象构造和析构也要符合先进后出的原则在这里插入图片描述
    我们可以来一一验证:

验证是否会自动调用析构函数

在这里插入图片描述

验证析构函数对于内置与自定义类型处理

在这里插入图片描述

输出如下:
在这里插入图片描述

验证先定义后析构,后定义先析构

在这里插入图片描述

  1. 局部对象(后定义先析构)
  2. 局部的静态
  3. 全局对象(后定义先析构)

可以得出,以上就是销毁顺序

若有向系统申请动态资源,那么就要考虑自己写显式析构函数了

四、拷贝构造函数

  拷贝构造函数也是构造函数的一种,这进一步说明了函数是可以重载的

拷贝构造函数的概念

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

拷贝构造函数的特性

  1. 拷贝构造函数本身属于构造函数一种重载,同类型对象进行初始化
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用(编译器可能会强制检查
  3. 若未显示定义拷贝构造函数,系统将生成默认的拷贝构造函数 -> 编译器自动生成的拷贝构造函数对内置类型会完成浅拷贝(值拷贝),对于自定义类型,编译器会再去调用它们自己的默认拷贝构造函数

拷贝构造函数为什么只有一个参数?

  拷贝构造函数需要拷贝对象参数即可,由于存在this指针,将调用对象地址传进来(编译器会自动处理)

为什么传值会引发无穷递归调用呢?

在这里插入图片描述
  请看上图,首先我们想要将d1拷贝给d2,可是传值的话,因为不是内置类型,所以d1赋值给d形参其实也要拷贝,即要d(d1),可是,要把d1给d形参,又要传值,又要把d1传给又一个d形参,按图形语言就像这样:
在这里插入图片描述

传值过程需要开辟空间去拷贝实参数据,这里就需要调用拷贝函数。

传值需要调用拷贝构造,调用拷贝构造需要传值
哈哈,这套娃!

什么是默认拷贝构造函数的浅拷贝,那何为深拷贝?

  其实,若未显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按照内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝(值拷贝),有点像memset

 我们不写拷贝构造函数,采用编译器默认生成的,看看效果:
在这里插入图片描述

Date d2 = d1; 与 Date d3(d2); 是等价的

  可有些时候,按字节复制也会出问题,比如说要申请动态内存的Stack类,假如创建两个栈st1、st2,那我们就会面临以下问题:
在这里插入图片描述
  程序报错的原因是重复析构,可是就算这个不报错,从逻辑上也有很大问题
  比如说st1压栈一个数,这与st2无关,两者交互了,这是我们所不愿意遇见的,终其根本就是两者动态数组指向了同一内存空间

  这才是我们愿意见到的:

在这里插入图片描述
  这很简单,无非就是自己写一下_array的动态开辟,其他直接浅拷贝过去:

Stack(const Stack& st)
{_array = (DataType*)malloc(st._capacity * sizeof(DataType));if (_array == nullptr){perror("malloc申请空间失败");return;}memcpy(_array, st._array, st._size * sizeof(DataType));//要记得把原来的数据拷贝过去_size = st._size;_capacity = st._capacity;
}

  所以说,关于是否显式写拷贝构造函数,答案是类中没有涉及资源申请,拷贝构造是否写都是可以;类中一旦涉及资源申请,拷贝构造一定要写,否则就是浅拷贝

拷贝构造函数的使用场景

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象

 来个实际代码感受一下传值返回吧!

class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;
}
private:int _year;int _month;int _day;
};Date Test(Date d)
{Date temp(d);return temp;
}int main()
{Date d1(2022,1,13);Test(d1);return 0;
}

在这里插入图片描述

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


总结

  本节内容好多,其实还没完,再开一篇吧!

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

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

相关文章

LLM - 理解 多模态大语言模型(MLLM) 的 对齐微调(Alignment) 与相关技术 (五)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/142354652 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 完备(F…

为什么git有些commit记录,只有git reflog可以看到,git log看不到?

文章目录 原因分析1. git log 只能显示 **可达的** 提交2. git reflog 记录所有引用的变更 常见导致 git log 看不到提交的原因1. git reset 操作2. git rebase 操作3. 分支删除4. git commit --amend5. 垃圾回收&#xff08;GC&#xff09;* 如何恢复 git log 看不到的提交&am…

带你0到1之QT编程:十七、Http协议实战,实现一个简单服务器和一个客户端进行http协议通信

此为QT编程的第十七谈&#xff01;关注我&#xff0c;带你快速学习QT编程的学习路线&#xff01; 每一篇的技术点都是很很重要&#xff01;很重要&#xff01;很重要&#xff01;但不冗余&#xff01; 我们通常采取总-分-总和生活化的讲解方式来阐述一个知识点&#xff01; …

DEPLOT: One-shot visual language reasoning by plot-to-table translation论文阅读

文章链接&#xff1a;https://arxiv.org/abs/2308.01979http://arxiv.org/abs/2212.10505https://arxiv.org/abs/2308.01979 源码链接&#xff1a;https://github.com/cse-ai-lab/RealCQA 启发&#xff1a;two-stage方法可能是未来主要研究方向&#xff0c;能够增强模型可解释…

利用AI增强现实开发:基于CoreML的深度学习图像场景识别实战教程

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

网络安全-利用 Apache Mod CGI

目录 一、环境 二、开始操作 三、总结 一、环境 蚁剑官网拉取 二、开始操作 蚁剑连接 一样终端命令不能执行 可以看到putenv已经禁用 我们开始一下&#xff0c;跳入一个新终端且可以执行命令 我们具体看一下干了什么事情 上传了一个htaccess这个文件的作用是让以后所有ant文…

LAMP架构搭建

目录 LAMP架构搭建 编译安装Apache httpd服务 1、需要的安装包 2、关闭防火墙和核心防护 3、安装环境依赖包 4、配置软件模块 5、编译及安装 6、优化配置文件路径&#xff08;可不做&#xff09; 7、添加httpd系统服务 8、修改httpd 服务配置文件 9、浏览器访问验证…

【Pycharm】Pycharm创建Django提示pip版本需要升级

目录 1、现象 2、分析 3、本质 前言&#xff1a;经常使用pycharm创建django、flask等项目时候提示pip版本需要升级&#xff0c;解决方案 1、现象 使用Pycharm创建Django项目提示安装Django超时&#xff0c;报错建议pip升级22升级到24 2、分析 之前使用命令升级了pip到了24…

linux 基础(一)mkdir、ls、vi、ifconfig

1、linux简介 linux是一个操作系统&#xff08;os: operating system&#xff09; 中国有没有自己的操作系统&#xff08;华为鸿蒙HarmonyOS&#xff0c;阿里龙蜥(Anolis) OS 8、百度DuerOS都有&#xff09; 计算机组的组成&#xff1a;硬件软件 硬件&#xff1a;运算器&am…

思通数科开源产品:免费的AI视频监控卫士安装指南

准备运行环境&#xff1a; 确保您的服务器或计算机安装了Ubuntu 18.04 LTS操作系统。 按照产品要求&#xff0c;安装以下软件&#xff1a; - Python 3.9 - Java JDK 1.8 - MySQL 5.5 - Redis 2.7 - Elasticsearch 8.14 - FFmpeg 4.1.1 - RabbitMQ 3.13.2 - Minio &#xff08;…

240912-设置WSL中的Ollama可在局域网访问

A. 最终效果 B. 设置Ollama&#xff08;前提&#xff09; sudo vim /etc/systemd/system/ollama.service[Unit] DescriptionOllama Service Afternetwork-online.target[Service] ExecStart/usr/bin/ollama serve Userollama Groupollama Restartalways RestartSec3 Environme…

​OpenAI最强模型o1系列:开启人工智能推理新时代

前不久OpenAI发布全新模型——o1模型&#xff0c;也就是业界说的“草莓模型”&#xff0c;包含三款型号&#xff1a;OpenAI o1、OpenAI o1-preview和OpenAI o1-mini。 其中&#xff0c;OpenAI o1-mini和 o1-preview已经对用户开放使用&#xff1a; OpenAI o1&#xff1a;高级推…

C语言之预处理详解(完结撒花)

目录 前言 一、预定义符号 二、#define 定义常量 三、#define定义宏 四、宏与函数的对比 五、#和## 运算符 六、命名约定 七、#undef 八、条件编译 九、头文件的包含 总结 前言 本文为我的C语言系列的最后一篇文章&#xff0c;主要讲述了#define定义和宏、#和##运算符、各种条件…

植物大战僵尸【源代码分享+核心思路讲解】

植物大战僵尸已经正式完结&#xff0c;今天和大家分享一下&#xff0c;话不多说&#xff0c;直接上链接&#xff01;&#xff01;&#xff01;&#xff08;如果大家在运行这个游戏遇到了问题或者bug&#xff0c;那么请私我谢谢&#xff09; 大家写的时候可以参考一下我的代码思…

前端工程化4:从0到1构建完整的前端监控平台

前言 一套完整的前端监控系统的主要部分&#xff1a; 数据上报方式数据上送时机性能数据采集错误数据采集用户行为采集定制化指标监控sdk 监控的目的&#xff1a; 一、数据上报方式 本文的方案是&#xff0c;优先navigator.sendBeacon&#xff0c;降级使用1x1像素gif图片…

群晖NAS使用Docker本地部署网页版Ubuntu系统并实现无公网IP远程访问

文章目录 前言1. 下载Docker-Webtop镜像2. 运行Docker-Webtop镜像3. 本地访问网页版Linux系统4. 群晖NAS安装Cpolar工具5. 配置异地访问Linux系统6. 异地远程访问Linux系统7. 固定异地访问的公网地址 前言 本文旨在详细介绍如何在群晖NAS部署docker-webtop&#xff0c;并结合c…

《财富之眼:用经济思维看清世界》pdf电子书下载

《财富之眼&#xff1a;用经济思维看清世界》pdf电子书下载 内容简介 一切社会现象都是经济现象&#xff0c;我们只能赚到自己认知范围内的 钱。我国社会主要矛盾已经转化为人民日益增长的美好生活需要和不 平衡不充分的发展之间的矛盾&#xff0c;其中“不平衡不充分”很大程…

【网络】高级IO——Reactor版TCP服务器

目录 1.什么是Reactor 1.1.餐厅里的Reactor模式 2.Reactor的由来 2.1.单 Reactor 单进程 / 线程 2.2.单 Reactor 多线程 / 多进程 2.3.多 Reactor 多进程 / 线程 3.实现单 Reactor 单进程版本的TCP服务器 3.1.Connection类 3.2.TcpServer类 3.3.Connection的真正用处 …

C++—vector的常见接口与用法(正式进入STL)

目录 0.提醒 1.介绍 2.构造 1.正常构造 2.默认值构造 3.调用默认构造函数构造 3.遍历 1.迭代器 2.范围for 3.下标访问 4.容量 1.capacity&#xff1a;返回当前容器的容量 2.reserve&#xff1a;如果传的k比当前容量大&#xff0c;则扩容到比k大或者等于k的数&…

Windows10安装cuda11.3.0+cudnn8.5.0,以及创建conda虚拟环境(pytorch)

1、检查电脑驱动版本为561.09&#xff0c;选择cuda版本&#xff0c;下图可知cuda版本<12.6。 nvidia-smi #查看驱动版本&#xff0c;以及最大可以安装的cuda版本 2、Anaconda3-2024.06-1-Windows-x86_64.exe下载&#xff1a; 官网&#xff1a;https://www.baidu.com/link?…