vector
- 一、vector-简单介绍
- 二、vector的常用接口
- 1.常见构造
- 2.iterator的使用
- 3.容量操作
- 4.增删查改操作
- 5.迭代器失效问题
- 6.动态二维数组
- 三、vector实现
- 1.vector类重要的方法实现分析介绍
- (1)、涉及memcpy深浅拷贝问题
- (2)、成员变量
- 2.vector类整体实现代码
- 四、vector< char > 和 string区别
一、vector-简单介绍
Vector是一个封装了动态大小数组的顺序容器(Sequence Container)。可以简单的认为,vector是一个能够存放任意类型的动态数组。
vector是一个类模板,其中封装了类的多种方法。例如,在使用时要改变大小,它的大小容器会自动处理。字符串的增删查改等操作,通过调用类中的方法便捷的进行操作…。
二、vector的常用接口
1.常见构造
(constructor)构造函数声明 | 接口说明 |
---|---|
vector() | 无参构造 |
vector(size_t n, const T& val = T() | 构造并初始化n个val |
vector(const vector& x) | 拷贝构造 |
vector(InputIterator first, InputIterator last | 使用迭代器进行初始化构造 |
test:
void test_vector_constructor()
{//因为vector是一个模板类,所以string也可以当参数的类型//无参构造vector<string> v1;string name("ren");//正常尾插一个string类对象v1.push_back(name);//匿名对象v1.push_back(string("yv"));//隐式类型转换v1.push_back("qing");//构造并初始化n个valvector<string> v2(5, "xxx");//拷贝构造vector<string> v3(v1);//使用迭代器初始化构造string str("hello world");vector<char> v4(str.begin(), str.end());vector<int> v5(str.begin(), str.end());//用char初始化int会有整型提升,类型转换。 ASCII//使用数组构造int a[] = { 16,2,77,29 };vector<int> v6(a, a + 4); //a本质是指针
}
2.iterator的使用
iterator的使用 | 接口说明 |
---|---|
begin + end | 获取第一个数据位置的iterator/const_iterator,获取最后一个数据的下一个位置的iterator/const_iterator |
rbegin + rend | 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator |
test:
注意: 在string中说过,迭代器实现了之后,范围for可以直接使用。范围for本质就是迭代器
void test_iterator()
{int a[] = { 16, 2, 77, 239, 23, 3, 43, 2, 3, 3, };int size = sizeof(a) / sizeof(a[0]);vector<int> v1(a, a + size);vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;vector<int>::reverse_iterator rit = v1.rbegin();while (rit != v1.rend()){cout << *rit << " ";++rit;}cout << endl;
}
3.容量操作
函数名称 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
reserve | 改变vector的capacity |
resize | 改变vector的size |
reserve负责开空间,如果预先知道需要使用开多少空间,reserve可以缓解vector增容的代价。
test:
void test_space()
{vector<int> v1;v1.resize(10);for (size_t i = 0; i < 10; i++){//使用[]改变v1[i] = i;}for (auto e : v1){cout << e << " ";}cout << endl;cout << " size:" << v1.size() << endl;cout << "capacity:" << v1.capacity() << endl;cout << "empty:" << v1.empty() << endl << endl;vector<int> v2;v2.reserve(10);for (size_t i = 0; i < 5; i++){//尾插v2.push_back(i);}for (auto e : v2){cout << e << " ";}cout << endl;cout << " size:" << v2.size() << endl;cout << "capacity:" << v2.capacity() << endl;v2.clear();cout << "empty:" << v2.empty() << endl;
}
test(vector默认扩容机制):vector的增容是1.5倍
void test_vector_expand()
{size_t sz;vector<int> v;sz = v.capacity();for (size_t i = 0; i < 100; i++){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "New capacity:" << sz << endl;}}
}
4.增删查改操作
函数名称 | 接口说明 |
---|---|
push_back | 尾插 |
pop_back | 尾删 |
erase | 删除pos位置的数据 |
insert | 在pos之前插入val |
swap | 交换两个vector的数据空间 |
operator[] | 像数组一样访问 |
test:
void test_modifiers()
{vector<int> v;//push_backv.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;//删除操作v.pop_back();v.erase(v.begin());v.erase(v.end() - 1); //vector可以直接减1,list不行for (auto e : v){cout << e << " ";}cout << endl;//insertv.insert(v.begin(), 3);v.insert(v.end(), 10);for (auto e : v){cout << e << " ";}cout << endl;//swapvector<int> v1;v1.swap(v); //v现在没有值for (auto e : v1){cout << e << " ";}cout << endl;for (size_t i = 0; i < v1.size(); i++){//operatorv1[i]++;cout << v1[i] << " ";}cout << endl;
}
5.迭代器失效问题
引起底层空间改变的操作,都有可能是迭代器失效
这里使用insert和erase举例,当然别的方法也存在这个情况,例如:resize,reserve,push_back…。
解决迭代器失效的方法:在使用前,对迭代器重新赋值即可。
test:
void test_iterator()
{vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);for (auto e : v){cout << e << " ";}cout << endl;//vector<int>::iterator it = v.begin();//v.insert(it, 100);//高危行为//*it += 10;//删除pos位置的值,导致迭代器失效/*vector<int>::iterator pos = find(v.begin(), v.end(), 3);v.erase(pos);cout << *pos << endl;*///如何解决问题:对迭代器重新赋值//eg:删除所有偶数vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0){//因为erase在调用完成后返回pos的后一个位置,//如果pos是最后一个位置则这是容器结束it = v.erase(it);}}
}
6.动态二维数组
三、vector实现
1.vector类重要的方法实现分析介绍
(1)、涉及memcpy深浅拷贝问题
在构造函数里涉及到自定义类型元素中的资源管理。
如果在vector< string >类的单参构造中,使用memcpy。如果仅仅对自定义类型的元素进行拷贝完全没问题,但是拷贝的自定义类型元素中涉及到资源管理就会出现问题。
- 内存泄漏
- 多次释放一块地址,野指针。程序崩溃。
以拷贝构造举例,这里就不能使用memcpy了,而是一步步遍历赋值
vector(const vector<T>& v){_start = new T[v.capacity()];//如果是vector<string> 在拷贝时需要是深拷贝//memcpy(_start, v._start, sizeof(T) * v.size());for (size_t i = 0; i < v.size(); i++){//_start[i] = v._start[i];_start[i] = T(v._start[i]);}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}
所以在实现单参构造函数时,不能使用memcpy
(2)、成员变量
在vector类实现中,我们采用和库中相同的成员变量,三个迭代器,在前面的动态二维数组知识点提到。
private://这里最好给默认值,这样在构造函数就可以不用刻意初始化为nullptr//使用未定义的值进行构造会出现大问题iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;
_finish - _start = size();
_end_of_storage - _start = capacity();
2.vector类整体实现代码
namespace kpl
{template<class T>class vector{public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;// 无参构造vector()//: _start(nullptr) //这里都可以省略,因为在成员变量声明的时候给了默认值nullptr//, _finish(nullptr)//, _endOfStorage(nullptr){}vector(size_t n, const T& value = T()){/*reserve(n);while (n--){push_back(value);}*/resize(n, value);}//vector<int> v(10, 1);//vector<int> v(10u, 1);//vector<string> v(10, "11111");//解决例子匹配问题vector(int n, const T& value = T()){resize(n, value);}// 若使用iterator做迭代器,会导致初始化的迭代器区间[first,last)只能是vector的迭代器// 重新声明迭代器,迭代器区间[first,last)可以是任意容器的迭代器template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}//拷贝构造vector(const vector<T>& v){_start = new T[v.capacity()];//如果是vector<string> 在拷贝时需要是深拷贝//memcpy(_start, v._start, sizeof(T) * v.size());for (size_t i = 0; i < v.size(); i++){//_start[i] = v._start[i];_start[i] = T(v._start[i]);}_finish = _start + v.size();_endOfStorage = _start + v.capacity();}//运算符重载,现代写法void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage , v._endOfStorage );}vector<T>& operator=(vector<T> v){//给成员变量都初始化成nullptr的好处就体现出来了,如果交换之后是一个随机的地址就会出现错误swap(v);return *this;}~vector(){if (_start){delete[] _start;_start = _finish = _endOfStorage = nullptr;}}// 迭代器相关iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}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()){size_t sz = size();// 1. 开辟新空间T* tmp = new T[n];// 2. 拷贝元素if (_start){for (size_t i = 0; i < sz; ++i)tmp[i] = _start[i];// 3. 释放旧空间delete[] _start;}_start = tmp;//这里不能使用size(),因为size的实现是_finish-_start//这时_start改变了,但是finish还没改变_finish = _start + sz;_endOfStorage = _start + n;}}void resize(size_t n, const T& value = T()){if (n <= size()){_finish = _start + n;return;}else{//增容reserve(n);//赋值while (_finish != _start + n){*_finish = value;++_finish;}}}// 元素访问T& operator[](size_t pos) { assert(pos < size());return _start[pos]; }const T& operator[](size_t pos)const { assert(pos < size());return _start[pos]; }T& front(){return *_start;}const T& front()const{return *_start;}T& back(){return *(_finish - 1);}const T& back()const{return *(_finish - 1);}// vector的修改操作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() - 1); }//vector erase和insert迭代器对象使用后,不能在访问这个迭代器//我们认为它失效了,访问的结果是未定义的iterator insert(iterator pos, const T& x){assert(pos <= _finish && pos >= _start);// 空间不够先进行增容if (_finish == _endOfStorage){//保存pos的相对位置size_t len = pos - _start;size_t newCapacity = (0 == capacity()) ? 1 : capacity() * 2;reserve(newCapacity);//解决pos迭代器失效问题,对pos位置更新pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}// 返回删除数据的下一个数据,如果是最后一个元素,则是容器结束// 方便解决:一边遍历一边删除的迭代器失效问题iterator erase(iterator pos){assert(pos < _finish && pos >= _start);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;++it;}--_finish;return pos;}private:iterator _start = nullptr; // 指向数据块的开始iterator _finish = nullptr; // 指向有效数据的尾iterator _endOfStorage = nullptr; // 指向存储容量的尾};
}
四、vector< char > 和 string区别
- 结构不同,string受’\0’的影响,并且要和C语言兼容
- string接口丰富,有很多专用接口。eg:+=, find…