C++相关概念和易错语法(5)(析构函数、拷贝构造、运算符重载、赋值重载)

上篇文章分享了一些构造函数和析构函数的易错点,这篇文章则将继续分享一些构造函数、拷贝构造函数的易错点。

1.变量声明处赋缺省值

我们已经知道了自动构造函数的初始化规则了。我们可以认为这个初始化规则比较保守,能不修改成员变量的值就不修改,只有在自定义类型明确规定了初始化方案后才会去初始化。

f1cc63ccf5294a7197a7e72a3a3ae54f.png

但是,有的时候我们又觉得不太方便,因为很多时候我们都想让成员变量被初始化的。因此规定了一种给成员变量赋初始值的方法,在没有显式定义构造函数时,能将成员变量按我们的想法来自动初始化。

0a4a250f08fa449e9d86e42923c2b14a.png

这里可以看到定义后_x和_y按照声明处的缺省值进行赋值了。

但如果有构造函数那应该怎么赋值呢?

d53c9fc9c0d24e91afbe9ff8ce70413a.png

我们可以看到,当实例化对象时成员变量首先会被按照声明处的缺省值进行赋值。

076238564b04473d8679b89b2e71825b.png

然后再对c1进行初始化。

9db438ab37a8408fa61787b12bb9f11c.png

最后调用构造函数,这个时候_x和_y就会得到我们想要的初始值了。

从上面的顺序可以看出,成员变量的缺省值可以让我们的成员变量创建时就得到一个值,如果后续有构造函数,再按照规则对这些成员变量进行覆盖,如果是编译器生成的默认构造函数,那么不会进行任何处理,此时我们的成员变量就是声明处的缺省值。

2.析构函数

析构函数和构造函数有相似之处:自动生成的默认析构函数同构造函数的自动生成(内置类型不处理,自定义类型调用它的析构函数)。

析构函数和构造函数有一个区别:析构函数可以显式调用,而构造函数不能显式调用。

析构函数的使用场景:如果有资源要清理(堆区开辟空间),防止调用自动生成的析构函数导致的内存泄漏,就要写析构函数。但是如果嵌套的自定义类型有构造和析构函数,也可以不显式地写。内置类型的变量是自动变量,是编译器自动开辟的,不需要人为销毁,因此也不需要写析构函数。

3.拷贝构造函数

拷贝构造可以说是构造函数的重载,它看起来和构造函数十分相似,但是它的使用场景、功能还是有很大的区别的。

拷贝构造最大的用处就是使得类的对象的定义可以像内置类型那样,使用已经存在的对象以赋值的形式来初始化新的对象。

在这里可以看出,在C这个类中的函数可以访问调用该函数的对象的私有成员,也可访问同类型的参数对应的私有成员。但是如果在C1类中传了个C2类的参数,自然就不能正常访问

注意事项:

(1)拷贝构造的实现的参数有且仅有一个,是同类型的引用,否则会发生无穷递归:

要理解这个,我们要探究拷贝函数为什么不能用传值调用,本质是要理解如果传值的话形参和实参之间本身就要进行一次拷贝,这样就会永远去调用拷贝构造。

要解决这个无穷递归的情况,可以选择传址或传引用,因为这两者本质相同,而传引用不用显式地写&,从而实现C c2 = c1这样方便的写法,所以规定拷贝构造都是传引用。

(2)默认拷贝构造:默认拷贝构造是浅拷贝(值拷贝),直接按字节拷贝,如果有指针会导致两个指向同一块空间,如栈、队列等数据结构。因此,我们多采用深拷贝来处理这些情况,这个时候就必须显式实现拷贝构造函数

(3)拷贝构造和析构函数的使用场景:没有资源管理(堆空间开辟)的情况可以不写拷贝构造,内部有指针或有一些值指向堆,需要显式写析构函数,也要写拷贝构造,如栈、队列、二叉树等数据结构。我们发现它们需要显式定义的场景很相似
(4)普通构造和拷贝构造之间的误区:普通构造不会干扰拷贝构造,即参数必须是引用才算拷贝构造,否则编译器都会自己生成拷贝构造函数:调用自动生成的拷贝构造,内置类型浅拷贝,自定义类型调用它的拷贝构造

4.默认构造:无参、全缺省、不写构造函数编译器自己生成的构造函数,默认构造就是不传参数,自动调用的就是默认构造。

5.运算符重载

