各位好友,欢迎来到本期博客 !下面将为大家 讲解 本期模块 最后一个重要的 默认构造成员函数
---->赋值运算重载函数
1. 运算符重载
--->C++ 为了增强代码的可读性引入了运算符重载, 运算符重载是具有特殊的函数名的函数 !有其返回值类型, 函数名以及参数列表,其返回值类型 ---->同参数列表与普通函数类似 !
函数名称 :>关键字 --->operator 后面需要重载的运算符号。
函数原型 :>返回值类型 ---> operator 操作符。
---->注意 :>
(1)不能通过连接其他符号来创建新的操作符:比如, operator@
(2)重载操作符必须有一个类 -->类型参数
(3)用于内置类型的运算符, 其含义不能被改变。例如,内置的整形 “+”,不可以改变其含义
(4)作为类成员函数重载时, 其形参比操作数目少 1, 因为 成员函数的第一个参数为隐含的 “this”
(5)不能重载的运算符 :> " .* " <-->“ :: ” <-->“ sizeof ” <-->“ ?:” <-->“ . ”
----->实现 --->测试环节 -->初步探索 :>
---->赋值重载函数 --->成员函数 :>
---->赋值重载函数 --->类外部 :>
--->错误示范 :>访问符限制:>
--->正确样例:>
现 对特别新颖的写法, 进行点赞说明 :>
各位好友,请注意观察,上述红色框框 !是不是这种写法,特别香呢 !
这就是 重载运算符 “ operator ” 运用 !下面,继续推进,这种专属于 C++ 味道--->香喷喷 😊
现 对上述有新颖点的代码 --->进行解析说明 :>
请各位好友,注意上述红色框框 !隐藏版 “this” 指针, 是不能写出来的 !因此, 将 赋值重载函数 放在类外部 是比 放在类中 成为成员函数 少一个参数 !
------>赋值运算符重载
--->格式
(1)参数类型:const X& --->传递引用可以提高传参效率;
(2)返回值类型: X&, 返回引用可以提高返回的效率, 有返回值目的是 支持 --->连续赋值;
(3)检测是否给自己赋值;
(4)返回 *this :要复合连续赋值的操作(注意:此处并没有打错字,就是 “复合”)
--->验证 格式(2)
--->解析 -->格式(2)
--->返回值 类 类型运用, 并且此处用了 & 引用符号, 只因 传引用的效率 --->最高,能用引用 绝不用传值
--->已经存在的两个对象之间的拷贝,如下:
---->同构造函数之间 -->比较说明 :>
各位好友, 两者之间 有很大的不同 !请注意:>注释 -->提示解析!
--->验证 格式(4)
各位好友, 格式 4 最为关键 --->连续赋值 -->如下 :>
针对以上代码, 是不是 赋值重载函数 挺香的 !下面, 请大家跟着节奏, 继续推进 --->新的知识点 !
----->赋值运算符只能重载成类的成员函数, 不能重载成全员函数(即是 -->放在全局)
--->验证如下 :>
各位好友,VS2019 还是很强大的 ! 将 赋值重载函数 放在 类外部,竟然 自动定位 -->错误 !并且指明错误原因 !
下面进行原因分析 :>
赋值运算符放在类 外部实现, 编译器仍然会在类中自动生成一个默认的重载函数 !此时用户再在外部实现一个全局的赋值重载函数,就会与 内部 -->编译器生成的默认重载函数发生冲突 !
因此, 赋值重载函数只能放在类中, 成为类的成员函数 !
各位好友,以下的探究, 是老生常谈了 !仍然是,用户没有显示实现时, 编译器会默认生成一个 赋值运算符重载函数,以值的方式逐字节拷贝。其中, 内置成员类型变量是直接赋值,而自定义类型成员变量会去调用对应类的赋值运算符重载 --->完成相应的赋值 !
----->探究过程:>
----->调试结果:>
各位好友,上述调试结果 !很好地符合 --->用户没有显示定义时, 编译器会自动生成 默认重载函数;而自定义类型对象,则会去调用它本身存在的的重载函数 !其实,好友们,上述话语,是解释不清楚的 !这需要大家,自己摞列代码,之后打上断点,进行调试 ! VS 2019 调试是蛮香的, 是比 VS Code 要好用 !😊
下面继续推进, 对于上述日期类的实现,调用赋值重载函数 -->编译器可自动生成进而完成 --->实现日期类的赋值重载函数 !那对于栈区类实现呢?
---->答案 :>程序会崩溃 !这同栈区实现默认拷贝构造函数是同样道理,二者都需要自己去进行定义 --->才能够用来实现显示!
----->以下是测试代码环节 :>
#include <iostream>
using std::cout;
using std::endl;
#include <stdlib.h>
typedef int DataType;
class Stack
{
public:
// 初始化函数 --->又名, 构造函数
Stack(int capacity = 5)
{
_a = (DataType*)malloc(sizeof(DataType) * capacity);
if(_a == nullptr)
{
perror("malloc::fail");
return ;
}
_capacity = capacity;
top = 0;
}
void Push(DataType x)
{
// CheckCapacity();
_a[top++] = x;
}
// 销毁函数 --->又名, 析构函数
~Stack()
{
if(_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
top = 0;
}
}
private:
DataType* _a;
int _capacity;
int top;
};
int main()
{
// ··· 此处不需要书写上构造函数用来初始化函数
Stack s1;
s1.Push(12);
s1.Push(13);
s1.Push(16);
s1.Push(17);
s1.Push(19);
// ··· 此处也不需要写上析构函数用来销毁函数
return 0;
}
为了方便好友们, 更好地观感体验, 与更好地理解 !现 附上有彩色的代码图样 :>
为了解析上述答案 :>程序会崩溃 !下面采用图示解析过程, 进行讲解 :>
------>解析 :>
(1)s1 对象调用构造函数创建, 在构造函数中, 默认申请了 5 个字节空间, 然后存储了 4 个元素 19 17 16 13 12 (入栈次序)
(2)s2 对象调用构造函数创建, 在构造函数中, 默认申请了 5 个元素空间, 没有存储元素 ;
(3)由于Stack没有显示实现赋值运算符重载, 编译器会以浅拷贝方式实现一份默认的赋值运算符重载, 即只要发现 Stack 对象之间相互进行赋值, 就会将一个对象中的内容 --->原封不动拷贝到另一个对象当中;
(4)s2 = s1 ;当 s2 给 s1 对象进行赋值操作的时候,编译器会将s1对象内容原封不动地拷贝到另一个对象当中
但是,这样会到导致两个问题出现 :>
a. s2 原来的空间丢失了, 存在内存泄露 ;
b. s1 对象 与 s2 对象共同享用同一块空间, 最终的销毁, 导致同一份内存空间进而连续释放两次
致使, 程序出现崩溃 !
各位好友, 本期博客已经讲解完成 !希望 本期博客,可以为好友 带来新的体验 与收获 !
下一期, 继续推进 本模块 !进而转战 --->日期类计算 😊