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所在位置的iteratorvector<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时,一定要小心迭代器失效!