一、基本概念
vector数据结构和数组非常相似,也称为单端数组。与普通数组的区别在于数组是静态空间,而vector可以动态扩展。并不是在原空间之后续接新空间,而是找到一块更大的内存空间,将原数据拷贝到新空间,释放原空间。因为它以数组的形式储存,所以它的内存空间是连续的。
vector容器可以进行尾插,插入,遍历,头删,尾删等操作,用图表示如下:
注意,vector不能进行头插,即不能直接对头端元素进行操作,这也是 vector 容器和 deque 容器最重要的区别之一。
二、基本操作
2.1 构造函数
函数原型 | 实现功能 |
---|---|
vector<T> v | 创建一个vector对象,默认构造函数 |
vector(v.begin(),v.end()) | 将v[begin(),end())区间中的元素拷贝给本身,左闭右开 |
vector(n,elem) | 构造函数将n个elem拷贝给本身 |
vector(const vector &vec) | 拷贝构造函数 |
举例代码:
vector<int>v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
//用区间的方式构造
vector<int>v2(v1.begin(), v1.end());
//n个值构造
vector<int>v3(10, 5); //10个5
//拷贝构造
vector<int>v4(v3);
2.2 容量和大小
函数原型 | 实现功能 |
---|---|
capacity() | 得到vector对象的容量 |
size() | 得到vector对象的大小 |
resize() | 重新指定大小 |
举例代码:
vector<int>v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
cout << "v1的容量为:" << v1.capacity() << endl; //13
cout << "v1的大小为:" << v1.size() << endl; //10
//重新指定大小
v1.resize(15); //后面会补5个0
v1.resize(15, 100); //后面会补5个100
2.3 插入和删除
函数原型 | 实现功能 | 注意事项 |
---|---|---|
push_back(a) | 在尾部插入一个元素a | |
insert(a.begin+i,k) | 在下标为i的元素前插入k | 第一个参数是迭代器 |
insert(a.end(),n,m) | 在末尾插入n个值为m的元素 | 第一个参数是迭代器 |
insert(a.end,{1,2}) | 在末尾插入值“1,2” | 第一个参数是迭代器 |
pop_back() | 删除末尾元素 | 容器大小(size)会减一,容量不变 |
erase(a.begin()+i,a.begin()+j) | 将[i,j-1]的元素都删除 | 两个参数都是迭代器 |
erase(a.begin()+i) | 将[i,j-1]的元素都删除 | 参数是迭代器 |
remove() | 删除容器中所有和指定元素相等的元素,并返回指向最后一个元素下一个位置的迭代器 | |
clear() | 清空数组 |
先看举例代码,然后再解释什么是迭代器,迭代器又有什么用。(该示例代码可以运行)
#include<iostream>
#include<string>
using namespace std;
#include<vector>
void printVector(vector<int>& v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
void text01() {
vector<int>v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i); //尾插法
}
//尾删
v1.pop_back();
printVector(v1);
//插入
v1.insert(v1.begin(), 100); //在开头插入一个100,第一个参数是一个迭代器
printVector(v1);
v1.insert(v1.begin(), 5, 100); //在开头插入5个100
printVector(v1);
//删除
v1.erase(v1.begin()+8); //删除下标为8的元素
printVector(v1);
//清空
v1.clear();
printVector(v1);
}
int main() {
text01();
}
2.4 迭代器(iterator)
然后我们来解释迭代器(iterator),这里只需要清楚迭代器在不同的函数中的作用是什么就可以了,更详细的了解比较费力,就先不作解释。
如上例,我们实现了插入和删除操作,但是你可能会想,我们做的操作都是在开头或末尾进行插入或删除,即使删除“下标为8的元素”,也是用 v1.erase(v1.begin()+8) 实现。如果我们采用如下做法:
v1.insert(0, 100);
看似是在“下标为0的元素前插入一个元素 100”,好像没什么差别,但是编译器会报错。那么有两个疑问就油然而生:为什么一定要用 v1.begin() 和 v1.end()?如果我无法确定插入位置和起始位置的距离(即无法确定 i )时该怎么办?
第一个问题先不做解释,大多数情况下我们只需要知道怎么灵活使用即可。
我们可以定义一个 vector<int> 类型的迭代器 it,然后对 it 进行操作,修改 it 的值,让它指向我们想指向的位置,可以用 it 代替 v1.begin()+i 。具体代码如下:
vector<int>::iterator it = v1.begin();
it++; //可以对it操作,修改it的值
v1.insert(it,5); //在下标为it的元素前插入5
此法在其他容器(如deque容器)对应操作也同样适用。
2.5 其他常用函数
函数原型 | 实现功能 |
front() | 返回容器中第一个数据元素 |
back() | 返回容器中最后一个数据元素 |
swap(vector) | 将vector与本身的元素互换 |
2.5.1举例代码
可运行
#include<iostream>
#include<string>
using namespace std;
#include<vector>
void printVector(vector<int>& v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
void text01() {
vector<int>v1;
vector<int>v2;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
for (int i = 10; i > 0; i--) {
v2.push_back(i);
}
v1.swap(v2);
printVector(v1);
printVector(v2);
}
int main() {
text01();
return 0;
}
2.5.2 swap实际应用(收缩内存)
void text02(){
vector<int>v;
for (int i = 0; i < 100000; i++) {
v.push_back(i);
}
cout << "v的容量为:" << v.capacity() << endl; //138255
cout << "v的大小为:" << v.size() << endl; //100000
v.resize(100); //为v重新指定大小
cout << "v的容量为:" << v.capacity() << endl; //138255
cout << "v的大小为:" << v.size() << endl; 100
vector<int>(v).swap(v); //收缩内存,匿名对象
cout << "v的容量为:" << v.capacity() << endl; //100
cout << "v的大小为:" << v.size() << endl; //100
}
v刚开始容量为138255,大小为100000;当重新指定大小之后,容量没变,大小变为100,那么剩余的空间就全都浪费掉了(占用内存但并未存储数据);当使用swap收缩内存后,v的大小和容量都改变了,就避免了空间浪费。
解释swap代码:
vector<int>v 是个匿名对象,相当于创建了一个新的对象,暂且看做x;
x会按照当前v所用的内存大小来初始化,比如现在v的大小为100,就初始化x的大小为100,容量也是100;
之后又“swap”相当于交换了x和v,交换后x指向容量为138255的内存空间,v指向容量和大小为100的内存空间;
x由于是匿名对象,所以当39行代码执行完毕后,编译器会回收该代码,即不用担心x过多占用空间。
2.6 预留空间
函数原型 | 实现功能 |
reserve(a) | 预留出a的长度大小的空间 |
由于vector是一个动态数组,所以当我们插入元素过多时,会不断地扩展内存,更新容量,这是非常耗费时间的。我们举例说明:
#include<iostream>
#include<string>
using namespace std;
#include<vector>
void text01() {
vector<int>v;
int num = 0; //统计开辟空间的次数
int* p = NULL; //指针指向v的首元素地址
for (int i = 0; i < 100000; i++) {
v.push_back(i);
if (p != &v[0]) { //如果p不指向首元素地址,就代表开辟了新的空间
p = &v[0];
num++;
}
}
cout << "num=" << num << endl;
}
int main() {
text01();
return 0;
}
上例我们插入100000次数据,得到 num = 30 ,即扩展了30次内存,太耗费时间。我们可以使用 reserve 预留空间,这将大大提高代码的运行效率。添加进去后,完整代码如下:
#include<iostream>
#include<string>
using namespace std;
#include<vector>
void text01() {
vector<int>v;
v.reserve(100000); //预留空间
int num = 0; //统计开辟空间的次数
int* p = NULL; //指针指向v的首元素地址
for (int i = 0; i < 100000; i++) {
v.push_back(i);
if (p != &v[0]) { //如果p不指向首元素地址,就代表开辟了新的空间
p = &v[0];
num++;
}
}
cout << "num=" << num << endl;
}
int main() {
text01();
return 0;
}
这次我们计算得 num = 1 ,只扩展了一次内存,大大缩短了程序执行时间。
注意:预留位置不初始化,元素不可访问。
以上就是我对 vector 容器的总结,希望对您有所帮助。