C++类和对象中(构造函数,析构函数,拷贝构造函数)详解

news2024/11/24 4:12:37

C++类和对象中[构造函数,析构函数,拷贝构造函数]详解

  • 一.前言
    • 1.类的6个默认成员函数
  • 二.构造函数
    • 1.构造函数的引出
    • 2.无参构造函数
    • 3.缺省参数在构造函数中的应用
    • 4.编译器实现的默认构造函数
    • 5.广义的默认构造函数
    • 6.默认构造函数的形成规则
  • 三.析构函数
    • 1.析构函数的语法
    • 2.编译器实现的默认析构函数
  • 四.拷贝构造函数
    • 1.拷贝构造函数的引出
      • 1.浅拷贝的局限性
    • 2.拷贝构造函数语法形式
      • 1.浅拷贝的另一大坏处:程序死递归(以date类为例引出)
      • 2.日期类的拷贝构造函数的实现
      • 3.Stack类的拷贝构造函数的实现
        • 1.思路
        • 2.具体实现
      • 4.指针作为拷贝构造函数的参数
      • 5.const Date& d1
    • 3.拷贝构造函数的"传递性"
    • 4.拷贝构造函数典型调用场景
    • 5.拷贝构造函数的总结

一.前言

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

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,当一个类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
在这里插入图片描述
这篇博客我们重点介绍构造函数和析构函数还有拷贝构造函数
这里请大家先建立一个观念:
构造函数和析构函数是函数中的贵族,我们不能用普通函数的视角去看待它们,而拷贝构造函数是构造函数中的一种
它们是非常独特的函数

二.构造函数

1.构造函数的引出

在这里插入图片描述
注意:构造函数虽然叫做构造函数,但是它的任务并不是开辟空间去创建一个对象,而是初始化一个对象
在这里插入图片描述
在这里插入图片描述
这里的构造函数是:

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

注意:构造函数没有返回值,不写返回值类型,不写void

你发现没有,这个Date的构造函数跟Init的实现一样啊,这也就说明了构造函数的功能就是初始化对象,不是开辟空间创建一个对象

那它为什么不叫作初始化函数呢?C++创始人就是这么定义的,没有为什么

构造函数是自动调用的,不过我们在构造函数中写了三个形参,所以即使构造函数是自动调用的,我们也要去传参啊,怎么传参呢?

C++语法这么规定:在创建对象的时候直接一起传参

Date d1(2023,10,20);

2.无参构造函数

这不还是需要传参吗?
如果我在创建对象的时候都忘了传参呢?
因为构造函数是支持重载的
所以我们可以再去定义一个无参构造函数
在这里插入图片描述

Date()
{
	_year = 1;
	_month = 2;
	_day = 3;
}
调用:Date d2;

注意:调用无参构造函数的时候不能加()
也就是不允许这样去调用:
Date d2();

为什么呢?
因为这行代码也可以被认为是一个函数的声明,这个函数的函数名是d2,函数的返回值类型是Date类型,函数没有任何形参
所以会产生歧义

因此C++创始人就规定不要加括号,直接Date d2;就行

3.缺省参数在构造函数中的应用

还是有点麻烦啊,我想去随心所欲地创建一个对象
就像这样:我还要定义这么两个构造函数,
能不能就只定义一个构造函数,而且还能让我更加随心所欲地去传参呢?

int main()
{
	Date d1(2023, 10, 20);
	d1.Print();
	Date d2;
	d2.Print();
	return 0;
}

答案是:当然可以了,我们之前学的缺省参数就派上用场了
然后我们写完之后迫不及待地去运行,结果:报错…
在这里插入图片描述
为什么呢?
在这里插入图片描述
两个报错,
1.其实这个全缺省参数的构造函数跟

Date(int year,int month,int day);

无法构成重载,(因为函数名相同,参数的个数,类型均相同)
2.这个全缺省参数的构造函数尽管与无参构造函数形成了重载,但是当我们无参调用的时候就会发生歧义,因此这两个构造函数也不能同时存在
在这里插入图片描述
不错,达到了我们的要求

但是有一个问题:当我们不写构造函数的时候会发生什么呢?

4.编译器实现的默认构造函数

