文章目录
一、关于赋值的疑问
什么时候需要重载赋值操作符?编译器是否提供默认的赋值操作?
- 编译器为每个类默认重载了赋值操作符
- 默认的赋值操作符仅完成浅拷贝
- 当需要进行深拷贝时必须重载赋值操作符
- 赋值操作符与拷贝构造函数有相同的存在意义
下面看一段代码:
#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 printf()
{
cout << "m_pointer = " << hex << m_pointer << endl;
}
~Test()
{
delete m_pointer;
}
};
int main()
{
Test t1 = 1;
Test t2;
t2 = t1;
t1.printf();
t2.printf();
return 0;
}
输出结果如下:
m_pointer = 0x8d17008
m_pointer = 0x8d17008
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x08d17008 ***
======= Backtrace: =========
/lib/libc.so.6(+0x6c0c1)[0x7880c1]
/lib/libc.so.6(+0x6d930)[0x789930]
/lib/libc.so.6(cfree+0x6d)[0x78ca1d]
/usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x1ba441]
./a.out[0x8048a51]
./a.out[0x804883c]
/lib/libc.so.6(__libc_start_main+0xe7)[0x732ce7]
./a.out[0x8048741]
======= Memory map: ========
00110000-001ef000 r-xp 00000000 08:02 264496 /usr/lib/libstdc++.so.6.0.14
001ef000-001f3000 r--p 000de000 08:02 264496 /usr/lib/libstdc++.so.6.0.14
001f3000-001f4000 rw-p 000e2000 08:02 264496 /usr/lib/libstdc++.so.6.0.14
001f4000-001fb000 rw-p 00000000 00:00 0
001fb000-0021f000 r-xp 00000000 08:02 4643 /lib/libm-2.12.1.so
0021f000-00220000 r--p 00023000 08:02 4643 /lib/libm-2.12.1.so
00220000-00221000 rw-p 00024000 08:02 4643 /lib/libm-2.12.1.so
0024b000-00265000 r-xp 00000000 08:02 102 /lib/libgcc_s.so.1
00265000-00266000 r--p 00019000 08:02 102 /lib/libgcc_s.so.1
00266000-00267000 rw-p 0001a000 08:02 102 /lib/libgcc_s.so.1
0071c000-00873000 r-xp 00000000 08:02 4645 /lib/libc-2.12.1.so
00873000-00875000 r--p 00157000 08:02 4645 /lib/libc-2.12.1.so
00875000-00876000 rw-p 00159000 08:02 4645 /lib/libc-2.12.1.so
00876000-00879000 rw-p 00000000 00:00 0
00acb000-00acc000 r-xp 00000000 00:00 0 [vdso]
00d49000-00d65000 r-xp 00000000 08:02 4629 /lib/ld-2.12.1.so
00d65000-00d66000 r--p 0001b000 08:02 4629 /lib/ld-2.12.1.so
00d66000-00d67000 rw-p 0001c000 08:02 4629 /lib/ld-2.12.1.so
08048000-08049000 r-xp 00000000 08:05 525237 /home/delphi/a.out
08049000-0804a000 r--p 00000000 08:05 525237 /home/delphi/a.out
0804a000-0804b000 rw-p 00001000 08:05 525237 /home/delphi/a.out
08d17000-08d38000 rw-p 00000000 00:00 0 [heap]
b7600000-b7621000 rw-p 00000000 00:00 0
b7621000-b7700000 ---p 00000000 00:00 0
b7764000-b7767000 rw-p 00000000 00:00 0
b7774000-b7777000 rw-p 00000000 00:00 0
bfed5000-bfef6000 rw-p 00000000 00:00 0 [stack]
已放弃
t1 和 t2 在程序崩溃之前指向了相同的堆空间,main 函数在结束之前会销毁 t1 和 t2 对象,这会触发析构函数的调用,会去释放 m_pointer 所指向的堆空间,但是 t1 和 t2 是指向同一片堆空间,这是不合法的,所以程序就会崩溃。
- 问题分析
因为 m_pointer 指向堆空间的一片内存, 所以浅拷贝是不够用的,需要进行深拷贝,因此需要重载赋值操作符,代码可以这么写:
#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 printf()
{
cout << "m_pointer = " << hex << m_pointer << endl;
}
~Test()
{
delete m_pointer;
}
};
int main()
{
Test t1 = 1;
Test t2;
t2 = t1;
t1.printf();
t2.printf();
return 0;
}
输出结果如下:
赋值操作符重载注意事项 :
- 返回值类型是一个引用,为了能够连续赋值
- 参数必须是 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 = (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;
}
输出结果如下:
二、面试小问题
- 编译器默认提供的函数
三、关于 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;
}
输出结果如下:
按理说第二次打印 p 会输出 12345abced,为什么只输出 12345 呢?
- 问题分析
string 对象内部维护了一个指向数据的 char* 指针,这个指针可能在程序运行的过程中发生改变。
所以可以这么写:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = "12345";
s.append("abced");
cout << s << endl;
return 0;
}
输出结果如下:
这个告诉我们,要么使用 C 语言,要么使用 C++ 语言,千万不要混合使用,可能会产生难以预料的 bug。
- 下面的程序输出什么?为什么?
下面通过编程来看看:
#include <iostream>
#include <string>
using namespace std;
int main()
{
const char* p = "12345";
string s = "";
s.reserve(10);
for (int i = 0; i < 5; i++)
{
s[i] = p[i];
}
if( !s.empty() )
{
cout << s << endl;
}
return 0;
}
可以看到,没有任何输出:
- 问题分析
表示字符串长度的值在 for 循环执行后还是 0,所以打印不了值,究其原因,还是混合使用了 C 语言和 C++。
编写代码验证下:
#include <iostream>
#include <string>
using namespace std;
int main()
{
const char* p = "12345";
string s = "";
s.reserve(10);
for (int i = 0; i < 5; i++)
{
s[i] = p[i];
}
cout << s.length() << endl;
return 0;
}
可以看到确实为 0。
四、小结
- 在需要进行深拷贝的时候必须重载赋值操作符
- 赋值操作符和拷贝构造函数有同等重要的意义
- string 类通过一个数据空间保存字符数据
- string 类通过一个成员变量保存当前字符串的长度
- C++ 开发时尽量避开 C 语言中惯用的编程思想