C嘎嘎~~ [类 下篇]

news2024/10/6 6:42:15

类 下篇

  • 1.类的6个默认成员函数
  • 2.构造函数
    • 2.1 构造函数出现的原因
    • 2.2 特性
    • 2.3 深刻解读---构造函数可以重载
    • 2.4 深刻解读---默认构造函数
      • 补充:
  • 3.析构函数
    • 3.1概念
    • 3.2 特性
    • 3.3深刻解读
    • 例子 总结
  • 4.拷贝构造函数
    • 4.1 概念
    • 4.2 特性
    • 4.3深刻解读---拷贝构造是构造的一种重载
    • 4.4深刻理解---形参必须用引用来修饰
    • 4.5深刻理解---编译器自动生成的默认拷贝构造函数

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

如果一个类中什么都没有, 简称为空类
空类中真的就什么都没有吗? 并不是, 任何类在什么都不写的时候, 编译器会自动生成 6 个默认成员函数
==>默认成员函数: 用户没有显示实现, 编译器生成的成员函数称为默认成员函数
就是这个6个默认成员函数, 如果你没有定义出来, 编译器就会自动调用

2.构造函数

2.1 构造函数出现的原因

相信很多小伙伴们跟我想的一样, 当我们调用函数时, 比如: 栈, 队列, 堆… …, 我们都要初始化一下~. 一个两个的还好说, 十几个的我们就受不了了, 成百上千的更不成遑论!!
这时候就想, 如果编译器能够帮我们自己初始化函数就好了 ⇒ 这样, 我们就害怕初始化函数, 而且也不会忘记初始化函数
我们的构造函数就闪亮登场了!!

构造函数是一个特殊的成员函数, 函数名和类名相同, 对象实例化时会自动调用构造函数,以保证类中的每一个成员变量都有一个合适的初始值, 并且在对象的整个生命周期内只调用一次.

2.2 特性

构造函数 虽然名字叫做构造, 听的有点像是开辟一块空间来创建对象, 实际不然 ==> 构造函数并不会开辟空间创建对象, 而是初始化对象
在明确了构造函数的主要功能, 来了解一下构造函数的特性:

  1. 函数名 和 类名相同
  2. 没有返回值
  3. 对象实例化时编译器会自动调用对应的构造函数(分为内置类型 和 自定义类型)
  4. 构造函数可以重载
  5. 如果我们没有显示定义构造函数, 编译器会自动生成一个无参默认构造函数, 一旦我们默认显示定义, 编译器将不会生成, 而是去使用我们定义的构造函数
  6. 默认构造函数有三个: 无参构造函数, 全缺省构造函数, 编译器默认生成的构造函数, 但是编译器的默认构造函数只能是其中的一个

2.3 深刻解读—构造函数可以重载

class Date
{

public:

    // 无参构造
	Date()
	{

	}
    
    // 有参构造
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;

};

int main()
{
	Date d1;
    // 无参构造不能这样调用
    // Date d1();
	d1.Print();
	
	Date d2(2023, 5,5);
	d2.Print();

}

*****
-858993460 -858993460 -858993460
2023 5 5
*****

总结:

  • 这个构造函数虽然也是函数, 但跟普通的函数有所不同:
    一. 即使没有返回值, 也没有 void
    二. 函数名跟普通函数也有所不同, 函数名是 类名
    三. 调用无参构造函数时, 不能跟普通函数一样, 只能 类名 + 对象 <== 这个也从侧面说明了对象实例化会 自动调用构造函数
    四. 调用有参构造函数时, 就跟上面不一样了, 类名 + 对象 (参数)

疑问:

  1. 为什么调用无参构造, 不能用 类名 + 对象 ()?

因为这样会跟函数声明有冲突. 以 Date d1( )为例子, 这样编译器就不知道 这个 d1是对象还是函数名~

  1. 上面的有参构造能不能用全缺省啊?

上面的函数如果用全缺省的话, 当无参调用时, 虽然语法上是构成重载的, 但是当无参调用时, 就会构成歧义 ==> 这个到底是真的无参调用 还是 全缺省啊~

2.4 深刻解读—默认构造函数

