模板2——顺序表的实现(现代写法的进一步解析,更深层次的深浅拷贝)

时间:2022-03-27 19:52:18

之前我们讲解了关于模板方面的知识,今天我们用模板来实现顺序表。
这次顺序表的实现和之前没有太大区别,但还是有几点需要我们来重点进行详解。
我们先来看下面这段代码

#include<iostream>
#include<string>
#include<assert.h>

using namespace std;
template<typename T>

class SeqList
{
public:
SeqList()
:_a(NULL), _size(0), _capacity(0)
{}

SeqList(const SeqList<T>& d)
{
_a = (T*)malloc(d._a, sizeof(T)*_size); //注:问题一
memcpy(d._a, _a, sizeof(T)*_size);
_size = d._size;
_capacity = d._size;
}

//l1 = l2;
SeqList<T>& operator=(SeqList<T> d)
{
swap(_size, d._size);
swap(_capacity, d._capacity);
swap(_a, d._a);

return *this;
}

~SeqList() //注:问题二
{
if (_a)
{
free(_a);
_size = _capacity = 0;
}
}

void PushBack(const T& x)
{
CheckCapacity();
_a[_size] = x;
_size++;
}

void PopBack()
{
if (_size > 0)
_size--;
}

void PushFront(const T& x)
{
CheckCapacity();
for (int i = _size; i>0; i--)
{
_a[i] = _a[i - 1];
}
_a[0] = x;
_size++;
}

void PopFront()
{
if (_size == 0)
{
return;
}
else
{
for (int i = 1; i < _size; i++)
{
_a[i - 1] = _a[i];
}
_size--;
}
}

T& Back()
{
assert(_size>0);
return _a[_size-1];
}

bool Empty()
{
return _size == 0;
}

void Insert(size_t pos, const T& x)
{
if (pos > _size)
{
cout << "pos错误" << endl;
return;
}
else
{
CheckCapacity();
for (int i = _size; i > pos; i--)
{
_a[i] = _a[i - 1];
}
_a[pos] = x;
_size++;
}
}

void Erase(size_t pos)
{
if (_size == 0)
{
return;
}
else
{
if (pos >= _size)
{
cout << "pos 错误" << endl;
return;
}
for (int i = pos; i < _size; i++)
{
_a[i] = _a[i + 1];
}
_size--;
}
}

int Find(const T& x)
{
for (int i = 0; i < _size; i++)
{
if (x == _a[i])
{
return i;
}
}
return -1;
}

void CheckCapacity() //注:问题三
{
if (_size > _capacity)
{
_capacity = _capacity * 2 + 3;
_a = (T*)realloc(_a, (_capacity) * sizeof(T));
}
}

void print()
{
for (size_t i = 0; i < _size; i++)
{
cout << _a[i] << " ";
}
cout << endl;
}

protected:
T* _a;
size_t _size;
size_t _capacity;
};

这里有必要再讲讲实现下面赋值运算符重载的现代写法的实现原理
首先看上面代码(l1 = l2),赋值运算符重载中的局部变量d是由l2拷贝构造而来,函数体内通过swap函数将this指针指向的l1与d发生了交换,即l1与l2发生了交换,局部变量d现在指向之前l1指向的地址,而l1指向d原先指向的地址,也就是l1被赋值成了l2,而局部变量d一出函数就会自动销毁,就会调用它的析构函数,会使得它指向的内存释放,从而实现l1与l2的交换。看下图

模板2——顺序表的实现(现代写法的进一步解析,更深层次的深浅拷贝)
注:swap函数也不能乱用,一般来说,swap函数适用于内置类型,下面看这句代码

swap(*this,l);   //直接交换两个对象岂不更好?