这里所提到的默认构造函数是编译器为我们实现的默认构造函数(大家可以理解为这里是狭义的默认构造函数,后面我们会提到广义的默认构造函数)
在这里插入图片描述
编译器会给我们实现一个默认构造函数,不过它会给我们初始化为随机值,
那我要它有什么用?
别急,我们先来看一个代码
在这里插入图片描述
这不还是没有什么好的初始化吗?
还是随机值啊
大家不要急吗,下面才是重头戏
我们先给Stack这个类写上我们定义的构造函数

Stack(int capacity = 4)
{
	_a = (DataType*)malloc(sizeof(DataType) * capacity);
	if (NULL == _a)
	{
		perror("malloc申请空间失败!!!");
		return;
	}
	_capacity = capacity;
	_top = 0;
}

在这里插入图片描述
在这里插入图片描述
注意:如果这里的Stack没有我们自定义的构造函数,那么Stack就会调用Stack的默认构造函数(编译器生成的默认构造函数),也就是初始化为随机值

所以:
如果一个类中有自定义类型的成员变量,
那么我们可以考虑使用这个类的默认构造函数(编译器给我们生成的默认构造函数)

既然你去使用这个类的默认构造函数,那么这个类中的内置类型的成员变量怎么办呢?
C++11支持我们去在类的成员变量当中给缺省值

class MyQueue
{
private:
	Stack pushst;
	Stack popst;
	int _size = 1;
	这个依然是声明,只不过是给了一个缺省值而已
	永远记住:对于非函数来说,到底是声明还是定义要看是否真的开辟了空间
	函数的话看什么声明还是定义就很简单了吧
};

在这里插入图片描述
在VS2019中,编译器给我们处理了,而在VS2013中编译器没有给我们去处理
在这里插入图片描述
总结:
1.一般情况下,我们都要自己写构造函数
2.成员都是自定义类型,或者声明时给了缺省值:可以考虑让编译器自己生成构造函数

5.广义的默认构造函数

上面第四点提到的都是狭义的默认构造函数(有一个地方提到了广义的默认构造函数,我在那个地方已经标明了)
下面我们来看看广义的默认构造函数是什么?
在这里插入图片描述
这个初始化列表我们后面会进行详细介绍的

6.默认构造函数的形成规则

那么在什么情况下编译器才会给我们自动去生成一个无参的默认构造函数呢?

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
在这里插入图片描述
当我们调用默认构造函数时发现不存在默认构造函数,这也就说明了上面那个规则

三.析构函数

1.析构函数的语法

在这里插入图片描述
析构函数是非常有价值的,回想一下我们在用C语言去实现数据结构并且使用的时候,
是不是经常很容易就会忘了调用destroy销毁函数,所以析构函数是很有价值的

我们以日期类为例,熟悉一下析构函数的语法

class Date
{
public:
	Date(int year = 1, int month = 2, int day = 3)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	~Date()
	{
		cout << "~Date4()" << endl;
	}
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year = 0;
	int _month = 0;
	int _day = 0;
};

在这里插入图片描述
因此:
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;
有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

2.编译器实现的默认析构函数

当我们不写析构函数的时候,会发生什么现象呢?

默认生成的析构函数,行为跟构造函数类似
内置类型成员不做处理
自定义类型回去调用它的析构

在这里插入图片描述

四.拷贝构造函数

1.拷贝构造函数的引出

在这里插入图片描述
在C++中结构体扩展为了类,所以对于类而言,它同时也包含了许多结构体的属性

1.浅拷贝的局限性

