第36课 经典问题(上)----重载赋值操作符
什么时候需要重载赋值操作符?
编译器是否提供默认的赋值操作符?
编译器为每个类默认重载了赋值操作符
默认的赋值操作符仅完成浅拷贝
当需要进行深度拷贝时必须重载赋值操作符
赋值操作符与拷贝构造函数有相同的存在意义
#include <iostream>
using namespace std;
class Test
{
private:
int *m_pointer;
public:
Test()
{
m_pointer = NULL;
}
Test(int i)
{
m_pointer = new int(i);
}
void print()
{
cout << "m_pointer=" << hex << m_pointer << endl;
}
~Test()
{
delete m_pointer;
}
};
int main()
{
Test t1 = 1;
Test t2;
t2 = t1;
t1.print();
t2.print();
return 0;
}
编译时可以通过,运行时程序发生崩溃。
原因:t2 = t1;
程序在崩溃之前,指向了相同的堆空间,打印完之后,main函数即将结束,将对象t1和t2销毁,将触发析构函数的调用。两次删除同一个堆空间,程序肯定要崩溃,此时必须进行深拷贝。
只要一个类中它有成员指向了外部的资源,此时必须进行深拷贝,于是必须要重载赋值操作符,在有必要的情况下自定义拷贝构造函数。赋值操作符和拷贝构造函数具有同等重要的意义
#include <iostream>
using namespace std;
class Test
{
private:
int *m_pointer;
public:
Test()
{
m_pointer = NULL;
}
Test(int i)
{
m_pointer = new int(i);
}
Test(const Test& obj) //定义
{
m_pointer = new int(*obj.m_pointer);
}
Test& operator = (const Test& obj) //赋值操作符的重载,1.返回值必须是一个引用,就是为了连续赋值。2.参数必须是一个const的引用类型。
//3.赋值操作不是自赋值,就是自己赋给自己。4.必须将当前的对象返回。
{
if(this != &obj)
{
delete m_pointer; //先将当前对象中的m_pointer指针所指的内存空间删除。下面会重新生成。
m_pointer = new int(*obj.m_pointer);
}
return *this;
}
void print()
{
cout << "m_pointer=" << hex << m_pointer << endl;
}
~Test()
{
delete m_pointer;
}
};
int main()
{
Test t1 = 1;
Test t2;
t2 = t1; //编译器会去看,在当前的Test类中有没有重载赋值操作符。如果发现重载赋值操作符,就使用所定义的实现了。
t1.print();
t2.print();
return 0;
}
(1)要熟练记住,重载赋值操作符的那4点注意事项。只要记住了这4点,以后工作中如果遇到重载赋值操作符,基本上就不会出现bug。
(2)t2 = t1,这个地方它会不会调用拷贝构造函数,即深拷贝。答案是不会的,为什么?
因为这个地方是赋值,不是初始化。赋值的时候是不会触发拷贝构造函数的调用。这点在前面已经说过。如果是下面的这种形式:
Test t2 (t1);等价于Test t2 = t1(这是初始化),此时会触发拷贝构造函数的调用。要注意:构造函数只有在定义对象或初始化对象的时候调用,这与赋值操作符是完全不同的,不要混淆。
(3)假设在程序中,没有重载赋值操作符,t2 = t1;只会调用默认的拷贝构造函数,实现浅拷贝的工作。结果就是两个对象的指针指向了同一片内存空间,程序运行时崩溃。
(4)在程序中,如果将t2 = t2,会出现什么情况。
编译可以通过,运行也没有问题,但是没有任何的意义。没有意义,为什么这种情况还存在,就是为了兼容C语言。
在C语言中,这样是合法的:
int i = 0;
i = i
C++为了兼容C语言,不得已也必须支持这种写法。因此我们在重载赋值操作符的时候,必须处理自赋值的情况。如何处理呢?此处是通过地址判断的。
数组类的优化
#ifndef _INTARRAY_H_
#define _INTARRAY_H_
class IntArray
{
private:
int m_length;
int* m_pointer;
IntArray(int len);
IntArray(const IntArray& obj);//拷贝构造函数定义为私有的,它在外部是无法调用的。实际上,使用了二阶构造之后,拷贝构造函数就不起作用了。
//不允许拷贝构造,但是允许赋值,因此赋值操作符的重载还是有必要的。
bool construct();
public:
static IntArray* NewInstance(int length);
int length();
bool get(int index, int& value);
bool set(int index ,int value);
int& operator [](int index);
IntArray& operator = (const IntArray& obj);
IntArray& self();
~IntArray();
};
#endif
#include "IntArray.h"
IntArray::IntArray(int len)
{
m_length = len;
}
bool IntArray::construct()
{
bool ret = true;
m_pointer = new int[m_length];
if( m_pointer )
{
for(int i=0; i<m_length; i++)
{
m_pointer[i] = 0;
}
}
else
{
ret = false;
}
return ret;
}
IntArray* IntArray::NewInstance(int length)
{
IntArray* ret = new IntArray(length);
if( !(ret && ret->construct()) )
{
delete ret;
ret = 0;
}
return ret;
}
int IntArray::length()
{
return m_length;
}
bool IntArray::get(int index, int& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
int& IntArray::operator [](int index)
{
return m_pointer[index];
}
IntArray& IntArray::operator =(const IntArray& obj)
{
if(this != &obj)
{
int* pointer = new int[obj.m_length];
for(int i=0; i<obj.m_length; i++)
{
pointer[i] = obj.m_pointer[i];
}
m_length = obj.m_length;
delete[] m_pointer;
m_pointer = pointer;
}
return *this;
}
IntArray& IntArray::self()
{
return *this; //返回this指针指代的当前对象即可。
}
IntArray::~IntArray()
{
delete[]m_pointer;
}
#include <iostream>
#include <string>
#include "IntArray.h"
using namespace std;
int main()
{
IntArray* a = IntArray::NewInstance(5);
IntArray* b = IntArray::NewInstance(10);
if( a && b )
{
IntArray& array = a->self();
IntArray& brray = b->self();
cout << "array.length() = " << array.length() << endl;
cout << "brray.length() = " << brray.length() << endl;
array = brray;
cout << "array.length() = " << array.length() << endl;
cout << "brray.length() = " << brray.length() << endl;
}
delete a;
delete b;
return 0;
}
一般性原则:重载赋值操作符,必然需要实现深拷贝。反过来,如果要实现深拷贝,就必须提供赋值操作符的自定义实现以及自定义拷贝构造函数。
编译器默认提供的函数
补充:
1. new int[]是创建一个int型数组,数组大小是在[]中指定
int *p = new int[3];//申请一个动态整形数组,数组的长度为[]中的值
2. new int()是创建一个int型数,并且用()括号中的数据进行初始化,例如:
int *p = new int(10); //p指向一个值为10的int数。