目录
概述
vector 是一种C++标准模板库STL中定义的一种序列容器,它允许你在运行时动态地插入和删除元素。
vector 是基于数组的数据结构,但它可以自动管理内存,这意味着你可以添加任意多的元素在其中,并且你不需要手动分配和释放内存。
它同时具有动态类型和数组的特性:
动态大小:内存空间可以根据需要自动增长和缩小。
连续存储:元素在内存中是连续存储的,这使得访问元素非常快速。
可迭代:可以被迭代,你可以使用循环来访问它的元素。
元素容器:可以存储任何类型的元素,包括基本类型、对象、指针等。
接下来,我们介绍vector类型定义的成员函数。
*注意*:本文不涉及allocator空间配置器和C++23新增的range类函数。
创建销毁
STL库提供了以下方式创建一个vector。
以T表示任意类型。
无参构造 | vector<T>vec(); | 生成一个存储T类型的动态数组vector。 |
提供初始空间的构造 | vector<T>vec(int num); | 生成初始长度为num的vector,初值为0。 |
提供初始长度和初值的构造 | vector<T>vec(int num,T target); | 生成初始长度为num的vector,初值为target。 |
提供源数据地址的构造 | vector<T>vec(T* begin,T* end) | 从定长数组(初末指针)或另一容器(初末迭代器)中获取数据进行构造。 需要注意的是,迭代器指向的容器不一定是另一个vector,它只需是一个提供随机访问迭代器(即支持it++、it--)的容器即可。 |
拷贝构造 | vector<T>vec(const vector& another); | 将another整体赋值给新vector。 |
移动构造 (C++11) | vector<T>vec(vector&& another); | 窃取另一个vector容器,即直接获取它维护的底层指针地址,再将它的指针置空。 |
初始化列表构造(C++11) | vector<T>vec(std::initializer_list<T> ini_list); | 为了让vector模仿int a[]={1,2,3},C++11提供了这种构造。initializer_list(初始化列表)是由C++11提供的类型,使用{}进行初始化。 vector<int>vec={1,2,3};隐式构造了一张无名initializer_list,然后它被作为参数传入vec的初始化构造中 |
析构函数 | ~vector(); | 无须手动调用。程序自动在代码块结束时调用,清理掉vector。 |
vector<int>a;//{}无参构造
vector<int>b(3); //{0,0,0,}//提供初始长度
vector<int>c(3,5);//{5,5,5,}//提供初始长度和初始值
int x[] = {1,2,3,4,5,6};
vector<int>d(x,x+6);//{1,2,3,4,5,6,}//提供源地址
vector<int>e(d);//{1,2,3,4,5,6,}//拷贝构造
vector<int>f={9,8,7,6,5};//{9,8,7,6,5}//初始化列表构造
赋值重构
在必要时我们会对vector进行重新初始化。
重载拷贝赋值= | vector& (const vector& another); | 作用同拷贝构造。不仅可以在初始构造时使用。返回自身。 |
重载移动赋值= | vector& (vector&& another); | 作用同移动构造。不仅可以在初始构造时使用。返回自身。 |
再分配 | void assign(); 参数列表与提供初始长度和初值的构造;提供源数据地址的构造;初始化列表构造相同。 | 功能同对应构造函数。仅能在已存在的vector上使用。 |
数据访问
vector同时提供了类形式的函数访问和数组形式的运算符访问。
重载[]号访问 | operator[](int pos); | 模仿定长数组的数据访问形式,如arr[3]=5; vector因此使用起来与定长数组无异。 |
成员函数式访问 | at(int pos); | 返回pos处元素的引用。它与[]号相同,除了越界。(使用[]造成越界会直接崩溃,用at则会抛异常,可以用try catch捕获) |
访问头元素 | front(); | 返回头元素。 |
访问尾元素 | back(); | 返回尾元素。 |
直接访问底部 | data(); | 返回vector维护的底部数组的头地址。 (vector是基于数组这种数据类型的容器,它的所有操作都是在维护他的成员指针指向的定长数组) |
//d的内部:{1,2,3,4,5,6,}
d.at(2) = 7;
d.front() = 9;
d.back() = 8;
for (int i = 0; i < 6; i++)cout << d[i];//输出927458
int* p = d.data();//p得到了头元素d[0]的地址
cout << *p;//输出9
迭代器
迭代器是c++STL库提供的指向容器的类型,有着类似指针的作用。
通常定义vector<T>::iterator it=.....来获得一个指向某处的it迭代器。
头位置 | begin(); | 返回头位置(vector第一个元素的位置,vector为空时和end相等)。 |
尾位置 | end(); | 返回尾位置。(它指向的位置是第一个不属于vector的位置而不是vector的最后一个元素。) |
常量头位置 (C++11) | cbegin(); | 返回头位置,赋值给const_iterator类型。这种迭代器不可修改容器。 |
常量尾位置 (C++11) | cend(); | 返回常量尾位置。 |
逆序头位置 | rbegin(); | 返回逆序头位置,赋值给reverse_iterator类型。指向最后一个元素(与end不同),对逆序迭代器的++操作会使其向头部移动,--向尾部移动(与正序相反) |
逆序尾位置 | rend(); | 返回逆序尾位置,指向vector前面的那个不属于vector的位置(与front不同) |
常量逆序头位置 (C++11) | crbegin(); | 返回常量逆序头位置,赋值给const_reverse_iterator类型。 |
常量逆序尾位置 (C++11) | crend(); | 返回常量逆序尾位置。 |
//d的内部:{1,2,3,4,5,6,}
for (vector<int>::const_iterator cit = d.begin(); cit != d.end(); cit++)
cout << *cit;
//输出123456
for (vector<int>::const_reverse_iterator crit = d.crbegin(); crit != d.crend(); crit++)
cout << *crit;
//输出654321
内存管理
vector不仅可以自动管理内部空间,还支持程序员手动管理内部空间。
vector内部关于内存同时拥有size和capacity两种参数,为什么?
试想:每次申请长度都只是当前长度+1,则会在内存空间中反复向后利用函数申请新空间来维护数据,这同时耗费了时间和空间。
那么如果每次都申请真实长度size的两倍空间capacity,则每次申请后都有一段空闲区域容纳新元素,此时不需要申请新空间,只有当再次填满时才申请新空间,则减少了时间成本,提高了空间利用率。
获取实际长度 | size(); | 返回vector中的有效元素个数。 |
控制实际长度 | resize(int num); resize(int num,T target); | 将有效元素限制到num个。size>num时抛弃多余部分;num>size则将将多出的位置置为target,无target时置为0. |
获取真实空间大小 | capacity(); | 获取vector维护的底层数组的全部容量。 |
控制真实空间 | reserve(int num); | num>capacity时扩容,否则无事发生。 |
判断是否为空 | empty(); | size==0时返回true,否则返回false。 |
空间收缩 | shrink_to_fit(); | 将capacity收缩至size大小。 |
获取整个程序的极限容纳空间 | max_size(); | 返回整个程序支持的理论最大vector长度。 |
数据控制
vector类型通过成员函数维护数据。
iterator即为上文的迭代器。
尾部压入 | push_back(T elem); | 在尾部加入元素。 |
尾部弹出 | pop_back(); | 删除尾部最后一个元素。 |
插入 | insert(vector<T>::iterator it,T elem); insert(vector<T>::iterator it,T* begin,T*end); | 在迭代器it(后文提及)指向的位置加入元素,原元素依次向后移动。 在it指向的位置加入多个元素,源地址为begin到end。源地址的类型不局限于定长数组和vector,只需是一个包含T类型的完整范围。 |
删除 | erase(vector<T>::iterator it); erase(vector<T>::iterator it1,vector<T>::iterator it2) | 删除it指向的元素。 删除it1到it2之间的元素,满足左闭右开原则(it1位置被删除,it2不删除) |
交换 | swap(vector& another); | 交换两个容器的元素。 |
清空 | clear(); | 清空元素。 |
尾部安置(C++11) | emplace_back(T elem); | C++11后可用,在尾部原地构造一个元素,作用与push_back()相同,但没有赋值过程而是原地生成新元素,所以效率更高。 |
头部安置(C++11) | emplace_front(T elem); | 与empalce_back()类似。 |
插入安置(C++11) | emplace(vector<T>::iterator it,T elem); emplace(vector<T>::iterator it,T* begin,T*end); | 作用于insert相同,但因为上述原因效率更高。 |
Tips
vector<bool>不是装载bool类型的标准vector容器,这个奇异搞笑的类型不是vector<T>的一个实现,它就叫"vector<bool>",常年被c++标准委员会诟病。它本来是用来演示如何使用STL库的一个小白鼠,存储bool这个bit大小的元素,在一个字节上存了八个。它的运行效率极低,不推荐使用。