也可叫操作符重载,可以重载多种符号

operator>构成函数名,可以直接用a > b,可以支持比较了。

>        <        >=        <=        ==        !=        <<        >>        +        -        *        /        %        +=                    -=        *=        /=        %=        等都可以构成运算符重载

但        .*        : :        sizeof        ? :        .        这五个运算符

.*(成员函数取地址要加&才能取到函数指针,全局函数的可以不加,调用时要用.*才能访问函数。用函数指针来访问类里面的函数,.相当于取成员,*是函数指针解引用,temp.*func,使用场景较少。)

注意事项:

(1)operator不能链接其他符号如@#等无实际运算意义的构成新的操作符,要加运算符

(2)运算符重载的返回值按照实际含义给,注意返回值加不加引用的区别(在重载函数内定义的变量不能返回引用,这和野指针同理)

(3)运算符重载必须要有一个类的类型,不能完全对内置类型进行该操作,如不能对int-int进行重载操作

(4)对于内置类型的运算符,含义最好不要改变,不要把+实现成了+=

(5)要对类里面的数据进行运算符重载,访问私有成员,常用重载operator为成员函数:
重载operator作为成员函数时,它的第一个参数强制是this指针,形参比实参少1,看上去只有两个参数其实有3个,而参数多于运算符的操作数的时候,就会报错。

因此,代码上要调整,要注意函数内实现的顺序,转换调用d1 == d2相当于d1.operator==(d2),注意根据后者的实质调用来写代码,有的调换方向后功能完全不同了。

(6)调用运算符重载时会根据传参的类型优先到类里面去找,然后去全局找。

(7)可以显式调用运算符重载,也可以转换调用(编译器自动处理),一般推荐后者,更直观

6.赋值重载(赋值拷贝)

注意事项:

(1)和拷贝构造的区别:赋值重载已存在的对象给另一个已存在的对象赋值,拷贝构造已存在的对象给另一个要初始化的对象拷贝值

(2)operator=赋值重载要考虑写返回值,当遇到连续赋值的时候能够处理,注意可以考虑使用引用作为返回值,因为传值返回要调用拷贝构造(效率受到影响),返回引用就不会了,返回的是别名(实际上是指针),但返回后可能对象被销毁出现野引用(会产生越界访问),引用返回是存在风险的。所以要特别注意是否有在函数里创建变量并把它的引用返回的情况发生

(3)默认的赋值运算符重载类似于拷贝构造,都是按字节拷贝(浅拷贝),如果遇到深拷贝的情况,要自己显式地实现

(4)默认成员函数的规定:赋值重载函数不能写成全局的注意运算符重载可以,运算符重载不是六大默认成员函数之一。 

(5)区别赋值重载函数和运算符重载函数。这两个不一样,赋值重载函数可以自动生成且只能作为成员函数,运算符重载函数不自动生成且可以在全局定义

7.直接在类里面定义的函数默认是内联函数,不用写inline,会自动展开。建议频繁调用的函数直接定义在类里,不要声明和定义分离。
8.运算符重载的复用

基本思路:先实现几个基本功能,后续功能直接嵌套,用逻辑联系起来。

这样能极大减少代码错误和编写代码的效率。

下面是日期类实现中复用的经典体现:


Date& Date::operator+= (int day)
{
	_day += day;

	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;

		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+ (int day)//日期+天数
{
	Date tmp = *this;

	tmp += day;

	return tmp;
}

Date& Date::operator-= (int day)//日期-天数
{
	_day -= day;

	while (_day <= 0)
	{
		_month--;
		if (!_month)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);

	}

	return *this;
}


Date Date::operator- (int day)//日期-天数
{
	Date tmp = *this;

	tmp -= day;

	return tmp;
}

