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在规划领域模型生成中的应用五、结论与展望 一、引言 随着人工智能技术的迅猛发展࿰…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
