前言

C 语言里我们用数组,大小固定,想扩容就得手动realloc,又麻烦又容易出错。
到了 C++,有了vector—— 一个可以自动增长的动态数组,不用自己管内存,想加就加、想删就删,可以说非常方便。

本文主要讲解构造函数、迭代器、容量操作、增删查改,迭代器失效和二维vector等内容,帮助新手快速入门。


提示:以下是本篇文章正文内容,下面案例可供参考

一、vector 是什么?

vector 英文翻译过来叫“向量”,你可以把它理解成一个可以自动扩容的动态数组

和普通数组比,vector有三大优势:

  1. 自动管理内存:不用手动申请释放内存,空间不够会自动扩容;
  2. 支持随机访问:和数组一样可以用方括号[]下标访问;
  3. 丰富的接口:插入、删除、查找、交换…… 全都有现成的。

使用 vector 需要包含头文件:#include <vector>,且在std命名空间下。

二、vector 的构造函数

vector 有多种构造方式,最常用的有 4 种:

构造函数功能说明
vector<T> v无参构造一个没有数据的 vector
vector<T> v(n, val)构造一个有 n 个元素的值为 val 的 vector
vector<T> v(v2)拷贝构造
vector<T> v(first, last)用迭代器区间构造

补充:vector<T> v(n, val)也可以不传 val,用vector<T> v(n)表示构造一个有 n 个元素的值为 0 的vector。T 表示传入的数据类型。

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector<int> v1;          // 空的int vector
    //vector<int> v2(5);     // 5个0
    vector<int> v2(5, 10);   // 5个10
    vector<int> v3(v2);      // 拷贝构造v3
    vector<int> v4(v2.begin(), v2.end()); // 迭代器区间构造

    // 还可以用数组构造
    int arr[] = { 1, 2, 3, 4, 5 };
    vector<int> v5(arr, arr + sizeof(arr) / sizeof(int));

    return 0;
}

三、vector 迭代器

迭代器是 STL 的通用遍历方式,vector 的迭代器本质就是原生指针,用起来非常方便。

1、四种迭代器

迭代器功能
begin() / end()正向迭代器,begin 指向第一个元素,end 指向最后一个元素的下一个位置
rbegin() / rend()反向迭代器,rbegin 指向最后一个元素,rend 指向第一个元素的前一个位置
cbegin() / cend()const 正向迭代器,只读不可改
crbegin() / crend()const 反向迭代器,也是只读不可改

注:end()、rend() 都是哨兵位迭代器,不指向有效元素,因此不能被解引用。

2、四种遍历方式

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    // 方式1:下标[]遍历(最常用,像数组一样)
    for (size_t i = 0; i < v.size(); i++)
        cout << v[i] << " ";
    cout << endl;

    // 方式2:正向迭代器
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 方式3:反向迭代器(从后往前)
    vector<int>::reverse_iterator rit = v.rbegin();
    while (rit != v.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;

    // 方式4:范围for(最简洁的方法,是C++11新增的语法)
    for (auto e : v)
        cout << e << " ";
    cout << endl;

    return 0;
}

在这里插入图片描述
技巧点:用auto自动推导迭代器类型,不用写长长的vector<int>::iterator

四、vector 容量操作

函数功能说明
size()返回有效元素个数
capacity()返回底层总容量
empty()判断vector是否为空
resize(n, val)把有效元素个数改成 n,多出的用 val 填充
reserve(n)预留 n 个元素的空间,只改容量,不改有效个数

1、函数使用

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> v;
	cout << v.size() << endl;      // 0
	cout << v.capacity() << endl;  // 0
	cout << v.empty() << endl;     // 1(空)

	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	cout << v.size() << endl;      // 3
	cout << v.capacity() << endl;  // 容量(自动扩容,大于等于size)

	// resize:改变有效元素个数
	v.resize(5, 0);  // 变成5个元素,新增的位置用0填充 → 1 2 3 0 0
	v.resize(2);     // 变成2个元素 → 1 2
	cout << v.capacity() << endl;  // 注意:resize只改size,capacity不会缩小

	// reserve:只预留空间,不改size
	v.reserve(100);  // 容量变成100,但size还是2
	cout << v.size() << endl;      // 2
	cout << v.capacity() << endl;  // 100

	return 0;
}

在这里插入图片描述

2、vector 的扩容机制(了解)

vector 空间不够时会自动扩容,但不同编译器扩容倍数不一样:

  • VS(PJ 版 STL):按1.5 倍扩容
  • G++(SGI 版 STL):按2 倍扩容

这里用 VS 来演示:

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> v;
	size_t sz = v.capacity();
	cout << "making v grow:" << endl;
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}
	return 0;
}

在这里插入图片描述

3、优化效率技巧:提前 reserve

在使用vector处理大量数据时,如果知道大概要存多少个元素,提前用reserve预留空间,可以避免边插入边扩容,大幅提升效率

int main() {
    vector<int> v;
    v.reserve(100);  // 提前预留100个空间
    for (int i = 0; i < 100; ++i)
        v.push_back(i); // 全程不扩容,效率高

	return 0;
}

五、vector 增删查改

1、接口总览

