C++ vector的使用和简单模拟实现(超级详细!!!)
目录
前言
1.STL是什么
2.vector使用
2.1 vector简介
2.2 常用接口函数
1. 构造函数
2.operator[ ]和size,push_back
3. 用迭代器进行访问和修改
4. 范围for遍历
5.修改类型函数 pop_back find insert erase
6. 容量相关函数capacity resize reserve
3. vector模拟实现
3.1 模版问题
3.2 vector成员变量和迭代器
3.3 capacity,size,operator[ ]
3.3 push_back和reserve函数
3.5 构造函数,析构函数
3.6 拷贝构造函数和赋值拷贝函数
3.7 insert和erase
3.8 迭代器失效问题
总结
前言
今天将开启对C++STL的学习,STL作为强大的模版库,十分值得我们学习!在此途中,提升自己的C++代码能力。
1.STL是什么
标准模板库(Standard Template Library,STL)是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。STL是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
STL版本
- 原始版本:Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,并且是开源的,HP 版本--所有STL实现版本的始祖。
- P. J. 版本:由P. J. Plauger开发,继承自HP版本,不开源。
- RW版本:由Rouge Wage公司开发,继承自HP版本,不开源。
- SGI版本:由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,是开源的。
STL有六大组件:
2.vector使用
2.1 vector简介
vector是表示可变大小数组的序列容器。就像数组一样,vector也采用的连续存储空间来存储元素。也就意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
因为vector内部实现没有使用具体类型,而是给出模版,所以实例化一个vector变量时需要给出具体类型。记得包含头文件<vector>。
2.2 常用接口函数
1. 构造函数
vector(const allocator_type& alloc = allocator_type());//函数原型void test_vector()
{vector<int> v1;
}
- 默认构造函数:构造一个没有元素的空容器。在test_vector函数内定义一个int类型的空容器。
tip:其中的allocator是空间适配器,用于分配内存空间的,暂时不用深究。
vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());void test_vector()
{vector<int> v1(10, 1);
}
- 填充构造函数:构造一个包含n个元素的容器。每个元素都是val的一个副本。
- test_vector函数中,实例化了一个int类型的vector容器,里面有10个1整型变量。
template <class InputIterator>vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());void test_vector()
{vector<int> v1(10, 1);vector<int> v2(v1.begin(), v1.end());
}
- 迭代器构造函数:构造一个包含与[first,last)范围相同数量元素的容器,每个元素以相同的顺序从该范围内的相应元素构造。
vector (const vector& x);void test_vector()
{vector<int> v1(10, 1);vector<int> v2(v2);
}
- 拷贝构造函数:用x中每个元素的副本按相同顺序构造一个容器。
2.operator[ ]和size,push_back
vector是一个类似数组的容器,物理存储上是连续的,那自然少不了下标访问。
- push_back函数,望文生义就是在尾部插入一个元素。
- size函数是获取当前容器元素个数。
- [ ]是可以访问填入下标的元素,并进行修改。
void test_vector1()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (size_t i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;
}
运行结果如下:
3. 用迭代器进行访问和修改
使用迭代器前,我们需要了解什么是迭代器。
在C++中,迭代器(Iterator)是一种检查容器中元素并遍历元素的数据类型。迭代器提供了一种通用的方式来访问容器中的元素,而不需要关心容器底层的具体数据结构。通过迭代器,可以对容器进行遍历、读取、修改和删除元素等操作。
- 使用迭代器时,需要定义一个变量,变量类型就是int类型下的vector容器中的iterator类。不管vector容器中具体类型是什么。在vector中,约定俗成迭代器类型名是iterator。
- 定义迭代器变量后,赋值为vector成员函数begin,在vector中,指向第一个元素。还有end函数,表示最后一个元素的下一个位置。
- 还有!=,前置++,!=和*操作符,++表示往后指向下一个元素,*操作类似于指针的解引用操作,代表该元素。但是自定义类型没有这些操作,都是通过运算符重载的方式,将其的行为编程跟指针变量相类似的操作。
- 你可以类比成指针,但是iterator这个类型不一定是指针,可能还是一个类。
void test_vector2()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);vector<int>::iterator it1 = v1.begin();while (it1 != v1.end()){cout << *it1 << " ";++it1;}cout << endl;it1 = v1.begin();while (it1 != v1.end()){++*it1;//对容器元素进行修改cout << *it1 << " ";++it1;}cout << endl;//全部展开成运算符重载函数it1.operator=(v1.begin());while (it1.operator!=(v1.end())){cout << it1.operator*() << " ";it1.operator++();}cout << endl;
}
上面最后一段代码将这些运算符写成调用函数的形式,也可以进行遍历,但是代码的可读性比较差。运行结果如下:
但是有些情况下,只是遍历数据进行打印,不想修改元素。如果使用一般迭代器iterator,会导致权限放大,误改其中的元素。我们可以使用const_iterator,对该类型的容器进行访问,即使不小心修改,系统也会报错。
void test_vector3()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);vector<int>::const_iterator it1 = v1.begin();while (it1 != v1.end()){//*it = 5 error,不可以修改cout << *it1 << " ";++it1;}cout << endl;
}
运行结果如下:
迭代器不仅可以正向遍历,还有反向迭代器,倒着遍历。跟正向迭代器类似,也有可修改和不可修改两种反向迭代器。
void test_vector4()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);vector<int>::reverse_iterator it1 = v1.rbegin();while (it1 != v1.rend()){*it1 += 4;cout << *it1 << " ";++it1;}cout << endl;vector<int>::const_reverse_iterator it2 = v1.rbegin();while (it2 != v1.rend()){//*it2 = 1 errorcout << *it2 << " ";++it2;}cout << endl;
}
运行结果如下:
4. 范围for遍历
范围for是一个比较实用的语法,支持遍历各种容器,但是只能正向遍历。
- 范围for看起来十分高级,但是最终的底层逻辑,还是跟迭代器相关,只要相关容器提供了begin和end函数,就支持遍历。
void Test()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << " ";}cout << endl;//想要修改内容需要使用引用for (auto& e : v1){e++;cout << e << " ";}cout << endl;
}
运行结果如下:
5.修改类型函数 pop_back find insert erase
- pop_back函数,是删除最后一个元素,效率高。如果容器为空继续删除,会直接终止程序。发出断言警告。
之后的Print函数就是正向输出容器中的所有元素。
void Print(vector<int>& v)
{vector<int>::iterator it1 = v.begin();while (it1 != v.end()){cout << *it1 << " ";++it1;}cout << endl;
}void test_vector5()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);Print(v1);v1.pop_back();v1.pop_back();Print(v1);
}
运行结果如下:
find,insert和erase
template <class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val);
- find函数,给两个相同的类型的迭代器,范围是从first位置的元素到last前一个位置的元素,是一个左闭右开的区间[first, last),寻找与val相同的元素,并返回该元素位置的迭代器。如果没有找到,返回last迭代器。
//1.
iterator insert (iterator position, const value_type& val);
//2.
void insert (iterator position, size_type n, const value_type& val);
//3.
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);
insert函数原型如下,有三种类型。
- 第一种是传插入位置的迭代器变量,插入值为val。
- 第二种是传插入位置的迭代器变量,但是插入n个val元素进去。
- 第三种是传插入位置的迭代器变量,但插入的元素是别的容器的元素,范围是从first位置的元素到last前一个位置的元素,是一个左闭右开的区间[first, last)。
//1.
iterator erase (iterator position);
//2.
iterator erase (iterator first, iterator last);
- erase函数有两种类型
- 第一种是删除一个指定位置的元素,只用传该元素位置的迭代器。
- 第二种是删除某个范围内的元素,传入first迭代器和last迭代器,是一个左闭右开的区间[first, last)。
void test_vector6()
{int arr[] = { 1,2,3,4,5,6,};vector<int> v1(arr, arr + 6);Print(v1);vector<int>::iterator pos = find(v1.begin(), v1.end(), 4);v1.insert(pos, 20);Print(v1);pos = find(v1.begin(), v1.end(), 5);v1.insert(pos, 2, 10);Print(v1);vector<int> v2;v2.insert(v2.begin(), v1.begin(), v1.end());Print(v2);pos = find(v1.begin(), v1.end(), 6);v1.erase(pos);Print(v1);v2.erase(v2.begin(), --v2.end());Print(v2);
}
运行结果如下:
6. 容量相关函数capacity resize reserve
- capacity函数是获取该容器的容量大小。我们设计一个函数测试vector的扩容机制,分别在VS和Linux环境下进行测试。
void TestExpand()
{size_t sz;vector<int> v;sz = v.capacity();cout << "making v grow:\n";for (int i = 0; i < 100; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}
在VS环境下,扩容情况如下图所示。一开始,插入四个元素时,每次扩容只增加一个容量。插入第五个元素开始就是1.5倍扩容的速度。
在Linux环境下,我们可以看到是遵循2倍扩容的原则进行扩容。
void resize (size_type n, value_type val = value_type());
- resize可以调整容器元素个数的大小,使其包含n个元素。
- 如果n小于当前容器元素个数,则内容减少到前n个元素,并删除超出的元素。
- 如果n大于当前容器元素个数,则通过尾插插入所需元素,扩展内容,以达到n的大小。如果有给定val,插入元素就为val,否则将其进行值初始化(自定义类型调用默认构造函数,自定义类型也有自己的初始化)。如果超过容器容量大小,会扩容。
比如说,你创建了一个含有四个元素容器。如果用下标访问该容器元素,下标值范围是0~3,不能超出这个范围。当你想使用下标给第五个元素赋值,就会断言报错。此时就可以使用resize函数,将容器元素个数扩充,便可以使用下标给之后的元素赋值。
void test_vector7()
{int arr[] = { 1,2,3,4 };int size = sizeof(arr) / sizeof(int);vector<int> v1(arr, arr + size);//v1[4] = 0; //errorv1.resize(8);v1[4] = 1;v1[5] = 2;v1[6] = 3;v1[7] = 4;Print(v1);
}
运行结果如下:
void reserve (size_type n);
- reserve是调整容器容量大小。
- 只用传一个无符号整型参数。如果n大于当前容器的容量,会给该容器重新分配存储空间,将其容量增加到n。在其他情况下,函数调用不会导致重新分配,容器容量不变。
- reserve不会对容器里的元素做出改变,容器元素个数不变。
在上面测试不同平台下vector的扩容机制,扩容的本质是开辟新空间,将原来容器的内容拷贝过去,再释放原有的空间。如果要频繁插入数据,就会频繁的扩容,会造成许多消耗,所以如果知道要插入数据个数,可以一次性扩容完毕,减少不必要的消耗。
void TestExpand()
{size_t sz;vector<int> v;sz = v.capacity();v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容cout << "making v grow:\n";for (int i = 0; i < 100; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}
在VS中,运行结果如下:
3. vector模拟实现
3.1 模版问题
- 模拟实现vector会用到类型模版,一般建议将实现的函数内容都放在一个头文件中。因为给定一个参数类型实例化vector容器时,这个过程发生编译的期间,如果函数的声明和定义分离,放在两个文件中,编译器无法根据这个类型进行代码分发。
- 直接创建一个vector.h的头文件。还有为了不和C++标准库里面的vector发生命名冲突,可以自己创建一个命名空间User。再包含上需要的头文件。
#include <assert.h>namespace User
{template <class T>class vector{//...};}
3.2 vector成员变量和迭代器
- vector类内部通常有三个迭代器类型的成员变量。
- _start指向第一个元素,_finish指向末尾元素的后一位,_end_of_storage表示容器容量末尾。
#include <assert.h>namespace User
{template <class T>class vector{private:iterator _start;iterator _finish; iterator _end_of_storage; };}
因为vector物理存储和逻辑存储都是连续的,所以我们可以将他的迭代器类型用原生指针实现。
typedef T* iterator;
typedef const T* const_iterator;
再提供begin和end函数,返回容器第一个元素的位置和末尾元素的后一位。其中const_iterator类型的迭代器,需要保证容器元素不能被修改,需要在函数后加上const,成为const成员函数。
const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}iterator begin()
{return _start;
}iterator end()
{return _finish;
}
3.3 capacity,size,operator[ ]
- 指针相减可以得到其中该类型元素个数之差。因此size和capacity,分别用_finish和 _end_of_storage指针减去_start指针,就可以实现。
size_t size() const
{return _finish - _start;
}size_t capacity() const
{return _end_of_storage - _start;
}
- operator[ ]实际上返回_start指针用下标访问的元素即可,及得要先检查i的大小,是否符合要求。还提供了const类型的[ ]重载。
T& operator[](size_t i)
{assert(i < size());return _start[i];
}T& operator[](size_t i) const
{assert(i < size());return _start[i];
}
3.3 push_back和reserve函数
我们先完成一个尾插和扩容的函数,之后的构造函数就依靠这个接口进行初始化。
- push_back是在末尾插入元素,一开始需要检查容器容量大小够不够在插入多一个元素。如果_finish指针与_end_of_storage指针相等时,说明容量不够,需要扩容。扩容时先判断capacity是否为零,为零还没插入数据,给四个元素空间,之后就是两倍扩容。
- 再讲一下扩容的逻辑,首先判断传递的参数n是否大于当前容量,如果大于才能继续扩容,出现其他情况不做处理。扩容一般动态开辟一块为n个T类型参数大小的内存空间,然后将原有的数据拷贝过来,在释放原有空间内存。
- 其重要注意需要先记录之前空间的元素个数,方便之后调整_finish指针的位置。
void reserve(size_t n)
{if (n > capacity()){T* tmp = new T[n];size_t oldsize = size();if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}
}void push_back(const T& x)
{if (_finish == _end_of_storage){//两倍速扩容size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;
}
写一个测试函数,测试一下之前实现的各种接口函数,用正常的for循环遍历,迭代器遍历,还有范围for进行遍历。
void test_vector1()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (size_t i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;for (auto e : v1){cout << e << " ";}cout << endl;
}
测试结果如下:
但是reserve函数实现就没有问题吗?看看下面的测试函数
void test_vector2()
{vector<string> v1;v1.push_back("xxxxxxxxxx");v1.push_back("xxxxxxxxxx");v1.push_back("xxxxxxxxxx");v1.push_back("xxxxxxxxxx");v1.push_back("xxxxxxxxxx");for (auto e : v1){cout << e << " ";}cout << endl;
}
当你运行这段代码的时候,打印前面四个元素会出现乱码。这是为什么呢?
如下图所示,tmp拷贝过来的内容,把指针变量也原封不动按字节拷贝,导致_start和tmp中的_str指针指向同一块空间。之后,会delete[ ] _start,而delete释放空间之前,会调用里面自定义类型的析构函数,再释放这个空间,这就会造成我们数据的丢失。
- 所以要进行一个深拷贝,写一个for循环,传统写法是赋值,如果是内置类型直接赋值,如果是自定义类型,调用该类型的赋值拷贝函数。
- 现代的写法是直接将tmp[i]与_start[i]进行交换,直接调用C++标准库里的交换函数,交换的好处是不用再拷贝值,并且delete掉_start里面没有自定义类型,不用调用析构函数。
void reserve(size_t n)
{if (n > capacity()){T* tmp = new T[n];size_t oldsize = size();if (_start){for (size_t i = 0; i < oldsize; i++){//tmp[i] = _start[i];//传统写法std::swap(tmp[i], _start[i]);}delete[] _start;}_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}
}
3.5 构造函数,析构函数
vector构造函数我们三个,其中一个是给定个数n,给定T类型的x变量,进行初始化。
- 为什么这个会重载成两个呢?因为有的时候传参可能是int或者size_t类型,编译器无法判断,需要重载成两个构造函数。
- 我们可以用传统的方式,开辟空间,进行赋值,然后再赋值成员变量。不过也可以使用我们实现好的push_back函数接口,因为符合尾插的特点,不过在尾插时,可以先扩容,这样就不会频繁扩容,提升效率。
vector(size_t n, const T& x = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(x)}
}vector(int n, const T& val = T())
{reserve(n);for (int i = 0; i < n; i++){push_back(val);}
}
写一个测试函数,用一个Print函数实现打印整个容器元素,之后打印就用这个Print函数。
template <class T>
void Print(const vector<T>& v)
{for (auto e : v){cout << e << " ";}cout << endl;
}void test_vector3()
{vector<int> v1(4, 10);Print(v1);}
- vector构造函数还有,给定一个容器迭代器区间进行初始化,也是跟上面的类似,复用push-back函数。不过需要再使用一个函数模版,定义first和last变量,用while循环,类似我们使用迭代遍历容器一样,其他的容器都会对!=,*和++进行重载。
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
写一个测试函数。
void test_vector4()
{vector<int> v1(4, 10);Print(v1);vector<int> v2(v1.begin(), v1.end());Print(v2);
}
我们来看看这段代码,在STL的Vector可以支持带花括号的初始化。
void test_vector5()
{std::vector<int> v1 = { 1,2,3,4,5,6 };for (auto e : v1){cout << e << " ";}cout << endl;
}
结果如下:
这是为什么呢?因为C++有一个类型叫做initializer_list,如下面的代码实例,任何一个带花括号的里面是相同元素,用逗号分隔,就会是该类型。这个类型里面只有两个指针维护,一个指向第一个元素,另外一个指向最后一个元素。
void Test()
{auto il = { 1,2,3,4,5,6 };initializer_list<int> il1 = { 1,2,3,4,5,6 };cout << typeid(il).name() << endl;cout << sizeof(il) << endl;
}
这个构造函数跟之前也是类似,只是传递的参数是initializer_list,直接用范围for尾插。
vector(initializer_list<T> il)
{reserve(il.size());for (auto e : il){push_back(e);}
}
- 最后别忘了实现默认构造函数,可以使用关键字default,强制生成默认构造函数,还有可以给成员变量一个缺省值。
class vector
{public:vector() = default;private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;
}
- 析构函数,先判断_start是否不为空,然后再释放空间,成员变量赋值为空指针。
~vector()
{if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}
}
3.6 拷贝构造函数和赋值拷贝函数
拷贝构造函数,跟之前的构造函数类似,先扩容,再复用push_back函数。
vector(const vector<T>& v)
{reserve(v.capacity());for (auto e : v){push_back(e);}
}
- 赋值拷贝函数,可以借鉴上面写reserve的现代写法,可以实现一个swap函数,专门交换vector<T>类型的变量,里面套用标准库里的swap函数,直接交换成员变量,不需要再开辟新空间并拷贝数据。
- 然后赋值拷贝函数参数写成一个普通vector<T>类型的变量,传递参数过来就会调用vector的拷贝构造函数,然后再用我们刚实现swap,进行交换,不会涉及浅拷贝的问题。并且v还是个临时变量,出了作用域自动调用析构函数,并销毁变量。
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)
{swap(v);return *this;
}
3.7 insert和erase
- insert和erase函数实现跟顺序表类似,都需要移动数据。
- insert中,当容量不足要扩容时,需要先记录pos相对于_start差多少个元素个数,因为扩容之后,原有空间被释放,pos指向的就是一块被释放的空间,变成野指针。
void insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;//记录pos相对于start的位置size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;}iterator end = _finish;while (end > pos){*end = *(end - 1);--end;}*pos = x;++_finish;
}void erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator end = pos + 1;while (end < _finish){*(end - 1) = *end;++end;}--_finish;
}
3.8 迭代器失效问题
void test_vector2()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);int x;cin >> x;std::vector<int>::iterator pos = find(v1.begin(), v1.end(), x);if (pos != v1.end()){v1.insert(pos, 1000);}for (auto& e : v1){cout << e << " ";}cout << endl;
}
- 我们运行下面的代码,会发现程序崩溃了,这是为什么呢?
- 这是因为当再插入一个元素的话,会发生扩容。扩容时,我们insert扩容时注意到pos指向一块被释放的空间,更新了pos。但是在insert函数中,会创建一个形参pos,形参是实参的一份临时拷贝,改变形参不会对外部的pos实参有影响,因此pos迭代器失效。
- 解决的办法就是,让insert函数的返回类型为iterator,像下面一样更新pos的值。
//...if (pos != v1.end()){pos = v1.insert(pos, 1000);}//...
所以insert的函数返回参数需要修改,返回第一个新插入的元素。
iterator insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;//记录pos相对于start的位置size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len;}iterator end = _finish;while (end > pos){*end = *(end - 1);--end;}*pos = x;++_finish;
}
再看一下下面的代码,与insert类似,我们删除一个元素,会造成迭代器失效吗?
- pos指向的位置不变,但是某些平台会缩容,或者造成野指针现象,所以一般认为是erase迭代器失效的,erase后会返回被删除元素的后一位。
void test_vector3()
{std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto& e : v1){cout << e << " ";}cout << endl;int x;cin >> x;std::vector<int>::iterator pos = find(v1.begin(), v1.end(), x);if (pos != v1.end()){v1.erase(pos);cout << *pos << endl;}for (auto& e : v1){cout << e << " ";}cout << endl;
}
erase函数也需要修改一下,返回删除元素的后一个位置。
iterator erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator end = pos + 1;while (end < _finish){*(end - 1) = *end;++end;}--_finish;return pos + 1;
}
总结
通过这篇文章,你应该了解了vector的使用,和一些简单的底层模拟实现。不过纸上得来终觉浅,绝知此事要躬行,需要多加练习!
创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!
相关文章:

C++ vector的使用和简单模拟实现(超级详细!!!)
目录 前言 1.STL是什么 2.vector使用 2.1 vector简介 2.2 常用接口函数 1. 构造函数 2.operator[ ]和size,push_back 3. 用迭代器进行访问和修改 4. 范围for遍历 5.修改类型函数 pop_back find insert erase 6. 容量相关函数capacity resize reserve 3.…...

MySQL中,不能在一个DML(数据操纵语言,如INSERT, UPDATE, DELETE)语句中直接引用目标表进行子查询
错误示例 <delete id"deleteOldRelations">DELETE FROM departments_closure_tableWHERE descendant IN ( SELECT descendant FROM departments_closure_tableWHERE ancestor #{departmentId})</delete>程序运行之后,会报错:You …...

【CH32V305FBP6】4. systick 配置
配置 main.c void SYSTICK_Init_Config(u_int64_t ticks) {SysTick->SR & ~(1 << 0);//clear State flagSysTick->CMP ticks - 1;SysTick->CNT 0;SysTick->CTLR 0xF;NVIC_SetPriority(SysTicK_IRQn, 15);NVIC_EnableIRQ(SysTicK_IRQn); }中断计数 …...

【PECL】在扩展中实现 autoload
【PECL】在扩展中实现 autoload 摘要PHP代码想这么写C 代码这么实现 摘要 php-8.3.x 用扩展写个框架。想实现类管理器,自动加载,上代码: PHP代码想这么写 $ws new \Ziima\Applet(); $ws->import(Ziima, ../base/core); $ws->runAu…...

企业微信H5授权登录
在企业中如果需要在打开的网页里面携带用户的身份信息,第一步需要获取code参数 如何实现企业微信H5获取当前用户信息即accessToken? 1.在应用管理--》创建应用 2.创建好应用,点击应用主页-》设置-》网页-》将授权链接填上去 官方文档可以看…...

玩机进阶教程------修改gpt.bin分区表地址段 完全屏蔽系统更新 fast刷写分区表 操作步骤解析【二】
上期博文简单说明了分区表的基本常识。我们在有些环境中需要屏蔽手机的系统更新选项。除了以前博文中说明的修改系统更新下载文件夹的方法。还可以通过修改分区表类达到目的。在一些辅助维修工具上面带修改分区表功能。修改后效果为屏蔽系统更新和可以恢复出厂。原则上不深刷都…...

Java实现数据结构---数组
文章目录 概念存储原理数组的操作完整代码 概念 数组是(Array)是有限个相同类型的变量所组成的有序集合,数组中的每一个变量为称为元素。数组是最简单、最常用的数据结构。 数组下标从零开始。 存储原理 数组用一组连续的内存空间来存储一…...

java解析excel文件,返回json
我这里用的是springboot项目,配合Maven使用的。首先需要引入依赖: <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency…...

uniapp 添加字体ttf
效果图如下 一、逻辑概述 在uniapp中使用字体,一共分成两种情况,一种是普通vue页面,一种是nvue页面引入字体。。 1.vue页面引入字体需要如下步骤 1. 先选择下载一种字体:字体格式一般为 ttf后缀名 黄凯桦律师手写体免费下载和在线…...

Linux入门攻坚——24、BIND编译安装、Telnet和OpenSSH
BIND编译安装 对于没有rpm包,需要源代码编译安装。 1、下载源代码:bind-9.12.2-P1.tar.gz,解压:tar -xf bind-9.12.2-P1.tar.gz 2、完善环境: 1)增加用户组named:groupadd -g 53 named 2&…...

1.5.3 基于Java配置方式使用Spring MVC
本实战教程主要介绍了如何使用Java配置方式来使用Spring MVC框架。相较于XML配置方式,Java配置方式提供了一种更为简洁和灵活的配置方法。 项目创建与配置 创建一个Jakarta EE项目,并设置项目名称和位置。选择Jakarta EE 10版本,不添加依赖&a…...

Artifactory清理二进制文件丢失的制品
一、摘要 当制品上传到 Artifactory 时,Artifactory 会在数据库中记录制品的相关元数据信息,包括文件路径、大小、校验和(如 MD5、SHA1)、上传时间、索引、依赖等。实际的制品二进制文件会存储在指定的存储后端,具体的…...

C#中的数组探索
在C#编程语言中,数组是一种基本的数据结构,用于存储固定大小的同类型元素序列。本文将深入探讨C#数组的各个方面,包括定义、赋值、范围操作、切片、多维数组(矩形与锯齿形)、简化初始化表达式以及边界检查。 数组定义…...

身份认证与口令攻击
身份认证与口令攻击 身份认证身份认证的五种方式口令认证静态口令动态口令(一次性口令)动态口令分类 密码学认证一次性口令认证S/KEY协议改进的S/KEY协议 其于共享密钥的认证 口令行为规律和口令猜测口令规律口令猜测 口令破解操作系统口令破解Windows密码存储机制Windows密码破…...

卷积网络迁移学习:实现思想与TensorFlow实践
摘要:迁移学习是一种利用已有知识来改善新任务学习性能的方法。 在深度学习中,迁移学习通过迁移卷积网络(CNN)的预训练权重,实现了在新领域或任务上的高效学习。 下面我将详细介绍迁移学习的概念、实现思想,…...

Ansible04-Ansible Vars变量详解
目录 写在前面6 Ansible Vars 变量6.1 playbook中的变量6.1.1 playbook中定义变量的格式6.1.2 举例6.1.3 小tip 6.2 共有变量6.2.1 变量文件6.2.1.1 变量文件编写6.2.1.2 playbook编写6.2.1.3 运行测试 6.2.2 根据主机组使用变量6.2.2.1 groups_vars编写6.2.2.2 playbook编写6.…...

Flutter 中的 SliverCrossAxisGroup 小部件:全面指南
Flutter 中的 SliverCrossAxisGroup 小部件:全面指南 Flutter 是一个功能丰富的 UI 开发框架,它允许开发者使用 Dart 语言来构建高性能、美观的移动、Web 和桌面应用。在 Flutter 的丰富组件库中,SliverCrossAxisGroup 是一个较少被使用的组…...

开源还是闭源这是一个问题
天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…...

数据结构与算法笔记:基础篇 - 栈:如何实现浏览器的前进和后退功能?
概述 浏览器的前进、后退功能,你肯定很熟悉吧? 当依次访问完一串页面 a-b-c 之后,点击浏览器的后退按钮,就可以查看之前浏览过的页面 b 和 a。当后退到页面 a,点击前进按钮,就可以重新查看页面 b 和 c。但…...

【AIGC】大型语言模型在人工智能规划领域模型生成中的探索
大型语言模型在人工智能规划领域模型生成中的新应用 一、引言二、LLM在规划领域模型生成中的潜力三、实证分析:LLM在规划领域模型生成中的表现四、代码实例:LLM在规划领域模型生成中的应用五、结论与展望 一、引言 随着人工智能技术的迅猛发展࿰…...

从零开始学习Slam-旋转矩阵旋转向量四元组(二)
本文参考:计算机视觉life 仅作笔记用 书接上回,上回不清不楚的介绍了旋转矩阵&旋转向量和四元组 现在回顾一下重点: 本着绕谁谁不变的变则 假设绕z轴旋转θ,旋转矩阵为: 再回顾一下旋转向量的表示以及这个基本记不…...

基于Spring Security添加流控
基于Spring Security添加流控的过程: 步骤1: 添加依赖 确保项目中包含了Spring Security和Sentinel-Core的相关依赖。在Maven项目中,可以在pom.xml中添加如下依赖: <!-- Spring Security --> <dependency><groupId>org.…...

Python | Leetcode Python题解之第119题杨辉三角II
题目: 题解: class Solution:def getRow(self, rowIndex: int) -> List[int]:row [1, 1]if rowIndex < 1:return row[:rowIndex 1]elif rowIndex > 2:for i in range(rowIndex - 1):row [row[j] row[j 1] for j in range(i 1)]row.inser…...

物联网应用系统与网关
一. 传感器底板相关设计 1. 传感器设计 立创EDA传感器设计举例。 2. 传感器实物图 3. 传感器测试举例 测试激光测距传感器 二. 网关相关设计 1. LORA,NBIOT等设计 2. LORA,NBIOT等实物图 3. ZigBee测试 ZigBee测试 4. NBIoT测试 NBIoT自制模块的测试…...

系统稳定性概览
系统稳定性 系统稳定性,包括:监控、 告警、性能优化、慢sql、耗时接口等。 系统的稳定性的治理,可以围绕这几方面展开。 监控 Prometheus 监控并收集数据。监控 qps,tps, rt , cpu使用率,cpu load&#…...

Redis-Cluster模式基操篇
一、场景 1、搞一套6个主节点的Cluster集群 2、模拟数据正常读写 3、模拟单点故障 4、在不停服务的情况下将集群架构改为3主3从 二、环境规划 6台独立的服务器,端口18001~18006 192.169.14.121 192.169.14.122 192.169.14.123 192.169.14.124 192.169.14.125 192…...

Golang | Leetcode Golang题解之第113题路径总和II
题目: 题解: type pair struct {node *TreeNodeleft int }func pathSum(root *TreeNode, targetSum int) (ans [][]int) {if root nil {return}parent : map[*TreeNode]*TreeNode{}getPath : func(node *TreeNode) (path []int) {for ; node ! nil; no…...

云计算与 openstack
文章目录 一、 虚拟化二、云计算2.1 IT系统架构的发展2.2 云计算2.3 云计算的服务类型 三、Openstack3.1 OpenStack核心组件 一、 虚拟化 虚拟化使得在一台物理的服务器上可以跑多台虚拟机,虚拟机共享物理机的 CPU、内存、IO 硬件资源,但逻辑上虚拟机之…...

golang语言的gofly快速开发框架如何设置多样的主题说明
本节教大家如何用gofly快速开发框架后台内置设置参数,配置出合适项目的布局及样式、主题色,让你您的项目在交互上加分,也是能帮你在交付项目时更容易得到客户认可,你的软件使用客户他们一般都是不都技术的,所以当他们拿…...

lynis安全漏洞扫描工具
Lynis是一款Unix系统的安全审计以及加固工具,能够进行深层次的安全扫描,其目的是检测潜在的时间并对未来的系统加固提供建议。这款软件会扫描一般系统信息,脆弱软件包以及潜在的错误配置。 安装 方式1 git下载使用git clone https://github…...