Date Date::operator++ (int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

Date Date::operator-- (int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

Date& Date::operator++ ()
{
	return *this += 1;
}

Date& Date::operator-- ()
{
	return 	*this -= 1;
}


int Date::operator- (const Date& date)
{
	int count = 0;

	Date tmp = *this;

	while (1)
	{
		--tmp;
		count++;

		if (tmp == date)
		{
			break;
		}
	}

	return count;
}


bool Date::operator> (const Date& date)
{
	if (_year > date._year)
	{
		return true;
	}

	else if(_year < date._year)
	{
		return false;
	}

	else
	{
		if (_month > date._month)
		{
			return true;
		}

		else if (_month < date._month)
		{
			return false;
		}

		else
		{
			if (_day > date._day)
			{
				return true;
			}

			return false;
		
		}
	}
}


bool Date::operator>= (const Date& date)
{
	return *this > date || *this == date;
}


bool Date::operator< (const Date& date)
{
	return !(*this >= date);
}

bool Date::operator<= (const Date& date)
{
	return *this < date || *this == date;
}

bool Date::operator!= (const Date& date)
{
	return !(*this == date);
}

Date& Date::operator= (const Date& date)
{
	_year = date._year;	
	_month = date._month;
	_day = date._day;

	return *this;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实在RPA设计器试用导引

一、产品概述 实在RPA设计器是一款将人工智能(AI)与机器人流程自动化(RPA)深度融合的可视化自动流程编辑器。它通过AI推荐与桌面嵌入式交互&#xff0c;极大简化了RPA的使用难度&#xff0c;让普通业务人员也能轻松使用。实在RPA设计器具备以下核心优势&#xff1a; 兼容性&a…

Redis详解和Spring Data Redis应用

注意事项 如何快速进入命令行窗口什么是配置类 Redis简介 Redis是一个开源的使用ANSI C语言编写的、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。它通常被称为数据结构服务器&#xff0c;因为值&#xff08;value&#xff09…

数电期末复习(二)逻辑代数基础

这里写目录标题 2.1 二值逻辑变量与基本逻辑运算2.1.1 与运算2.1.2 或运算2.1.3 非运算2.1.4 常用复合逻辑运算 2.2 逻辑函数的建立及其表示方法2.2.1 真值表表示2.2.2 逻辑函数表达式表示2.2.3 逻辑图表示方法2.2.4 波形图表示方法 2.3 逻辑代数2.3.1 逻辑代数的基本定律和恒等…

从例题出发,提高离散数学兴趣(一)集合关系

关系的性质&#xff1a;(反)自反性&#xff0c;&#xff08;反&#xff09;对称性&#xff0c;可传递性&#xff01; 例题一&#xff1a; 复合关系与逆关系&#xff1a; 例题二&#xff1a; 覆盖与划分与等价关系&#xff1a; 重要的证明&#xff1a; 偏序关系&#xff08;自反…

Java面试八股之System.gc和Runtime.gc的作用分别是什么

System.gc和Runtime.gc的作用分别是什么 从代码中我们能看出&#xff0c;这两个方法其实本质上都是调用的Runtime类中的gc()方法&#xff0c;并且Runtime类中的gc()是一个native方法。之前我们也讲过&#xff0c;这个仅仅是给JVM一个垃圾回收的信号&#xff0c;具体是否进行垃圾…

对组合模式的理解

目录 一、场景1、题目描述 【[案例来源](https://kamacoder.com/problempage.php?pid1090)】2、输入描述3、输出描述4、输入示例5、输出示例 二、实现&#xff08;假的组合模式&#xff09;1、代码2、为什么上面的写法是假的组合模式&#xff1f; 三、实现&#xff08;真的组合…

初识C++·类和对象(中)(3)

前言&#xff0c;最难的已经结束了&#xff0c;来点轻松了放松一下。 目录 1 流重载 2 const成员 3 取地址及const取地址操作符重载 1 流重载 C语言中printf和scanf是有局限性&#xff0c;只能直接打印内置类型&#xff0c;对于自定义类型就哦豁了&#xff0c;所以在C中就…

38. UE5 RPG 修改火球术的攻击方向以及按住Shift攻击

在前面&#xff0c;我们实现了火球术火球的制作&#xff0c;能够在释放火球术时&#xff0c;角色将播放释放技能动画&#xff0c;并实现了对火球的目标的服务器同步功能。 我们先回忆一下之前完成的内容。 在前面&#xff0c;我们先做了一个Actor&#xff0c;用于承载发射的火…

java线程-线程池

简介 工作原理 如何获取线程池对象 构造器的参数含义 注意事项 构造器-ThreadPoolExecutor // ArrayBlockingQueue 是一个有界的阻塞队列&#xff0c;它的内部实现是一个数组。有界的意思是它的容量是有限的&#xff0c;我们必须在创建 ArrayBlockingQueue 实例的时候指定容量…

01-服务与服务间的通信

这里是极简版&#xff0c;仅用作记录 概述 前端和后端可以使用axios等进行http请求 服务和服务之间也是可以进行http请求的spring封装的RestTemplate可以进行请求 用法 使用bean注解进行依赖注入 在需要的地方&#xff0c;自动注入RestTemplate进行服务和服务之间的通信 注…

[2021最新]大数据平台CDH存储组件kudu之启用HA高可用(添加多个master)

今天在做kudu高可用的时候没有参考官网&#xff0c;直接按照常规方式&#xff08;添加角色—>编辑属性—>启动&#xff09;结果发现报错&#xff1f;然后参考了一下文档之后发现这玩意儿还有点玄学&#xff0c;做一下记录。 1.添加两个master。kudu master有leader和foll…

深入解析Tomcat的工作流程

tomcat解析 Tomcat是一个广泛使用的开源Servlet容器&#xff0c;用于托管Java Web应用程序。理解Tomcat的工作流程对于开发人员和系统管理员来说是非常重要的。本文将深入探讨Tomcat的工作原理&#xff0c;包括请求处理、线程池管理、类加载、以及与Web服务器之间的通信。 ###…

思颜肌密:匠心独蕴,传世掠影

赋予延绵岁月以华彩乐章&#xff0c;将来自时间的承诺注入生活每分每秒&#xff0c;在思颜肌密的世界里&#xff0c;恒久之美并非遥不可及&#xff0c;它是艺术&#xff0c;亦是心意。华美节日翩然而至&#xff0c;思颜肌密拉开神秘帷幕&#xff0c;在惊鸿掠影中向世人展现传世…

[数据结构与算法]-什么是二叉树?

二叉树是一种数据结构&#xff0c;由节点组成&#xff0c;每个节点最多有两个子节点&#xff0c;分别称为左子节点和右子节点。二叉树的每个节点包含一个值&#xff0c;并且左子节点的值小于等于父节点的值&#xff0c;右子节点的值大于等于父节点的值。这个性质使得二叉树在搜…

Linux系统维护:增加空闲内存的大小,以便进程有足够的基础内存(空闲内存)来运行

目录 一、问题 二、解决思路 &#xff08;一&#xff09;问题分析 &#xff08;二&#xff09;思路 1. 清理缓存 2. 结束不必要的进程 3. 优化应用程序和服务 4. 增加物理内存 5、注意事项 三、实际处理 &#xff08;一&#xff09;结束不必要的程序 &#xff08;二…

批量规范化(batchnormalization)

ˆB 是小批量B的样本均值&#xff0c;σˆ B 是小批量B的样本标准差。应用标准化后&#xff0c;生成的小批量的平均 值为0和单位方差为1。由于单位方差&#xff08;与其他一些魔法数&#xff09;是一个主观的选择&#xff0c;因此我们通常包含 拉伸参数&#xff08;scale&#…

vulfocus靶场之redis命令执行cve-2022-0543漏洞复现

漏洞&#xff1a; Redis是著名的开源Key-Value数据库&#xff0c;其具备在沙箱中执行Lua脚本的能力。 Debian以及Ubuntu发行版的源在打包Redis时&#xff0c;不慎在Lua沙箱中遗留了一个对象package&#xff0c;攻击者可以利用这个对象提供的方法加载动态链接库liblua里的函数&…

初始化Git仓库时应该运行哪个命令?

文章目录 初始化Git仓库时&#xff0c;你应该运行git init这个命令。这个命令的作用是在你当前所在的目录里创建一个新的Git仓库。这样&#xff0c;你就可以在这个目录里开始使用Git来管理你的文件了。 下面我给你举个详细的例子来说明一下&#xff1a; 首先&#xff0c;你需要…

网络原理-IP协议

一、IP协议报头 版本号:用来表示IP协议的版本,现在常用的IP协议有两个版本,IPv4和IPv6&#xff0c;其他版本可能只存在于实验室中&#xff0c;并没有被广泛的使用。 首部长度:用来表示IP报头的长度,因为存在"选项"字段&#xff0c;所以IP报头是可变长的,此处单位为4…

春秋云镜 CVE-2023-51048

靶标介绍&#xff1a; S-CMS v5.0 被发现存在SQLI。 开启靶场 根据题目查找S-CMS v5.0漏洞&#xff0c;百度没有查询到&#xff0c;使用必应搜索S-CMS v5.0 查找到githubCVE-2023-51052的描述 S-CMS v5.0 was discovered to contain a SQL injection... CVE-2023-51052 Git…