【浅尝C++】C++类的6大默认成员函数——构造、析构及拷贝构造函数

news2024/11/18 20:18:55

在这里插入图片描述

🎈归属专栏:浅尝C++
🚗个人主页:Jammingpro
🐟记录一句:好想摆烂,又好想学习~~


文章前言:本篇文章简要介绍C++类的构造函数、析构函数及拷贝构造函数,介绍每个小点时,都会附上对应的代码,如果可能的话。余下3大默认成员函数将于后续文章中叙述,要不,整个文章太长了。


文章目录

  • 构造函数
    • 基本概念
    • 特性
  • 析构函数
    • 基本概念
    • 特性
  • 拷贝构造函数
    • 概念
    • 特征


我们知道,如果一个类什么都没有,那么它就是一个空类。空类真的什么都没有吗?答案是否定的。任何类在什么都不写时,编译器会自动生成以下6个默认成员函数,本文将先介绍构造函数、析构函数及拷贝构造函数。👇
ps:默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
在这里插入图片描述

构造函数

基本概念

在介绍构造函数之前,我们先回顾一下类的定义和调用👇。

#include<iostream>
using namespace std;

class Date
{
public:
	void Init(int year, int month, int day)
	{
		m_year = year;
		m_month = month;
		m_day = day;
	}
	void Print()
	{
		cout << m_year << " - " << m_month << " - " << m_day << endl;
	}
private:
	int m_year;
	int m_month;
	int m_day;
};