这段代码会出现什么情况呢?我们先看看swap函数是如何实现的。
模板2——顺序表的实现(现代写法的进一步解析,更深层次的深浅拷贝)
我们用的swap函数是这个模板实例化来的,它的倒数第二行,和倒数第三行都调用了赋值运算符重载,这样就造成递归调用,一直生成栈帧,直至出现栈溢出问题。
那我们一般如何实现这种问题呢?一般我们写自己的Swap函数实现这一问题

void Swap(SeqList<T>&  d)
{
swap(_size, d._size);
swap(_capacity, d._capacity);
swap(_a, d._a);
}

下面呢才是今天的重点,我们来看上面代码,这些代码在int, char等这种类型下是可以被顺利执行的,但是一旦换成string类型就会出现问题,在执行插入操作时,代码就会崩掉,这是什么问题呢?这就看问题三(看代码标识),在CheckCapacity()函数中,我们开空间用的是realloc函数,这就有个问题,realloc函a数只负责开空间,但不初始化,所以在插入操作的赋值语句时挂了,调试你会发现它的_a就是NULL,所以就会出现问题。所以我们用new[]来开空间,这时你前面析构函数中用free就会出现问题,所以连同拷贝构造函数中的malloc一起一改。这就完成了?哈哈,你太天真了。看下面这段验证代码:

void Test5()
{
SeqList<string> s1;
s1.PushBack("111");
s1.PushBack("222222222222222222222222222222222222222");
s1.PushBack("333");
s1.PushBack("444");
s1.print();
}

模板2——顺序表的实现(现代写法的进一步解析,更深层次的深浅拷贝)
但是改为s1.PushBack(“22222222222”);又是正常输出,或者你将s1.PushBaack(“444”)这句给屏蔽了,同样会正常输出。所以有理由相信在一个范围内就正常,超过就会出现异常。这里涉及到更深层次的深浅拷贝。

我们先来了解一下string是如何实现的。string里面由下面四部分组成,这是一种空间换时间的优化,如果存的字符串小于16,他就会将字符串存于它自带的空间_Buf,而大于等于16时,他就会重开一份空间存放这个字符串,用_Pre指向这块内存。你可以在vs08上面验证一下(调试窗口就可以看),我用的vs15不能展示出来。

struct string
{
sreing* _Buf;
string* _Ptr;
size_t _Mysize;
size_t _Myres;
};

看懂了上面string的具体实现后,上面所提问题就简单了,我用一幅图来说明问题
模板2——顺序表的实现(现代写法的进一步解析,更深层次的深浅拷贝)

所以这里就不能使用memcpy()函数,它的本质就是值拷贝,所以我们用下面代码解决这个问题。里面调用了赋值运算符的重载,即就是实现深拷贝。

for (int i = 0; i < _size; i++)
{
tmp[i] = _a[i];
}

下面是改善之后的代码:

#include<iostream>
#include<string>
#include<assert.h>

using namespace std;
template<typename T>

