关于赋值的疑问
什么时候需要重载赋值操作符?
编译器是否提供默认的赋值操作符?
- 编译器为每个类默认重载了赋值操作符
- 默认的赋值操作符仅完成浅拷贝
- 当需要进行深拷贝时必须重载赋值操作符
- 赋值操作符与拷贝构造函数有相同的存在意义
编程实验: 默认赋值操作符重载
test_1.cpp
#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);
}
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;
}
输出:【运行时发生内存错误】
m_pointer = 0x942f008
m_pointer = 0x942f008
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0942f008 ***
......
已放弃
现象:
t1.m_pointer 与 t2.m_pointer 指向同一片内存空间
修正: test_2.cpp
#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;
}
输出:
m_pointer = 0x83ac008
m_pointer = 0x83ac018
现象:
t1.m_pointer 与 t2.m_pointer 指向不同的内存空间
问题分析
- test_2.cpp 类中未定义赋值操作符重载函数。在 t2 = t1; 对象赋值时,编译器默认提供的赋值操作符重载函数被调用,发生浅拷贝;
- t1.m_pointer 与 t2.m_pointer 指向 t1.m_pointer 所指向的内存空间,未给 t2.m_pointer 重新分配;
- 在对象销毁,析构函数被调用时,同一片内存空间被释放两次,导致运行时发生内存错误。
一般性原则
重载赋值操作符,必然需要实现深拷贝!
重载赋值操作符注意事项
Test& operator = (const Test& obj)
{
if( this != &obj )
{
// ...
}
return *this;
}
- 返回类型为引用(连续赋值)
- 参数为 const 引用类型
- 进行自赋值判断
- 返回当前对象
编程实验: 数组类的优化
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 = (index >= 0) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
bool ret = (index >= 0) && (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 "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;
}
输出:
array.length() = 5
brray.length() = 10
array.length() = 10
brray.length() = 10
经典的面试问题
class T
{
};
==>
class T
{
public:
T();
T(const T&);
T& operator = (const T&);
~T();
};
关于 string 的疑问
- 下面的代码输出什么?为什么?
void code()
{
string s = "12345";
const char* p = s.c_str();
cout << p << endl;
s.append("abcde");
cout << p << endl;
}
编程实验: 字符串问题 1
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = "12345";
const char* p = s.c_str();
cout << p << endl;
s.append("abcde");
cout << p << endl; // p 成为了野指针!
return 0;
}
输出:
12345
12345
问题分析
【string 对象内部维护了一个指向数据(位于堆空间中)的 char* 指针,这个指针可能在运行的过程中发生改变】
s = "12345"; ==> s 对象在堆空间申请内存并将字符串"12345"拷贝进入,同时内部指针指向这一空间 (0xFF112233)
p = s.c_str(); ==> p 指针指向保存数据的内存空间 (0xFF112233)
s.append("abcde"); ==> s 对象重新在堆空间申请内存,并把两部分字符串拷贝进入,原有的堆空间被释放(0xFF112233),内部指针指向新的内存空间 (0xFF445566)
- 下面的程序输出什么?为什么?
void code()
{
const char* p = "12345";
string s = "";
s.reserve(10);
for(int i=0; i<5; i++)
{
s[i] = p[i];
}
cout << "s.length : " << s.length() << endl;
cout << "s.empty : " << s.empty() << endl;
cout << "s : " << s << endl;
}
编程实验: 字符串问题 2
#include <iostream>
#include <string>
using namespace std;
int main()
{
const char* p = "12345";
string s = "";
s.reserve(10); // 将 s 的容量调整为 10
for(int i=0; i<5; i++) // 不要使用 C 语言中的方式操作 C++ 中的字符串
{
s[i] = p[i];
}
cout << "s.length : " << s.length() << endl;
cout << "s.empty : " << s.empty() << endl;
cout << "s : " << s << endl;
return 0;
}
输出:
s.length : 0
s.empty : 1
s :
问题分析
for 循环前:
m_cstr 指向10个字节的堆空间(0xFF010203)
m_length 字符长度为 0 (空串)
for 循环后:
m_cstr 指向 10 个字节的堆空间前 5 个字节发生改变(0xFF010203)
m_length 字符长度为 0 (空串)
警告:
在 C++ 中尽量只采用 C++ 编程思想,而不采用混合方式开发,否则可能产生意想不到的问题
小结
- 在需要进行深拷贝的时候必须重载赋值操作符
- 赋值操作符和拷贝构造函数有同等重要的意义
- string 类通过一个数据空间保存字符数据
- string 类通过一个成员变量保存当前字符串的长度
- C++ 开发时尽量避开 C 语言中惯用的编程思想
以上内容参考狄泰软件学院系列课程,请大家保护原创!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。