C++ vector使用技巧:高效管理动态数据
hello,家人们,今天咱们来介绍vector相关的操作,好啦,废话不多讲,开干.
1:vector的介绍与使用
1.1:vector的介绍
1.2:vector的使用
1.2.1:vector的定义
1.2.1.1:代码1
1.2.1.2:代码2(C++11的构造方式)
1.2.2:vector iterator的使用
1.2.2.1:代码1
1.2.2.2:代码2(迭代器失效)
1.2.2.3:代码3
1.2.3:vector空间增长问题
1.2.3.1:代码1(测试resize)
1.2.3.2:代码2(测试reserve)
1.2.4:vector增删查改
1.2.4.1:代码1(测试assign与push_back)
1.2.4.2:代码2(测试insert)
1.2.4.3:代码3(测试erase)
1.2.4.3:代码4(测试find与swap)
1.2.5:迭代器失效
1.2.5.1:可能令迭代器失效的操作
1.2.5.1.1:resize
1.2.5.1.2:insert
1.2.5.1.3:reserve
1.2.5.1.4:push_back
编辑
1.2.5.1.5:assign
1.2.5.2:指定位置元素的删除操作(erase)
1.2.5.2.1:代码1
1.2.5.2.2:代码2
1.2.5.3:string的迭代器失效
1.2.5.3.1:代码1
1.2.5.3.2:代码2
1.3:动态二维数组
1:vector的介绍与使用
1.1:vector的介绍
1.vector是表示可变大小数组的序列容器。2.就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。6. 与其它动态序列容器相比(deque, list and forward_list),vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好
1.2:vector的使用
1.2.1:vector的定义

1.2.1.1:代码1
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>int main()
{//无参构造函数vector<int> v1;//构造并且初始化8个1vector<int> v2(8, 1);for (auto& element : v2)cout << element << " ";cout << endl;//使用v2对v3进行拷贝构造vector<int> v3(v2);for (auto& element : v3)cout << element <<" ";cout << endl;//使用迭代器进行构造vector<int> v4(v2.begin(), v2.end());for (auto& element : v4)cout << element << " ";cout << endl;return 0;
}


1.2.1.2:代码2(C++11的构造方式)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>int main()
{//C++11的构造方式,底层原理是存在两个指针,一个指向begin,一个指向endvector<int> v1 = { 1,2,3,4,5,6,7 };cout << typeid(v1).name() << endl;cout << sizeof(v1) << endl;//单参数的构造函数支持隐式类型转换,构造 + 拷贝构造 ----->优化成直接构造string str1 = "1234567";//隐式类型转换产生临时对象,临时对象具有常性const string& str2 = "123456";vector<string> v2;v2.push_back(str1);v2.push_back(str2);//匿名对象v2.push_back("1234");v2.push_back(string("34546"));for (auto& element : v2){cout << element << " ";}cout << "------------------------------" << endl;cout << endl;//直接构造vector<int> v3({ 1,2,3,4,5,6,7,8,9 });vector<int> v4{ 1,2,3,4,5,6,7,8,9 };for (size_t i = 0; i < v3.size(); i++)cout << v3[i] << " ";cout << endl;cout << "------------------------------" << endl;for (size_t i = 0; i < v4.size(); i++){cout << v4[i] << " ";}
}


1.2.2:vector iterator的使用
| iterator的使用 | 接口说明 |
| begin + end | begin用于获取第一个数据位置的iterator/const_iterator,end用于获取最后一个数据的下一个位置的iterator/const_iterator |
| rbegin + rend | rbegin用于获取最后一个数据位置的reverse_iterator,rend用于获第一个数据的前一个位置的reverse_iterator. |

1.2.2.1:代码1
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>void Test()
{vector<double> v1{ 1.2,1.3,1.4,1.5,1.6 };vector<double>::iterator it = v1.begin();vector<double>::iterator end = v1.end();while (it != end){cout << *it << " ";it++;}cout << endl;vector<int> v2({ 1,2,3,4,5,6,7 });vector<int>::reverse_iterator rit = v2.rbegin();vector<int>::reverse_iterator rend = v2.rend();while(rit != rend){cout << *rit << " ";rit++;}
} int main()
{Test();
}

1.2.2.2:代码2(迭代器失效)
在讲迭代器失效以前我们一起来看一段代码.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>void Test()
{vector<double> v1{ 1.2,1.3,1.4,1.5,1.6 };vector<double>::iterator it = v1.begin();v1.insert(it, 3.8);while (it != v1.end()){cout << *it << " ";it++;}
} int main()
{Test();
}

那么通过运行上面的代码,我们可以很清楚地发现,此代码是有问题的,但是编译是可以通过的,那么我们一起来调试看看.

通过调试我们可以清晰地发现,在insert以后,it就变成了随机值,这是因为insert了以后,it就失效了,就不可以再进行使用了,那么此时要么不再使用,要么就更新迭代器.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>void Test()
{vector<double> v1{ 1.2,1.3,1.4,1.5,1.6 };vector<double>::iterator it = v1.begin();it = v1.insert(it, 3.8);while (it != v1.end()){cout << *it << " ";it++;}
} int main()
{Test();
}