前面我们已经知道, 我们的成员变量的类型分为两种: 内置类型 和 自定义类型. 那么编译器在面对这两种类型生成的构造函数是否相同 是我们现在主要思考的一个问题?? ==> 这里先给出结论: 自定义类型会调用它的默认构造函数, 而编译器不会对内置类型做处理.

那么问题来了: 什么是默认构造函数??
默认构造函数有三种形式: 无参构造函数, 全缺省构造函数, 编译器自动生成的构造函数

由于编译器的不同, 编译器版本的不同… …, 编译器对内置类型所做的处理也就不同. ==>在这里, 我们就默认编译器对内置类型都不做处理~~(本人偷个懒)


那问题来了,自定义类型构造函数是调用默认构造函数. 默认构造函数是从上面三种形式选一个, 那么哪一种比较好呢??
我的建议是全缺省的比较好, 相比较无参, 全缺省可以随时符合我们的选择
相比较于编译器自动生成的构造函数, 编译器生成的构造函数对内置类型不做处理~


补充:

  1. C++ 11 针对 “编译器对内置类型不处理” 做了一些 ‘补丁’ : 用户可以在类的成员变量声明的时候赋初值(注意这里还不是初始化, 因为初始化是针对对象来说的), 以此作为编译器生成的构造函数的缺省值

当然, 编译器的处理我们还是当做不放心的来看, 就跟我们没有初始化 a 数组, 这个版本下的编译器会自动处理, 有的编译器就不会处理. 这种情况下, 我们要不就对全部的内置类型都做处理, 要不然就都不做处理吧~
2.


对构造函数做一个总结吧:

3.析构函数

3.1概念

前面已经了解了构造函数, 现在我们来看一下它的反面 — 析构函数

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

3.2 特性

析构函数是特殊的成员函数, 其特性有:

  1. 函数名: ~ + 类名
  2. “三无”: 无参数, 无返回值 和 无void
  3. 一个类只能有一个析构函数. 如果没有显示定义, 系统会自动生成默认的析构函数. 因为三无⇒ 析构函数能重载 ⇒ 所以析构函数比构造函数简单一点
  4. 对象生命周期结束时, 编译器会自动调用析构函数
  5. 编译器默认生成的析构函数, 对内置类型不做处理, 对自定义类型调用它的析构函数

3.3深刻解读

  1. 对象生命周期结束时, 编译器会自动调用析构函数
class Stack
{
public:
	// ...
	
	//...

	~Stack()
	{
		cout << "~Stack()" << endl;
		_a = NULL;
		_top = 0;
		_capacity = 0;
	}

private:
	int* _a;
	int _top = 0;
	int _capacity = 4;
};

int main()
{
	Stack st1;
	Stack st2;
	Stack st3;

}

*****
~Stack()
~Stack()
~Stack()
*****

通过上面的代码可以发现: 我们并没有调用析构函数, 当对象 st1, st2, st3的生命周期结束时, 即出了main函数, 就会自动调用析构函数来清理对象里面的资源

  1. 编译器默认生成的析构函数对内置类型不做处理, 对自定义类型调用它的析构函数
  • 细心的小伙伴应该就会发现上面的析构函数其实是多此一举的. <== 因为成员变量都是内置类型, 出了函数作用域, 编译器就会自动地进行销毁, 我们就不需要写一个析构函数来处理

  • 那么, 问题来了: 什么时候适合我们写析构函数, 什么时候又可以偷个懒??
    有些老铁肯定说可以用上面构造函数的情形将成员变量 分成 内置和自定义来处理一下~, 但是大家有没有发现有些内置类型不是在栈区的, 而是在堆区或是静态区的, 这又如何区分得了自定义
    所以大方向上是以是否动态开辟空间来划分:

  1. 有动态申请资源的, 一般都是要自己写析构函数
  2. 没有动态申请资源的, 可以偷个懒
  3. 需要释放动态资源的都是自定义类型, 也可以偷个懒, 使用编译器的析构函数
  • 有些小伙伴有些问题了: 如果成员变量里面有自定义类型还有没有申请动态资源的内置类型, 可不可以偷个懒
    答案是当然可以 <== 因为没有申请动态资源的内置类型就不用析构函数区处理, 出了作用域就会销毁.

