浅拷贝
还是顺序栈
#include <iostream>
using namespace std;
class SeqStack
{
public:
//构造函数
SeqStack(int size = 10)
{
cout << this << " SeqStack() " << endl;
_pstack = new int[size];
_top = -1;
_size = size;
}
//析构函数
~ SeqStack()
{
cout << this << " ~SeqStack() " << endl;
delete[]_pstack;
_pstack = nullptr;
}
void push(int val)
{
if (full())
{
resize();
}
_pstack[++_top] = val;
}
void pop()
{
if (empty())
{
return;
}
--_top;
}
int top()
{
return _pstack[_top];
}
bool empty()
{
return _top == -1;
}
bool full()
{
return _top == _size - 1;
}
private:
int *_pstack;//动态开辟数组,存储顺序栈的元素
int _top;//指向栈顶元素的位置
int _size;//数组扩容的总大小
void resize()
{
int *ptmp = new int[2 * _size];
for (int i = 0; i < _size; ++i)
{
ptmp[i] = _pstack[i];
}
delete[]_pstack;
_pstack = ptmp;
_size *= 2;
}
};
int main()
{
SeqStack s;//没有提供任何构造函数时,会默认构造与默认析构,是空函数
SeqStack s1(10);
SeqStack s2 = s1;//#1 默认拷贝构造函数,做直接内存数据拷贝
//SeqStack s3(s1);#2
return 0;
}
在main函数中,#1与#2都调用了拷贝构造函数 。
这个程序会运行失败,原因是先构造出s1对象,再利用s1构造s2对象时,只是简单的将s1里成员变量的值复制到了s2里,s1与s2中_pstack成员同指向一块堆空间。而当析构时,s2先析构,s2中_pstack所指向的堆空间内存被释放掉了。但是s1中_pstack成员变量所存的依然是这块内存的地址,但是这块内存已经被释放掉了,s1中_pstack成了一个野指针,当s1析构时,就会出现一个释放野指针的操作,程序当然就挂掉了。这就是浅拷贝,也就是对象直接进入内存拷贝。
并不是所有的浅拷贝都会出错,当对象中成员对象,尤其是指针指向了对象之外的资源时,当发生浅拷贝时,导致两个对象的指针指向了同一个资源。第一个对象析构时,把这个资源释放,第二个对象析构时,就会出错,因为同一个资源不能释放两次。
1.对象默认的拷贝构造是做内存的数据拷贝。
2.如果对象占用外部资源,浅拷贝会出问题。这时候该使用深拷贝!
3.如果发生浅拷贝,则需要自定义拷贝构造函数和赋值重载函数。
深拷贝
就是当发生拷贝时,对象的成员变量不只只把值复制下来,如果对象的成员变量有指针指向外部资源,应该给拷贝对象的成员变量也开辟一个外部资源,让新对象的指针指向这个外部资源。这样,每个对象的指针都指向不同的外部资源,析构时也就互不影响。
#include <iostream>
using namespace std;
class SeqStack
{
public:
//构造函数
SeqStack(int size = 10)
{
cout << this << " SeqStack() " << endl;
_pstack = new int[size];
_top = -1;
_size = size;
}
//自定义拷贝构造函数(深拷贝),因为对象的浅拷贝出了问题
SeqStack(const SeqStack &src)
{
cout << "SeqStack(const SeqStack &src)" << endl;
_pstack = new int[src._size];
for(int i=0; i<=src._top; ++i)
{
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
//析构函数
~ SeqStack()
{
cout << this << " ~SeqStack() " << endl;
delete[]_pstack;
_pstack = nullptr;
}
void push(int val)
{
if (full())
{
resize();
}
_pstack[++_top] = val;
}
void pop()
{
if (empty())
{
return;
}
--_top;
}
int top()
{
return _pstack[_top];
}
bool empty()
{
return _top == -1;
}
bool full()
{
return _top == _size - 1;
}
private:
int *_pstack;//动态开辟数组,存储顺序栈的元素
int _top;//指向栈顶元素的位置
int _size;//数组扩容的总大小
void resize()
{
int *ptmp = new int[2 * _size];
for (int i = 0; i < _size; ++i)
{
ptmp[i] = _pstack[i];
}
delete[]_pstack;
_pstack = ptmp;
_size *= 2;
}
};
int main()
{
SeqStack s;//没有提供任何构造函数时,会默认构造与默认析构,是空函数
SeqStack s1(10);
SeqStack s2 = s1;//#1 默认拷贝构造函数,做直接内存数据拷贝
//SeqStack s3(s1);#2
return 0;
}
可以看到
可以看到,程序运行没有问题,构造三次,析构三次。
赋值重载函数
与深拷贝的区别在于,深拷贝需要重新构造对象,而拷贝赋值中,对象已经存在了,只是将一个对象里的值拷贝给另一个对象。
int main()
{
SeqStack s1(10);
SeqStack s2 = s1;
s2 = s1;//赋值操作,也属于内存拷贝
return 0;
}
//赋值拷贝函数
void operator=(const SeqStack &src)
{
cout << " operator=" << endl;
//防止自己给自己赋值
if(this == &src)
{
return;
}
//需要先释放当前对象占用的外部资源
delete[]_pstack;
//拷贝构造
_pstack = new int[src._size];
for(int i=0; i<=src._top; ++i)
{
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
可以看到实现了拷贝赋值操作。
总结下来拷贝赋值主要有三步:
1.防止自赋值
2.释放当前对象占用的外部资源
3.进行拷贝构造(深拷贝)