下面先请大家看一下这个代码
在这里插入图片描述
这里我们对Date类进行了传值传参,
而这个传值传参:形参d是实参d1的一份拷贝,d拷贝了d1的所有数据,这个过程是采用值拷贝的方式进行的
而这个代码运行完全正常,别急,下面请大家再看一份代码
在这里插入图片描述
我们给Stack函数定义了构造函数和析构函数(这份代码中这两个函数都没有任何问题
但是程序还是崩了,这是为什么呢?

这里Func2函数的形参st是实参st1的一份拷贝,这里的拷贝依然是采用值拷贝的方式来进行的
也就是说:
在这里插入图片描述
在这里插入图片描述

这种编译器默认形成的采用值拷贝的方式来进行拷贝构造的方式,我们称之为浅拷贝

然后我们再来仔细地看一下上面那个报错的代码
在这里插入图片描述
结合这个控制台打印出的信息和上面画的那一张分析图片,
我们再来调试看一下这个过程

这里的st和st1的_a的值是一样的:
在这里插入图片描述
当_a指向的空间随着形参st的销毁被释放后,实参st1的_a依然是指向这个已经被释放了的空间的,也就是说这个st1的_a已经成为了一个野指针,因此发生了同一空间多次释放的问题:
在这里插入图片描述
我们就能更好地理解浅拷贝的局限性了

也正是因为这种局限性,
对于日期类这种没有在堆上去申请空间的类来说值拷贝没有报错的风险,
但是对于Stack这种在堆上申请了空间的类来说,值拷贝有着致命的缺陷

怎么解决呢?
在这里插入图片描述
这就需要我们对Stack类自己写一个拷贝构造函数了,我们要怎么写呢?
这就涉及到深拷贝的知识点了,不要急,我们先来实现一下Date类的拷贝构造函数

2.拷贝构造函数语法形式

在这里插入图片描述
这个无穷递归调用是怎么一回事?
下面就会给大家说明

1.浅拷贝的另一大坏处:程序死递归(以date类为例引出)

拷贝构造函数:顾名思义,就是用来拷贝一个对象的所有属性的,
所以不难写出下面的代码
在这里插入图片描述
可是编译器直接给我们报错了
这是为什么呢?
这就是无穷递归所引发的错误,为什么会无穷递归呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.日期类的拷贝构造函数的实现

既然传值不行,那就传引用试试
毕竟引用就是取别名,不需要进行值拷贝,在语法上引用作为形参,这个形参就是实参
不难写出这样的代码:

	Date(Date& dd)
	{
		_year = dd._year;
		_month = dd._month;
		_day = dd._day;
	}

在这里插入图片描述
传引用就非常好地解决了这个无限递归的问题,
其实我们在这里实现的拷贝构造函数是浅拷贝,但是对于Date类来说,浅拷贝没有危害(因为Date类并没有在堆上开辟空间)
下面我们就去实现Stack类的拷贝构造函数

3.Stack类的拷贝构造函数的实现

注意:我们在这里就要去实现深拷贝了
因为浅拷贝无法解决Stack类的同一空间多次释放的错误

那么怎么去实现呢?

1.思路

在这里插入图片描述

2.具体实现
	Stack(Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int) * st._top);
		_top = st._top;
		_capacity = st._capacity;
	}

在这里插入图片描述
我们一上来遇到的问题就成功解决了
顺便祝大家1024节日快乐

那我们直接试试拷贝构造函数呗
在这里插入图片描述
调试看了一下,st1和st2指向的空间的确是不一样的

4.指针作为拷贝构造函数的参数

经过了上面的探索,我们发现引用作为拷贝构造函数的参数是真的好用
那么我用指针行吗
当然可以
不过就是没有引用更加简洁
我们以Date类为例,来看一下

	Date(Date* dd)
	{
		_year = dd->_year;
		_month = dd->_month;
		_day = dd->_day;
	}
	调用方式:Date d2(&d1);

在这里插入图片描述
行是行,不过还是没有引用简洁啊~

5.const Date& d1

如果有人写代码写着写着写懵了,写出了这样的代码

	Date1(Date1& dd)
	{
		dd._year = _year;//这里写反了
		_month = dd._month;
		_day = dd._day;
	}

在这里插入图片描述
这就坑了
我想要给d1拷贝一份数据命名为d2,结果年份那里我非但没有拷贝成功,还把我自己给改了
怎么办呢?
因此,C++语法建议在拷贝构造函数的参数那里这么定义:

	Date1(const Date1& dd)
	{
		dd._year = _year;//这里就会报错了
		_month = dd._month;
		_day = dd._day;
	}

在这里插入图片描述
在这里插入图片描述
然后这个懵了的程序员就意识到问题所在了,然后就能轻而易举地将代码修改正确

3.拷贝构造函数的"传递性"

既然拷贝构造函数是一种特殊的构造函数
那么拷贝构造函数会跟构造函数一样具有"传递性"吗?
答案是:是的.
在这里插入图片描述
在这里插入图片描述

