vector
0. vector的介绍
vector是用数组实现的、可变长度的顺序容器,本质是一种类模板。
template <
class T,
class Alloc = allocator<T> >
class vector;
1. vector的接口
1.1 默认成员函数
接口声明 | 解释 |
---|
vector() | 默认构造 |
vecotr(size_type n, const_value_type& val=value_type()) | 填充构造,填充n个元素 |
vector(InputIter first, InputIter last) | 范围构造,迭代器区间初始化 |
vector(const vector& v) | 拷贝构造 |
vector& operator=(const vector& x) | 赋值重载 |
1.2 容量操作
容量操作 | 解释 |
---|
size_type size() | 元素个数 |
size_type capacity() | 容量大小 |
size_type max_size() | 最大能存储的元素个数(无意义) |
void resize(size_type n, value_type val = value_type()); | 增减有效元素个数 |
v.reserve(100);
v.resize(100, 1);
v.resize(10);
由图可知,vs下vector按1.5倍增容。
1.3 访问操作
接口声明 | 解释 |
---|
reference operator[](size_type n) | 返回下标位置的引用 |
const_reference operator[] (size_type n) const | |
reference at(size_type n) | |
const_reference at (size_type n) const | |
[]
重载和at
的区别是,[]
越界会断言报错,at
是抛异常。
迭代器接口 | 解释 |
---|
begin | 起始位置的迭代器 |
end | 末尾元素的下一个位置的迭代器 |
rbegin | 反向起始位置的迭代器 |
rend | 反向末尾元素的下一个位置的迭代器 |
cbegin ,cend | begin 和 end 的 const 版本 |
[]
重载就已经能方便的访问 vector,但并不意味着放弃迭代器。大部分容器都支持迭代器访问,且迭代器使用简单规范统一。
STL 中容器的迭代器区间都是采用
[
f
i
r
s
t
,
l
a
s
t
)
[first,last)
[first,last) 左闭右开的方式。
for (size_t i = 0; i < v.size(); i++) {
v1[i] += 1;
}
vector<int>::iterator it = v.begin();
while (it != v.end()) {
cout << *it << " ";
it++;
}
for (auto e : v) {
cout << e << " ";
}
1.4 修改操作
接口声明 | 解释 |
---|
void push_back (const value_type& val) | 尾插 |
void pop_back() | 尾删 |
iterator insert (iterator pos, const value_type& val) | 迭代器位置插入 |
void insert (iterator pos, size_type n, const value_type& val); | 迭代器位置插入 |
void insert (iterator pos, InputIter first, InputIter last) | 迭代器位置插入一段区间 |
iterator erase (iterator pos) | 迭代器位置删除 |
iterator erase (iterator first, iterator last) | 删除一段迭代器区间 |
void assign (size_type n, const value_type& val) | 覆盖数据 |
v.insert(ret, 30);
v.insert(ret, 2, 30);
v.insert(ret, v2.begin(), v2.end());
v1.erase(pos);
v1.erase(v1.begin(), v1.end());
#include <algorithm>
template <class InputIter, class T>
InputIter find (InputIter first, InputIter last, const T& val);
2. vector的模拟实现
2.1 类的定义
template <class T, class Alloc = alloc>
class vector {
public:
typedef T* iterator;
private:
iterator start;
iterator finish;gggg
iterator end_of_storage;
}
这个结构和顺序表结构稍有不同,但本质是一样的。只是将容量和元素个数的变量用指向对应位置的迭代器代替。
class Seqlist {
T* _a;
size_t _size;
size_t _capacity;
}
2.2 默认成员函数
vector()
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
vector(size_t n, const T& val = T())
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
resize(n, val);
}
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
_start = new T[v.capacity()];
for (size_t i = 0; i < v.capacity(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
template <class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first++);
}
}
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
vector(const vector<T>& v)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
从范围构造可以看出类模板中的函数也可以是函数模板。
迭代器的分类
函数模板的模板参数要传迭代器区间时,命名是有规定的,范围构造中的InputIterator
就是一种指定的迭代器类型。因容器的结构各有不同,迭代器分为五种类型:
迭代器类型 | 名称 | 解释 | 适用容器 |
---|
input/output_iterator | 输入/输出迭代器 | 只读迭代器只能读取,只写迭代器可以写入该位置的值 | 无实际容器 |
forward_iterator | 向前迭代器 | 只能向前移动(++),允许在迭代器位置进行读写 | forward_list |
bidirectional_iterator | 双向迭代器 | 可以双向移动(++,––),允许在迭代器位置进行读写 | list, map, set |
random_access_iterator | 随机迭代器 | 支持指针运算,可移动(++,––)任意跳转(+,–)读写(*) | deque,vector,string |
可以看出,下方的迭代器类型是上方的父类,也就是说下方迭代器满足上方的所有要求。
划分出不同的迭代器类型,是为了限制传入的迭代器,因为其必须满足要求才能完成接下来的函数。
函数如果指明迭代器类型为InputIterator
,意思是满足输入迭代器的要求的迭代器都可以作此参数。故此处我们可以传入任意的迭代器。
一般底层是数组连续空间的容器,例如 vector, string 等都是随机迭代器。
像双向链表这样的非连续空间的容器是双向迭代器。
2.3 容量接口
memcpy 浅拷贝问题
vector<string> v;
v.push_back("11111111111111");
v.push_back("11111111111111");
v.push_back("11111111111111");
v.push_back("11111111111111");
v.push_back("11111111111111");
出现问题是因为正好数组需要增容。模拟实现的reserve
函数使用memcpy
将原空间的内容按字节拷贝至新空间。
- 若 vector 存储的是内置类型,则浅拷贝没问题。
- 若 vector 存储的是自定义类型,浅拷贝使得新旧变量指向同一块空间。深拷贝调用拷贝构造或者赋值重载。
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t oldSize = size();
if (_start)
{
for (int i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
delete[] _sta rt;
}
_start = tmp;
_finish = _start + oldSize;
_end_of_storage = _start + n;
}
}
void resize(size_t n, T val = T())
{
if (n > size())
{
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
else
{
_finish = _start + n;
}
}
2.4 修改接口
iterator insert(iterator pos, const T& val)
{
assert(_start <= pos && pos <= _finish);
if (_finish == _end_of_storage)
{
size_t sz = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + sz;
}
for (iterator end = _finish; end > pos; --end)
{
*end = *(end - 1);
}
*pos = val;
++_finish;
return pos;
}
- 增容改变
_start
,但迭代器pos
并没有跟着改变,仍然指向原空间,也就是迭代器失效。 - 迭代器
pos
实参并没有改变仍然指向错误位置,故函数返回更新的pos
。
iterator erase(iterator pos)
{
assert(_start <= pos && pos < _finish);
for (iterator begin = pos + 1; begin < _finish; begin++)
{
*(begin - 1) = *begin;
}
_finish--;
return pos;
}
- erase 挪动数据后 pos 指向元素会发生变化,同样会导致迭代器失效。
- 返回删除数据的下一个位置,通过返回值更新迭代器。
3. vector的oj题
题目 | 题解 |
---|
只出现一次的数字 | 【简洁明了】只出现一次的数字 I |
杨辉三角 | 【简洁明了】cpp 杨辉三角 |
删除有序数组中的重复项 | 【双指针 简单明了】数组去重 |
只出现一次的数字 II | 【简单明了 注释】只出现一次的数字 |
只出现一次的数字 III | 【详细解析】只出现一次的数字 系列 I II III |
多数元素 | 【哈希 排序 投票】三种解法 简洁明了 |
连续子数组的最大和 | 【动归】连续子数组的最大和 |
电话号码的字母组合 | 【注释解析 回溯】电话号码字母组合 |
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)