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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大厂高频面试题:ReentrantLock 与 synchronized异同点对比

写在开头 在过去的博文中我们学习了ReentrantLock 与 synchronized这两种Java并发使用频率最高的同步锁&#xff0c;在很多大厂面试题中有个经典考题&#xff1a; ReentrantLock 与 synchronized异同点对比&#xff01; 今天我们针对这一考题来做一个尽可能全面的总结哈。 Re…

UML/SysML建模工具更新情况-截至2024年4月(1)5款-Trufun建模平台 v2024

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 工具最新版本&#xff1a;itemis CREATE 5.2.2 更新时间 2024年3月22日 工具简介 原名YAKINDU Statechart Tools。状态机建模工具&#xff0c;支持各种语言的代码生成&#xff0c;提…

B2024 输出浮点数 洛谷题单

首选需要进行了解的就是%a.bf所代表的含义就行了&#xff0c;直接莽了&#xff0c;没啥解释的笑脸&#x1f644; 在 Python 中&#xff0c;%a.bf 中的参数 a 和 b 是用来格式化浮点数的输出的&#xff0c;具体含义如下&#xff1a; a 表示总输出宽度&#xff0c;包括小数点、…

链表基础4——带头双向循环链表(c语言实现)

什么是带头双向循环链表 我们直接看图片 定义结点类型 typedef int LTDataType;//存储的数据类型typedef struct ListNode {LTDataType data;//数据域struct ListNode* prev;//前驱指针struct ListNode* next;//后继指针 }ListNode;链表的初始化 //创建一个新结点 ListNod…

什么是反向 ETL?为什么它很有价值?

提取、转换、加载 &#xff08;ETL&#xff09; 过程已经成熟并被广泛采用。 它只涉及从各种源系统中获取数据&#xff0c;将其转换为标准化数据模型&#xff0c;然后将其加载到数据仓库中。从那里&#xff0c;您的团队使用其商业智能 &#xff08;BI&#xff09; 和分析工具中…

FPGA - ZYNQ Cache一致性问题

什么是Cache&#xff1f; Cache是一种用来提高计算机运行速度的一种技术。它是一种小而快的存储设备&#xff0c;位于CPU与内存之间&#xff0c;用于平衡高速设备与低速设备之间的速度差异。Cache可以存储常用的数据或指令&#xff0c;以便CPU更快地获取&#xff0c;从而减少对…

基于单片机的智能病床呼叫系统设计与仿真

摘 要 本文设计的病床呼叫系统采用单片机作为控制器。该系统具有远程控制、病人的身体情况检测、报警呼叫、显示和执行器运动的功能。远程控制由红外线传感器和矩阵键盘组成&#xff0c;检测电路由温湿度传感器DH22、心率传感器Pulse Sensor、压力传感器MPX4115组成&#x…

操作系统——进程

进程定义 是计算机中已经运行的程序是系统进行资源分配和调度的一个独立单位。 进程的特性 独立性&#xff1a;进程在内存中可以独立寻址&#xff0c;每个进程都有一个独立的堆栈空间。动态性&#xff1a;进程在执行过程中可以申请资源、使用资源、释放资源。并发性&#xf…

OceanBase开发者大会2023届视频及PPT汇总

数据库技术趋势 我眼中的数据库技术 阳振坤OceanBase 首席科学家 观看视频 下载 PDF 未来&#xff0c;中国需要什么样的数据库&#xff1f; 周傲英华东师范大学副校长&#xff0c;CCF 会士 观看视频 下载 PDF 云原生技术趋势解读 Keith ChanCNCF 云原生计算基金会中国区总监 …

批量规范化(batchnormalization)

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

Linux关闭swap分区操作[适用于CDH报警等]

1.查看swap分区挂载路径(没卵用) swapon -s 2.设置配置文件的swap配置 echo “vm.swappiness 0” > /etc/sysctl.conf 3.设置内存中的swap状态。有时候配置文件为0&#xff0c;但集群或服务仍然使用了swap分区&#xff0c;可能原因就是内存没有同步配置 echo “0” > …

【配电网故障定位】基于二进制蝙蝠算法的配电网故障定位 33节点配电系统故障定位【Matlab代码#80】

文章目录 【获取资源请见文章第6节&#xff1a;资源获取】1. 配电网故障定位2. 二进制蝙蝠算法3. 算例展示4. 部分代码展示5. 仿真结果展示6. 资源获取 【获取资源请见文章第6节&#xff1a;资源获取】 1. 配电网故障定位 配电系统故障定位&#xff0c;即在配电网络发生故障的…