class SeqList
{
public:
SeqList()
:_a(NULL), _size(0), _capacity(0)
{}

SeqList(const SeqList<T>& d)
{
if (_a)
{
_a = new T[d._size];
for (int i = 0; i < d._size; i++)
{
_a[i] = d._a[i];
}
}
else
{
_a = NULL;
}
_size = d._size;
_capacity = d._size;
}

SeqList<T>& operator=(SeqList<T> d)
{
swap(_size, d._size);
swap(_capacity, d._capacity);
swap(_a, d._a);

return *this;
}


~SeqList()
{
if (_a)
{
delete[] _a;
_size = _capacity = 0;
}

}

void PushBack(const T& x) //尾插
{
CheckCapacity();
_a[_size] = x;
_size++;
}

void PopBack()
{
if (_size > 0)
_size--;
}

void PushFront(const T& x) //头插
{
CheckCapacity();
for (int i = _size; i>0; i--)
{
_a[i] = _a[i - 1];
}
_a[0] = x;
_size++;
}

void PopFront()
{
if (_size == 0)
{
return;
}
else
{
for (int i = 1; i < _size; i++)
{
_a[i - 1] = _a[i];
}
_size--;

}
}

T& Back()
{
assert(_size>0);
return _a[_size-1];
}

bool Empty()
{
return _size == 0;
}

void Insert(size_t pos, const T& x) //指定位置处插入指定值
{
if (pos > _size)
{
cout << "pos错误" << endl;
return;
}
else
{
CheckCapacity();
for (int i = _size; i > pos; i--)
{
_a[i] = _a[i - 1];
}
_a[pos] = x;
_size++;
}
}

void Erase(size_t pos) //删除指定位置的数值
{
if (_size == 0)
{
return;
}
else
{
if (pos >= _size)
{
cout << "pos 错误" << endl;
return;
}
for (int i = pos; i < _size; i++)
{
_a[i] = _a[i + 1];
}
_size--;
}
}

int Find(const T& x)
{
for (int i = 0; i < _size; i++)
{
if (x == _a[i])
{
return i;
}
}
return -1;
}

void CheckCapacity()
{
if (_size >= _capacity)
{
_capacity = _capacity * 2 + 3;
T* tmp = new T[_capacity];
if (_a)
{
//memcpy(tmp, _a, sizeof(T)*_size);//会使得指向同一块内存,下面delete[]之后,会调用析构函数,释放这块内存,造成另一指针野指针
for (int i = 0; i < _size; i++)
{
tmp[i] = _a[i];
}
delete[] _a;
}
_a = tmp;
}
}

void print()
{
for (size_t i = 0; i < _size; i++)
{
cout << _a[i] << " ";
}
cout << endl;
}


private:
T* _a;
size_t _size;
size_t _capacity;
};

//头插,头删
void Test1()
{
SeqList<int> s1;
s1.PushFront(1);
s1.PushFront(2);
s1.PushFront(3);
s1.PushFront(4);
s1.print();
s1.PopFront();
s1.PopFront();
s1.PopFront();
s1.PopFront();
s1.PopFront();
s1.print();
}

//尾插,尾删
void Test2()
{
SeqList<int> s1;
s1.PushBack(1);
s1.PushBack(2);
s1.PushBack(3);
s1.PushBack(4);
s1.print();
s1.PopBack();
s1.PopBack();
s1.PopBack();
s1.PopBack();
s1.PopBack();
s1.print();
}

//Insert/Erase/Find
void Test3()
{
SeqList<int> s1;
s1.PushBack(1);
s1.PushBack(2);
s1.PushBack(3);
s1.PushBack(4);
s1.print();
//int ret = s1.Find(1);
//s1.Insert(ret, 99);
//ret = s1.Find(2);
//s1.Insert(ret, 88);
//ret = s1.Find(3);
//s1.Insert(ret, 77);
//ret = s1.Find(4);
//s1.Insert(ret, 66);
//s1.print();
//int ret = s1.Find(1);
//s1.Erase(ret);
//s1.print();

//ret = s1.Find(3);
//s1.Erase(ret);
//s1.print();

//ret = s1.Find(4);
//s1.Erase(ret);
//s1.print();
//
//ret = s1.Find(2);
//s1.Erase(ret);
//s1.print();
s1.Erase(0);
s1.print();
}
//构造函数,赋值运算符重载
void Test4()
{
SeqList<int> s1;
SeqList<int> s2(s1);
SeqList<int> s3;
s3 = s1;
s3.PushBack(1);
s3.PushBack(2);
s3.PushBack(3);
s3.PushBack(4);
s3.print();
s3.PopBack();
s3.PopBack();
s3.PopBack();
s3.PopBack();
s3.PopBack();
s3.print();
}

//深层次的深浅拷贝
void Test5()
{
SeqList<string> s1;
s1.PushBack("111");
s1.PushBack("2222222222222222222222222222222222222222222222");
s1.PushBack("333");
s1.PushBack("444");
s1.print();
}

int main()
{
Test5();
system("pause");
return 0;
}