例子 总结

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}

private:

	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:

	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;

	// 自定义类型
	Time _t;

};

int main()
{
	Date d;

	return 0;
}

*****
~Time()
*****
  • 在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类生成的默认析构函数
    注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

4.拷贝构造函数

4.1 概念

双胞胎, 两个外表一样却又独立思想的两个人. 这是一个多么美妙的事情啊~~
如果在我们创建对象时, 能不能创建一个 和已存在对象一模一样的新对象呢? ⇒

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

4.2 特性

拷贝构造函数的特性有:

  1. 拷贝构造函数是构造函数的一种重载形式
  2. 拷贝构造函数的参数只有一个 且 必须是类型对象的引用. 使用传值方式编译器会直接报错, 否则将会无限递归下去
  3. 如果没有显示定义, 编译器会生成默认的拷贝构造函数. 默认拷贝构造函数是一种浅拷贝(值拷贝)的行为⇒ 将对象按内存存储按字节完成拷贝(相当于memcpy)

4.3深刻解读—拷贝构造是构造的一种重载

class Date
{

public:

	//Date(int year = 2023, int month = 5, int day = 5)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	// 拷贝构造
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1; // error C2512: “Date”: 没有合适的默认构造函数可用

}

上面的代码运行结果就可以看出: 如果我们手动写拷贝构造, 那么我们就必须要写一个构造函数 . 如果我们不写, 就会报错 — 该类没有合适的默认构造函数.
上面的代码中, 我们期望编译器给我们生成一个默认构造函数出来, 但是我们通过报错就可以看出它是把拷贝构造看成了一个构造, 它们的功能都是初始化对象, ⇒ 这样就肯定调用错误啊~~


拷贝构造是一种特殊的构造的几个点:

  1. 都是 类(形参列表), 都是无返回值, 无void
  2. 功能都是一样的: 都是在对象实例化时初始化对象

不同的几个点:
3. 形参列表不同: 构造函数⇒ 可以无参, 可以有参, 形参数量不确定; 拷贝构造⇒ 只有 且只能有一个形参, 且形参必须用 cons 类名& 来修饰
4. 构造函数是没有依赖性的, 而拷贝构造函数是有依赖性的⇒ 有一个已经存在的对象, 让这个对象来初始化新建的对象

4.4深刻理解—形参必须用引用来修饰

相信大家都有一些疑问: 自定义类型为什么非要传引用啊, 我直接传值不行吗?

我先问一下: 函数传参的实质是什么呢? ⇒ 让实参去初始化形参~

我们来假设一下如果自定义类型进行传值传参.
C++规定: 进行自定义类型传值传参时要先调用拷贝构造函数. 因为要借助拷贝构造函数去初始化形参, 然后再回到调用函数

根据上图推导过程, 就会发现⇒ 自定义类型如果是传值传参, 就会无限递归下去, 永远不会进去构造函数内部⇒ 所以编译器就会自动把他停止掉⇒ 所以, 即使我们自定义类型 传值传参就不会导致栈溢出~~

那么, 我们自定义类型该怎么传参呢??

  1. 实参传指针 — — 因为不管任何指针都是内置类型, 直接拷贝地址
  2. 形参用引用 — — 上面的主要原因是进入不了构造函数内部, 直接形参用引用修饰呗 <== 语法上, 引用并不会开辟空间, 直接进入拷贝构造函数内部

补充:

  • 建议用引用修饰形参的时候, 再加上一个 const ⇒ 这样就能自动检查是否写错方向
    在这里插入图片描述
  • 自定义类型传值传参赋值 都是要调用拷贝构造的, 因为中间要生成一个临时拷贝来充当中间量, 这时候就要用到拷贝构造~~

4.5深刻理解—编译器自动生成的默认拷贝构造函数

前面, 已经知道编译器生成的默认拷贝构造函数是对内置类型也做了处理 — 浅拷贝, 而且非常爽~, 那我们可不可以直接偷懒, 不写拷贝构造函数呢?
答案是不行的, 因为是浅拷贝, 相当于 memcpy, 如果遇到有资源申请的类型, 这样是非常危险的~

总结:

