<vector>
底层
底层使用的是数组来存储,实际上是一个顺序表(见数据结构与算法)
与string的对比
底层都是数组,我们能不能用vector<char>来代替string呢?
不能,与string不同的,vector没有连续不同元素输入的需求,也就没有重载find swap +=等一系列的运算符,无法对字符数组进行操作。
我们可以发现vector没有find/swap这样的重载,本质是因为使用算法库中的就够用了
使用
1.遍历
一样的三种遍历方法,[]/iterator/for(auto)
需要注意的是,在储存自定义类型时,用范围for遍历一定是用引用,否则在堆上反复操作,代价太大。
eg:vector<string> a;a.push_back("张三");a.push_back("李四");for (auto& e : a) {cout << e << " "; } cout << endl; return 0;
2.模拟二重数组
(一)生成杨辉三角
eg:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv;vv.resize(numRows,vector<int>());for(size_t i=0;i<numRows;i++){vv[i].resize(i+1,1);}for(size_t i=2;i<numRows;i++){for(size_t j=1;j<vv[i].size()-1;j++){vv[i][j]=vv[i-1][j-1]+vv[i-1][j];}}return vv;}
3.构造(C11)
auto e = { 1,2,2,3,4,5,7,9 };
//法一,显式初始化
vector<int> a(e);
//法二,隐式转化初始化
vector<int> b({1,2,3});
//法三,隐式转化拷贝复制
vector<int> c={ 1,2,3 };
//法四,特殊规则
vector<int> d{ 1,2,3 };
成员函数
这里选讲,由于底层和string一样是数组,其实库中成员函数的使用也差不多(详见string)
下面主要进行一些补充
modifier
insert
注意,会导致传入的迭代器失效,不可二次使用该迭代器(本质缘由为空间拓展导致)
erase
注意,会导致传入的迭代器失效,不可二次使用该迭代器(本质缘由为VS编译器规定,本身不会产生野指针,除非操作不当)
emplace
这里主要介绍emplace的用法与改进,emplace作用和insert一样,而emplace_back则和push_back作用一致,唯一的不同是不难看出这个是C++11的新语法,其底层进行了复杂的优化,速度更快(后面介绍),同时默认构造传参语法也有一个新增。
eg:vector<A> v2;A aa1(1, 1);//push_backv2.push_back(aa1);v2.push_back(A(2,2));v2.push_back({3,3});cout << "**************************" << endl;//emplacev2.emplace_back(aa1);v2.emplace_back(A(2, 2)); //v2.emplace_back({ 3,3 }); // 传构造A的参数,效率较高,也是新增改动v2.emplace_back(3,3);
vector的模拟实现与坑介绍
模拟实现gitee链接:(等待更新)
坑点一:size_t的遍历
我们以反向历遍打印vector为例
print(const xx::vector& a) for (size_t i=size()-1;i>0;i--) {cout << a[i] << " " ; }
cout << endl;
这里i永远不会小于0,出现越界访问与死循环问题
可以改进为
print(const xx::vector& a) for (size_t i=size()-1; i!=0;i--) {cout << a[i] << " " ; } cout << endl;
坑点二:模板重载匹配问题
我们看到vector的拷贝构造,这里想用InputIterator来接收所有容器的不同的iterator,用模板适配,下面写的是用n个值进行初始化
template <class InputIterator> vector(InputIterator first, InputIterator last) {while (first != last) {push_back(*first);++first;} } vector(size_t n, T val = T()) {resize(n, val); } vector(int n, T val = T()) {resize(n, val); }
在测试用例:
dr::vector<int> f(3,90);
执行时,如果没有
vector(int n, T val = T())
{
resize(n, val);
}
则会匹配到模板的部分(size_t版本会进行一次隐式转化,而模板更加精准匹配)。是不是这样就解决了其他相似的匹配问题?
我们再看到测试用例
dr::vector<size_t> f(3,90);
现在是传入(int ,int),又匹配到模板去了。
只要使用隐式类型转换进行传参就会产生类似的问题。在SGI STL3.0中,也就是解决了部分匹配问题(方法类似上面的,写几个确定的重载优先匹配,也存在上面dr::vector<size_t> f(3,90);的无法解决的问题),现有的语法无法解决,C++11之后推出了解决方法:SFINAE (Substitution Failure Is Not An Error),创造语法判定类型为你指定类型时才使用模板。
坑点三:深层次的拷贝问题
看到vector的赋值重载/拷贝,我们要考虑到底层数组中存储的可能是自定义类型,使用memcopy的方式是错误的
eg:vector(const vector<T>& v): _start(new T[v.capacity()]),_finish(_start+v.size()), _EndOfStorage(_start + v.capacity()) {//错误处理/** memcpy(_start,v._start,v.size());*/reserve(v.capacity());for (size_t i = 0; _start + i != _finish; i++){_start[i] = v._start[i];} }
迭代器失效处理
在有迭代器传入的函数,一定记得返回一个新迭代器,用于函数外迭代器的更新(最好是不论底层指针是否还有效都设计更新,VS编译器检查严苛,之间报错,GCC略好)
eg: void reserve(size_t n) {if (n > size()){size_t oldsize = size();T* temp = new T[n];for (size_t i = 0; _start + i != _finish; i++){temp[i] = _start[i];}delete[] _start;_start = temp;_finish = _start + oldsize;_EndOfStorage = _start + n;} }
持续优化ing……………………………………………………………………………………………………………………………………………………………………………………………………………………
