Cpp类和对象(中)(4)

news2024/11/11 22:46:23

文章目录

  • 前言
  • 一、类的六个默认成员函数
  • 二、构造函数
    • 构造函数的概念
    • 构造函数的特性
    • 构造函数的两种分类
    • 编译器默认生成构造函数意义及相关问题
    • 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(); // err

	d1.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.coloradmin.cn/o/2155496.html

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

相关文章

【学习笔记】数据结构(六 ②)

树和二叉树&#xff08;二&#xff09; 文章目录 树和二叉树&#xff08;二&#xff09;6.3.2 线索二叉树 6.4 树和森林6.4.1 树的存储结构6.4.2 森林与二叉树的转换6.4.3 树和森林的遍历 6.5 树与等价问题6.5.1 等价定义6.5.2 划分等价类的方法6.5.3 划分等价类的具体操作 - 并…

【LeetCode热题100】位运算

这篇博客先介绍了常见位运算操作&#xff0c;然后记录了关于位运算的几道题&#xff0c;包括判定字符是否唯一、丢失的数字、两整数之和、只出现一次的数字2、消失的两个数字。 在这一部分&#xff0c;我们不妨先来总结一下常见位运算操作&#xff1a; 1.基础位运算 >>…

vite 使用飞行器仪表示例

这里写自定义目录标题 环境vue代码效果图 环境 jquery npm install -S jqueryjQuery-Flight-Indicators 将img、css、js拷贝到vite工程目录中 打开 jquery.flightindicators.js&#xff0c;在文件开头加上import jQuery from "jquery"; vue代码 <template>&…

C#(.NET FrameWork库)逆向基础流程(纯小白教程)

一&#xff0c;例题链接 限时题目&#xff0c;只能用网盘来分享了&#xff0c;侵权联系删->百度网盘 请输入提取码 二&#xff0c;文件特征 使用工具查看文件信息&#xff0c; 能看到分析出文件编写语言为C#&#xff0c;使用了.NET库 三&#xff0c;做题流程 &#xff08…

浙版传媒思迈特软件大数据分析管理平台建设项目正式启动

近日&#xff0c;思迈特软件与出版发行及电商书城领域的领军企业——浙江出版传媒股份有限公司&#xff0c;正式启动大近日&#xff0c;思迈特软件与出版发行及电商书城领域的领军企业——浙江出版传媒股份有限公司&#xff0c;正式启动大数据分析管理平台建设项目。浙版传媒相…

Java之继承1

1. 继承 1.1 为什么要继承 在Java中我们定义猫类和狗类&#xff0c;如下 public class Cat {public String name;public int age;public String color;public void eat(){System.out.println(name "正在吃饭");}public void sleep(){System.out.println(name &qu…

基于pytorch本地部署微调bert模型(yelp文本分类数据集)

项目介绍 本项目使用hugging face上提供的Bert模型API&#xff0c;基于yelp数据集&#xff0c;在本地部署微调Bert模型&#xff0c;官方的文档链接为https://huggingface.co/docs/transformers/quicktour&#xff0c;但是在官方介绍中出现了太多的API调用接口&#xff0c;无法…

React 中的延迟加载

延迟加载是 Web 开发中的一种有效的性能优化技术&#xff0c;尤其是对于 React 等库和框架。它涉及仅在需要时加载组件或资源&#xff0c;无论是响应用户操作还是当元素即将在屏幕上显示时。这可以减少应用程序的初始加载时间&#xff0c;减少资源消耗&#xff0c;并改善用户体…

ETLCloud:新一代ETL数据抽取工具的定义与革新

数据集成、数据治理已经成为推动企业数字化转型的核心动力&#xff0c;现在的企业比任何时候都需要一个更为强大的新一代数据集成工具来处理、整合并转化多种数据源。 而ETL&#xff08;数据提取、转换、加载&#xff09;作为数据管理的关键步骤&#xff0c;已在企业数据架构中…

串口助手的qt实现思路

要求实现如下功能&#xff1a; 获取串口号&#xff1a; foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) {qDebug() << "Port: " << serialPortInfo.portName(); // e.g. "COM1"qDebug() <<…

【JavaEE】——线程的安全问题和解决方式

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 一&#xff1a;问题引入 二&#xff1a;问题深入 1&#xff1a;举例说明 2&#xff1a;图解双线程计算…

SwiftUI 实现关键帧动画

实现一个扫描二维码的动画效果&#xff0c;然而SwiftUI中没有提供CABasicAnimation 动画方法&#xff0c;该如何实现这种效果&#xff1f;先弄清楚什么关键帧动画&#xff0c;简单的说就是指视图从起点至终点的状态变化&#xff0c;可以是形状、位置、透明度等等 本文提供了一…

(done) 声音信号处理基础知识(3) (一个TODO: modulation 和 timbre 的关联)(强度、响度、音色)

来源&#xff1a;https://www.youtube.com/watch?vJkoysm1fHUw sound power 通常可以被认为是能量传输的速率 声源往所有方向传输的每时间单位能量 用 瓦特(W) 作为单位测量 Sound intensity 声音强度&#xff0c;每单位面积的 sound power W/m^2 人类实际上能听到非常小强…

八. 实战:CUDA-BEVFusion部署分析-coordTrans Precomputation

目录 前言0. 简述1. 案例运行2. coordTrans3. Precomputation总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习下课程第八章—实战&#xff1a;CUDA-BEVFusion部署分…

Python Selenium 自动化爬虫 + Charles Proxy 抓包

一、场景介绍 我们平常会遇到一些需要根据省、市、区查询信息的网站。 1、省市查询 比如这种&#xff0c;因为全国的省市比较多&#xff0c;手动查询工作量还是不小。 2、接口签名 有时候我们用python直接查询后台接口的话&#xff0c;会发现接口是加签名的。 而签名算法我…

keil5 MDK 最新版本官网下载(v5.40为例) ARM单片机环境搭建安装教程(STM32系列为例)

正所谓授之以鱼不如授之以渔。本文将细讲从官网下载keil5MDK来保证keil5为最新版本的实时性 &#xff08;注意新老版本可能出现版本兼容问题&#xff0c;若不放心&#xff0c;跟着老弟我一起下载5.40版本即可&#xff09; 目录 一、下载keil5 MDK 方法①:CSDN下载&#xff0…

计算机毕业设计 基于 Hadoop平台的岗位推荐系统 SpringBoot+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

【深入学习Redis丨第六篇】Redis哨兵模式与操作详解

〇、前言 哨兵是一个分布式系统&#xff0c;你可以在一个架构中运行多个哨兵进程&#xff0c;这些进程使用流言协议来接收关于Master主服务器是否下线的信息&#xff0c;并使用投票协议来决定是否执行自动故障迁移&#xff0c;以及选择哪个Slave作为新的Master。 文章目录 〇、…

Django 5 学习笔记 2024版

1. 官方中文文档 Django 文档 | Django 文档 | Django (djangoproject.com) 2. 第一个应用 博客 总目录 <1>依赖安装: pip install django <2> 创建 工程 myapp django-admin startproject myapp cd myapp <3>创建 应用 app > python manage.py s…

算法-排序算法(冒泡选择插入希尔快速归并堆计算)

1.算法概述 1.1什么是算法 算法是特定问题的求解步骤的描述&#xff0c;是独立存在的一种解决问题的思想和方法。对于算法而言计算机编程语言并不重要&#xff0c;可以用任何计算机编程语言来编写算法。 程序数据结构算法 1.2数据结构和算法的区别和联系 数据结构只是静态…