问题
什么时候需要重载赋值操作符?
编译器是否提供默认的赋值操作?
关于赋值的疑问
编译器为每个类默认重载了赋值操作符
默认的赋值操作符仅完成浅拷贝
当需要进行深拷贝时必须重载赋值操作符
赋值操作符与拷贝构造函数有相同的存在意义
默认赋值操作符重载
#include <iostream>
#include <string>
using namespace std;
class Test
{
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)
{
if( this != &obj )
{
delete 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;
t1.print();
t2.print();
return 0;
}
如果类中没有重载拷贝赋值操作符,则编译器会为每个类提供一个默认的赋值操作符的重载,实现的是浅拷贝
第 44 行,我们定义的类对象 t1,它的成员变量 m_pointer 会指向堆空间的一片内存,第 45 行,我们定义的类对象 t2,它的成员变量 m_pointer 为 NULL,第 47 行,将 t2 赋值给 t1,会调用到拷贝赋值函数,如果拷贝赋值函数实现的是浅拷贝,则 t1 和 t2 的 m_pointer 会指向同一个堆空间,那么在 t1 和 t2 在被析构的时候,同一个堆空间会被释放两次,从而导致程序崩溃
所以,当类中存在系统资源的时候,我们要重载拷贝构造函数和拷贝赋值操作符,实现深拷贝
第 18 - 21 行,为拷贝构造函数深拷贝的实现,第 22 行 - 31 行,为拷贝赋值操作符深拷贝的实现
程序运行结果如下图所示:
问题分析
一般性原则
重载赋值操作符,必然需要实现深拷贝!!!
数组类的优化
IntArray.h
#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
IntArray.cpp
#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];
if( pointer )
{
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;
}
IntArray::~IntArray()
{
delete[]m_pointer;
}
main.cpp
#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;
}
我们为 IntArray 类重载了拷贝赋值操作符,实现了深拷贝
程序运行结果如下图所示:
编译器默认提供的函数
下面的代码输出什么?为什么?
字符串问题1
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = "12345";
const char* p = s.c_str();
cout << p << endl;
s.append("abcedefghij");
cout << p << endl;
return 0;
}
程序运行结果如下图所示:
string 类内部有一个字符指针, 第 9 行,p 指向了这个字符指针;第 13 行,我们为字符串类 s 追加了字符串
通过打印可以看出 p 的第二次输出并不符合我们的预期,这是因为我们在追加字符串的时候,s 里的字符指针会因为剩余的空间无法存在要追加的字符串,所以会分配一个更大的空间,然后字符指针会指向这片内存空间,但我们并没有更新指针 p,导致 p 指向的是一片已经释放了的内存空间,导致 p 的第二次打印结果不符合我们的预期
问题分析
下面的程序输出什么?为什么?
字符串问题2
#include <iostream>
#include <string>
using namespace std;
int main()
{
const char* p = "12345";
string s = "";
s.reserve(10);
// 不要使用 C 语言中的方式操作 C++ 中的字符串
for(int i=0; i<5; i++)
{
s[i] = p[i];
}
cout << s << endl;
return 0;
}
程序运行结果如下图所示:
通过打印可以看出,输出的 s 为空,这是因为,我们是通过数组方式改变 s 的值,这个是 C 语言的方式,这种方法不会改变字符串 s 的长度,s 的长度还是为 0,所以打印出 s 为空
问题分析
小结
在需要进行深拷贝的时候,必须重载赋值操作符
赋值操作符和拷贝构造函数有同等重要的意义
string 类通过一个数据空间保存字符数据
string 类通过一个成员变量保存当前字符串的长度
C++ 开发时尽量避开 C 语言中惯用的编程思想