目录
这是STL源码剖析中对于vector逻辑结构的一张图。
和之前的顺序表不一样,成员变量我们采用的是一个个指针,start指向数组的首元素,finish指向末尾元素的下一个。endofstage指向数组容量的下一个。扩容Linux下是两倍,vs下是一倍。
1. 迭代器
数组的迭代器是一个原生指针,直接typedef即可,但是注意要重命名两个版本,一个为普通迭代器,一个为const迭代器。
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
2. 构造函数
2.1 无参构造
无参构造函数,全部初始化为null,插入的时候内部有容量检查,会自动扩容
vector()
:_start(nullptr)
,_finish(nullptr)
, _end_of_stage(nullptr)
{}
2.2 迭代器构造
根据迭代器区间构造,只要是一段迭代器都可以来构造,比如这是一个vector<int>
对象,
则可以任意的list<int>
来构造他。所以将它写成模板。
内部开成和构造你迭代器一样大的空间,将解引用取得数据一步步尾插进vector。
template <class Inputiterator>
vector(Inputiterator first,Inputiterator last)
: _start(nullptr)
, _finish(nullptr)
, _end_of_stage(nullptr)
{
reserve(last - first);
while (first != last)
{
push_back(*first);
first++;
}
}
2.3 拷贝构造
用形参的对象,迭代器构造一个临时对象,然后交换两个对象的成员变量即可(不用直接交换两个对象,自己实现一个交换数据就好)
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_stage, v._end_of_stage);
}
这个临时对象出作用域,自己就把null析构了(析构null是不会报错的)
//拷贝构造,现代写法
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_stage(nullptr)
{
vector<T> temp(v.begin(), v.end());
swap(temp);
}
2.4 赋值重载
这里的形参没有传递引用,形参是外面对象的一份拷贝,由于是赋值重载肯定是两个对象都存在,所以这里不用对被赋值对象初始化,直接和赋值对象交换数据,完全不会影响外面的对象。最后返回即可。而且由于形参是外面对象的拷贝,出作用域析构被赋值对象原来的数据。
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
3. 析构函数
析构函数,资源清理工作,需要释放里面申请的资源。必需要用delete(先析构释放资源后free(_start)),万一数组里面存的是一个个自定义类型则需要调用他们的析构函数。
~vector()
{
delete[] _start;
_start = _finish = _end_of_stage=nullptr;
}
4. 有效元素的个数
const size_t size()const
{
return _finish - _start;
}
5. 容量大小
const size_t capacity()const
{
return _end_of_stage - _start;
}
6. 重载[]
[]时vector最经常使用的一个函数。
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t ops)const
{
assert(pos < size());
return _start[pos];
}
7. 检查容量
当指向有效元素的指针和指向容量的指针相遇,代表要扩容了。首先假如开始什么都没有,链各个指针为NULL,相减为0,那么就给他初始的4个容量,假如不是开始为0,那么就给他原来容量的2倍。
void check_capacity()
{
if (_end_of_stage == _finish)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
}
8. 扩容
当要给的容量大于当前容量,进行扩容,首先申请一段空间(4个空间或者传进来,乘以2倍的参数),把原来空间的元素拷贝过来,memcpy是很方便,但是他是值拷贝,对于内置类型,没区别,假如里面有自定义类型,只是拷贝了地址。
用for循环,将他们的值一个个赋值进当前空间,delete掉旧空间,让_start指向新空间。因为虽然我们的容量变大了,_start指向新空间之前先把原空间size记录下来,因为有效元素的个数没有变化,你在_start指向之后再算size,_finish还在指向原来的空间,_finish-_start,简直就是在乱减。所以提前记录下size,_finish=_start+oldsize.
_finish指向了我们新申请空间的有效元素的下一个位置。容量大小就是_start+传进来的扩容大小参数。
//扩容
void reserve(size_t n)
{
if (n > capacity())
{
T* temp = new T[n];
//memcpy(temp,_start,sizeof(T)*size());memcpy为浅拷贝。vector<int>没有问题,vector<string>析构的时候会报错
for (size_t i = 0; i < size(); i++)
{
temp[i] = _start[i];
}
size_t oldsize = size();
delete[] _start;
_start = temp;
_finish = _start + oldsize;
//_finish = _start + size();此时不能调用size(),finsh还在指向那块释放的空间
_end_of_stage = _start + n;
}
}
9. resize
先判断够不够,不够然后扩容。假如n<size(),就说明要缩小空间。假如n>size,就把x赋值到后面。
//扩容+初始化,缩小size
//resize给了个缺省值,因为是匿名对象,所以要带const
void resize(size_t n, const T& x=T())
{
//假如不够就会扩容
if (n > capacity())
{
reserve(n);
}
//假如n<size(),就缩小空间
if (n < size())
{
_finish = _start + n;
}
else{
//假如n>size()就把x赋值进去
while (_finish != _start + n)
{
*_finish = x;
_finish++;
}
}
}
10. 插入
find找到pos,然后传进来。在此位置插入x对象。assert的意思就是不能大于finish,不能小于_start。
提前记录下pos与_start的距离,由于扩容会引起重开一段空间
在来看插入
虽然在函数里面我们处理了,但是不要忘记了pos是外面find得来的,而你是传值里面处理了,出作用域,不影响外面,迭代器就会失效,这里用返回值来解决了这个问题(即函数里面处理返回,外面接收)。其实不接收并不一定失效,因为不一定会增容。
//参数是迭代器,配合find使用
iterator insert(iterator pos, const T& x)
{
assert(pos<=_finish && pos>=_start);
//假如扩容会引起迭代器失效,增容重开一段空间,导致pos还在指向释放的空间
//提前记录posi的值,扩容的话让_start+posi就又找到了pos的位置
size_t posi = pos - _start;
check_capacity();
pos = _start + posi;
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
return pos;
}
11. erase
//返回的pos的下一个位置(挪前来了)
iterator erase(iterator pos)
{
assert(pos < _finish && pos >= _start);
iterator it=pos+1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
--_finish;
return pos;
}
erase一定会导致失效,因为意义变了也算是失效的一种,原本指向5,现在指向1,所以返回值返回1,在外面接收,赋予pos新的意义(即此时pos代表1)
此时pos代表3,由于只是–finish,所以pos依旧指向3,但是此时3已经无意义。通过返回值返回告诉外面pos以无意义,当再次使用它时由于assert(pos<finish)就不会进入函数。
对于erase更多的是意义变了导致的失效,所以当在检查严格的编译器运行会导致崩溃。
例如一个删除偶数场景
所以当erase一个数据的时候都要当成失效,要用返回值来接收在函数里修改之后的pos。
插入与删除迭代器失效总结
insert不一定会失效,因为不是每次插入都会扩容,那假如会扩容呢,函数内部通过记录位置处理,因为传值外部不会改变,调用时统一用返回值接收来预防迭代器失效的场景。
erase一定会失效,因为意义变了也是失效的一种,会引起各种各样的错误,同样通过返回值来解决。
vector实现代码
#pragma once
#include<iostream>
#include<assert.h>
#include<algorithm>
#include<string>
namespace zjn
{
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_stage, v._end_of_stage);
}
vector()
:_start(nullptr)
,_finish(nullptr)
, _end_of_stage(nullptr)
{}
template <class Inputiterator>
vector(Inputiterator first,Inputiterator last)
: _start(nullptr)
, _finish(nullptr)
, _end_of_stage(nullptr)
{
reserve(last - first);
while (first != last)
{
push_back(*first);
first++;
}
}
//拷贝构造,现代写法
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_stage(nullptr)
{
vector<T> temp(v.begin(), v.end());
swap(temp);
}
//赋值重载,现代写法
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
~vector()
{
delete[] _start;
_start = _finish = _end_of_stage=nullptr;
}
const size_t size()const
{
return _finish - _start;
}
const size_t capacity()const
{
return _end_of_stage - _start;
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t ops)const
{
assert(pos < size());
return _start[pos];
}
//扩容
void reserve(size_t n)
{
if (n > capacity())
{
T* temp = new T[n];
//memcpy(temp,_start,sizeof(T)*size());memcpy为浅拷贝。vector<int>没有问题,vector<string>析构的时候会报错
for (size_t i = 0; i < size(); i++)
{
temp[i] = _start[i];
}
size_t oldsize = size();
delete[] _start;
_start = temp;
_finish = _start + oldsize;
//_finish = _start + size();//此时不能调用size(),finsh还在指向那块释放的空间
_end_of_stage = _start + n;
}
}
//扩容+初始化,缩小size
//resize给了个缺省值,因为是匿名对象,所以要带const
void resize(size_t n, const T& x=T())
{
//假如不够就会扩容
if (n > capacity())
{
reserve(n);
}
//假如n<size(),就缩小空间
if (n < size())
{
_finish = _start + n;
}
else{
//假如n>size()就把x赋值进去
while (_finish != _start + n)
{
*_finish = x;
_finish++;
}
}
}
void check_capacity()
{
if (_end_of_stage == _finish)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
}
void push_back(const T& x)
{
//check_capacity();
//*_finish= x;
//_finish++;
insert(_finish,x);
}
void pop_back()
{
erase(_finish-1);
}
//参数是迭代器,配合find使用
iterator insert(iterator pos, const T& x)
{
assert(pos<=_finish && pos>=_start);
//假如扩容会引起迭代器失效,增容重开一段空间,导致pos还在指向释放的空间
//提前记录posi的值,扩容的话让_start+posi就又找到了pos的位置
size_t posi = pos - _start;
check_capacity();
pos = _start + posi;
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
return pos;
}
//返回的pos的下一个位置(挪前来了)
iterator erase(iterator pos)
{
assert(pos < _finish && pos >= _start);
iterator it=pos+1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
--_finish;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_stage;
};
}