NzN的C++之路--拷贝构造函数赋值运算符重载

news2024/7/2 20:55:37

目录

Part 1 拷贝构造函数

一、概念

二、特征

Part 2 赋值运算符重载

一、运算符重载

二、赋值运算符重载

三、前置++和后置++重载

Part 3 const成员

Part 4 取地址及const取地址操作符重载 


Part 1 拷贝构造函数

一、概念

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

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 4, 10);
	//拷贝构造:用同类型的对象初始化(下面两种写法都对)
	Date d2(d1);
	Date d3 = d1;
}

二、特征

  • 拷贝构造函数是构造函数的一个重载形式。
//全缺省的默认构造
Date(int year = 1, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
}
//拷贝构造
Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
  • 拷贝构造函数只有一个参数且必须是类类型对象的引用,使用传值方式会引发无穷递归调用,编译器会强制检查并直接报错。如果是传指针,不会构成无穷递归,但此时按规定,它只是普通的构造函数,不是拷贝构造函数。
void Func(Date& d)
{
	d.Print();
}
int main()
{
	Date d1(2024, 4, 10);
	//内置类型会直接拷贝,自定义类型传值传参会调用拷贝构造
	Func(d1);//调用完拷贝构造,才会调用Func
}
void Func(Date& d)
{
	d.Print();
}
int main()
{
	Date d2(2024, 4, 11);
	//不想调用拷贝构造,我们就可以传指针/传引用
	//Func(&d2);
	Func(d2);
}
class Date
{
public:
	//全缺省的默认构造
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 4, 10);
	Date d2(d1);//如果这里是传值,就会调用拷贝构造Date d(d1)
	//调用的拷贝构造还要继续调用拷贝构造Date d(d1),就构成了无限递归
	d2.Print();
}
  • 建议在拷贝构造的参数部分加上const。

  • 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节顺序完成拷贝,这种拷贝叫做浅拷贝(值拷贝)。

//栈的深拷贝
Stack(const Stack& st)
{
	//开辟和st1一样大的空间
	_array= (DataType*)malloc(sizeof(DataType) * st._capacity);
	if (NULL == _array)
	{
		perror("malloc fail");
		return;
	}
	//把st1数组里的数据拷贝到新开辟的这块空间
	memcpy(_array, st._array, sizeof(DataType) * st._size);
	//size和capacity都要跟st1一样大
	_size = st._size;
	_capacity = st._capacity;
}

【总结】

       类中如果没有涉及资源申请时,拷贝构造可写可不写;一旦涉及到资源申请,则一定要写拷贝构造函数。一般情况下,不需要显式写析构函数,就不需要显式写拷贝构造。如果内部有一些指针或值指向资源,需要显式写析构函数,一般就要显式写深拷贝。

       那如果实例化了两个日期,那该如何比较它们的大小呢?

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
//private://这里如果是private,在下面比较大小中就无法使用成员变量
	int _year;
	int _month;
	int _day;
};

//bool DateGreater(const Date& dt1, const Date& dt2)//引用传参,防止传参时调用拷贝构造
bool operator>(const Date& dt1, const Date& dt2)//引用传参,防止传参时调用拷贝构造
{
	if (dt1._year > dt2._year)
	{
		return true;
	}
	else if (dt1._year == dt2._year)
	{
		if (dt1._month > dt2._month)
		{
			return true;
		}
		else if (dt1._month == dt2._month)
		{
			return dt1._day > dt2._day;
		}
	}
	return false;
}

//bool DateLess(const Date& dt1, const Date& dt2)
bool operator<(const Date& dt1, const Date& dt2)//引用传参,防止传参时调用拷贝构造