int main()
{
	Date date;
	date.Init(2023, 1, 1);
	date.Print();
	return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象
特征如下:
①函数名与类名相同。
②无返回值。
③对象实例化时编译器自动调用对应的构造函数。
④构造函数可以重载。

#include<iostream>
using namespace std;

class Date
{
public:
	//以下两个构造函数,参数个数不同,构成重载
	Date(int year, int month, int day)
	{
		//定义对象时,会自动打印下面这句内容
		cout << "Date(int year, int month, int day)被调用" << endl;
		m_year = year;
		m_month = month;
		m_day = day;
	}
	//构造函数名称与类名相同,且无返回值
	Date(int year)
	{
		cout << "Date(int year)被调用" << endl;
	}
private:
	int m_year;
	int m_month;
	int m_day;
};

int main()
{
	Date d1(2022, 12, 12);
	Date d3(2023);
	return 0;
}

⑤如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

#include<iostream>
using namespace std;

class Date
{
public:
	/*提供该构造函数后,编译不再生成默认构造函数
	Date(int year, int month, int day)
	{
		m_year = year;
		m_month = month;
		m_day = day;
	}
	*/
private:
	int m_year;
	int m_month;
	int m_day;
};

int main()
{
	Date d1;
	//若将上面类中定义的构造函数注释掉,则此处不会出错,因为编译器自动生成了默认构造函数(不带参数的函数)
	//若使用上面类中定义的构造函数,则此处定义d1对象会报错,因为编译器不再自动生成默认构造函数
	return 0;
}

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

#include<iostream>
using namespace std;

class Time
{
public:
	Time()
	{
		cout << "Time构造函数" << endl;
		m_hour = m_minute = m_second = 0;
	}
	void Print()
	{
		cout << m_hour << ":" << m_minute << ":" << m_second << endl;
	}
private;
	int m_hour;
	int m_minute;
	int m_second;
};

class Date
{
public:
	void Print()
	{
		cout << m_year << "-" << m_month << "-" << m_day << endl;
		m_t.Print();
	}
private:
	int m_year;
	int m_month;
	int m_day;
};

int main()
{
	Date d;
	//d未调用Time的构造函数,但打印时,Time的各个成员变量均已初始化为1。说明编译器自动实现的Date的构造函数,会自动调用Time的构造函数。
	d.Print();
	return 0;
}

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

#include<iostream>
#include<string>
using namespace std;

class Jammingpro
{
public:
	void Print()
	{
		cout << "我是 " << m_name << ",今年 " << m_age << " 岁" << endl;
	}
private:
	string m_name = "xioaming";
	int m_age = "18";
};

int main()
{
	Jammingpro jam;
	jam.Print();//打印的是默认值
	return 0;
}

⑦无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。下面代码演示的是无参、全缺省构造函数,两个函数只能留下其中一个,不能同时存在。

class Date
{
public:
	Date()
	{}
	Date(int year = 2022, int month = 12, int day = 12)
	{
		m_year = year;
		m_month = month;
		m_day = day;
	}
private:
	int m _year;
	int m_month;
	int m_day;
}

析构函数

基本概念

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

特性

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

#include<iostream>
using namespace std;

class Stack
{
public:
	//对象生命周期结束时,需要调用下面的析构函数
	~Stack()
	{
		cout << "~Stack()" << endl;
	}
private:
	char* m_mem;
	int m_size;
	int m_capacity;
};

int main()
{
	Stack stk;
	return 0;
}

⑤关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

#include<iostream>
using namespace std;

class Time
{
public:
	//下面这句话会被打印,因为Date的自动生成的析构函数会调用自定义数据类型的析构函数
	~Time()
	{
		cout << "Time析构函数" << endl;
	}
private;
	int m_hour;
	int m_minute;
	int m_second;
};

class Date
{
private:
	int m_year;
	int m_month;
	int m_day;
};

int main()
{
	Date d;
	return 0;
}

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

class Stack 
{
public:
	Stack()
	{
		m_mem = new char[100];//在堆区申请了内存空间
		m_size = 0;
		m_capcpity;
	}
	~Stack()
	{
		delete[] m_mem;//必须使用析构函数释放资源
	}
private:
	char* m_mem;
	int m_size;
	int m_capacity;
}

拷贝构造函数

概念

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

特征

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

class Date
{
public:
	Date(int year, int month, int day)
	{
		m_year = year;
		m_month = month;
		m_day = day;
	}
	//Date(const Date d) ->错误写法,将引发无穷递归
	Date(const Date& d)//->正确写法
	{
		m_year = d.m_year;
		m_month = d.m_month;
		m_day = d.m_day;
	}
private:
	int m_year;
	int m_month;
	int m_day;
}

③若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

class Stack
{
public:
	Stack()
	{
		m_mem = new char[100];
		m_size = 0;
		m_capacity = 100;
	}
	/*下面这个是编译器自动生成的
	Stack(const Stack& s)
	{
		m_mem = s.m_mem;
		m_size = s.m_size;
		m_capacity = s.m_capacity;
	}
	*/
	Stack(const Stack& s)
	{
		m_mem = new char[s.m_capacity];
		strcpy(m_mem, s.m_mem);
		m_size = s.m_size;
		m_capacity = s.m_capacity;
	}
private:
	char* m_mem;
	int m_size;
	int m_capacity;
}

④编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像Date类这样的类是没必要的。而像上面的Stack类是需要自己显示实现拷贝构造函数的。
编译器自动生成的拷贝构造函数是浅拷贝,也就是将属性值复制一份。对于下面图片中的s1s2,由于s2复制了s1m_mem的地址,导致两个栈指向同一内存空间,两个栈生命周期结束后均会调用析构函数,此时将执行两次delete[] m_mem,将导致内存重复释放而报错。
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
在这里插入图片描述
⑤拷贝构造函数典型调用场景:
使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象

#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
	{
		cout << "Date create" << endl;
		m_year = year;
		m_month = month;
		m_day = day;
	}
	Date(const Date& d)
	{
		cout << "Date copy" << endl;
		m_year = d.m_year;
		m_month = d.m_month;
		m_day = d.m_day;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int m_year;
	int m_month;
	int m_day;
};

Date Test(Date d)
{
	return d;
}

int main()
{
	Date date(2023, 11, 11);
	Test(date);
	Date cp(date);
	return 0;
}

这段代码的执行结果如下,即调用1次构造函数,3次拷贝构造函数,4次析构函数:

Date create
Date copy
Date copy
~Date()
~Date()
Date copy
~Date()
~Date()

对于上面的代码,执行Date date(2023, 11, 11);时调用1次构造函数;执行Test(date);时传递参数,调用了1次拷贝构造函数,返回时,返回值又调用了1次拷贝构造函数,由于返回值产生的临时变量和Test的参数d离开Test函数后生命周期就结束了,因此分别调用了析构函数,即2次析构函数;执行Date cp(date)构造cp对象时调用了1次拷贝构造函数,main函数执行结束,date与cp生命周期结束,分别调用析构函数,即2次析构函数。
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用


文章结语:这篇文章对C++中的构造函数、析构函数及拷贝构造函数进行了简要的介绍。后续文章将介绍余下3大默认成员函数。
🎈欢迎进入浅尝C++专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

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

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

相关文章

U-boot(六):命令体系,环境变量,iNand/SD卡驱动

本文主要探讨210的uboot命令体系&#xff0c;黄金变量,iNand/SD卡驱动相关知识。 命令体系 uboot命令体系 位置:uboot/common/ 参数:uboot命令支持传递参数(argc,argv) 函数:xxx命令的实现算数为do_xxx /** Use puts() inst…

MVVM 模式与 MVC 模式:构建高效应用的选择

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

金山办公前端二面

1. react 和 vue的区别 还有jquery&#xff1f; &#xff08;1&#xff09; jquery 和 vue、react 的区别&#xff1a; vue 和 react : 数据和视图分离 以数据驱动视图&#xff0c;只关心数据变化 dom 操作被封装&#xff08;数据驱动&#xff09; jquery&#xff1a;依靠 do…

wmvcore.dll丢失怎么办?解决电脑出现wmvcore.dll丢失问题5个方法

wmvcore.dll缺失5个解决方法与wmvcore.dll丢失原因及文件介绍 引言&#xff1a; 在日常使用电脑的过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是wmvcore.dll缺失。wmvcore.dll是Windows Media Video编码解码相关动态链接库文件之一&#xff0c;它对…

Java中关于ArrayList集合的练习题

目录 题目内容​编辑 完整源码 题目内容 根据下图所示数据&#xff0c;定义学生类Student&#xff0c;设置对应的字段并进行封装在Test中&#xff0c;定义ArrayList集合 ,将上述学生对象实例化&#xff0c;并放入集合&#xff0c;定义方法t1&#xff0c;参数为学生类集合&am…

使用C语言操作kafka

文章目录 1 安装librdkafka2 开启kafka相关服务2.1 启动zookeeper2.2 启动Kafka2.3 创建topic 3 c语言操作kafka的范例3.1 消费者3.2 生产者3.3 生产者和消费者的交互 总结 1 安装librdkafka git clone https://github.com/edenhill/librdkafka.git cd librdkafka git checkou…

linux用户身份切换su和 sudo

su 切换root&#xff0c;但是&#xff0c;环境变量是之前用户的 可以看到利用su切换&#xff0c;根目录还是pro1的 su - 连同环境一起切换成root&#xff0c;切换后工作目录都不一样了&#xff0c;看输入内容左侧信息&#xff0c;和第一个图片比较 -c仅执行一次命令&#xff0…

docker限制容器内存的方法

在服务器中使用 docker 时&#xff0c;如果不对 docker 的可调用内存进行限制&#xff0c;当 docker 内的程序出现不可预测的问题时&#xff0c;就很有可能因为内存爆炸导致服务器主机的瘫痪。而对 docker 进行限制后&#xff0c;可以将瘫痪范围控制在 docker 内。 因此&#…

RK3568笔记六:基于Yolov8的训练及部署

若该文为原创文章&#xff0c;转载请注明原文出处。 基于Yolov8的训练及部署&#xff0c;参考鲁班猫的手册训练自己的数据集部署到RK3568,用的是正点的板子。 1、 使用 conda 创建虚拟环境 conda create -n yolov8 python3.8 ​ conda activate yolov8 2、 安装 pytorch 等…

Deep Image Prior

深度图像先验 论文链接&#xff1a;https://sites.skoltech.ru/app/data/uploads/sites/25/2018/04/deep_image_prior.pdf 项目链接&#xff1a;https://github.com/DmitryUlyanov/deep-image-prior Abstract 深度卷积网络已经成为一种流行的图像生成和恢复工具。一般来说&a…

摇滚史密斯2014重置版外接声卡

摇滚史密斯2014重置版外接声卡 前提 由于rs_asio是通过模拟原厂线的方法来使游戏可以支持声卡的&#xff0c;因此&#xff0c;声卡的采样频率需要满足原厂线要求&#xff0c;即采样率可以设置为 48000 Hz。 我使用的是 Sonic Cube 这款声卡&#xff0c;非常幸运&#xff0c;…

微服务--05--配置管理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 配置管理这些问题都可以通过统一的配置管理器服务解决。而Nacos不仅仅具备注册中心功能&#xff0c;也具备配置管理的功能&#xff1a; 1.配置共享1.1.添加共享配置…

【mmseg】ValueError: Only one of `max_epochs` or `max_iters` can be set.报错解决

目录 &#x1f49c;&#x1f49c;1背景 ❤️ ❤️2分析 &#x1f525;2.1config查看 &#x1f525;2.2BaseRunner基类 &#x1f49a;&#x1f49a;3解决 &#x1f525;3.1按照epoch &#x1f525;3.2按照iters 整理不易&#xff0c;欢迎一键三连&#xff01;&#xff01…

Kubernetes入门学习(上)

文章目录 Kubernetes入门学习&#xff08;上&#xff09;介绍云原生 Kubernetes架构基础概念Kubernetes架构控制平面组件Node组件 组件关系 安装Kubernetes基本对象和操作Pod&#xff08;容器集&#xff09;Deployment(部署)与ReplicaSet(副本集)Service&#xff08;服务&#…

【工业智能】Solutions

各类问题对应的解决方案 工艺参数推荐APC 排产调度智能算法强化学习 运筹优化空压机群控 预测 工艺参数推荐 APC 排产调度 智能算法 遗传算法 强化学习 DDQN 运筹优化 空压机群控 MIP混合整数规划 能耗优化 预测 电池容量预测 时序预测&#xff0c;回归预测 点击剩余…

python基础练习题库实验5

文章目录 题目1代码实验结果题目2代码实验结果题目3代码实验结果![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6058fb4b66994aed838f920f7fe75706.png)题目4代码实验结果题目总结题目1 编写一个程序,使用while循环语句和字符串格式显示以下精确输出。 例如: …

企企通相继出席首届百家新锐企业融通创新交流会与采购数字化创新沙龙,持续深化数字赋能

近期&#xff0c;企企通受邀分别参加了广州、上海业界重磅活动&#xff0c;针对新形势下企业数字化采购升级的新技术与新思路、产业链上下游协同发展等进行探讨&#xff0c;赋能数字化信息技术产业生态发展&#xff0c;并对各方主体如何协作共赢助推企业数字化发展建言献策。 0…

7.Spring源码解析-parseBeanDefinitions解析beanDefinitions

默认解析的命名空间由parseDefaultElement方法去处理&#xff0c;即import, alias, bean, 嵌套的beans四种元素 import 写法示例: <import resource"CTIContext.xml" /> <import resource"customerContext.xml" /> importBeanDefinitionRe…

idea 2023使用技巧(一)

IntelliJ IDEA在业界被公认为最好的java开发工具之一。它能给你良好的开发体验。 idea版本号为2023.2.5。 1 基础操作 1.1索引 idea首次加载项目时&#xff0c;都会创建索引&#xff0c;创建索引的时间跟项目的文件多少成正比。idea的缓存和索引主要是用来加快文件查询&…

Python入职某新员工大量使用Lambda表达式,却被老员工喷是屎山

Python中Lambda表达式是一种简洁而强大的特性,其在开发中的使用优缺点明显,需要根据具体场景权衡取舍。 Lambda表达式的优点之一是它的紧凑语法,适用于一些短小而简单的函数。这种形式使得代码更为精炼,特别在一些函数式编程场景中,Lambda表达式可以提高代码的表达力。此外…