默认拷贝构造函数对内置成员完成浅拷贝, 这个也叫做值拷贝; 自定义类型会调用他的拷贝构造.

换句话说:

如果成员都是没有资源申请的类型, 建议直接偷个懒, 使用默认构造函数; 如果成员里面有资源申请的类型, 建议自己写一个深拷贝构造(深拷贝构造这个以后再说)~~


好大喜功则为宇宙汪洋所吞没,开动脑筋则领悟世界。
——帕斯卡《感想录》

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

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

相关文章

【Linux网络】传输层中UDP和TCP协议

文章目录 1、再谈端口号2、UDP协议3、TCP协议3.1 TCP协议段格式3.2 TCP的三次握手和四次挥手&#xff08;连接管理机制&#xff09;3.3 TCP的滑动窗口3.4 TCP的流量控制3.5 拥塞控制3.6 延迟应答和捎带应答3.7 面向字节流和粘包问题3.8 TCP总结 1、再谈端口号 端口号port标识一…

动态规划算法——40道leetcode实例入门到熟练

目录 t0.解题五部曲1.基础入门题目1.509. 斐波那契数2.70. 爬楼梯3.746. 使用最小花费爬楼梯4.62. 不同路径5.63. 不同路径 II6.343. 整数拆分7.96. 不同的二叉搜索树 2.背包问题1.01背包&#xff08;二维数组实现&#xff09;2.01背包&#xff08;滚动数组实现&#xff09;1.4…

OpenPCDet系列 | 4.数据集数据加载流程

文章目录 数据加载流程0. create_kitti_infos1. __getitem__函数2. prepare_data函数3. collate_batch函数数据加载流程 这里记录一下具体用到的那些数据形式,整个kitti数据集的处理框架图如下所示: 在数据集处理到获取一个batch数据的整个流程的入口如下: # 开始迭代每…

01-微服务部署2023系列-centos安装nginx和jdk教程

centos安装nginx和jdk教程 一、centos安装nginx 0、前提:安装依赖 yum -y install gcc gcc-c++ make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel 1、压缩包 下载nginx 选择Stable version: http://nginx.org/en/download.html 上传压缩包到…

yolov3核心网络模型

1. 改进概述 yolov3主要围绕核心网络Darknet优化进行。 yolov3的速度和map值比之前的算法优秀。 改进包含&#xff1a;网络结构、特征融合、先验框&#xff1a; V1 2, V25,V39、Softmax等。 softmax 2. 多scale方法改进与特征融合 3. 经典变换方法 预测中目标时&#xff0c…

Nacos配置管理、Fegin远程调用、Gateway服务网关

1.Nacos配置管理 Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#xf…

Spring-IOC

IOC概念和原理 什么是IOC 控制反转&#xff0c;为了将系统的耦合度降低&#xff0c;把对象的创建和对象直接的调用过程权限交给Spring进行管理。 IOC底层原理 XML解析 ​ 通过Java代码解析XML配置文件或者注解得到对应的类的全路径&#xff0c;获取对应的Class类 Class clazz …

Django框架之模型查询介绍及示例

本篇文章所使用模型查询都在《Django框架之模型自定义管理器》基础上讲解查询和使用示例&#xff0c;通过看前篇可以有助于理解内容。 概述 查询集&#xff1a;从数据库获取的对象集合 查询集可以有多个过滤器 过滤器就是一个函数&#xff0c;根据所给的参数限制查询集结果 …

【Vue学习笔记5】Vue3中的响应式:ref和reactive、watchEffect和watch

所谓响应式就是界面和数据同步&#xff0c;能实现实时更新。 Vue 中用过三种响应式解决方案&#xff0c;分别是 defineProperty、Proxy 和 value setter。Vue 2 使用的方案是 defineProperty API。Vue3中使用的方案是Proxy和value setter。 1. ref和reactive vue3中实现响应…

基于docker部署ELK实战- ELK文章1

选择版本为elasticsearch:7.17.9&#xff0c;kibana:7.17.9&#xff0c;logstash:7.17.9 版本一定要一致 docker hub地址&#xff1a;https://hub.docker.com elk相关文档&#xff1a;https://www.elastic.co/guide/en/kibana/7.17 一、部署单点es 1.创建网络 因为我们还需要…