1.2.2.3:代码3
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>void Test()
{vector<int> v1({ 1,2,3,4,5 });vector<int>::iterator it = v1.begin();while (it != v1.end()){if(*it % 2 == 0){//erase也会导致迭代器失效,因此需要更新迭代器it = v1.erase(it);}cout << *it << " ";it++;}cout << endl;cout << "---------------------" << endl;vector<int> v2({ 1,2,3,4,5,6,7,8,9,10 });vector<int>::iterator it2 = v2.begin();while (it2 != v2.end()){if (*it2 % 2 != 0){//erase也会导致迭代器失效,因此需要更新迭代器it2 = v2.erase(it2);}cout << *it2 << " ";it2++;}
} int main()
{Test();
}

insert和erase都导致迭代器失效,若更新迭代器
- erase返回的是当前位置的下一个位置的迭代器.
- inerst返回的是插入的新元素的迭代器.
1.2.3:vector空间增长问题

1.2.3.1:代码1(测试resize)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>void Test_resize()
{vector<int> v1(20, 8);cout << "size:" << v1.size() << endl;;cout << "capacity:" << v1.capacity() << endl;for (auto & element : v1){cout << element << " ";}cout << endl;cout << "-----------------------" << endl;//1:小于size,则会进行删除,不改变capacityv1.resize(10);cout << "size:" << v1.size() << endl;;cout << "capacity:" << v1.capacity() << endl;for (auto& element : v1){cout << element << " ";}cout << endl;cout << "-----------------------" << endl;//大于size(capacity),进行扩容,后面的内容用0填充v1.resize(30);cout << "size:" << v1.size() << endl;;cout << "capacity:" << v1.capacity() << endl;for (auto& element : v1){cout << element << " ";}
}int main()
{Test_resize();return 0;
}

1.2.3.2:代码2(测试reserve)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>void Test_reserve()
{vector<int> v1(20, 8);cout << "size:" << v1.size() << endl;;cout << "capacity:" << v1.capacity() << endl;for (auto & element : v1){cout << element << " ";}cout << endl;cout << "-----------------------" << endl;//reserve无法进行缩容v1.reserve(10);cout << "size:" << v1.size() << endl;;cout << "capacity:" << v1.capacity() << endl;cout << "-----------------------" << endl;//大于capacity时则会进行扩容,且后面的内容不会被自动填充v1.reserve(40);cout << "size:" << v1.size() << endl;;cout << "capacity:" << v1.capacity() << endl;for (auto& element : v1){cout << element << " ";}cout << endl;cout << "-----------------------" << endl;//缩容至与size相同v1.shrink_to_fit();cout << "size:" << v1.size() << endl;;cout << "capacity:" << v1.capacity() << endl;
}int main()
{Test_reserve();return 0;
}

1.2.3.3:代码3(测试增长)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <vector>void test_expansion_mechansim()
{vector<int> v;size_t sz = v.capacity();for (size_t i = 0; i < 100; i++){v.push_back(i);if(sz != v.capacity()){sz = v.capacity();cout << "making a growing:>" << sz << endl;}}
}int main()
{test_expansion_mechansim();return 0;
}

- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的.不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
- resize在开空间的同时还会进行初始化,影响size.
1.2.4:vector增删查改

1.2.4.1:代码1(测试assign与push_back)

#include <iostream>
using namespace std;
#include <vector>
void test_assignAndpush_back()
{vector<int> v1;vector<int> v2({ 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 });for (size_t i = 0; i < 10; i++){v1.push_back(i);}cout << "size:>" << v1.size() << endl;cout << "capacity:>" << v1.capacity() << endl;for (auto & element : v1){cout << element << " ";}cout << endl;cout << "--------------------" << endl;//对v1重新进行赋值v1.assign(v2.begin(), v2.end());cout << "size:>" << v1.size() << endl;cout << "capacity:>" << v1.capacity() << endl;for (auto& element : v1){cout << element << " ";}
}int main()
{test_assignAndpush_back();
}

1.2.4.2:代码2(测试insert)

#include <iostream>
using namespace std;
#include <vector>void TestInsert()
{string s1("hello");vector <int> v1({ 1,2,3,4,5 });for (auto & element : v1){cout << element << " ";}cout << endl;cout << "---------------------------" << endl;//vector中的insert参数为迭代器//头插v1.insert(v1.begin(), 2);v1.insert(v1.end(), 8);//插入一迭代器区间v1.insert(v1.end(), s1.begin(), s1.end());for (auto& element : v1){cout << element << " ";}cout << endl;cout << "---------------------------" << endl;//尾插8个1v1.insert(v1.end(), 8, 1);for (auto& element : v1){cout << element << " ";}
}
int main()
{TestInsert();
}
1.2.4.3:代码3(测试erase)
#include <iostream>
using namespace std;
#include <vector>
void TestErase()
{string s1("hello");vector <int> v1({ 1,2,3,4,5 });for (auto & element : v1){cout << element << " ";}cout << endl;cout << "---------------------------" << endl;vector<int>::iterator it = v1.begin();it = v1.erase(it);while(it != v1.end()){cout << *it << " ";it++;}}
int main()
{TestErase();
}
1.2.4.3:代码4(测试find与swap)


#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>void TestFindAndSwap()
{vector <int> v1({ 1,2,3,4,5,6,7 });vector <int> v2({ 7,6,5,4,3,2,1 });cout << "交换前" << endl;for (auto & element : v1){cout << element << " ";}cout << endl;for (auto & element : v2){cout << element << " ";}cout << endl;v1.swap(v2);cout << "交换后" << endl;for (auto& element : v1){cout << element << " ";}cout << endl;for (auto& element : v2){cout << element << " ";}cout << endl;cout << "--------------------------" << endl;//通过传入迭代器进行查找,算法库的find返回的是迭代器auto it = find(v1.begin(), v1.end(), 5);cout << typeid(it).name() << endl;while (it != v1.end()){cout << *it << " ";it++;}
}int main()
{TestFindAndSwap();
}

1.2.5:迭代器失效
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了 封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的 空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器, 程序可能会崩溃).
1.2.5.1:可能令迭代器失效的操作
会引起底层空间改变的操作,都有可能使迭代器失效. 例如:resize,reserve,insert,assign,push_back等.
1.2.5.1.1:resize
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1,2,3,4,5,6 };vector<int>::iterator it = v.begin();// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容v.resize(100, 8);while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

