C++篇 Vector模拟实现(1) 初始化 迭代器遍历 插入尾插尾删 一文详解
🎬 胖咕噜的稞达鸭:个人主页
vector的使用
在学习vector之前必须了解vector文档中的相关描述;vector的介绍,这里我们挑重点的接口来进行阐述。
//注意:通篇都要看注释!精华总结感悟!!!
1.vector 的定义
构造函数声明 | 接口说明 |
---|---|
vector() | 无参构造 |
vector(size_type n, const value_type& val =value_type()) | 构造并初始化n个val |
vector (const vector& x); | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
2. vector iterator 的使用
iterator的使用 | 接口说明 |
---|---|
[begin +end] | 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator |
rbegin +rend | 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator |
3.vector 空间增长问题
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize | 改变vector的size |
reserve | 改变vector的capacity |
4.vector 增删查改
vector增删查改 | 接口说明 |
---|---|
push_back | 尾插 |
pop_back | 尾删 |
find | 查找。(注意这个是算法模块实现,不是vector的成员接口) |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
operator[] | 像数组一样访问 |
vector初始化
vector<int>v1;//无参数的构造vector<int>v2(10, 1);//带参数的构造,初始化1个数组,vector<int>v3(++v2.begin(), --v2.end());//还可以用一段迭代器区间去初始化//v3用v2去初始化,不想要第一个,也不想用最后一个,可以向上面那么写。v3有8个1
vector遍历
//遍历v3,下标第一种遍历方法for (size_t i = 0; i < v3.size(); i++){cout << v3[i] << " ";}cout << endl;//用迭代器去遍历,要指定类域,实例化的模板参数,第二种遍历vector<int>::iterator it = v3.begin();while (it != v3.end()){cout << *it << " ";it++;}cout << endl;//第三种遍历,范围forfor (auto e : v3){cout << e << " ";}cout << endl;
vector的扩容
void test_vector2()
{vector<int>v(10, 1);//初始化10个1v.reserve(20);//扩容到20cout << v.capacity() << endl;//20cout << v.size() << endl;//10v.reserve(15);cout << v.capacity() << endl;//20cout << v.size() << endl;//10v.reserve(5);cout << v.capacity() << endl;//20cout << v.size() << endl;//10 //坚决不缩容,capacity在增加到20之后,就没有变化了
}void test_vector3()
{vector<int>v(10.1);v.reserve(20);//扩容到capacity=20,size是10,capacity是20cout << v.capacity() << endl;//20cout << v.size() << endl;//10v.resize(15.2);//10个1,补了5个2(调试过程中),capacity=20cout << v.size() << endl;cout << v.capacity() << endl;v.resize(25.3);//capacity是30,按照2倍扩容,10个1,size=25cout << v.size() << endl;cout << v.capacity() << endl;v.resize(5);//capacity=30,size=5,只保留5个数据,5个1cout << v.size() << endl;cout << v.capacity() << endl;
}
vector遍历和流提取
void test_vector4()
{vector<int>v(10, 1);v.push_back(2);v.insert(v.begin(), 0);//遍历要用迭代器for (auto e : v){cout << e << " ";// 0 1 1 1 1 1 1 1 1 1 1 2}cout << endl;v.insert(v.begin() + 3, 10);//在第3个位置加一个10for (auto e : v){cout << e << " ";// 0 1 1 10 1 1 1 1 1 1 1 1 2}cout << endl;v.clear();//实现vector的流插入流提取,方法1int x;cin >> x;v.push_back(x);vector<int> v1(5, 0);//给5个数据初始化为0,方法2:用范围for进行遍历for (size_t i = 0; i < 5; i++){cin >> v1[i];//cin输入v1[i];}for (auto e : v1){cout << e << " ";//用范围for进行遍历,输入的数字用空格分割,最后打印出55个数字}cout << endl;
}
这里我们就有疑问了,string s2可不可以用vectorv2进行替代?
不可以! 1.string s2最后有\0,但是vectorv2没有\0
2.string可以支持比较大小,要按照ASCII码表进行比较,但是vector没有
vector的二维数组构建
void test_vector5()
{vector<string>v1;//vector中还可以存储自定义类型string s1("xxxxxxx");v1.push_back(s1);v1.push_back("yyyyyy");//const char* 还可以隐式类型转换for (const auto& e : v1)//*it取每个值,都是一个string,string给e,进行每次拷贝构造//有多少个string对象就要拷贝构造多少次,所以加& ,不改变遍历的值前面加const//以前都是拷贝一下Int,代价比较小,如果要拷贝string,不仅要开空间还要调用数据{cout << e << " ";}cout << endl;//假如要实现10*5的二维数组//vector<vector<int>>vv();//vector里面存vector,就是二维数组vector<int>v(5, 1);vector<vector<int>>vv(10, v);//10个v,v是v(5,1)//vv[2][1] = 2;//访问第二行第一个并且将其修改为2 ///看图!!!//编译器实例化了两个vector,vv.operator[](2).operator[](1) = 2;//等价于vv[2][1]=2;for (size_t i = 0; i < vv.size(); i++){for (size_t j = 0; j < vv[i].size(); j++){cout << vv[i][j] << " ";}cout << endl;}cout << endl;}
这里我们用C++做一道算法题来熟悉一下vector二维数组动态规划在算法题中的应用:杨辉三角OJ
clase Solution{
public:vector<vector<int>>generate(int numsRow){vector<vector<int>>vv(numsRow);//开n行for (size_t i = 0; i < numsRow; ++i){vv[i].resize(i + 1, 0);//第0行(索引)有1个,第1行(索引)有2个,每个数据默认给0//vv[i][0]=vv[i][vv[i].size()-1]=1;vv.front = vv.back = 1;}for (int i = 2; i < vv.size(); ++i)//第0行和第1行不需要{for (int j = 1; j < vv[i].size()-1; ++j)//避开每一行的第一个数据和组后一个数据{vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];//上一行的当前位置+上一行的前一个位置}}return vv;}
}
迭代器遍历,operator[]访问
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace Keda
{template<class T>class vector{public:typedef T* iterator;//使用typedef为T*(指向模板类型T的指针)定义了一个//别名iterator,用于遍历容器中的元素。表示vector类的迭代器是一个指向T类型的指针typedef const T* const_iterator;//const不可修改型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 _end_of_storage - _start;}T& operator[](size_t i)//遍历{assert(i < size());return _start[i];}const T& operator[](size_t i)const{assert(i < size());return _start[i];}private:iterator _start = nullptr;iterator _finish = nullptr;//有效元素的末尾位置的下一个位置iterator _end_of_storage = nullptr;//用于判断是否需要扩容,意为空间用完了};
}
vector模板
//模板
template<class T>
void print_vector(const vector<T>& v)//const对象就要用const迭代器
{//在没有实例化的类模板里面取东西,规定:没有实例化的类模板里面取东西//编译器不能区分这里的const_iterator是类型还是静态成员变量//typename vector<T>::const_iterator it = v.begin();//也可以直接由编译器自己推导//静态成员变量不需要有类型auto it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;for (auto e : v){cout << e << " ";}cout << endl;
}
vector插入!!!(内设及迭代器失效问题!!必看)
void reserve(size_t n)
{if (n > capacity()){size_t old_size = size();T* tmp = new T[n];//开新的空间memcpy(tmp, _start, size() * sizeof(T));//拷贝旧空间的数据给新的空间delete[]_start;//释放掉旧的空间_start = tmp;//指向新的空间_finish = tmp + old_size;_end_of_storage = tmp + n;}}void push_back(const T& x)
{if (_finish != _end_of_storage){*_finish = x;++_finish;}else{reserve(capacity() == 0 ? 4 : capacity() * 2);*_finish = x;++_finish;}iterator insert(iterator pos, const T& x){if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = len + _start;}iterator end = _finish - 1;while (end >= pos)//可以等于0{*(end + 1) = *(end);--end;}*pos = x;++_finish;return pos;
}
这里会出现迭代器失效的问题:
分析:当需要插入一个新的x,很有可能出现_finish==_end_of_storage的情况,需要扩容开辟一个新的tmp指针指向一块新的空间,然后释放掉旧的_start,让tmp所在的这块空间称为新_start,然后构建好新的_finish,_end_of_storage
为什么:但是!!!!!pos还指向的是旧_start,扩容的时候如果没有考虑到pos,释放旧的_start的时候会将pos一并释放,所以再插入这里使用的pos是一个野指针
解决办法就是找到新的_start中对应旧_start的pos位置
所以在扩容的地方算出相对len,size_t len=pos-_start;扩容好了之后更新pos即可: pos=len+_start;
续:测试插入
void test_vector2()
{vector<int>v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//v.push_back(5);print_vector(v);v.insert(v.begin() + 3, 45);print_vector(v);//迭代器区间要求传左闭右开int x;cin >> x;auto pos = find(v.begin(), v.end(), x);if (pos != v.end()){//v.insert(pos, 40);//查找x,找到之后让pos*=10,假如pos=2,我们乘等10,应该输出20//(*pos) *= 10;//实际上输出400,为什么?这里也是迭代器失效的问题,所以insert以后pos就失效了,不要访问//pos还指向的是旧空间,再解引用就是越界的野指针,即使在insert里面做了改变,已经更新过了,但是毕竟是在内部,实参的改变不影响形参//那要是一定要哦访问呢???//这里可以将insert操作完之后在内部操作的值pos返回,iterator insert (iterator pos,const T& s); 下面return pos//在测试中:pos=v.insert(pos,40); (*(p+1)) *=10pos = v.insert(pos, 40);(*(pos+1) ) *= 10;}print_vector(v);}
这里也是迭代器失效的问题,insert以后pos就失效了,不要访问;
pos还指向的是旧空间,再解引用就是越界的野指针,即使在insert里面做了改变,已经更新过了,但是毕竟是在内部,实参的改变不影响形参(内部的更新不影响外部)
vector尾删
bool empty()
{return _start == _finish;
}
void pop_back()
{assert(!empty());--_finish;
}