函数功能说明
push_back(x)尾插一个元素(最常用)
pop_back()尾删一个元素
insert(pos, x)在 pos 位置前插入元素 x
erase(pos)删除 pos 位置的元素
swap(v)交换两个 vector 的内容
operator[]下标访问(最常用)
find(first, last, x)查找元素(算法库函数,不是成员函数)

注意:find函数在<algorithm>头文件里,不是 vector 的成员函数!

2、代码使用

#include <iostream>
#include <vector>
using namespace std;
int main() {
	vector<int> v;

	// 尾插
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	// v: 1 2 3 4

	// 尾删
	v.pop_back();
	// v: 1 2 3

	// 任意位置插入
	v.insert(v.begin(), 0);   // 开头插入0 → 0 1 2 3
	v.insert(v.begin() + 2, 99); // 第3个位置插入99 → 0 1 99 2 3

	// 任意位置删除
	v.erase(v.begin());       // 删除第一个 → 1 99 2 3
	v.erase(v.begin() + 1);   // 删除第二个 → 1 2 3

	// 查找(注意:find是算法库的,不是vector的成员函数)
	auto pos = find(v.begin(), v.end(), 2);
	if (pos != v.end())
		cout << "找到了,下标:" << pos - v.begin() << endl;

	// 交换
	vector<int> v2(5, 10);
	v.swap(v2); // 交换v和v2的内容
	return 0;
}

六、大坑:vector 迭代器失效

1、什么是迭代器失效?

vector 的迭代器本质就是指针。迭代器失效 = 迭代器指向的空间被释放了,再用就会访问非法内存,程序崩溃。

2、两种导致失效的场景

场景 1:扩容导致失效(空间变了)

所有可能引起底层空间改变的操作,都可能导致迭代器失效:resize、reserve、insert、push_back、assign等。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	auto it = v.begin();

	v.reserve(100); // 扩容!旧空间被释放,it失效了

	// 再用it就是非法访问,程序崩溃
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	return 0;
}

原因:扩容时开了新空间、释放了旧空间,而it还指向旧空间的地址。

场景 2:erase 删除导致失效(位置变了)

erase 删除元素后,后面的元素会往前搬移。如果删除的是最后一个元素,删完后 pos 刚好等于 end,就失效了。

以下是错误的写法:

#include <iostream>
#include <vector>
using namespace std;
// 错误写法:删除所有偶数,会崩溃或结果不对
int main()
{
	vector<int> v{ 1,2,3,4 };
	auto it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);  // erase后it失效了!
		++it;             // 失效的迭代器再++,会崩溃或者结果与预期不符!
	}
	return 0;
}

正确操作是:接收 erase 返回值,erase会返回下一个有效位置的迭代器,用返回值更新 it 就不会失效了。
以下是正确的写法:

#include <iostream>
#include <vector>
using namespace std;
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); // 用返回值更新it
        else
            ++it;
    }
    return 0;
}

3、迭代器失效的通用解决办法

操作完之后,如果还要用迭代器,重新赋值一下就好。
比如扩容之后,重新it = v.begin();,就不会有问题了。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v{ 1,2,3,4,5 };
	auto it = v.begin();

	v.reserve(100); // 扩容!旧空间被释放,it失效了

	it = v.begin();	// 重新赋值
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	return 0;
}

七、vector 二维数组(以杨辉三角为例)

vector 的元素也可以是 vector,这样就形成了二维数组,而且每行长度可以不一样,比 C 语言的二维数组灵活多了。

1、基本用法

#include <iostream>
#include <vector>
using namespace std;
int main() {
	// 3行的二维数组,每行是一个vector<int>
	vector<vector<int>> vv(3);

	vv[0].push_back(1);
	vv[1].push_back(1);
	vv[1].push_back(2);
	vv[2].push_back(1);
	vv[2].push_back(2);
	vv[2].push_back(3);

	// 遍历二维vector
	for (size_t i = 0; i < vv.size(); i++)
	{
		for (size_t j = 0; j < vv[i].size(); j++)
			cout << vv[i][j] << " ";
		cout << endl;
	}

	return 0;
}

2、经典案例:杨辉三角

118. 杨辉三角

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
         vector<vector<int>> vv(numRows);
        // 每行大小设为i+1,全部初始化为1
        for (int i = 0; i < numRows; ++i)
            vv[i].resize(i + 1, 1);

        // 中间元素 = 上一行两个相加
        for (int i = 2; i < numRows; ++i)
            for (int j = 1; j < i; ++j)
                vv[i][j] = vv[i-1][j] + vv[i-1][j-1];

        return vv;
    }
};

119. 杨辉三角 II

class Solution {
public:
    vector<int> getRow(int rowIndex) {
         vector<vector<int>> vv(rowIndex+1);
        // 每行大小设为i+1,全部初始化为1
        for (int i = 0; i <= rowIndex; ++i)
            vv[i].resize(i + 1, 1);

        // 中间元素 = 上一行两个相加
        for (int i = 2; i <= rowIndex; ++i)
            for (int j = 1; j < i; ++j)
                vv[i][j] = vv[i-1][j] + vv[i-1][j-1];

        return vv[rowIndex];
    }
};

总结

本文主要是讲解了vector容器的用法和新手可能遇到的坑,希望对大家有所帮助。

更多推荐