文章目录
- 前言
- 一、vector
- 1.vector的定义接口
- 2.2 vector iterator 的使用
- 3.vector 空间增长问题
- 4.vector 增删查改
- 5.vector 迭代器失效问题。
- 二、vector的模拟实现
- 1.vector的定义
- 2.reserve扩容
- 3.迭代器(iterator)
- 4.operator[]
- 5.push_back()尾插
- 6.vector的构造函数
- 7.resize扩容
- 8. operator=
- 9 print_container 打印
- 三、vector的模拟实现代码展示
- 1. vector.h文件
- 2.vector.cpp文件
- 3.test.cpp文件
- 总结
前言
今天我们来看看vector的相关知识吧。
一、vector
想要学好vector这就要结合vector的文档了,这里呢跟之前string是大同小异的。大家一定要多多看看文档。这里小编就比较重要的接口吧。如果还想了解其他接口的可以查看文档的哦。
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) | 迭代器区间构造 |
2.2 vector iterator 的使用
iterator的使用 | 接口说明 |
---|---|
begin /end(重点) | 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator |
rbegin /rend | 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator |
3.vector 空间增长问题
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize(重点) | 改变svector的size |
reserve(重点) | 改变vector的capacity |
注意:
- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
我们现在可以来验证一下capacity在vs环境下的扩容:
#include<iostream>
#include<vector>
using namespace std;
int main()
{vector<int> v;size_t se = v.capacity();cout << "making se grow" << endl;for (size_t i = 0; i < 100; i++){v.push_back(i);if (se != v.capacity()){se = v.capacity();cout <<" capacity changed:" << se << endl;}}return 0;
}
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
#include<iostream>
#include<vector>
using namespace std;
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
int main()
{vector<int> v;size_t se = v.capacity();cout << "making se grow" << endl;for (size_t i = 0; i < 100; i++){v.reserve(100);//这里给出了要开辟的空间大小,已经全部开好了v.push_back(i);if (se != v.capacity()){se = v.capacity();cout <<" capacity changed:" << se << endl;}}return 0;
}
- resize在开空间的同时还会进行初始化,影响size
4.vector 增删查改
vector增删查改 | 接口说明 |
---|---|
push_back(重点) | 尾插 |
pop_back (重点) | 尾删 |
find | 查找。(注意这个是算法模块实现,不是vector的成员接口) |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
operator[] (重点) | 像数组一样访问 |
5.vector 迭代器失效问题。
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
这里我们用insert来举例:
insert是一个在指定位置插入的接口,先来看看他的代码:
void insert(iterator pos, const T& x)
{if (_finish == _end_of_storage) {reserve(capacity() == 0 ? 4 : capacity() * 2); }iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}
从逻辑上来看这段代码是完全没有问题的。在不扩容的情况下代码是可以正常云进行的。但是,如果在插入的过程中需要扩容,那代码就会崩溃。那是那是什么原因呢?其实这里就是一个迭代器失效。
当需要扩容时,_start要指向新的空间tmp,同时释放掉旧空间,但是pos是指向旧空间的,释放_start的时候就一起释放掉了,所以这里pos就是野指针了啊。因为他指向的内容已经被释放掉了。既然是野指针那就是不能对他解引用了。
解决方法: 这里的解决方法呢也是很简单,就是更新一下pos的位置就行了 。
void insert(iterator pos, const T& x)
{if (_finish == _end_of_storage) {size_t p = pos - _start;//记录一下pos的位置reserve(capacity() == 0 ? 4 : capacity() * 2); pos = p + _start; //更新pos的位置 }iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}
那是不是不扩容迭代器就不会失效呢?我们现在再来看看这段代码,现在我们想在2的位置插入一个数据10,同时我们还让2*10,看看代码的运行结果。
int main()
{vector<int>v;v.push_back(1);v.push_back(2); v.push_back(3);v.push_back(4);v.push_back(4); auto it = find(v.begin(), v.end(), 2);//find是查找的接口,可以去库里面看一下的v.insert(it, 10);*(it) *= 10;for (auto e : v){cout << e << " ";}return 0;
}
可以看到这里并没有成功,它直接把插入的数据扩大了10倍,这跟我们预想不一样啊?那是怎么回事呢?insert接口里面我们不是记录并且更新pos的嘛?那为什么不行呢?其实这里还是迭代器失效。看,这里的it是实参,pos是形参,那形参的改变能影响实参吗?当然不可以。所以这里it还是指向的是原来的空间,这里还是失效的。如果一定要访问,那就重新更新一下这个失效迭代器的值。一般情况下再insert之后就不要访问了。因为我们不知道它什么时候扩容!这里由于这里是我们自己模拟实现的代码,代码是可以跑的,但是我们用库里面的函数的时候vs就会强行检查 。
所以insert之后不要访问。
现在在来看看erase这个函数的迭代器失效的问题,这里erase是删除pos指向的位置的数据。erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此erase之后我们认为迭代器已经失效了。
现在我们以删除所有偶数来举例:
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){v.erase(it);}cout << *it << " ";++it;
}
看看这两种结果,最后一个数不是偶数和最后一个数是偶数的情况代码的运行结果是不一样的。
解决方法:这里的解决方法和上面的一样,就是重新跟新一下 原来的位置。
这里erase库里面给了一个返回值。就是返回指向下一个数据的位置。所以我们可以用库里面的函数来完成删除所有偶数这个 问题。
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
std::vector <int >::iterator it = v.begin();
while (it != v.end())
{if (*it % 2 == 0) {it=v.erase(it); } else{cout << *it << " ";it++; }
}
二、vector的模拟实现
1.vector的定义
vector就是一个类模板,底层还是数组,不过这里是用指针控制的。
#include<iostream>
#include<assert.h>
#include<vector>
template<class T>
class vector
{typedef T* iterator;typedef const T* const_iterator;
public:
//由于这两函数比较简单我就先放在这里面了
size_t size()
{return _finish - _start;
}
size_t capacity()
{return _end_of_storage - _start;
}
private:iterator _start=nullptr;iterator _finish=nullptr;iterator _end_of_storage=nullptr;
};
2.reserve扩容
这里我们要检查一下是否需要扩容。如果要扩容就申请空间拷贝数据 。
void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//memcpy(tmp, _start, old_size * sizeof(T); 浅拷贝 if (_start){for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i]; }delete _start;}_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}
}
3.迭代器(iterator)
迭代器分为两种,一种是普通迭代器,另一种就是const修饰的迭代器
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}
const_iterator begin()const
{return _start;
}
const_iterator end() const
{return _finish;
}
4.operator[]
运算符重载也有两种,一种是可修改的。另一种则是不可修改的
T& operator[](size_t i)
{return _start[i];
}
const T& operator[](size_t i)const
{return _start[i];
}
5.push_back()尾插
void push_back(const T& x)
{if (_finish == _end_of_storage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;
}
这里可以这样写,还可以写成复用一下insert。同时还可以写出头插的
void push_back(const T& x)//尾插
{insert(end(), x);
}void push_front(const T&x)//头插
{insert(begin(),x);
}
6.vector的构造函数
vector()
{}
vector() = default;//强制生成
//这两种都是可以的vector(const vector<T>& v)
{reserve(v.size());for (auto& e : v){push_back(e);}
}
~vector()
{delete _start;_start = _finish = _end_of_storage;
}
vector(size_t n, const T& val = T()) //用n个val构造
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}
template <class inputiterator >//这个是vector的区间构造函数
vector(inputiterator first,inputiterator last)
{while (first != last) {push_back(*first);++first; }
}
7.resize扩容
resize就是开辟n个空间,然后再用val初始化。如果原来的数组里面有数据,那就接着数据后面依次插入val。这里一共分为三种情况。
第一种:n<=size 直接删除大于n的部分数据,
第二种:size<n<capacity 在已有的空间里面插入val
第三种 :n>capacity 这里需要扩容,然后依次插入val
void resize(size_t n, const T& val = T())
{if (n <=size()) {_finish =_start+ n; return;}else{reserve(n);//这里就传的n会在reserve函数中在判断一下。while (_finish < _start + n){*_finish = val;++_finish;}}
}
8. operator=
赋值运算重载,这里我们可以先把被赋值的对象清理一下。然后再依次插入数据。这样就比之前那种先开辟赋值对象一样大的空间,,然后再拷贝过去的方法要巧妙得多了。这里我们让程序本身来完成这想问题。这里呢有两种写法:
第一种:就是用程序直接完成。
void clear()
{_finish=_start;
}
vector<T>& operator=( vector<T>& v)
{clear();for (auto& e : v){push_back(e);}return *this;
}
第二种:把两个对象里面得成员变量交换一下
void swap(vector<T>&v)
{std::swap(_start, v._start);std::swap(_finish, v._finish );std::swap(_end_of_storage, v._end_of_storage );
}
vector<T>& operator=( vector<T>& v)
{clear();swap(v);return *this;
}
9 print_container 打印
这就是一个打印函数。我们来想想,如果我们想打印vector 就要用vector得打印函数,打印string就要用string的打印函数。那我们可以不可以写一个打印的任何形式的模板啊?那我们来实现一下吧。
template <class container>
void print_container(const container&v)
{typename container::const_iterator it = v.begin(); while (it != v.end()) {cout << *it << " ";++it; }
}
但是这里要特别注意typename哪里。如果这里不加typename的话。那么程序是会报错的。那是因为编译器规定,不能再没有实例化的类模板中取东西。因为编译器无法确定const_iterator 中是类型还是成员变量。所以这用了typename就是要程序员自己确定。这里也可以用auto让他自己去推导。
template <class container>
void print_container(const container&v)
{auto it = v.begin(); while (it != v.end()) {cout << *it << " ";++it; }
}
三、vector的模拟实现代码展示
1. vector.h文件
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std;
namespace V
{template<class T>class vector{public: typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin()const {return _start;}const_iterator end() const {return _finish;}T& operator[](size_t i){return _start[i];}const T& operator[](size_t i)const {return _start[i]; }void reserve(size_t n){if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//memcpy(tmp, _start, old_size * sizeof(T); 浅拷贝 if (_start){for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i]; }delete _start;}_start = tmp;_finish = tmp + old_size;_end_of_storage = tmp + n;}}size_t size(){return _finish - _start;}size_t capacity(){return _end_of_storage - _start;}void push_back(const T& x){insert(end(), x); /*if (_finish == _end_of_storage) {reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;*/}void insert(iterator pos, const T& x) {if (_finish == _end_of_storage) {size_t p = pos - _start;//记录一下pos的位置reserve(capacity() == 0 ? 4 : capacity() * 2); pos = p + _start; //更新pos的位置 }iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;}void erase(iterator pos) {iterator it = pos + 1;while (it != end()){*(it - 1) = *it;it++;} --_finish; }vector(){}vector(const vector<T>& v){reserve(v.size());for (auto& e : v){push_back(e);}}~vector(){delete _start;_start = _finish = _end_of_storage; }vector(size_t n, const T& val = T()) {reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}void resize(size_t n, const T& val = T()){if (n <=size()) {_finish =_start+ n; return;}else{reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}}template <class inputiterator >vector(inputiterator first,inputiterator last){while (first != last) {push_back(*first);++first; }}void clear(){_finish=_start;}void swap(vector<T>&v){std::swap(_start, v._start);std::swap(_finish, v._finish );std::swap(_end_of_storage, v._end_of_storage ); }vector<T>& operator=( vector<T>& v) {clear();//swap(v);/* for (auto& e : v){push_back(e);}*/return *this;}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};void test_vector();template <class container>void print_container(const container&v) {auto it = v.begin(); while (it != v.end()) {cout << *it << " ";++it; }}
}
2.vector.cpp文件
主要是测试文件
#include"vector.h"
namespace V
{void test_vector(){vector<int> v;vector<int >x;x.push_back(1);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(3);x = v;print_container(v);}
}
3.test.cpp文件
#include"vector.h"
using namespace std;
int main()
{V::test_vector();return 0;
}
总结
好了,今天就分享到这里了。欢迎大家留言。