文章内容来源于狄泰软件唐老师C++课程课件
一、赋值的疑问
什么时候需要重载赋值操作符
编译器是否提供了默认的赋值操作?
- 编译器为每个类默认重载了赋值操作符
- 默认的操作符仅完成浅拷贝
- 当需要进行深拷贝时必须重载赋值操作符
- 赋值操作符与拷贝构造函数具有相同的存在意义
#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)//没有这个函数的话t2=t1,触发析构函数,将释放同一片堆空间,被释放两次将报错,
{
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;m_pointer = 0x7e1a90
Test t2;m_pointer = 0
t1.print();
t2.print();
return 0;
}
t2=t1;
m_pointer = 0x7e1a90
m_pointer = 0x7e1ab0
一般性原则:
重载赋值操作符,必然需要实现深拷贝。
实例:数组类的优化
#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;
}
#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 <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;
}
数组类中有 int* m_pointer;它将指向堆空间的一段内存。所以要实现深拷贝。
这时我们想到的是自己定义实现深拷贝函数。但是此时我们采用的是二阶构造,拷贝构造函数是私有的,外面不允许调用。也将说使用了二阶构造就不能再使用拷贝构造函数了。因此我们需要实现赋值操作符重载。
赋值操作符重载实现的四个关键步骤。
IntArray& IntArray::operator = (const IntArray& obj)
{
if( this != &obj )
{
//实现深拷贝
return *this;
}
IntArray& 函数返回一个引用
const IntArray& obj函数参数
if( this != &obj )自己赋值给自己的时候
return *this;返回自己
二、编译器默认提供的函数
三、关于string的疑问:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = "12345";
const char* p = s.c_str();
cout << p << endl;
s.append("abced"); // p 成为了野指针
cout << p << endl;
return 0;
}
12345
12345
结果不是12345abcde,插入失败。
string s ,s是字符串类。
const char* p = s.c_str();
s.c_str();将返回一个C语言方式的字符串。
说明这段代码混合了出语言和C++的编程方式。
将
cout << p << endl; 改成:
cout << s<< endl;
j结果就是12345abcde。
问题分析:
在string类的内部有一个字符串指针,当s="12345"时,字符指针将指向一个堆空间,并且这个堆空间存在了这个字符串。
p=s.c_str();通过一个指针指向这个堆空间,p这个字符指针所代表的字符串就是12345
s.append(“abced”);字符指针所指向的堆空间变成了新的内容。但是新的字符指针并没有指向原来的地址。
也就是说string对象内部维护的char指针,在运行时改变了,原来的指针会被释放,释放后char p就是一个野指针了。
避免方法:不要混合C语言和C++编程思想。
实例:
#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];
}
//if(!s.empty())
//{
cout << s << endl;
// }
return 0;
}
无输出
如果
for(int i=0; i<5; i++)
{
cout << s[i] << endl;
}
将输出1 2 3 4 5
for前后m_length始终为0;
解决方法,不需要for循环。直接s=p即可。
总结:
- 在血压进行深拷贝的时候必须重载赋值操作符
- 赋值操作符合拷贝构造函数有同等的重要意义
- string类通过一个数据空间保存字符数据
- string类通过一个成员变量保存当前字符串的长度
- C++开发是进来避开C语言中惯用的编程思想