vector
1. vector介绍
- vector文档
- vector其实就是一个顺序表,它表示可变大小数组的序列容器。
- 像数组一样,可以使用下标+
[]
来访问vector的元素,和数组一样高效;甚至,它的大小是可以动态改变的,其大小由容器自动处理。- 在底层上,vector使用动态分配空间来储存元素。当新元素插入,原空间不够时,需要重新分配一块连续的空间来增加存储空间,做法是开辟大一点的空间,然后将原内容拷贝过来,再释放原空间,此举的时间成本相对较高。
- vector会额外分配一些空间,以适应可能的增长,实际的存储空间可能比需要的空间更大。不同的STL库的实现采取不同的策略。
- vector的成员变量和string不同,string是两个整型存size和capacity,一个char指针指向动态开辟的空间;而vector是三个迭代器(底层类似于指针),分别是开头的迭代器、最后一个元素下一个位置的迭代器、开辟的空间的最末端的迭代器。
- string是管理字符串的类,那么vector< char>实例化为char是否能替代string呢?
当然不可以,因为string后都有’\0’,可以更好和C的字符串对接,另外string的接口也更加丰富,可以更好的管理字符串。
2. vector的常用接口
vector的许多接口中有很多别名:
相比于string,vector的接口数量就显得很少了,下面我们看看vector常用的接口。
-
构造函数(constructor)
(constructor) 功能 explicit vector (const allocator_type& alloc = allocator_type());
默认构造函数 explicit vector (size_type n, const value_type& val = value_type()
,const allocator_type& alloc = allocator_type());
用n个val值初始化 template <class InputIterator>
vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());
用迭代器初始化(可以允许其他类型作为参数初始化) vector (const vector& x);
拷贝构造函数 vector<int> v1; vector<int> v2(3, 2); int nums[] = { 1,2,3 }; vector<int> v3(arr, arr + 3); vector<int> v4(v3);
-
容量操作
函数 功能 size_type size(); const
返回有效元素个数 size_type capacity(); const
返回实际空间大小 bool empty(); const
判断是否为空 void resize (size_type n, value_type val = value_type());
改变有效元素个数 void reserve (size_type n);
改变空间大小 补充:
- resize:
当n小于有效元素个数时,会将n之后的所有元素删除,只保留从头到n位置的元素;
当n大于有效元素个数,却小于实际空间大小时,会在最后一个有效元素后填充值val,直到n位置;
当n大于实际空间大小时,会开辟空间,然后在最后一个有效元素后填充值val,直到n位置。 - reserve:
当n大于实际空间大小时,开辟空间。其余任何情况不做处理。
- resize:
-
迭代器
函数 功能 iterator begin();
const_iterator begin() const;
返回容器开头的位置的迭代器 iterator end();
const_iterator end() const;
返回容器最后一个有效元素的下一个位置的迭代器 reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
返回容器最后一个有效元素的位置的迭代器 reverse_iterator rend();
const_reverse_iterator rend() const;
返回容器开头的前一个位置的迭代器 -
访问元素
函数 功能 reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
访问下标为n的元素 reference at (size_type n);
const_reference at (size_type n) const;
访问下标为n的元素 vector<int> v(3, 2); cout << v[0] << endl; cout << v.at(1) << endl;
-
修改操作
函数 功能 void push_back (const value_type& val);
尾插一个值 void pop_back();
尾删一个值 insert
在某个位置插入元素 erase
删除某个位置的元素 void swap (vector& x);
交换两个vector对象 void clear();
清空有效元素
3. 模拟实现vector类(部分接口)
#include<iostream>
#include<assert.h>
using namespace std;
namespace Myspace
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
{ }
vector(size_t n, const T& value = T())
{
// 开空间
_start = new T[n];
_finish = _start + n;
_endofstorage = _finish;
// 放数据
for (auto& e : *this)
{
e = value;
}
}
vector(int n, const T& value = T())
{
// 开空间
_start = new T[n];
_finish = _start + n;
_endofstorage = _finish;
// 放数据
for (auto& e : *this)
{
e = value;
}
}
vector(long n, const T& value = T())
{
// 开空间
_start = new T[n];
_finish = _start + n;
_endofstorage = _finish;
// 放数据
for (auto& e : *this)
{
e = value;
}
}
template<typename InputIterator>
vector(InputIterator first, InputIterator last)
{
/*int len = last - first;
_start = new T[n];
_finish = _start + len;
_endofstorage = _finish;
for (auto& e : *this)
{
e = *first;
first++;
}*/
_start = new T[last - first];
for (size_t i = 0; i < last - first; i++)
{
_start[i] = first[i];
}
_finish = _start + (last - first);
_endofstorage = _start + (last - first);
}
vector(const vector& v)
{
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_endofstorage = _start + capacity();
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
vector<T>& operator= (vector<T> v)
{
swap(v);
return *this;
}
//---------------------------------- 迭代器 ---------------------------------------//
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
//---------------------------------- 容量 ---------------------------------------//
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
bool empty() const
{
return _start == _finish;
}
void reserve(size_t n)
{
if (n > capacity())
{
int len = size();
iterator tmp = new T[n];
// 这里拷贝数据不能用memcpy,如果T需要深拷贝,memcpy只是浅拷贝
if (_start)
{
for (size_t i = 0; i < n; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + len;
_endofstorage = _start + n;
}
}
void resize(size_t n, const T value = T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = value;
_finish++;
}
}
}
//---------------------------------- 修改 ---------------------------------------//
void push_back(const T value)
{
if (_finish == _endofstorage)
{
reserve(_start == _endofstorage ? 4 : capacity() * 2);
}
*_finish = value;
++_finish;
}
void pop_back()
{
assert(_start != _finish);
--_finish;
}
iterator insert(iterator pos, const T& value)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstorage)
{
int len = pos - _start; // 防止扩容后迭代器失效
reserve(_start == _endofstorage ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
_finish++;
*pos = value;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
private:
iterator _start = nullptr; // 给缺省值,在构造函数的初始化列表中自动初始化
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
}
注意:
- 构造函数
vector(int n, const T& value = T())
接口需要重载多个(int/size_t/long),以防止创建对象时(vector<int> v(2,3);
)编译器自动匹配到vector(InputIterator first, InputIterator last)
这个接口。
原因:编译器总会选择最匹配的接口。 - 在扩容时拷贝数据的时候,不要使用memcpy(上述代码的第151行),原因如下:
如果模板实例化为string,那么此时就相当于memcpy(string1, string2, size)
,将两个string类memcpy,一定是浅拷贝,因为string类本身有个char*的指针,需要动态开辟空间,memcpy仅仅是将两个指针指向了同一块空间,并没有开辟新的空间。
直接赋值即可,赋值会调用string自己的赋值运算符重载,它自己会实现深拷贝。
不止是string,其他任何动态管理空间的类都是如此。
4. 迭代器失效
-
迭代器的作用:
迭代器就是为了不管各个容器的底层如何实现,都能够使用算法。其底层实际是个指针,或是对指针的封装,比如string和vector的迭代器就是char* 和 T*。 -
迭代器失效:
当迭代器底层所指向的空间被销毁了,还依旧使用该迭代器,那么就会造成野指针的问题,后果是程序崩溃。在VS2022下直接报错崩溃,在Linux下可能不会报错,因此对于程序员来说,避免迭代器失效是必须的。 -
可能引起迭代器失效的场景:
- 扩容操作
#include <iostream> using namespace std; #include <vector> int main() { vector<int> v{1,2,3,4,5,6}; auto it = v.begin(); v.resize(100, 8); v.reserve(100); v.insert(v.begin(), 0); v.push_back(8); v.assign(100, 8); while(it != v.end()) { cout<< *it << " " ; ++it; } cout<<endl; return 0; }
以上五个接口可能会导致迭代器it失效,原因:
使用接口改变底层空间时,可能会扩容,而vector的扩容逻辑是:开辟一块新空间,将原数据拷贝至新空间,然后释放旧空间。扩完容之后底层地址空间就变了,而外部的迭代器it
依旧指向原来已经被释放的空间,对迭代器再次操作时,就是对已经释放的空间进行操作,会引起代码奔溃。- 删除指定位置元素 — erase
#include <iostream> using namespace std; #include <vector> int main() { int a[] = { 1, 2, 3, 4 }; vector<int> v(a, a + sizeof(a) / sizeof(int)); // 使用find查找4所在位置的iterator vector<int>::iterator pos = find(v.begin(), v.end(), 3); // 删除pos位置的数据,导致pos迭代器失效。 v.erase(pos); cout << *pos << endl; // 此处会导致非法访问 return 0; }
上述代码导致迭代器失效的原因:
erase删除pos位置的元素,后面的元素会往前挪动,理论上没有产生扩容,底层地址空间就不会改变,迭代器不应该失效。但是如果pos位置是最后一个元素,删除之后,pos位置就成了end(),是最后一个有效元素的下一个位置,此位置不属于有效数据的区间,此迭代器就失效了。在VS中再对pos迭代器进行操作,程序就会奔溃。 -
解决办法:
完成扩容或删除操作之后,给迭代器重新赋值即可。 -
Linux下,g++编译器对迭代器失效的检测并不是很严格,处理也没有VS下那么极端。
-
vs下迭代器失效
-
g++下迭代器失效
由此可见,SGI版本的STL(Linux下的g++编译),迭代器失效后代码不一定会奔溃(如果迭代器不在begin和end的范围内也会奔溃),但是它的结果一定不正确。
-
-
不仅vector存在迭代器失效的问题,string也有迭代器失效的问题,因此我们在使用STL时,一定要小心迭代器失效!