C++中的vector是一个可变容量的数组容器,它可以像数组一样使用[]进行数据的访问,但是又不像C语言数组空间是静态的,它的空间是动态可变的。
在日常中我们只需要了解常用的接口即可,不常用的接口查文档即可。
1.构造函数
//空构造
vector()
//拷贝构造
vector(const vector<T>& v)
//构造并初始化n个val
vector(size_t n,const T& val = T())
//使用迭代器初始化,这里写成模板
template<class InputIterator>
vector(InputIterator first, InputIterator last)
2.迭代器
对于vector的迭代器也可以看作是指针
//获取第一个位置数据的普通迭代器和const迭代器
iterator begin();
const_iterator begin() const;
//获取最后一个位置数据的普通迭代器和const迭代器
iterator end();
const_iterator end() const;
3.空间管理
//获取元素个数
size_t size() const;
//判断是否为空
bool empty() const;
//改变大小并且初始化
void resize (size_type n, value_type val = value_type());
//改变容量
void reserve (size_type n);
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问 题。 resize在开空间的同时还会进行初始化,影响size。
4.增删改查
//尾插
void push_back (const value_type& val);
//尾删
void pop_back();
//在任意位置插入
iterator insert (iterator position, const value_type& val);
//在任意位置删除
iterator erase (iterator position);
//交换两组数据空间
void swap (vector& x);
//[]重载
T& operator[](size_t pos)
这里需要了解一个问题就是迭代器失效的问题,对于vector而言,它的迭代器底层就是原生指针。因此迭代器失效的原因就是指针所指向的空间被销毁了,指向了一个已经被释放的空间。
vector<int> v{1,2,3,4,5,6};
auto it = v.begin();
// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
// v.resize(100, 8);
// reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
// v.reserve(100);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
// v.insert(v.begin(), 0);
// v.push_back(8);
例如上面这些例子,他们都引起了底层空间的改变,就会导致it失效,如果在后面的代码中使用失效的迭代器就会导致程序崩溃。
要解决这个问题的方法也很简单,就是在修改之后重新赋值即可。
下面进行vector的模拟实现
template<class T>
class vector
{
public:
//vector的迭代器就是原生指针,这里写成模板的形式
typedef T* iterator;
typedef const T* const_iterator;
//begin()相当于直接返回头指针
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
//end()相当于直接返回尾指针
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
//左闭右开
//迭代器构造函数,这里写成模板支持更多类型
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while(first != last)
{
push_back(*first);
++first;
}
}
//空构造函数
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
//拷贝构造
//不能使用memcpy,因为这是浅拷贝,很可能会造成内存泄漏
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
_start = new T[v.capacity()];
//memcpy(_start, v._start, sizeof(T) * v.size());
//这里与下面reserve是相同的问题
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
//如果是自定义类型,这里事实上调用的是自定义类型的赋值操作
}
//这里由于是原生指针,并且是顺序存储因此可以直接相加
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
//初始化n个val的vector,这里可以复用resize()
vector(size_t n,const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
resize(n, val);
}
//交换
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
//现代写法
//这里使用传值方式传参是因为能够生成临时拷贝不会影响原数据
//这里使用传引用返回是因为this出了作用域会销毁
vector<T>& operator=(vector<T> v)
{
swap(v);//相当于this->swap(v)
return *this;
}
//析构
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
//reserve()空间管理,同样不能使用memcpy(),会导致内存泄漏
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* temp = new T[n];
if (_start)
{
//memcpy(temp, _start, sizeof(T) * size());
for (size_t i = 0; i < sz; i++)
{
temp[i] = _start[i];
//如果是自定义类型,这里事实上调用的是自定义类型的赋值操作
}
delete[] _start;
}
_start = temp;
//_finish = _start + size()这样写是错的。
//由于_start的改变会导致size()报错,这就是迭代器失效。
//因为_start指向了新空间,但是_finish还是指向旧空间
_finish = _start + sz;
_endofstorage = _start + n;
}
}
//申请n个空间初始化为val,这里复用reserve()
void resize(size_t n,const T& val = T())//匿名对象
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
//尾插,这里自己实现需要注意扩容,但是也可以考虑直接复用insert()
void push_back(const T& x)
{
//if (_finish == _endofstorage)
//{
// //扩容
// size_t newcapacity = capacity() == 0 ? 4 : capacity() *2;
// reserve(newcapacity);
//}
//*_finish = x;
//++_finish;
insert(end(), x);
}
//这里与尾插是一样的思路
void pop_back()
{
erase(end());
}
//获得当前容器的容量
size_t capacity() const
{
return _endofstorage - _start;
}
//获取当前容器中有效数据的个数
size_t size() const
{
return _finish - _start;
}
//重载[],这里事实上相当于(*_start + pos)
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
//任意位置插入,需要注意迭代器失效的问题
void insert(iterator pos ,const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstorage)
{
//扩容
//这里如果直接扩容会引发迭代器失效,因为pos还是指向原来的位置
//因此需要更新pos的位置,这里保存的时相对位置
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
}
//iterator erase(iterator pos)
//{
// //检查 pos 是否在合法范围内
// if (pos < _start || pos >= _finish)
// {
// throw std::out_of_range("Iterator out of range");
// }
// //从删除位置开始,将后续所有元素向前移动一位
// iterator it = pos;
// while (it < _finish - 1)
// {
// *it = *(it + 1);
// ++it;
// }
// //更新 _finish,减少容器大小
// --_finish;
// //返回删除位置的下一个有效迭代器
// return pos; // 注意:这里返回的是删除位置的下一个迭代器
//}
//只要把pos位置后面的元素向前挪动覆盖即可,也不需要考虑迭代器失效的问题
iterator erase(iterator pos)
{
assert(pos >= _start && pos <= _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
private:
//定义头指针和尾指针以及一个管理空间的指针
iterator _start;
iterator _finish;
iterator _endofstorage;
};