namespace bit
{
template<class T>
class vector
{
public:
typedef T* iterator;
~vector()
{
if (_start)
{
delete[] _start;
_start = _finsh=_end_of_storage=nullptr;
}
}
iterator begin()
{
return _start;
}
size_t capacity()
{
return _end_of_storage - _start;
}
size_t size()
{
return _finsh - _start;
}
iterator end()
{
return _finsh;
}
void reverse(size_t n)
{
if (n > capacity())
{
size_t oldsize = size();
T* temp = new T[n];
if (_start)
{
//memcpy(temp, _start, size() * sizeof(T));
for (size_t i = 0; i < oldsize; i++)
{
temp[i] = _start[i];
}
delete[] _start;
delete[] _start;
}
_start = temp;
_finsh = _start + oldsize;
_end_of_storage = _start + n;
}
}
void push_back(const T& x)
{
if (_finsh == _end_of_storage)
{
size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
reverse(newcapacity);
}
*_finsh = x;
_finsh++;
}
T&operator[](size_t i)
{
assert(i < size());
return _start[i];
}
void insert(iterator pos, T x)
{
assert(pos >= _start);
assert(pos < _finsh);
if (_finsh == _end_of_storage)
{
size_t len = pos - _start;
size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
reverse(newcapacity);
pos = _start + len; //为了预防迭代器失效
}
iterator end = _finsh;
while (end>pos)
{
*end = *(end - 1);
end--;
}
(*pos) = x;
_finsh++;
}
void pop_back()
{
assert(size());
_finsh--;
}
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finsh);
iterator cur = pos;
while (pos < _finsh)
{
*pos = *(pos + 1);
pos++;
}
--_finsh;
}
private:
iterator _start = nullptr;
iterator _finsh = nullptr;
iterator _end_of_storage = nullptr;
};
}
在之前的博客里面,我们好像讲过,有关于模板的程序我们最好不要把申明和定义写在不同的文件里面,这样会引起链接错误,具体原因在后面的博客中应该会提到
上面一些函数的几点说明
1.关于reserve函数:
reverse的实现思路
通过上面的代码,我们不难看出,reverse的实现思路其实新申请一块空间,然后将原理的数据拷贝到新的空间里面去
但是,上面为什么要那样些嘞?那就请你先看看下面的代码
这样些编译器立马报错,为什么嘞?
这时候_start已经指向新空间temp了,但是_finsh还没有指向新空间,这句代码的下一句代码
_finsh=_start+size(),中的size()函数,不就是旧空间里面的_finsh减去新空间里面的_start吗,两块空间压根就不连续,这样写不就乱了吗
2.关于push_back函数
在第一个红色的方框之后,重新申请了一块空间,如果不记录一下pos的相对位置,后面将旧的空间释放了,会导致pos迭代器失效的
注意
迭代器失效1
这里的迭代器的本质收T*,但是我们为什么不写成T*嘞,而要写成iterator嘞,其实,这里体现的一个思想是封装的思想,不同容器的iterator是不一样的
上面的代码中的迭代器it是会失效的(在扩容的情况下),因为这里的实参传递给形参,并没有引起实参的改变,但是不知道它什么时候失效,所以,不管有没有扩容,我们统一认为失效了,我们的建议就是不要访问失效的迭代器,除非我们更新一下迭代器
这里的一个解决方法就是写一个返回值,那么问题来了,string有没有迭代器失效的场景?答案是肯定的?string里面的insert有下标控制的,也有迭代器控制的
迭代器失效2
其实这里的迭代器失效还有一种场景
当然就是这里的erase函数,删除了迭代器下对应的元素,那么,该迭代器也是会失效的,
当然,在vector的模拟实现里面并不会失效,只是在C++标准库里面会失效,C++标准库下面的迭代器类型很复杂,在这里可以认为,当erase迭代器下对应的元素了的话,会迭代器里面的有一个标志会改变,改变了你就无法去访问对应的结果
当然,这里也有解决方案
这里的erase函数是有返回值的,他会更新迭代器的,不至于你去访问失效的迭代器,导致程序崩溃,当然,这种迭代器失效的情况在vs的环境下会有但是在linux环境下是不会出现的,所以我们为了代码在任何平台上面都能运行,我们最好能够更新一下迭代器
构造函数
这里编译器报的是少默认构造函数,原因是我们实现的上面的拷贝构造函数,注意:拷贝构造函数也是构造,我们可以用下面的语句让编译器强制生成默认构造函数
迭代区间的默认构造函数
下面还有一个问题
加入实现了这两个构造函数,那么,下面的程序会报错的
这里涉及到的就是函数匹配的问题
构造函数里面传递的是两个整形变量,上面的两个构造函数,一个是拥有模板的构造函数,一个是参数是size_t和 T的,我们的本意是想要构造一个拥有10个2的顺序表,想要让他去调用第二个构造函数,但是他却去调用了第一个构造函数,原因就是第二个的参数是size_t,并非是int,第一个构造函数更符合,直接调用第一构造函数去了,结果去对整形变量解引用,程序里面出错
那是否有解决方案?答案是肯定的
这里可以直接在10的后面加上一个u,因为u是无符号整形的标志嘛,对应的就是size_t
还有一种解决方案,就是再写一个更匹配的构造函数
上面有一句代码需要注意:
这里的T()是一个匿名对象,无论是内置类型还是自定义类型都可以这样写
还有一种构造函数
这里是一个序列,方便我们像下面一样去构造一个对象
memcpy的浅拷贝问题
首先,我们明确一个问题memcpy进行拷贝的时候,进行的是浅拷贝,这对于内置类型几乎是没有什么影响,但是对于自定义类型的话,问题可就大了去了
我们来举一个例子
这里为什么打印的是随机值嘞,首先明确一点,这里肯定是因为扩容导致的,因为写插入四个string的时候,程序是好的,但是插入第五个的时候,程序开始奔溃了
都知道,红方框里面的是string的内部组成,memcpy拷贝的只是_str,_str是指向内容的地址,这里把地址拷贝过去了,实际内容没有拷贝过去,导致delete[]_start的时候把_start指向的内容给析构掉了,temp指向的也析构掉了,因为两个指向的是同一个内容嘛,最后出来的结果不就是随机值了嘛
将memcpy换成上面注释掉的部分就行了,让两个变量分别指向不同的内存
现在结果不就正确了吗