{
	if (dt1._year < dt2._year)
	{
		return true;
	}
	else if (dt1._year == dt2._year)
	{
		if (dt1._month < dt2._month)
		{
			return true;
		}
		else if (dt1._month == dt2._month)
		{
			return dt1._day < dt2._day;
		}
	}

	return false;
}
int main()
{
	Date d1(2024, 4, 10);
	Date d2(2024, 4, 11);
	//内置类型可以直接比较
	//自定义类型必须要自己构造比较函数
	/*int i = DateGreater(d1, d2);
	cout << i << endl;*/

	//operator+运算符就可以构成函数名,可以显式调用
	//一般不会显式调用,这样写是为了支持d1 > d2这种写法
	cout << operator<(d1, d2) << endl;
	cout << (d1 > d2) << endl;//构成了运算符重载,这样也可以比较自定义类型
	
	return 0;
}

        这样我们就引入了运算符重载的内容。

Part 2 赋值运算符重载

一、运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符,比如operator@
  • 重载操作符至少有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,但并没有强制要求,只是为了尽可能规范
  • .*    ::    sizeof    ? :    .  这5个运算符不能重载
class OB
{
public:
	void func()
	{
		cout << "void func()" << endl;
	}
};

typedef void(OB::* PtrFunc)();//成员函数指针类型

int main()
{
	//成员函数要加&才能取到函数指针
	PtrFunc fp = &OB::func;//定义成员函数指针p指向函数func
	OB tmp;//定义OB类对象tmp
	(tmp.*fp)();//用tmp对象调用函数指针就需要.*
	return 0;
}
class Date
{
public:
	Date(int year = 2024, int month = 4, int day = 14)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//private:
	int _year;
	int _month;
	int _day;
};
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2024, 4, 15);
	Date d2(2024, 4, 16);
	//operator==(d1, d2);//显式调用无法发挥其优势
	cout << (d1 == d2) << endl;//编译器会直接把d1 == d2转换成operator==(d1, d2)
}

        运算符重载成全局的需要成员变量是公有的,为了保证封装性,有三种方式:

        1. 提供这些成员的get和set(Java较为常用)

        2. 后面学习的友元

        3. 重载为成员函数(C++常用)

  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
//operator == ”的参数太多,因为成员函数会有一个隐含的参数this
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
//形参看上去会比操作数数目少1
bool operator==(const Date& d)
{
	return this->_year == d._year
		&& this->_month == d._month
		&& this->_day == d._day;
}

注意:如果运算符重载同时出现在类成员函数和全局中,优点调用类的成员函数。

二、赋值运算符重载

int main()
{
	Date d1(2024, 4, 15);
	//拷贝构造
	Date d2(d1);
	Date d3 = d1;

	Date d4(2024, 4, 16);
	d1 = d4;//这里就不是拷贝构造,而是赋值拷贝/赋值重载
	//拷贝构造是用一个对象去初始化另一个对象
	//赋值重载是复制一个对象,把值赋给另一个已经存在的对象
}

赋值运算符重载格式:

  • 参数类型:const 类名&,传递引用可以提高传参效率
  • 返回值类型:返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回值:*this ,要复合连续赋值的含义
//赋值重载
//返回值为类类型的引用,便于连续赋值
Date& operator=(const Date& d)
{
	_year == d._year;
	_month == d._month;
	_day == d._day;
	//d2=d1,this是d2的地址,*this是d2,d1就是d
	return *this;
}

 注意:如果是返回值,而不是引用,就会生成这个值的临时对象的拷贝。

总结:不能用引用返回一个临时对象或局部对象,因为该对象出了当前所在函数的作用域就会被销毁,引用对象在该函数栈帧已经销毁了。

        虽然引用返回可以减少一次调用拷贝构造,但是也要看情况。当出了函数作用域,引用对象还在,才可以返回引用。即返回对象没析构用引用返回,析构了就传值返回。

  • 没有显式实现时,编译器会默认生成一个赋值运算符重载,以值的方式逐字节拷贝(与拷贝构造一样)。

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符。

  • 赋值运算符只能重载成类的成员函数不能重载成全局函数。

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

【总结】

       跟拷贝构造类似,Date类等不涉及资源申请的可以使用默认生成的赋值重载;但是Stack类这种需要申请资源的需要自己实现赋值重载。

三、前置++和后置++重载

//前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
//C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数由编译器自动传递
//前置++:返回+1之后的结果
//this指向的对象函数结束后不会销毁,引用返回可以提高效率
Date& operator++()
{
	_day += 1;
	return *this;
}
//后置++:返回+1前的结果
//注意:后置++是先用后加,因此需要在实现时需要先将this保存一份,然后this+1
//tmp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
	Date tmp(*this);
	_day += 1;
	return tmp;
}

注意:函数重载是函数名相同而参数列表不同;赋值运算符重载是指让自定义类型也可以控制运算符,增强代码可读性。多个同一运算符的重载可以构成函数重载。

Part 3 const成员

        将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

Part 4 取地址及const取地址操作符重载 

class Date
{
public:
	//两个的区别就是返回值和参数类型的不同
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

        这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如不想让别人取到真实的地址,一般不会出现这种状况。

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

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

相关文章

LeetCode 全排列

思路&#xff1a;这是一道暴力搜索问题&#xff0c;我们需要列出答案的所有可能组合。 题目给我们一个数组&#xff0c;我们很容易想到的做法是将数组中的元素进行排列&#xff0c;如何区分已选中和未选中的元素&#xff0c;容易想到的是建立一个标记数组&#xff0c;已经选中的…

AI模型的奥运会:谁将在OlympicArena中夺冠?

获取本文论文原文PDF&#xff0c;请在公众号【AI论文解读】留言&#xff1a;论文解读 引言&#xff1a;AI模型的奥林匹克级评测 评估和比较不同AI模型的性能始终是一个核心话题。随着技术的不断进步&#xff0c;这些模型在处理复杂任务的能力上有了显著的提升。为了更精确地衡…

Nacos-注册中心

一、注册中心的交互流程 注册中心通常有两个角色: 服务提供者(生产者)&#xff1a;对外提供服务的微服务应用。它会把自身的服务地址注册到注册中心&#xff0c;以供消费者发现和调用。服务调用者(消费者)&#xff1a;调用其他微服务的应用程序。它会向注册中心订阅自己需要的服…

Python | Leetcode Python题解之第188题买卖股票的最佳时机IV

题目&#xff1a; 题解&#xff1a; class Solution:def maxProfit(self, k: int, prices: List[int]) -> int:if not prices:return 0n len(prices)k min(k, n // 2)buy [0] * (k 1)sell [0] * (k 1)buy[0], sell[0] -prices[0], 0for i in range(1, k 1):buy[i] …

【计算机图形学 | 基于MFC三维图形开发】期末考试知识点汇总(上)

文章目录 视频教程第一章 计算机图形学概述计算机图形学的定义计算机图形学的应用计算机图形学 vs 图像处理 vs模式识别图形显示器的发展及工作原理理解三维渲染管线 第二章 基本图元的扫描转换扫描转换直线的扫描转换DDA算法Bresenham算法中点画线算法圆的扫描转换中点画圆算法…

安全和加密常识(6)Base64编码方式

文章目录 什么是 Base64编码原理编解码示例应用什么是 Base64 Base64 是一种用于将二进制数据编码为仅包含64种ASCII字符的文本格式的编码方法,注意,它不是加密算法。它设计的目的主要是使二进制数据能够通过只支持文本的传输层(如电子邮件)进行传输。Base64常用于在需要处…

音频接口电路的PCB设计

Audio接口是音频插孔&#xff0c;即音频接口&#xff0c;可分为Audio in接口和Audio out接口。音频接口是连接麦克风和其他声源与计算机的设备&#xff0c;其在模拟和数字信号之间起到了桥梁连接的作用。对于平台的数字音频接RK3588口&#xff0c;需遵循《Rockchip RK3588 High…

RTMP推流到SRS流媒体服务器消息处理

RTMP推流到SRS流媒体服务器消息处理 SRS和客户端是怎么交换消息的&#xff1f;各个消息有什么作用&#xff1f;握手成功后&#xff0c;SRS和客户端进行消息交换&#xff0c;对应wiresharek这部分截图&#xff1a; 流程图&#xff08;之前画的&#xff0c;可能不够详细&#xf…

Linux文件系统与设备文件

一、Linux文件操作 Linux的文件系统API主要涉及创建、打开、读写、定位、关闭文件 创建 int creat(const char *filename, mode_t mode);mode: 代表新建文件的存取权限&#xff0c;需要和umask相与才能确定最终权限(mode&umask)。 umask代表文件在创建时需要去掉的存取…

Zookeeper:Zookeeper JavaAPI操作与分布式锁

文章目录 一、Zookeeper JavaAPI操作1、Curator介绍2、创建、查询、修改、删除节点3、Watch事件监听 二、Zookeeper分布式锁原理 一、Zookeeper JavaAPI操作 1、Curator介绍 Curator是Apache Zookeeper的Java客户端。常见的Zookeeper Java API&#xff1a; 原生Java API。ZkC…

[深入理解DDR] 总目录

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解DDR》 蓝色的是传送门&#xff0c;点击链接即可到达指定文章。 图。 DDR 分类 导论 [RAM] DRAM 导论&#xff1a;DDR4 | DDR5 | LPDDR5 | GDRR6 | HBM 应运而生 运存与内存&#xff1f;内存与存…

【每日刷题】Day77

【每日刷题】Day77 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. LCR 159. 库存管理 III - 力扣&#xff08;LeetCode&#xff09; 2. LCR 075. 数组的相对排序 - 力…

RedisAtomicInteger并发案例

&#x1f370; 个人主页:__Aurora__ &#x1f35e;文章有不合理的地方请各位大佬指正。 &#x1f349;文章不定期持续更新&#xff0c;如果我的文章对你有帮助➡️ 关注&#x1f64f;&#x1f3fb; 点赞&#x1f44d; 收藏⭐️ RedisAtomicInteger 提供了对整数的原子性操作&a…

策略模式在金融业务中的应用及其框架实现

引言 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为设计模式&#xff0c;它允许在不修改客户端代码的情况下&#xff0c;动态地改变一个类的行为。它通过定义一系列算法并将它们封装在独立的策略类中&#xff0c;使这些算法可以互相替换&#xff0c;而不会影响…

PyTorch使用GPU进行Tensor及模型计算

文章目录 1. 计算设备&#xff1a;GPU/CPU2. Tensor的GPU计算3. 模型的GPU计算 对复杂的神经网络和大规模的数据来说&#xff0c;使用CPU来计算可能不够高效。这里&#xff0c;我们将介绍如何使用单块NVIDIA GPU来计算。 首先&#xff0c;需要确保已经安装好了PyTorch GPU版本…

ThreadPoolExecutor 工作线程Worker自身锁设计

个人博客 ThreadPoolExecutor 工作线程Worker自身锁设计 | iwts’s blog 总集 想要完整了解下ThreadPoolExecutor&#xff1f;可以参考&#xff1a; 基于源码详解ThreadPoolExecutor实现原理 | iwts’s blog Worker-工作线程管理 线程池设计了内部类Worker&#xff0c;主…

谷歌个人号,20人连续封测14天所需设备该怎么解决?

现在&#xff0c;在Google Play上架应用&#xff0c;对于大部分开发者来说&#xff0c;真的是不小的挑战&#xff0c;因为目前谷歌上架政策越来越严格了。特别是从2023年11月13日起&#xff0c;新政策要求个人开发者账号的应用必须经过20个独立用户连续14天的封闭测试&#xff…

人工智能 (AI) 在能源系统中应用的机会和风险

现代文明极度依赖于电力的获取。电力系统支撑着我们视为理所当然的几乎所有基本生活功能。没有电力的获取&#xff0c;大多数经济活动将是不可能的。然而&#xff0c;现有的电网系统并未设计来应对当前——更不用说未来的——电力需求。与此同时&#xff0c;气候变化迫切要求我…

基于STM32的智能家用安全监控系统

目录 引言环境准备智能家用安全监控系统基础代码实现&#xff1a;实现智能家用安全监控系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;安全监控管理与优化问题解决方案与优化收尾与总结 1. 引言 智能家用安全监控系…

【C++深度探索】继承机制详解(一)

hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#xff1a;大耳朵土土垚的博客 &#x1…