- 类对象的默认赋值行为 {#title-0} ========================
class Demo1
{
public:
Demo1(int a, int b, int c) : m_a(a), m_b(b), m_c(c) {}
public:
int m_a;
int m_b;
int m_c;
};
ostream& operator<<(ostream& os, const Demo1& demo)
{
os << demo.m_a << " " << demo.m_b << " " << demo.m_c;
return os;
}
void test01()
{
Demo1 demo1(10, 20, 30);
Demo1 demo2(100, 200, 300);
// 对象的初始化、赋值是不同的概念
// 对象初始化只会发生一次,在对象创建的时候。
// 对象赋值是对象创建完成之后(构造函数调用完成),才能进行赋值行为。可以发生多次。
// 下列代码没有报错,说明类对象支持默认的赋值行为
// 接下来,我们看一下,默认的赋值行为:1. 什么不做 2. 其他行为
// 通过程序运行,我么发现默认的赋值行为:将 demo2 中的数据拷贝(逐字节)到 demo1
demo1 = demo2;
/*
demo1.m_a = demo2.m_a;
demo1.m_b = demo2.m_b;
demo1.m_c = demo2.m_c
*/
cout << demo1 << endl << demo2 << endl;
// 假设类的内部都是基本的数据类型,使用默认赋值行为是 OK 的。
// 当类的内部一旦包含动态指针(指针成员,并且该成员指向堆空间),类的赋值行为就会变得复杂。
}
- 类对象中深赋值和浅赋值问题 {#title-1} ===========================
class Demo2
{
public:
Demo2()
{
p_arr = new int[10];
for (int i = 0; i < 10; ++i)
{
p_arr[i] = rand() % 100 + 1;
}
}
// 针对使用左值给当前对象赋值
Demo2& operator=(const Demo2& demo)
{
cout << "赋值运算符函数" << endl;
// 赋值运算符函数实现的一般过程
// 1. 对象判断,避免出现自身给自身赋值
if (this == &demo)
{
return *this;
}
// 2. 释放当前对象 p_arr 指向的动态内存,避免内存泄漏【根据实际情况来定】
if (p_arr != nullptr)
{
delete[] p_arr;
p_arr = nullptr;
}
// 3. 重新按照 demo.p_arr 指向空间的大小给当前对象申请内存【根据实际情况来定】
p_arr = new int[10];
// 4. 将 demo.p_arr 指向的空间的数据拷贝到当前对象 p_arr 指向的内存
// 也可以使用 memcpy 函数
for (int i = 0; i < 10; ++i)
{
p_arr[i] = demo.p_arr[i];
}
// 5. 返回自身对象
return *this;
// 注意:如果能够确定无论 Demo2 创建出多少个对象,p_arr 指向的空间大小都是一样的,那么,第2、3步可以省略
// 提高赋值效率。
}
// 针对右值给当前对象赋值
Demo2& operator=(Demo2&& demo)
{
cout << "移动赋值运算符函数" << endl;
// 1. 避免当前对象给自身赋值, 避免无用的操作
if (this == &demo)
{
return *this;
}
// 赋值是对象创建完成之后进行的,对象的 p_arr 指针是指向动态内存
if (this->p_arr != nullptr)
{
delete[] this->p_arr;
this->p_arr = nullptr;
}
// 2. 将 demo 中的资源【移动】到当前对象身上
this->p_arr = demo.p_arr;
// 3. 将 demo 对象的 p_arr 指针设置为 nullptr, 避免出现动态内存多次释放
demo.p_arr = nullptr;
// 4. 将自身类型对象返回
return *this;
}
~Demo2()
{
if (p_arr)
{
delete[] p_arr;
p_arr = nullptr;
}
}
public:
int* p_arr;
};
void test02()
{
srand((unsigned int)time(nullptr));
Demo2 demo1;
Demo2 demo2;
// 当类的内部包含动态指针,默认的赋值函数进行的是浅赋值,会导致两个问题:
// 1. 被赋值对象的内存泄漏
// 2. 赋值和被赋值对象重复指向同一个动态空间,导致动态空间多次释放
// 解决方案:
// 手动进行赋值过程的编写。
// C++ 类对象提供了一种特殊的函数:赋值运算符函数,在该函数的内部编写赋值行为。
// 当给类对象提供了赋值运算符函数,默认的赋值运算符函数就失效了。
demo1 = demo2; // Demo2& demo1.operator=(const Demo2& demo)
}
- 类对象的移动赋值行为 {#title-2} ========================
当对象进行赋值的时,并不是所有的对象都需要完整的赋值过程(重新申请内存、数据拷贝)。
假设,赋值对象是:右值对象(匿名对象、将亡值对象)
void test03()
{
Demo2 demo;
// 1. 使用匿名对象给 demo 赋值
// demo = Demo2();
// 2. 将亡值对象 dead 给 demo 赋值
Demo2 dead;
demo = move(dead); // move 函数将 dead 左值对象转换为右值对象
cout << dead.p_arr << " " << demo.p_arr << endl;
// 如何让对象具有移动赋值行为呢?
// 移动赋值行为本质上还是赋值行为,无非赋值对象变成右值。
// 所以,只需要给类对象增加赋值运算符函数即可,并且该函数的参数为右值引用即可。
// Demo2& operator=(Demo2&&)
}