本文已收录至《C++语言和高级数据结构》专栏!
作者:ARMCSKGT
STL之vector的使用
- 前言
- 正文
- 默认成员函数
- 普通构造
- 拷贝构造
- 析构函数
- 赋值重载
- 迭代器
- 正向迭代器
- 反向迭代器
- const迭代器
- 容量类
- 空间容量查询
- 空间容量操作
- 扩容操作
- 元素数量操作
- 缩容操作
- 数据访问
- 下标访问
- 头尾元素访问
- 获取原生指针
- 元素插入删除操作
- 尾插尾删
- 任意位置插入删除
- 任意位置插入
- 任意位置删除
- 其他操作函数
- 交换函数
- 清空函数
- 最后
前言
vector是可变大小的数组序列容器,一般也叫向量;底层原理是顺序表,但是vector是泛型容器,可以支持int,double甚至自定义类型的存储,在平时应用非常频繁且广阔,vector在很多场景下可以提高我们的开发效率,所以学习vector这一利器的使用是必须的!
正文
本文将介绍关于vector的常用接口,依据C++ vector官方文档!
首先在使用vector前,需要声明头文件 < vector > 且声明命名空间std!
默认成员函数
vector有三种常规构造,但是在C++11中又增加了一种好用的构造方法!
普通构造
vector支持以下构造:
- 默认构造(生成一个空容器对象,没有任何数据)
- 构造n个元素为val的对象
- 迭代器区间构造
- 列表初始化构造
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v1; //默认构造 vector<int> v2(5, 10); //构造5个元素为10的对象 int arr[] = { 1,2,3,4,5 }; vector<int> v3(arr, arr + (sizeof(arr) / sizeof(arr[0]))); //迭代器区间构造 vector<int> v4 = { 6,7,8,9,10 }; //列表初始化构造 return 0; }
拷贝构造
我们在定义对象时,可以让新对象拷贝一个已存在的对象从而完成初始化!
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v1(10, 1); //两种拷贝构造方式 vector<int> v2(v1); vector<int> v3 = v1; return 0; }
析构函数
析构函数在vector对象生命周期结束时自动调用,我们使用阶段不需要关心!
赋值重载
vector支持赋值拷贝一个对象(前提是该对象已经存在而并非构造),对与C++11支持通过列表初始化赋值!
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v1 = {1,2,3}; vector<int> v2; v2 = v1; //赋值空对象 vector<int> v3 = { 4,5,6 }; v3 = {10,11,12}; //通过列表赋值已存在数据的对象 return 0; }
可以发现当对象中存在数据时,对其赋值会情况原有数据,且也可以通过列表对vector对象进行初始化!
迭代器
- vector支持两种迭代器正向和反向(C++11),这两种迭代器都有const版本!
- 迭代器是将指针进行封装,构造成为迭代器对象,自己控制可操作行为!
- vector的迭代器是随机迭代器,在支持++和- -的同时可以 ± 任意一个整数去往任意一个元素,所以可以使用sort排序!
正向迭代器
正向迭代器的遍历,默认从头begin()到尾end()!
begin指向vector中第一个元素的位置,end指向最后一个元素的下一个位置!
在判断结束条件时不能使用 > 和 < 等,因为迭代器封装后不知道是否为连续的空间,判断是否等于end即可!#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = { 1,2,3,4,5 }; vector<int>::iterator it = v.begin(); //定义vector<int>迭代器类型 //auto it = v.begin(); //觉得麻烦也可以使用auto while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; return 0; }
反向迭代器
顾名思义,与正向迭代器相反!
rend指向vector中第一个元素的前一个位置,rbegin指向最后一个元素的位置!
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = { 1,2,3,4,5 }; vector<int>::reverse_iterator rit = v.rbegin(); while (rit != v.rend()) { cout << *rit << " "; ++rit; } cout << endl; return 0; }
反向迭代器的++是从后向前走,与正向迭代器的行为相反!
const迭代器
在某些场景下例如:
//vector对象const引用传参,在函数中声明的迭代器是const迭代器 void Print(const vector<int>& v) {} //范围for使用const修饰元素时底层调用const迭代器 for (const auto& x : v) {}
平时我们很少会用到const迭代器,不过也可以主动定义!
vector<int>::const_iterator //const正向迭代器 vector<int>::const_reverse_iterator ///const反向迭代器
const迭代器无法修改迭代器指向的元素,但是可以更改迭代器的指向!
容量类
关于容量查询和修改的操作:
空间容量查询
vector支持以下容量查询操作:
- 查询元素数量size
- 查询当前容量capacity
- 查询当前对象是否为空(不存在任何元素)empty
- 查询对象可容纳最大元素数(预计,并非准确)max_size
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = { 1,2,3 }; cout << "size:" << v.size() << endl; cout << "capacity:" << v.capacity() << endl; cout << "empty:" << (bool)v.empty() << endl; cout << "max_size:" << v.max_size() << endl; return 0; }
关于max_size会因为实例类型不同,平台不同等因素发生改变!
空间容量操作
扩容操作
vector支持使用reserve函数手动扩容,在某些元素数已知的场景下可以提前开辟空间提高性能减少内存碎片!
首先,我们了解一下vector自己的扩容机制:
VS平台(PJ版本)
g++平台(SGI版本)
通过观察我们发现,VS下扩容是呈1.5倍,g++下是呈2倍扩容;这两种扩容策略各有优略,当数据量比较大时,VS频繁扩容导致性能下降;当数据量中等时,g++扩容冗余过多浪费空间!
针对这种情况,我们可以使用reserve提前扩容,避免这两种情况!vector<int> v; v.reverse(n); //提前将容量扩大到n
示例:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v; v.reserve(100); cout << "capacity:" << v.capacity() << endl; v.reserve(50); //reserve不支持缩容 cout << "capacity:" << v.capacity() << endl; return 0; }
元素数量操作
vector支持使用resize函数自定义元素数量大小,如果我们设置的数量大于当前容量就会触发扩容!
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1,2,3}; cout << "size:" << v.size() << endl; cout << "capacity:" << v.capacity() << endl; v.resize(50); cout << "size:" << v.size() << endl; cout << "capacity:" << v.capacity() << endl; v.resize(10); cout << "size:" << v.size() << endl; cout << "capacity:" << v.capacity() << endl; return 0; }
很显然,resize改变的是size的大小,无论怎样,也不会缩容!
如果resize(n)中,n大于当前的size,则新增长的空间会使用实例化类型的默认构造去初始化!
为了兼容自定义类型,内置类型也支持像定义对象一样初始化和构造,就是通过int()去构造,就像匿名对象一样,只不过int()相当于0!int main() { int a(1); //对象初始化方式 int b = int(2); //匿名对象拷贝构造方式 cout << a << endl; cout << b << endl; return 0; }
当然resize也支持指定值进行初始化增长的空间#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1,2,3}; v.resize(10,668); //指定初始化新元素空间为668 return 0; }
相当于reserve,resize既能扩容,还能完成初始化,两者各有应用场景!
缩容操作
C++11为容器提供缩容操作,缩容函数shrink_to_fit在平时用的非常少,因为对性能的消耗巨大!
vector<int> v(100,1); v.shrink_to_fit(10); //将capacity缩小为10
其底层原理大概是先开辟指定的缩小空间,然后从头开始截取拷贝数据到新空间,多于的数据会被截断删除!
数据访问
vector是顺序表,其范围方式有很多种,除了通用的迭代器范围,还有以下方式可以访问数据:
下标访问
vector底层是顺序表,那必然支持下标随机访问!
下标访问的方式有两种,在原生通过[ ]访问的基础上,库中还有一个at()也可以通过下标访问,两者的区别在于:
- 通过 [ ] 访问,如果下标越界则会报错
- 通过 at() 访问如果下标越界会抛异常
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1,2,3}; for (int i = 0; i < v.size(); ++i) { printf("v[%d]=%d,v.at(%d)=%d\n",i, v[i], i, v.at(i)); } return 0; }
头尾元素访问
vector支持取头尾元素的引用或const引用,平时用的比较少,了解即可!
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = { 1,2,3 }; cout << v.front() << endl; cout << v.back() << endl; return 0; }
获取原生指针
vector支持通过data函数获取存储元素的直接原生空间!
通过这个指针像访问数组一样,使用下标访问或*解引用访问和修改每一个空间的元素!
这个平时用的极少,且非常危险,不建议使用!#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = { 1,2,3 }; int* p = v.data(); p[1] = 4; for (const auto& x : v) { cout << x << endl; } return 0; }
元素插入删除操作
vector有许多修改元素的操作函数,但我们只需要了解最常用的一部分即可!
尾插尾删
- push_back(val)函数可以将val插入到当前vector容器尾部
- pop_back()函数可以从当前容器尾部删除一个元素
#include <iostream> #include <vector> using namespace std; void Print(const vector<int>& v) { for (const auto& x : v) { cout << x << " "; } cout << endl; } int main() { vector<int> v = { 4,5,6 }; v.pop_back(); Print(v); v.push_back(668); Print(v); return 0; }
任意位置插入删除
任意位置插入
insert函数有三种形式,支持:
- 在当前迭代器位置插入一个val元素(相当于在一个元素前插入一个元素)并返回新插入元素的迭代器
– 这个返回值至关重要!当我们插入元素后,可能触发扩容,那么原迭代器就会失效(迭代器失效),此时insert会返回新的迭代器指向插入元素,以方便后续操作!- 在当前迭代器位置插入n个元素val元素,没有返回值
- 在当前迭代器位置插入任意一个容器的迭代器区间,没有返回值
– 对于这两个函数,在插入后必须手动更新迭代器,否则编译器会报错!#include <iostream> #include <vector> using namespace std; void Print(const vector<int>& v) { for (const auto& x : v) { cout << x << " "; } cout << endl; } int main() { vector<int> v = { 4,5,6 }; v.insert(v.begin(), 1); //头插1 Print(v); v.insert(v.begin(), 2); //头插1 Print(v); v.insert(v.begin() + 2, 2, 668); //在2号元素前处插入2个668 Print(v); int arr[] = { 9,6,3 }; v.insert(v.begin() + 1, arr, arr + (sizeof(arr) / sizeof(arr[0]))); //在1号元素前插入一个数组 Print(v); return 0; }
任意位置删除
对于erase任意位置删除有两种形式:
- 删除当前迭代器指向的元素,并返回该元素后的元素迭代器
- 删除一个该容器迭代器区间,返回该区间后一个元素的迭代器
–删除操作必然会导致迭代器失效,所以每次删除操作都会更新迭代器!
#include <iostream> #include <vector> using namespace std; void Print(const vector<int>& v) { for (const auto& x : v) { cout << x << " "; } cout << endl; } int main() { vector<int> v = {1,2,3,4,5,6,7,8,9,10}; Print(v); v.erase(v.begin()); //头删 Print(v); v.erase(v.begin() + 2, v.begin() + 7); //删除2-7号元素 Print(v); return 0; }
删除对于性能的消耗巨大,因为涉及挪动数据,平时使用也不多!
其他操作函数
交换函数
vector有自带的交换函数,其底层是交换双方所管理的空间!
#include <iostream> #include <vector> using namespace std; void Print(const vector<int>& v) { for (const auto& x : v) { cout << x << " "; } cout << endl; } int main() { vector<int> v1 = { 1,2,3,4,5 }; vector<int> v2 = { 6,7,8,9,10 }; v1.swap(v2); Print(v1); Print(v2); return 0; }
当然,vector也支持库中统一的swap函数,但是需要声明算法头文件algorithm才能使用!#include <iostream> #include <vector> #include <algorithm> //算法头文件 using namespace std; int main() { vector<int> v1 = { 1,2,3,4,5 }; vector<int> v2 = { 6,7,8,9,10 }; swap(v1,v2); return 0; }
清空函数
clear函数可以将当前vector对象的元素全部清空,但是不会缩容也不会销毁对象!
#include <iostream> #include <vector> using namespace std; void Print(const vector<int>& v) { for (const auto& x : v) { cout << x << " "; } cout << endl; } int main() { vector<int> v = { 1,2,3,4,5 }; cout << "size:" << v.size() << endl; cout << "capacity:" << v.capacity() << endl; v.clear(); cout << "size:" << v.size() << endl; cout << "capacity:" << v.capacity() << endl; return 0; }
最后
vector的使用介绍到这里就结束了,vector是一款强大的泛型顺序表,他弥补了数组无法动态扩容的缺点,以及泛型思想使得vector可以实例化成任何类型的顺序表,但只其实现只有一份代码;后面我会为大家揭开vector底层的秘密,为大家介绍vector的底层实现!
本次 <C++ vector使用> 就先介绍到这里啦,希望能够尽可能帮助到大家。
如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!
🌟其他文章阅读推荐🌟
C++ <STL之string的使用> -CSDN博客
C++ <STL之string模拟实现> -CSDN博客
C++ <STL简介> -CSDN博客
C++ <模板> -CSDN博客
C++ <内存管理> -CSDN博客
🌹欢迎读者多多浏览多多支持!🌹