4.拷贝构造函数典型调用场景

在这里插入图片描述

Stack func1()
{
	Stack st;
	return st;//这里st也要调用一个拷贝构造函数生成一个临时拷贝,返回临时拷贝
}
//引用是C++入门最重要的知识
Stack& func2()
{
	static Stack st;
	return st;//static 修饰,直接返回引用就行
}

5.拷贝构造函数的总结

那么我们什么时候适合需要自己去写拷贝构造函数,什么时候适合直接用编译器默认生成的拷贝构造函数呢?
在这里插入图片描述

以上就是C++类和对象中(构造函数,析构函数,拷贝构造函数)详解的全部内容,希望能对大家有所帮助!

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

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

相关文章

DALL·E 3怎么用?DALL·E 3如何申请开通 ?DALL·E 3如何免费使用?AI绘画教程来喽~

一、引言 DALLE 3 是 OpenAI 在上个月&#xff08;2023 年 9 月&#xff09;发布的一个文生图模型。 相对于 Midjourney 以及 Stable Diffusion&#xff0c;DALLE 3 最大的便利之处在于&#xff0c;用户不需要掌握 Prompt 的写法了&#xff0c;直接自然语言描述即可。 甚至还…

Linux截断文件truncate和ftruncate

truncate()和 ftruncate()系统调用将文件大小设置为 length 参数指定的值。 函数原型 若文件当前长度大于参数 length&#xff0c;调用将丢弃超出部分&#xff0c;若小于参数 length&#xff0c;调用将在文件尾部添加一系列空字节或是一个文件空洞。 示例 区别 两个系统调用之…

【数据分享】2023年我国上市公司数据(Excel格式/Shp格式)

企业是经济活动的参与主体&#xff0c;一个城市的企业数量决定了这个城市的经济发展水平&#xff01;之前我们分享过2023年高新技术企业数据&#xff08;可查看之前的文章获悉详情&#xff09;&#xff0c;我国专精特新“小巨人”企业数据&#xff08;可查看之前的文章获悉详情…

阿里云服务器续费流程_一篇文章搞定

阿里云服务器如何续费&#xff1f;续费流程来了&#xff0c;在云服务器ECS管理控制台选择续费实例、续费时长和续费优惠券&#xff0c;然后提交订单&#xff0c;分分钟即可完成阿里云服务器续费流程&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云服务器详细续费方法&am…

设计模式:策略模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

简介&#xff1a; 策略模式&#xff0c;它是一种行为型设计模式&#xff0c;它定义了算法族&#xff0c;分别封装起来&#xff0c;让它们之间可以互相替换。策略模式让算法的变化独立于使用算法的客户&#xff0c;降低了耦合&#xff0c;增加了系统的可维护性和可扩展性。 策…

JSX看着一篇足以入门

JSX 介绍 学习目标&#xff1a; 能够理解什么是 JSX&#xff0c;JSX 的底层是什么 概念&#xff1a; JSX 是 javaScriptXML(HTML) 的缩写&#xff0c;表示在 JS 代码中书写 HTML 结构 作用&#xff1a; 在 React 中创建 HTML 结构&#xff08;页面 UI 结构&#xff09; 优势&a…

mybatis书写

mybatis <select id"selectUserList" resultType"map"> select * from user </select><!--根据主键查询一条--> <select id"selectById" resultType"map" parameterType"java.lang.Integer"> sele…

跨越单线程限制:Thread类的魅力,引领你进入Java并发编程的新纪元

线程的概述 线程是一个程序的多个执行路径&#xff0c;执行调度的单位&#xff0c;依托于进程存在。 线程不仅可以共享进程的内存&#xff0c;而且还拥有一个属于自己的内存空间&#xff0c;这段内存空间也叫做线程栈&#xff0c;是在建立线程时由系统分配的&#xff0c;主要用…

SCSS动态生成类

前言 在项目开发中&#xff0c;为了方便样式的复用和规范化&#xff0c;通常都会统一一些公共的样式类&#xff0c;如果用传统的css来写就会显得很臃肿。 最近看了看接手的项目的公共css文件&#xff0c;发现很多重复的样式声明&#xff0c;还有全局的样式使用不统一问题。 例…