iframe嵌套grafana (前端视角)

1、grafana 启动方式 ①.grafana目录鉴赏。咱们就是直接拿到配置好的grafana。咱们暂时不涉及配置数据啥。 ①.双击grafana-server.exe &#xff0c;会出现黑色命令框。 ②.在浏览器中访问 http://localhost:3000 此时就可以看到配置好的grafana 2.前端嵌入 ①.html <…

消息队列中间件 - Docker安装RabbitMQ、AMQP协议、和主要角色

概述 不管是微服务还是分布式的系统架构中&#xff0c;消息队列中间件都是不可缺少的一个重要环节&#xff0c;主流的消息队列中间件有RabbitMQ、RocketMQ等等&#xff0c;从这篇开始详细介绍以RabbitMQ为代表的消息队列中间件。 AMQP协议 AMQP协议是一个提供统一消息服务的应…

图像处理:基于cv2.inpaint()图像修补

前言 今天我们将学习如何通过一种“修复”的方法消除旧照片中的小噪音&#xff0c;笔画等。当然&#xff0c;经过我的测试你也可以将其用于削弱混杂了其他的颜色的图像。 实验背景 大多数人家都会有一些旧的的旧化照片&#xff0c;上面有黑点&#xff0c;一些笔触等。你是否…

从零实现深度学习框架——常见学习率调整策略原理与实现

引言 本着“凡我不能创造的&#xff0c;我就不能理解”的思想&#xff0c;本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架&#xff0c;该框架类似PyTorch能实现自动求导。 &#x1f4a1;系列文章完整目录&#xff1a; &#x1f449;点此&#x1f448; 要深入理解…

day24_多线程

今日内容 零、 复习昨日 一、作业 二、线程安全的集合 三、死锁 四、线程通信 五、生产者消费者 六、线程池 零、 复习昨日 见晨考 一、作业 售卖后车票 见代码二、线程安全的类[了解] StringBuffer是线程安全的,是因为每个方法都加上synchronized,即都是同步方法 StringBuil…

【前端】前后端分离ruoyi-vue初步学习

1.了解vue基础知识。 Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org) 2.将ruoyi-vue项目拉下来&#xff0c;并成功运行。 开源项目网址&#xff1a;RuoYi 若依官方网站 |后台管理系统|权限管理系统|快速开发框架|企业管理系统|开源框架|微服务框架|前后端分离框架|…

《Netty》从零开始学netty源码(五十四)之PoolThreadLocalCache

PoolThreadLocalCache 前面讲到PoolThreadCache&#xff0c;它为线程提供内存缓存&#xff0c;当线程需要分配内存时可快速从其中获取&#xff0c;在Netty中用PoolThreadLocalCache来管理PoolThreadCache&#xff0c;它的数据结构如下&#xff1a; PoolThreadLocalCache相当…

【网络】UDP网络服务器简单模拟实现

【网络】UDP网络服务器简单模拟实现 文章目录 makefile服务端udpServerudpServer.ccudpServer.hpp初始化启动测试 客户端udpClientudpClient.ccudpClient.hpp初始化启动 整体代码 UDP的封装: UDP网络服务器模拟实现&#xff1a;主要分为makefile文件进行编译 UDP客户端&#xf…

Java开发 - 不知道算不算详细的分布式事务详解

前言 前日对JUC进行了一个深度总结&#xff0c;不过现在博主能记得的也不多了&#xff0c;只是这东西&#xff0c;不是看几遍写几遍就能完全记住的&#xff0c;功夫在平时&#xff0c;很多知识点都需要反复的看&#xff0c;不光要看&#xff0c;还要用&#xff0c;这样才能了解…

在CentOS上安装Jenkins并配置Docker

文章目录 步骤1 - 安装Java 11步骤2 - 安装Jenkins步骤3 - 安装Docker步骤4 - 配置Docker Cloud步骤 5 - 验证步骤 6 - 可能会遇到的问题 在本教程中&#xff0c;我们将展示如何在CentOS上安装Jenkins和Docker&#xff0c;并将它们配置在同一台机器上&#xff0c;使Jenkins能够…