1.2.5.1.2:insert
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1,2,3,4,5,6 };vector<int>::iterator it = v.begin();//插入元素期间,可能会引起扩容,而导致原空间被释放v.insert(it, 8);while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

1.2.5.1.3:reserve
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1,2,3,4,5,6 };vector<int>::iterator it = v.begin();//eserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变v.reserve(10);while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

1.2.5.1.4:push_back
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1,2,3,4,5,6 };vector<int>::iterator it = v.begin();//插入元素期间,可能会引起扩容,而导致原空间被释放v.push_back(10);while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}
1.2.5.1.5:assign
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1,2,3,4,5,6 };vector<int>::iterator it = v.begin();// 给vector重新赋值,可能会引起底层容量改变v.assign(100,8);while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

上面的五段代码都出现了报错,原因是,上述的操作,都有可能导致vector扩容,也就是说vector底层原理旧空间被释放掉,而在打印时,it还使用的是释放之前的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃.
解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可。
1.2.5.2:指定位置元素的删除操作(erase)
#include <iostream>
using namespace std;
#include <vector>
int main()
{int a[] = { 1, 2, 3, 4 };vector<int> v(a,a + sizeof(a) / sizeof(int));// 使用find查找3所在位置的iteratorvector<int>::iterator pos = find(v.begin(), v.end(), 3);// 删除pos位置的数据,导致pos迭代器失效。v.erase(pos);cout << *pos << endl; // 此处会导致非法访问return 0;
}

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是 没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。
我们来看两段代码,这两段代码是删除vector中所有的偶数.
1.2.5.2.1:代码1
#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)v.erase(it);++it;}return 0;
}

1.2.5.2.2:代码2
#include <iostream>
using namespace std;
#include <vector>int main()
{vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)it = v.erase(it);else++it;}return 0;
}

为什么代码1会发生报错呢
1.2.5.3:string的迭代器失效
与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效.
1.2.5.3.1:代码1
#include <iostream>
using namespace std;
#include <vector>
#include <string>int main()
{string s("hello");auto it = s.begin();//resize到20会string会进行扩容// 扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了// 后序打印时,再访问it指向的空间程序就会崩溃s.resize(20, '!');while (it != s.end()){cout << *it;++it;}cout << endl;
}
1.2.5.3.2:代码2
#include <iostream>
using namespace std;
#include <vector>
#include <string>int main()
{string s("hello");auto it = s.begin();while (it != s.end()){//erase(it)之后,it位置的迭代器就失效了s.erase(it); ++it;}
}

1.3:动态二维数组
我们通过杨辉三角的代码来学习和了解动态二维数组
#include <iostream>
using namespace std;
#include <vector>
#include <string>void Test(size_t n)
{vector<vector<int>> vv(n);// 将二维数组每一行中的vecotr<int>中的元素全部设置为1for (size_t i = 0; i < n; ++i)vv[i].resize(i + 1, 1);// 给杨慧三角出第一列和对角线的所有元素赋值for (int i = 2; i < n; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}for (size_t i = 0; i < vv.size(); i++){for (size_t j = 0; j < vv[i].size(); j++){/*cout << vv[i][j] << " ";*/cout << vv.operator[](i).operator[](j) << " ";}cout << endl;}
}int main()
{Test(5);
}

vector<vector<int>> vv(n); 构造一个vv动态二维数组,vv中总共有n个元素,每个元素都是vector类型的,每行没有包含任何元素,如果n为5时如下图所示.

vv中元素填充完成之后,如下图所示;

好啦,uu们,vector的这部分滴详细内容博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力,同时也欢迎大家来指正博主滴错误~