港联证券:哪家证券公司开户好?

在现代社会&#xff0c;出资理财已经成为了一个不可或缺的部分。出资者在进行股票生意时&#xff0c;不可避免地需求选择一家证券公司进行开户。可是&#xff0c;哪家证券公司开户好&#xff1f;这是每个出资者都需求考虑的问题。本文将从多个角度分析&#xff0c;为您供给一些…

不知道怎么选CRM系统?看这篇就够了

CRM客户管理系统近年来已经从简单的客户管理软件发展成为了帮助企业运营发展的工具。它能够帮助企业优化业务流程、提高客户转化率、获得更多业绩。那么企业在选择CRM系统时有什么要点吗&#xff1f; 1、明确是否有自动化功能 自动化功能可以自动处理那些手动且琐碎的销售流程…

AI语音机器人的重点功能配置之话术

AI机器人运营中的重中之重就是对话术的配置&#xff0c;如何将话术运营好将是影响AI机器人效果的关键因素&#xff0c;那接下来我们了解一下AI机器人的话术模块的几个重点功能。 话术配置 有节点库、关键词、话术内容、转接人工、发送短信、知识库标签、客户意向、允许打断、…

VS Code关闭受限模式,关闭信任工作区

打开VS code每次出现这个界面&#xff0c;烦戳死!今天&#xff0c;贷款也要把它关掉&#xff01; 1、打开设置&#xff1a; 2、搜索以下值 security.workspace.trust3、重新启动VS Code即可&#xff01; 4、或者直接在用户的设置文件 settings.json中加入以下&#xff1a; &…

win10下u2net tensorrt模型部署

TensorRT系列之 Win10下yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov7 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov6 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov5 tensorrt模型加速部署…

企业需要拓展出海业务?CRM为您保驾护航(上)

2022年企业似乎格外艰难&#xff1a;线上流量看似见顶&#xff0c;线下受疫情影响严重&#xff0c;展会推迟&#xff0c;出差受阻&#xff0c;增长乏力沦为常态。为了寻求增长&#xff0c;一批又一批企业将目光看向海外&#xff0c;那里尚有流量红利和增长空间等待挖掘。CRM客户…

港联证券:短债基金收益?

跟着人们对理财的需求不断增加&#xff0c;短债基金成为了许多出资者关注的焦点。那么&#xff0c;短债基金可以带来什么样的收益呢&#xff1f;本文将从多个角度剖析短债基金的收益。 一、短债基金的概念 短债基金是一种基金类型&#xff0c;风险相对较低&#xff0c;一般出资…

【Docker从入门到入土 4】使用Harbor搭建Docker私有仓库

私有仓库 一、Harbor简介1.1 什么是Harbor?1.2 Harbor的特性1.3 Harbor和docker registry的关系1.4 Harbor的构成1.4 Harbor 配置文件中的两类参数1.4.1 所需参数1.4.2 可选参数 二、Harbor部署2.1 部署Docker-Compose服务2.2 部署 Harbor 服务Step1 下载或上传 Harbor 安装程…

密码即服务-初探vault

欢迎关注微信公众号 singless 1 介绍 https://www.vaultproject.io/ https://lonegunmanb.github.io/essential-vault/ 简单来说&#xff0c;在我们日常的工作中&#xff0c;免不了要和许多的机密信息打交道&#xff0c;可以是云服务的 Access Key 和 Secret Key&#xff0c;也…

whois人员信息python批处理读入与文本输出

使用pytho读取一个ip列表文本&#xff0c;批量获取whois输出并写入到一个文本 import socketif __name__ __main__:# 江苏电信DNS地址mylog open(whois.log, mode a,encodingutf-8)for line in open("ip.txt"):s socket.socket(socket.AF_INET, socket.SOCK_STR…

uniapp map polygons 区域填充色(fillColor)在ios显示正常,但在安卓手机显示是黑色的,怎么解决?

uniapp map polygons 区域填充色&#xff08;fillColor&#xff09;在ios显示正常&#xff0c;但在安卓手机显示是黑色的,怎么解决&#xff1f; <MapPage :longitude"item.centerCoord[0]" :latitude"item.centerCoord[1]":polygons"[{ points: it…