类和对象【2】默认成员函数

news2024/12/28 17:40:03

文章目录

  • 引言
  • 构造函数
    • 定义
    • 默认构造函数及相关问题
  • 析构函数
  • 拷贝构造
    • 定义
    • 使用时可能引发的问题
  • 赋值运算符重载
    • 运算符重载
    • 赋值运算符重载
  • 总结

引言

在上一篇文章中,初步介绍了类和对象:戳我看初识类和对象
不难发现,类类型极大的方便了用户的使用以及与对象之间的交互。比如我们之前实现的链表、栈和队列等。

对于内置类型,有操作符可以实现初始化、赋值、等操作。但是对于自定义类型,要实现初始化、赋值、销毁等操作就需要我们通过函数去实现了。但是每个类都实现并且使用时显式调用这些基础普遍的函数是很麻烦的,所以在一个类类型被创建时,会自动生成6个默认成员函数:构造函数、析构函数、拷贝构造、赋值重载、取地址与const取地址,并且在需要用到这些功能的地方自动调用。
本篇文章重点介绍前四个默认成员函数:

构造函数

构造函数的函数名与类名相同,它的作用是初始化类对象。
构造函数会在类对象创建时由编译器自动调用,以保证每个类对象都有一个合适的初始值。在每个对象的生命周期内只调用一次构造函数。

定义

构造函数的函数名与类名相同,无返回值,支持函数重载与缺省参数
例如上一篇文章中简单写的日期类Date的构造函数:

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 2, 10);
	return 0;
}

在这里插入图片描述

默认构造函数及相关问题

构造函数虽然是自动调用,但需要我们去实现,如果没有实现,编译器会生成一个无参的默认构造函数。
默认构造函数是无参或全缺省的构造函数,对于存在默认构造函数的类类型,在创建类对象时就不需要传递参数(像上例中那样传参)。

需要注意的是:当定义了一个构造函数后,编译器就不会生成无参的默认构造函数。所以如果在上例中不给初始值的创建类对象就会报错:

int main()
{
	Date d1(2023, 2, 10);//正确代码:给初始值会调用有参的构造函数
	//Date d2; 错误代码:无合适的默认构造函数可用
	return 0;
}

在这里插入图片描述
这种情况下,我们就可以将这个带参的构造函数定义为全缺省的默认构造函数,这样在调用时就可以不用给初始值而使用默认构造的缺省参数值:

class Date
{
public:
	Date(int year = 2023, int month = 5, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

int main()
{
	Date d1(2023, 2, 10);
	Date d2;
	return 0;
}

在这里插入图片描述
那么,编译器生成的无参的默认构造函数会实现什么效果呢?
对于内置类型,这个无参的默认构造不能实现给初始值,对于自定义类型会调用其自身的默认构造函数。
这就导致这个自动生成的默认构造很鸡肋,并不能实现我们初始化类对象的目的。所以在C11标准中,我们可以在类对象中声明变量时就给一个默认值,这个默认值会在编译器中生成的默认构造中用于初始化类对象(这个默认值其实是在构造函数的初始化列表中利用的,后续会讲到):

class Date
{
public:
private:
	int _year = 2023;
	int _month = 2;
	int _day = 10;
};

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

在这里插入图片描述

析构函数

析构函数在对象生命周期结束时被自动调用,用于销毁类对象中的资源。(注意这里是销毁对象中的资源,而不是对象本身)
对于类类型中的资源全部是内置类型,没有申请空间,则编译器会在该类对象生命周期结束后自动释放这块空间;而对于有申请空间的类,就必须要实现析构函数去释放资源的空间了:

析构函数的函数名为~类名,无返回值,不能重载。
如这个栈对象的析构:

class Stack
{
public:
	Stack()
	{
		_top = 0;
		_capacity = 10;
		_data = new int[_capacity] {0}; //new动态申请空间
	}
	~Stack()
	{
		_top = 0;
		_capacity = 0;
		delete[] _data; //delete释放new申请的空间
	}
private:
	int* _data;
	int _top;
	int _capacity;
};
int main()
{
	Stack s1;
	return 0;
}

在这里插入图片描述
当然,当没有实现析构函数时,编译器会自动生成一个析构函数。这个默认生成的析构函数对于内置类型当然不做处理,对于自定义类型就会调用其自身的析构函数。

拷贝构造

定义

拷贝构造是构造函数的一种重载形式,只有单个形参,即本类类型的引用(一般用const修饰,以可以传常量)。
在用一个类对象初始化创建同类型新类对象时编译器自动调用(即在任何需要拷贝类对象的时候):

class Date
{
public:
	Date(int year = 2023, int month = 2, int day = 10)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year = 2023;
	int _month = 2;
	int _day = 10;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

在这里插入图片描述

使用时可能引发的问题

  1. 拷贝构造函数的参数必须传引用,不能传值
    拷贝构造函数会在发生类对象拷贝操作时自动调用,除了上面提到的用一个类对象初始化一个新对象之外,还有值传参时的拷贝,返回值时的拷贝,强制类型转换时的拷贝等。
    如果在实现拷贝构造函数时使用值传递,就会发生要拷贝调拷贝构造,拷贝构造时又会发生拷贝行为,再调,又发生拷贝行为…… 就会无限调用下去,所以拷贝构造函数必须传引用。

  2. 编译器自动生成的拷贝构造产生的问题
    当然,如果不实现拷贝构造函数,编译器也会生成一个拷贝构造函数。这个自动生成的拷贝构造是按照逐字节拷贝的方式实现的,也就是说对于没有申请空间的变量,是可以实现拷贝的,例如这个日期类:

class Date
{
public:
	Date(int year = 2023, int month = 2, int day = 10)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year = 2023;
	int _month = 2;
	int _day = 10;
};

int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

在这里插入图片描述
但是,当类对象中存在申请资源的情况时,默认生成的拷贝构造就只能将指向空间的指针变量逐字节的拷贝过去,而不是我们希望的再开一块空间去拷贝申请的空间中的内容。
不但如此,由于有两个类对象的成员变量指向这同一块空间,在结束编译后就会调用两次析构函数,对这块空间释放两次,这显然会出现问题。例如这个栈类:

class Stack
{
public:
	Stack()
	{
		_top = 0;
		_capacity = 10;
		_data = new int[_capacity] {0};
	}
	~Stack()
	{
		_top = 0;
		_capacity = 0;
		delete[] _data;
	}
private:
	int* _data;
	int _top;
	int _capacity;
};
int main()
{
	Stack s1;
	Stack s2(s1);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
这种情况下就必须自己实现拷贝构造函数了,后面会讲到。

赋值运算符重载

在介绍运算符重载前,我们需要先了解运算符重载的知识:

运算符重载

对于内置类型,有多种多样的操纵符帮助我们实现对内置类型的操作。例如两个int的相加,使用a + b就可以实现,不仅很简洁,且具有很高的可读性;
但对于自定义类型,我们只能写一些函数来实现。例如Add(Date& d, int day),调用时也只能Add(d, 100);这样显然可读性不高,且使用也很麻烦。所以C++引入了运算符重载:

运算符重载是具有特殊函数名的函数,其参数列表与返回值与普通函数一致:返回值 operator操作符(参数列表);(operator是一个关键字,由于运算符重载)

需要注意的是:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@;
  2. 重载操作符必须有一个类类型参数;
  3. 用于内置类型的运算符,其含义不能改变(也是为了可读性);
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this;
  5. .* :: sizeof ?: . 以上5个运算符不能重载。
class Date
{
public:
	Date(int year = 2023, int month = 2, int day = 10)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
private:
	int _year = 2023;
	int _month = 2;
	int _day = 10;
};

int main()
{
	Date d1;
	Date d2(d1);
	cout << (d1 == d2) << endl; //相当于d1.operator==(d2)
	return 0;
}

在这里插入图片描述
在上面的代码中,重载了==用来判断两个日期类是否相等。
因为在类外访问不到私有成员变量,所以将这个重载定义在类中。d1 == d2本质上就是调用对象d1的成员函数:d1.operator==(d2)

赋值运算符重载

赋值运算符重载即对 = 进行重载

  1. 只有一个参数const T&
  2. 返回值 T& ,方便其实现连续赋值的情况,使其更像 = ;
  3. 赋值运算符重载必须为成员函数,而不能是全局函数,因为它是默认成员函数,如果在全局的话就会与类中自动生成的冲突(其他默认成员函数同理);
  4. 编译器自动生成的赋值运算符重载只能实现逐字节的赋值,也就是浅拷贝(与默认生成的拷贝构造类似):
class Date
{
public:
	Date(int year = 2023, int month = 2, int day = 10)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	    return *this;
	}
private:
	int _year = 2023;
	int _month = 2;
	int _day = 10;
};

int main()
{
	Date d1;
	Date d2(2023, 5, 21);
	d2 = d1;
	return 0;
}

在这里插入图片描述

总结

到此,关于默认成员函数的介绍就结束了
相信大家可以意识到,默认成员函数确实可以使我们使用类对象时更加方便

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

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

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

相关文章

【5.15】一、软件测试基础—软件测试概述

目录 1.3 软件测试概述 1.3.1 软件测试简介 1.3.2 软件测试的目的 1.3.3 软件测试的分类 1.3 软件测试概述 软件测试工作能保证软件产品的质量。 1.3.1 软件测试简介 IEEE&#xff1a;软件测试是使用人工或自动手段运行或测定某个系统的过程&#xff0c;其目的在于检验它…

mac读不到内置硬盘为什么 ntfs读取不了硬盘

电脑内置硬盘可用于启动电脑、存储文件、保存注册表信息等多项功能&#xff0c;一旦无法读取&#xff0c;轻则可能导致某个分卷无法使用&#xff0c;严重时会导致整台mac电脑无法开机停留在Logo界面。那么mac读不到内置硬盘为什么&#xff0c;ntfs读取不了硬盘是何原因&#xf…

【5.16】二、黑盒测试方法—等价类划分法

目录 2.1 等价类划分法 2.1.1 等价类划分法概述 2.1.2 实例&#xff1a;三角形问题的等价类划分 2.1.3 实例&#xff1a;余额宝提现的等价类划分 2.1 等价类划分法 等价类划分法是一种常用的黑盒测试方法&#xff0c;主张从大量的数据中选择一部分数据用于测试&#xff0…

微信小程序 nodejs+vue+uniapp学科竞赛作品管理系统java+python

每个系统也都将通过计算机进行整体智能化操作&#xff0c;对于基于微信小程序学科竞赛作品管理系统功能所牵扯的数据都是通过用户进行基于微信小程序学科竞赛作品管理系统等相关的数据信息内容、并且可以进行管理员在线处理首页、个人中心、用户管理、教师管理、辅导员管理、作…

IS420UCSBH1A 用于帮助实现控制器逻辑

通用电气公司的Mark VIe控制器IS420UCSBH1A类似于UCSC控制器&#xff0c;所有相同的一般信息都可以应用于UCSB模型。IS420UCSBH1A型号通常是一种紧凑型独立控制器&#xff0c;用于帮助实现控制器逻辑。 制造商通用电气与…类似马克维UCSC控制器产品类型Mark VIe、EX2100e或LS2…

JUC并发编程(一)

JUC并发编程 文章目录 JUC并发编程一、进程与线程1 进程2 线程3 进程与线程的对比4 并行与并发5 同步与异步 二、Java线程1 创建和运行线程2 查看进程线程的方法3 栈与栈帧4 线程中常见方法5 start()与run()6 sleep()与yield()7 线程优先级8 join()方法9 interrupt()方法10 使用…

软考初级程序员上午五单选(9)

1、在Windows中&#xff0c;用鼠标左键单击某应用程序窗口的最小化按钮后&#xff0c;该应用程序处于______的状态。 A&#xff0e;被强制关闭 B&#xff0e;不确定 C&#xff0e;被暂时挂起 D&#xff0e;在后台继续运行 2、将某ASCII字符采用偶校验编码(7位字符编码1位校验码…

【5.15】一、软件测试基础—软件测试与软件开发

目录 1.4 软件测试与软件开发 1.4.1 软件测试与软件开发的关系 1.4.2 常见的软件测试模型 1.4 软件测试与软件开发 软件开发是生产制造软件产品&#xff0c;软件测试是检验软件产品是否合格&#xff0c;两者密切合作才能保证软件产品的质量。 1.4.1 软件测试与软件开发的关…

CSAPP复习(2)

CH3 虽然x在rdi中 但是在赋值的时候已经把rdi给rax作为返回值了 第二个问是取他的低位 一定要把寄存器那个图搞明白。 画函数执行栈的情况&#xff1a; 常数100存在于代码段&#xff1a; CH8 进程 CH8--2信号 进程作业任务 进程与任务或作业管理教材【优质文档】 - 百度文库…

VMware Workstation 与 Device/Credential Guard 不兼容解决方案(亲测有效)

前言 VMware Workstation是由VMware公司开发的桌面虚拟化软件&#xff0c;它能够在一台物理计算机上模拟多个独立的虚拟计算机环境&#xff0c;每个虚拟计算机环境都可以运行一个独立的操作系统&#xff0c;并具有各自独立的应用程序和文件库&#xff0c;使得运行多个不同操作…

Hive on Spark调优(大数据技术7)

第7章 数据倾斜优化 7.1 数据倾斜说明 数据倾斜问题&#xff0c;通常是指参与计算的数据分布不均&#xff0c;即某个key或者某些key的数据量远超其他key&#xff0c;导致在shuffle阶段&#xff0c;大量相同key的数据被发往一个Reduce&#xff0c;进而导致该Reduce所需的时间远…

研报精选230518

目录 【行业230518华西证券】医药行业2023年中期投资策略报告&#xff1a;行业复苏势头显著&#xff0c;创新打开未来空间 【行业230518国金证券】半导体行业深度二&#xff1a;存储拐点将至&#xff0c;新需求点亮曙光 【行业230518太平洋】食品饮料行业23年中期策略&#xff…

chatgpt赋能Python-python3_5如何安装

Python3.5安装指南 Python是一种高级编程语言&#xff0c;它广泛用于Web开发、数据分析、机器学习等领域。在Python的众多版本中&#xff0c;Python3.5是最常用的版本之一。如果你正在考虑学习Python3.5或者需要使用Python3.5进行项目开发&#xff0c;那么接下来的安装指南将会…

管道通信,共享内存

什么是有名管道 有名管道&#xff08;Named Pipe&#xff09;是一种特殊的文件类型&#xff0c;也称为 FIFO。它提供了一种进程间通信的机制&#xff0c;允许不相关的进程通过读写共享的文件来交换数据。 与普通文件不同的是&#xff0c;有名管道在文件系统中存在一个路径名&…

【云计算与虚拟化】第四章 实验二 vCenter Server网络部署及基本操作

实验二 vCenter Server网络部署及基本操作 一、实验内容 通过vSphere client 客户端登陆vCenter服务器&#xff0c;为新建的虚拟机Windows7 激活&#xff0c;并修改计算机名为姓名拼音的首字母-学号通过vSphere client 客户端登陆vCenter服务器&#xff0c;将虚拟机window…

【5.15】一、软件测试基础—软件缺陷管理

目录 1.2 软件缺陷管理 1.2.1 软件缺陷产生的原因 1.2.2 软件缺陷的分类 1.2.3 软件缺陷的处理流程 1.2.4 常见的软件缺陷管理工具 1.2 软件缺陷管理 软件测试工作就是查找软件中存在的缺陷&#xff0c;反馈给开发人员使之修改&#xff0c;从而确保软件的质量。 1.2.1 软…

【5.16】一、软件测试基础—软件测试的基本流程

目录 1.6 软件测试的基本流程 1.6.1 软件测试的流程 1.6.2 实例&#xff1a;摩拜单车App开锁用车功能测试流程 1.6 软件测试的基本流程 为了使测试工作标准化、规范化&#xff0c;并且快速、高效、高质量地完成测试工作&#xff0c;需要制订完整且具体的测试流程。 1.6.1 …

ChatGPT将如何影响大学录取和大学申请文书?

ChatGPT是OpenAI于2022年11月推出的人工智能聊天机器人。这个聊天机器人在一个大型文本数据库上训练&#xff0c;能够对各种提示生成对话响应。这款软件的影响是巨大的&#xff0c;整个世界都在讨论这项技术对各个行业的发展意味着什么。 就像所有的新发明一样&#xff0c;人们…

springboot+java+jsp网上超市水果蔬菜果蔬商城购物管理系统

本文首先介绍了设计的背景与研究目的&#xff0c;其次介绍系统相关技术&#xff0c;重点叙述了系统功能分析以及详细设计&#xff0c;最后总结了系统的 通过前面的功能分析可以将超市果蔬管理系统的功能分为管理员&#xff0c;员工和用户三个部分&#xff0c;系统的主要功能包…

进程,会话,守护进程

会话 在Linux中&#xff0c;会话&#xff08;session&#xff09;是一组相关联的进程的集合&#xff0c;它们可以接收相同的信号&#xff0c;并且通常由一个领导进程来控制。会话中的所有进程都共享同一个控制终端&#xff08;controlling terminal&#xff09;&#xff0c;这…