c++ STL---vector使用
本文对STL---中的vector的使用是参考网站 vector - C Reference 的内容该网站是外文的哦大家可以尝试打开看看更深刻的理解。本文是抽取其中常使用然后加上自己的是理解哦。前言vector其实是顺序表类模板的封装啊 数据结构那简单来说就是数组的封装。if 你想懂一个人你先得了解它的心啊同样的你想了解vetor你得从它的成员变量和成员函数开始了解这里的c网站做了一个分类。成员变量我们后面模拟实现 如下成员变量成员函数构造迭代器容量相关修改一.构造这里涉及到了空间配置器的我们都忽略本文不讲 就是上面的allocator1.无参构造2. vector( size大小val 初始化的值)你细心可以发现这里使用的值是value_type()这是因为你可能啊写了一个vector内部嵌套了一个自定义类型比如vector嵌套vector这就很正常啊 。3. 用迭代器进行构造赋值4. 拷贝构造vectorint v4(str.begin(), str.end());vectorint v5(v4);自行可以用下面这个函数进行学习相关构造。void test_vector1() { // 构造 vectorint v1;// 无参构造 vectorint v2(10, 0);// size_t size T T() 一个是个数 一个是初始值 vectorint v3(v2.begin(), v2.end());// 用迭代器去初始化 string str(hello world); vectorint v4(str.begin(), str.end()); vectorint v5(v4); // 使用下标访问内容 for (size_t i 0; i v3.size(); i) { cout v3[i] ; } cout endl; // 创建一个迭代器 //vectorint::iterator it v4.begin(); auto it v4.begin(); while (it ! v4.end()) { cout *it ; it; } cout endl; for (auto e : v5) { cout e ; } cout endl; }二.迭代器2.1 迭代器-- begin和end我们这里的迭代器使用主要是正向迭代器其实也没有很多内容需要说了后面我们模拟实现的话会有一些地方比如考虑到迭代器失效的原因啊。我们主要用迭代器进行一个变量访问得到vector中的值。这里的vector正向的主要介绍 verctor中的 end() 返回迭代器指向的末尾元素的下一个内容注意这里是一个内容哦而不是末尾元素。所以遍历的时候的遍历条件参考下面代码。 至于begin当然指向的是第一个元素啦。对于vector中你可以认为这里的iterator底层实现其实是用的指针。这样你就会更深刻的理解了2.2 获取迭代器 以及遍历获取迭代器从一个实例化中的vector中获取一个迭代器参考如下代码主要是注意写好类型吧vectorint v1(10,2); vectorint::iterator it v1.begin(); auto it v1.begin();// 有时候感觉变量类型有点长 可以直接用auto // 遍历 while (it ! v1.end()) { cout *it endl; // 你看你理解它是一个指针的话 那 *it访问内容 // 下一个元素遍历就要进行it it; }三.容量3. 1 size()size----》返回当前容器元素个数注意返回值是无符号整形 unsigned integral type。3.2 max_size()个人认为不喜勿喷其实max_size()没什么用至少对我们日常使用来说但是它的内容值得我们去了解。max_size()主要是访问当前vector能达到理论大小的个数你看我这里写了一个v1 然后调用它的max_size() 这么大对于它的解释这么说因为它实际大小其实受到系统和库的约束等等所以这个值是不一定精准的。哈哈哈哈。为什么要实例化模板还要用对象来调用它其实我有一个疑问的但是刚刚问了ai解决了 我想的是一个vector的容器个数大小那顶多跟他实例化模板的类型有关就比如vectorint 跟vectordouble 他们大小不一样对应的个数肯定不同了。 但是不然不知道你怎么想就是因为这个max_size的计算还受到当前容器的指针啊以及内存啊等等都受到约束。3.3 resize()若 n 小于当前容器大小则内容缩减至前 n 个元素删除超出部分并销毁它们。若 n 大于当前容器大小则通过在末尾插入所需元素扩展内容至 n 个。若指定 val新元素初始化为 val 的副本否则执行值初始化。若 n 同时大于当前容器容量将自动重新分配存储空间。注意该函数通过插入或删除元素直接修改容器的实际内容代码学习参考下面我这里搞了一个小聪明转牛角尖但是我们深挖我们的底层跟定义说的很清楚了如果啊你nsize我这个val几乎无用我只会保留前n个vectorint v1; for (int i 0;i 10 ;i) { v1.push_back(i*10); } // resize(n) 比他小 我们就 缩短到前n个 // m大于当前 size 就 插入 如果指定了 val从末尾插入n个val // 大于容器大小了 会基于上一步 并且 重新指定内存 for (auto e : v1) { cout e ; } cout endl; v1.resize(2);// 变小相当于删除 for (auto e : v1) { cout e ; } cout endl; v1.resize(15);// 变小相当于删除 不指定 对插入的值都初始化0 否则 初始化为val for (auto e : v1) { cout e ; } // 我想搞鬼 你看看这个 /*v1.resize(2,2); for (auto e : v1) { cout e ; }*/3.4 reserve() 与resize() 一对欢喜冤家啊reserve 本义是保存 和更新的意思它在这里做到底是上面目的呢很简单啊就是更新它的内存。它主要跟容量有关正常情况不会跟resize一样去影响size比容量大的话它就主要改变容量大小然后重分配内存。若 n 大于当前向量容量该函数会使容器重新分配存储空间将容量增加至 n或更大。其他情况下函数调用不会导致重新分配vector容量不受影响。此函数对vector大小无影响也不会修改其元素。若n小于当前size()reserve不会触发任何操作因为条件if (n capacity())不成立。这符合标准库行为reserve仅在请求容量大于当前capacity()时重新分配内存从不缩减容量。若。区分reserve 和resize()1. resize(n) 针对 sizen 会改变容器内size的个数甚至是大小 通过使用插入删除真正的改变其中的内容 特别的区别就是 当n大于size的时候相当于插入啊它可以根据你补充的val去初始化值。 说白了ncapacity 会进行插入值 且重分配内存。 nsize了 会调用easer对容量进行删减size但是capacity不会变保持缩减只缩小size的原则。2.resever 它其实初衷是为了更新内存扩大内存从来不缩减内存。3.5 shrink_to_fit 注意这是 11以后的版本其实它是为了改变capacity来的还记得我们的resize和reserve吗 resize缩小的时候不改变capcity啊 reserve只用于扩容啊。 那谁来调节容量呢 所以 11以后就搞了一个这个。但是它是请求容器改变容量大小哈然后适应它的size。// vector::shrink_to_fit #include iostream #include vector int main () { std::vectorint myvector (100); std::cout 1. capacity of myvector: myvector.capacity() \n; myvector.resize(10); std::cout 2. capacity of myvector: myvector.capacity() \n; myvector.shrink_to_fit(); std::cout 3. capacity of myvector: myvector.capacity() \n; return 0; }3.6 empty()返回当前容器元素是不是为空 bool类型。四. 元素获取4.1 operator[ ]作用通过下标返回指定元素的引用这里其实用起来就让你感觉跟数组一样非常方便哈 值得一提的是它跟std::vector at 的功能非常类似也是通过下标来访问内容但是呢at更好吧因为它会有边界检测并且抛异常检测防止你运行时发生越界从而照成段错误使得访问越界的问题。4.2 at()使用举个例子吧。注意 段错误是程序运行时操作系统层面的错误属于崩溃级问题会直接终止程序并可能丢失数据。而边界检查抛异常如out_of_range是程序可控的错误处理机制允许开发者通过try-catch捕获异常并执行恢复逻辑如提示用户、回滚操作等相比段错误更具鲁棒性能让程序在异常情况下保持可控而非直接崩溃。用 [] 访问会报段错误at对应的情况这就是抛异常的好处啊。4.3 front与back返回容器中第一个元素的引用。翻译 它不向begin()它返回的是一个迭代器啊这里的迭代器我们底层可以认为它其实就是一个指针啊 这个函数是返回一个之间的引用注意得到了引用我们可以修改它对应的内容。back与它类似。参考学习代码如下// vector::front #include iostream #include vector int main () { std::vectorint myvector; myvector.push_back(78); myvector.push_back(16); // now front equals 78, and back 16 myvector.front() - myvector.back(); std::cout myvector.front() is now myvector.front() \n; return 0; }4.4 data()返回一个指向内存数组的指针这个内存数组是vector存放元素的。为什么呢 因为元素在vector中必然顺序存储的所以我们可以通过指针在利用偏移量访问在数组中任意的一个元素。五 修改5.1 assign1. assign在调用之前会把之前容器中的元素全部销毁掉并且会分配新的内容以及根据实际的size 更改当前的容量。2. 当且仅当新的的vector的size 大于原来的capacity才会重新分配内存。使用void myvector_modify() { vectorint v1(5, 1); // assign(n,val()) 一个是 n个 和 val值 v1.assign(10,99); for (auto e : v1) { cout e ; } cout endl; vectorint v2; // 用迭代器去 重分配其中的内容 v2.assign(v1.begin()1,v1.end()); for (auto e : v2) { cout e ; } }5.2 push_back() 和 pop_back()尾插和尾删使用很简单的。5.3 insert使用方式1. 指定位置插入 元素 posval2. 指定位置开始 插入n个val元素 possizeval3. 范围for插入 posfirstlast大意1.vector是通过在指定pos位置插入进行拓展的有效的增加了容器的size。2. 当且仅当这个新的vector的size大于当前容器的capacity会进行内存的重分配3.因为vector的底层是数组插入位置出来插入末尾元素效率很高外插入其他位置比如中间位置的效率是很低的同样的操作比起其他的容器比如list链表因为它需要把pos后面的元素一个一个进行移动啊。使用实例代码vectorint v1; v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(5); vectorint v2; v2.push_back(10); v2.push_back(9); v2.push_back(10); v2.push_back(10); v2.push_back(10); vectorint::iterator it v2.begin(); // 指定位置插入一个值 v2.insert(it2,999); // 指定位置插入 而且 插入了3个 val值 v2.insert(it2,3,999); for (auto e : v2) { cout e ; } // 迭代器插入 cout endl; v2.insert(v2.begin(),v1.begin()2,v1.end()); for (auto e : v2) { cout e ; } //从这个begin位置开始插入 也就是从头部开始5.4 eraser1. 移除指定位置的元素2. 通过迭代器移除一个range范围的元素3. 这可以有效的删除容器的指定元素减小size 同时呢销毁指定位置的元素不仅仅是被覆盖还会调用它的析构后期底层实现我们说一下4. 再次提及效率问题底层是数组嘛所以删除中间位置的自然效率挺低的。5.5 clear1. 它这么说 这里的clear会把vector的全部元素移除掉让size变成0但是capacity是不一定以及内存的重分配也不一定发生。所以它建议用下面那个访问来代替写了一个匿名对象 vectorT() 然后把他跟原本的vector互换这样互换以后新的内存指向给我们的vector原本的内存被匿名对象结束后调用它的析构被销毁。这个想法是很经典的哈 后面模拟底层实现的时候我们会提及的哦。5.6 swap()将当前容器的内容与另一个同类型vector对象x的内容进行交换两者的大小可以不同。调用此成员函数后当前容器中的元素变为调用前x中的元素而x中的元素则变为调用前当前容器中的元素。所有迭代器、引用和指针对于被交换的对象仍然有效。注意存在一个同名的非成员函数swap其对该算法进行了重载优化后的行为与本成员函数一致。显然这是把他们vector指向内存的指针进行了互换 参考如下代码int main() { std::vectorint foo(3, 100); // three ints with a value of 100 std::vectorint bar(5, 200); // five ints with a value of 200 foo.swap(bar); std::cout foo contains:; for (unsigned i 0; i foo.size(); i) std::cout foo[i]; std::cout \n; std::cout bar contains:; for (unsigned i 0; i bar.size(); i) std::cout bar[i]; std::cout \n; return 0; }六 关于插入 capacity的变化 以及eraser涉及到的迭代器失效问题6.1 capacity我这是 vs2020编译器下的情况这个扩容的情况 前面几个都是很小的扩容的哈导论后面差不多一倍作用不同的编译器下这个扩容是不一样的。gcc就是 2倍扩容的。6.2 eraser涉及到的迭代器失效问题这是 一个很经典的问题删除指定元素 val 在std库中的 vector的迭代器它会帮你 对应一律用过的iterator它默认为失效了不准你再用哦。 后期我们实现我们也会发现其中更深的逻辑。void eraser_spevar() { int key 2; vectorint v1; v1.push_back(2); v1.push_back(2); v1.push_back(1); v1.push_back(3); v1.push_back(3); v1.push_back(3); v1.push_back(3); v1.push_back(2); v1.push_back(123); v1.push_back(14); v1.push_back(15); auto it v1.begin(); while (it ! v1.end()) { if (*it key) v1.erase(it); it; } }