阅读《STL源码剖析》有感,根据vector特性,基于数组,手动模拟一个vector。首先定义几个typedef,方便指针的定义。
template<typename T>
class Vector
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef value_type& reference;
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef value_type& reference;
protected:
iterator start; //表示目前使用空间的头
iterator finish; //目前使用空间的尾
iterator end_of_storage; //目前可用空间的尾
};
定义一个迭代器类型iterator(指针),之后定义三个迭代器,start(表示目前使用空间的头)、finish(目前使用空间的尾)、end_of_storage(目前可用空间的尾)。之后通过对指针的操作,以及数组的扩容模拟vector。全部源码如下:
//Vector.h
#pragma once
#include <memory>
template<typename T>
class Vector
{
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef value_type& reference;
public:
Vector(int lenght = 0) :start(nullptr), finish(nullptr), end_of_storage(nullptr)
{
start = new value_type[lenght];
finish = start;
end_of_storage = &start[lenght];
}
~Vector()
{
delete[]start;
start = nullptr;
}
public:
iterator begin()
{
return start;
}
iterator end()
{
return finish;
}
int size()
{
return static_cast<int>(end() - begin());
}
void push_back(const T & x)
{
if (finish != end_of_storage)
{
*finish = x;
finish++;
}
else
{
int num = size();
if (num <1)
{
iterator newstart = new value_type[2];
delete []start;
start = nullptr;
start = newstart;
finish = start;
end_of_storage = &start[2];
*finish = x;
finish++;
}
else
{
iterator newstart = new value_type[num * 2];
memcpy(newstart,start, sizeof(value_type) * num);
delete[]start;
start = nullptr;
start = newstart;
finish = start+num;
*finish = x;
end_of_storage = &start[num * 2];
finish++;
}
}
}
void pop_back()
{
if (finish != start)
{
finish--;
}
}
reference operator[](int n)
{
return *(begin() + n);
}
iterator erase( const iterator position)
{
if (position + 1 != end())
{
int num = static_cast<int>(finish - (position+1));
memmove(position, position+1, sizeof(value_type)*(num));
}
--finish;
return position;
}
iterator erase(iterator first, iterator last)//[first,last)
{
if (first + 1 != end())
{
int num = static_cast<int>(finish - last);
memmove(first, last, sizeof(value_type)*(num));
finish = finish - (last - first);
}
return first;
}
void clear()
{
erase(begin(),end());
}
int capacity()
{
return static_cast<int>(end_of_storage - begin());
}
protected:
iterator start; //表示目前使用空间的头
iterator finish; //目前使用空间的尾
iterator end_of_storage; //目前可用空间的尾
private:
};
在vector的构造函数中,通过传入的参数,开辟空间大小。默认参数为零。在构造函数中,start和finish指针,指向开辟空间的首地址,end_of_storage指向最后的地址。begin()和end() ,分贝返回start指针和finish指针。push_back()函数,在传入值时,所占空间已满,开辟之前二倍的空间,并将之前的值拷贝到刚开辟的空间中,同时,释放原来的空间,finish指针向后移动。pop_back()函数实现较为简单,将finish指针向前移动。其他的各个操作,都是对指针操作得到结果。测试代码:
#include <iostream>
#include "Vector.h"
int main()
{
Vector<int> vector;
vector.push_back(1);
vector.push_back(2);
vector.push_back(3);
vector.push_back(4);
vector.push_back(5);
int temp = vector.size();
int temp1 = vector.capacity();
printf("输入的元素:\n");
for (Vector<int>::iterator item = vector.begin();item !=vector.end();item++)
{
printf("%d\n",*item);
}
printf("所存数据 = %d,空间所占大小 = %d\n", temp, temp1);
vector.pop_back();
vector.pop_back();
vector.pop_back();
vector.pop_back();
int temp3 = vector.size();
int temp4 = vector.capacity();
printf("出栈后剩余的元素:\n");
for (Vector<int>::iterator item = vector.begin(); item != vector.end(); item++)
{
printf("%d\n", *item);
}
printf("所存数据 = %d,空间所占大小 = %d\n", temp3, temp4);
return 0;
}
输出结果:
最后聊聊erase() 成员函数,此函数,在STL vector中,通过vs2015测试时,通过迭代器删除一个元素时,不管它 “ * ”取值,还是++操作,都会崩溃。
for (std::vector<int>::iterator item =hvector.begin();item!=hvector.end();item++)
{
printf("%d\n", *item);
if (*item == 3)
{
hvector.erase(item);
int i = *item;//崩溃
}
//item++时崩溃
}
网上更多的说法,item被删除掉,成了野指针。但是当我看见《STL源码剖析》时,erase()的实现如下:
在这里只是进行了位置的偏移,同时释放掉最后一个元素。(至此,我在模拟时,也进行了位置的偏移), 从这里可以看到,并不是因为野指针的缘故。
参看这篇博客C++ vector容器erase操作后iterate失效真相 ,这里作者提到vs中erase()参数是const_iterator,所以++时会报错。我没理解到作者的观点。各位读者知道后,请互相交流学习一下,十分感激。网上众说纷纭,自己也没找到很有说服力的答案。至此,技术有限,这里留下一个疑点。我怀疑可能是,内存发生了改动,导致迭代器失效报错。
更新追加内容:
再次对erase操作后,迭代器失效进行了探索,有了新的发现:
vs2015,vector删除的源码:
从这里可以看出,大体的逻辑和上述删除内容相似。最后 通过调用 --this->_Mylast() 来做到finish减一,最后通过构建 _Make_iter一个新的迭代器返回。但是在_Destroy的时候其实确实涉及不到,内存重置的问题。真正涉及迭代器失效的原因是_Orphan_range();源码如下:
这里首先会调用_Getpfirst() 获取当前要删除的迭代器(函数的返回值在,迭代器移动的时候随之移动),(*_Pnext)->_Clrcont() 是对迭代器成员_Mycont赋空的操作。
那么此时对于*操作 报错原因查看 *操作符源码 就可知道,会抛出异常:
同理,查看++操作源码,就可知道,报错原因。
综上所述,erase()删除元素,其实并未释放空间。测试验证:
通过调用capacity()获取配置空间大小,删除一个元素后,发现其实它的空间并未释放掉。所以这里进一步验证上面的逻辑。
如果读者不想复制粘贴,可下载: