vector
1、vector的介绍
- vector是数组大小可变化的序列容器
- 就像数组一样,vector使用连续的存储空间来存储元素 ,可以采用下标对vector的元素进行访问,他的大小是可以动态改变的
- vector可能会分配一些额外的存储空间以适应可能的生长,因此实际容量可能大于严格容纳其元素所需的容量
- 与数组相比,vector消耗更多内存,以换取高效管理和动态增长的能力
- 与其他动态序列容器(deques, lists and forward_lists )相比,向量访问其元素非常高效,对于涉及在非末尾位置插入或移除元素的操作,它们的性能不如其他操作,且迭代器和引用的一致性不如list和forward_lists。
2、vector的使用
2.1 vector的定义
| (constructor) 构造函数声明 | 接口说明 |
|---|---|
| vector() (重点) | 无参构造 |
| vector (size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
| vector (const vector& x); (重点) | 拷贝构造 |
| vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
size_type表示无符号整形,value是第一个模板参数,使用迭代器区间的构造函数是函数模板
c
vector<int> first;
vector<int> second (4,100);
vector<int> third (second.begin(),second.end());
vector<int> fourth (third);
int myints[] = {16,2,77,29};
vector<int> fifth (myints, myints + sizeof(myints) / sizeof(int));
cout << "The contents of fifth are:";
for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
cout << ' ' << *it;
cout << '\n';
2.2 vector iterator
| iterator 的使用 | 接口说明 |
|---|---|
| begin + end(重点) | 获取第一个数据位置的 iterator/const_iterator,获取最后一个数据的下一个位置的 iterator/const_iterator |
| rbegin + rend | 获取最后一个数据位置的 reverse_iterator,获取第一个数据前一个位置的 reverse_iterator |

迭代器遍历:
c
void Print(vector<int>& v)
{
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << endl;
++it;
}
cout << endl;
}
2.3 vector空间增长问题
| 容量空间 | 接口说明 |
|---|---|
| size | 获取数据个数 |
| capacity | 获取容量大小 |
| empty | 判断是否为空 |
| resize(重点) | 改变 vector 的 size |
| reserve(重点) | 改变 vector 的 capacity |
- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。vs是PJ版本STL,g++是SGI版本STL。
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
- resize在开空间的同时还会进行初始化,影响size。
c
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "making v grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
在VS下的输出结果:
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141
在Linux下的结果:
making v grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
由于VS在扩容时按大约1.5倍扩容,所以如果知道vector中存储元素的大概个数,可以提前将空间设置足够,避免边插入边扩容导致效率低下
c
void TestVectorExpandOP()
{
vector<int> v;
size_t sz = v.capacity();
v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
cout << "making bar grow:\n";
for (int i = 0; i < 100; ++i)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
2.4 vector增删查改
| vector 增删查改 | 接口说明 |
|---|---|
| push_back(重点) | 尾插 |
| pop_back(重点) | 尾删 |
| find | 查找。(注意这个是算法模块实现,不是 vector 的成员接口) |
| insert | 在 position 之前插入 val |
| erase | 删除 position 位置的数据 |
| swap | 交换两个 vector 的数据空间 |
| operator[](重点) | 像数组一样访问 |
注意:reserve只改变vector的capacity,不改变size
eg:
c
int main() {
vector<int> v;
// 一开始:size=0,capacity=0
cout << "初始:size=" << v.size() << " capacity=" << v.capacity() << endl;
// 调用 reserve(100) ------ 只改容量
v.reserve(100);
// 重点:size 还是 0!capacity 变成 100
cout << "reserve(100)后:size=" << v.size() << " capacity=" << v.capacity() << endl;
return 0;
}
正确做法是把reserve改成resize,resize会改变size的大小,如果使用reserve,插入数据时需要使用push_back
2.5 vector和string的区别
- vector插入时结尾没有\0,string插入后结尾有\0
- string可以实现+=一个字符串,vector一次只能插入一个字符串
3、vector的模拟实现

3.1 成员变量
c
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
3.2 成员函数
3.2.1 构造函数和析构函数
c
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
vector(size_t n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
resize(n, val);
}
vector(int n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
resize(n, val);
}
vector(initializer_list<T> il)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(il.size());
for (auto& e : il)
{
push_back(e);
}
}
template <class InputIterator>
vector (InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//强制编译器生成默认构造
vector() = default;
//析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
- 迭代器区间初始化采用的是函数模板,可能会有不同类型的迭代器
- 必须要提供vector(int n, const T& val = T()),如果没有这个构造函数,vector v(10, 20)就会选择最匹配的**vector(size_t n, const T& val = T())**构造函数,但10被认为是int型,和size_t匹配不上,因此会和迭代器区间初始化函数进行匹配,不符合逻辑,所以要对int型单独提供一个构造函数
- vector(initializer_list il)构造函数作用是可以用vector v = {1,2,3,4};这种花括号方式初始化对象,il就是接受这串数据的变量名,最后用尾插把元素x添加到vector的末尾
- vector() = default;是强制编译器生成默认构造
3.2.2 拷贝构造
c
vector(initializer_list<T> il)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(il.size());
for (auto& e : il)
{
push_back(e);
}
}
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
vector(const vector& v)作用是用一个已有的vector对象v来创建一个新的vector对象,这里涉及深拷贝
- 浅拷贝是只复制指针地址,不复制实际数据,修改时会互相影响
- 深拷贝不仅复制指针,还复制指针指向的全部数据,给新对象开辟一块独立的内存空间,修改时互不影响
3.2.3 operator=
c
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
// v1 = v7
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
3.2.4 size
c
size_t size() const
{
return _finish - _start;
}
3.2.5 capacity
c
size_t capacity() const
{
return _endofstorage - _start;
}
3.2.6 迭代器
c
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
加const后,迭代器只读,不会意外修改const对象
3.2.7 reserve
c
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size();
T* tmp = new T[n];
// 拷贝旧空间数据到新空间 迭代器失效
if (_start)
{
memcpy(tmp, _start, sizeof(T) * old_size);
delete[] _start;
}
_start = tmp; // 更新_start(更新新内存的起始位置)
_finish = _start + old_size;
_endofstorage = _start + n;
}
}
注意:
- 关于old_size:因为拷贝旧空间后被释放,所以需要重新设置_start的位置,同时需要更新_finish和_endofstorage,更新_start后,_start指向新空间的开头,而_finish指向旧空间的结尾,此时调用size()计算出的结果有问题,所以需要先将旧空间的size()保存一份
- 关于拷贝数据的方式:memcpy是逐字节的拷贝数据,是浅拷贝,当执行delete[] _start;销毁空间时,会影响到新空间,所以用for循环实现深拷贝
修改后:
c
void reserve(size_t n)
{
if (n > capacity())
{
size_t old_size = size();
T* tmp = new T[n];
// 拷贝旧空间数据到新空间 迭代器失效
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * old_size);
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp; // 更新_start(更新新内存的起始位置)
_finish = _start + old_size;
_endofstorage = _start + n;
}
}
修改后执行tmp[i] = _start[i];会调用赋值运算符重载进行深拷贝
3.2.8 resize
c
void resize(size_t n, T val = T())
{
if (n > size())
{
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
else
{
_finish = _start + n;
}
}
3.2.9 operator[]
c
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
const T& operator[](size_t i) const //只读
{
assert(i < size());
return _start[i];
}
3.2.10 insert(迭代器失效问题)
c
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
//检查容量
if (_finish == _endofstorage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
// 更新pos
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
再进行insert操作时,会引发迭代器失效,需要将pos重新移动位置,在pos位置插入一个数据时,pos是一个迭代器,首先检查容量,进行扩容,在reserve后,_start、_finish、_endofstorage都指向了新空间,旧空间被释放,而pos指向的还是原来空间的某个位置,此时pos成为野指针,造成非法访问,为了解决这个问题,可以先保存pos的相对位置,扩容后再更新pos
3.2.11 erase(迭代器失效问题)
c
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
// 挪动数据
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
erase删除pos位置的元素后,导致缩容,pos位置之后的元素会往前移动,使pos指向已释放的空间,使迭代器失效,因此采用返回值的方式返回pos位置下一个位置的元素
3.2.12 push_back
c
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
先检查扩容,再插入元素,最后移动指针
3.2.13 pop_back
c
void pop_back()
{
//assert(_finish > _start);
assert(!empty());
--_finish;
}
3.2.14 clear
c
void clear()
{
_finish = _start;
}
3.2.15 empty
c
bool empty() const
{
return _start == _finish;
}