【C++ STL篇(四)】一文拿捏vector常用接口!

C++ STL篇(四)------vector

还在被固定数组限制?vector前来救场!本篇带你看懂vector常用接口,新手也能轻松上手!话不多说,我们发车啦!ദ്ദി˶ー̀֊ー́ )✧

文章目录

  • [C++ STL篇(四)------vector](#C++ STL篇(四)——vector)
    • [1. vector 是什么?](#1. vector 是什么?)
    • [2. 创建 vector 的 N 种姿势(构造函数)](#2. 创建 vector 的 N 种姿势(构造函数))
    • [3. 遍历 vector 的三种经典方式](#3. 遍历 vector 的三种经典方式)
    • [4. 容量管理](#4. 容量管理)
      • [4.1 size 和 capacity](#4.1 size 和 capacity)
      • [4.2 reserve:预定空间,但不改变 size](#4.2 reserve:预定空间,但不改变 size)
    • [5. 增删改查:最常用的修改操作](#5. 增删改查:最常用的修改操作)
      • [5.1 push_back:尾部追加](#5.1 push_back:尾部追加)
      • [5.2 insert:任意位置插入](#5.2 insert:任意位置插入)
      • [5.3 erase:删除元素](#5.3 erase:删除元素)
    • [6. 用 vector 优雅地输入输出](#6. 用 vector 优雅地输入输出)
    • [7. vector 存放复杂类型与二维数组](#7. vector 存放复杂类型与二维数组)
      • [7.1 存放 string](#7.1 存放 string)
      • [7.2 二维 vector(重点!)](#7.2 二维 vector(重点!))
    • 结语:

1. vector 是什么?

简单说,vector 是 C++ 标准模板库(STL)中的一个序列容器,可以把它理解成"可以动态改变大小的数组"。它背后是一块连续的内存空间,所以支持用下标 [] 快速访问元素,同时还提供了丰富的成员函数,帮你完成增删改查。

要使用 vector,必须包含头文件:

cpp 复制代码
#include <vector>

2. 创建 vector 的 N 种姿势(构造函数)

先看一段代码,我们逐行解释:

cpp 复制代码
vector<int> v1;                        // 1. 空容器,里面啥也没有
vector<int> v2(10, 1);                 // 2. 10个元素,每个都是1
vector<int> v3(++v2.begin(), --v2.end()); // 3. 用迭代器区间构造

1. 默认构造 vector v1;

这个最常见,创建一个空 vectorsizecapacity 都是 0。后面可以用 push_back 慢慢加元素。

2. 填充构造 vector v2(10, 1);

创建 10 个元素的 vector,每个元素初始化为 1。等价于你写了一个长度为 10 的数组,并且全都赋值为 1。如果只写 vector<int> v2(10);,那么 10 个元素会被默认初始化,对于 int 类型就是 0。

3. 迭代器区间构造 vector v3(++v2.begin(), --v2.end());

这个稍微高级一点,但非常实用。v2.begin() 返回指向第一个元素的迭代器,++ 后变成指向第二个元素;v2.end() 返回指向最后一个元素下一个位置的迭代器,-- 后指向最后一个元素。所以这一句的意思是:用 v2 的第 2 个到倒数第 2 个元素来初始化 v3

结果: v2 是 10 个 1,去掉一头一尾,v3 就包含 8 个 1。


3. 遍历 vector 的三种经典方式

创建完 vector,总得看看里面有什么。

cpp 复制代码
// 假设 v3 已经有 8 个元素,值都是 1

// 方式1:用下标,像数组一样
for (size_t i = 0; i < v3.size(); i++) 
{
    cout << v3[i] << " ";
}
cout << endl;

// 方式2:用迭代器,指针风格
vector<int>::iterator it = v3.begin();
while (it != v3.end()) 
{
    cout << *it << " ";  // *it 解引用,取迭代器指向的值
    it++;
}
cout << endl;

// 方式3:C++11 范围 for 循环,最简洁!
for (auto e : v3) 
{
    cout << e << " ";
}
cout << endl;

输出:

三种方式对比:

  • 下标法: 直观,但不适用于所有容器。
  • 迭代器法: 通用性强,所有 STL 容器都支持,而且可以通过 *it 修改元素。
  • 范围 for: 简洁优雅,强烈推荐日常使用。注意如果用 auto& e 可以修改原元素,避免拷贝。

4. 容量管理

4.1 size 和 capacity

size : 表示当前容器里实际拥有的元素个数

capacity: 表示vector当前已经分配好的内存空间最多能存多少元素

扩容机制演示:

cpp 复制代码
void TestVectorExpand()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "capacity change:" << sz << endl;

	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 change:" << sz << endl; 
		}
	}
}

在VS下的输出:

在 VS 编译器中,扩容通常是 1.5 倍增长;在 GCC 中则是 2 倍增长。原因是为了在"减少扩容次数 "和"避免内存浪费"之间取得平衡。

4.2 reserve:预定空间,但不改变 size

cpp 复制代码
vector<int> v(10, 1);  // size=10, capacity=10
v.reserve(20);         // 申请至少能容纳20个元素的空间
cout << v.size() << endl;      // 输出 10,size 不变!
cout << v.capacity() << endl;  // 输出 20

reserve(n) 只会增大 capacity,不会添加元素,所以 size 不变。如果你提前知道要存多少数据,用 reserve 一次性开好空间,可以避免多次扩容带来的拷贝开销,提升性能。

reserve 不会缩容

cpp 复制代码
v.reserve(5);  // 试图缩小容量
cout << v.capacity() << endl;  // 依然是 20,不会变小!

C++ 标准规定,reserve 只保证容量 >= n,不会主动缩容。

resize:改变 size,可能引发扩容

cpp 复制代码
vector<int> v(10, 1);
v.resize(30, 2);  // 增加到30个元素,新增的元素用 2 填充
cout << v.size() << endl;      // 30
cout << v.capacity() << endl;  // >=30,可能触发了扩容

v.resize(5);      // 减少到5个元素,多出的被删除
cout << v.size() << endl;      // 5
cout << v.capacity() << endl;  // 容量不变,不会缩容!

总结一下:

操作 影响 size 影响 capacity 备注
push_back +1 不够时扩容 最常用添加方式
reserve(n) 不变 增大至 >= n 不会减小
resize(n) 变为 n n>capacity时扩容 多余元素删除/默认填充

5. 增删改查:最常用的修改操作

5.1 push_back:尾部追加

cpp 复制代码
vector<int> v(10, 1);   // 10个1
v.push_back(3);         // 现在变成11个元素:1,1,...1,3

简单粗暴,往最后面加一个元素。如果容量不够,自动扩容。

5.2 insert:任意位置插入

cpp 复制代码
v.insert(v.begin(), 2);      // 在开头插入 2
v.insert(v.begin() + 3, 6);  // 在第4个位置(下标3)插入 6

insert(pos, val)pos 位置之前插入 val

注意:

  • pos 是迭代器,不是下标。
  • 插入后,后面的所有元素都要向后移动,时间复杂度 O(n),所以如果频繁在中间插入,vector 并不是最佳选择。

5.3 erase:删除元素

cpp 复制代码
v.erase(v.begin());  // 删除第一个元素

erase(pos) 删除 pos 位置的元素,返回下一个元素的迭代器。同样,删除后所有后面的元素要向前移动,效率较低。

完整示例:

cpp 复制代码
vector<int> v(10, 1);
v.push_back(3);
v.insert(v.begin(), 2);
v.insert(v.begin() + 3, 6);
// 现在 v 内容:2,1,1,6,1,1,1,1,1,1,1,3

v.erase(v.begin());
// 删除了开头的2

迭代器失效问题

执行插入或删除操作后,原来的迭代器可能会失效!因为 vector 可能会重新分配内存,导致原来的迭代器指向的位置变得非法。安全做法是:每次操作后重新获取迭代器。


6. 用 vector 优雅地输入输出

很多人会这样写:

cpp 复制代码
vector<int> v(5, 0);
for (size_t i = 0; i < v.size(); i++) 
{
    cin >> v[i];
}

这完全没问题。但更推荐用范围 for 输出:

cpp 复制代码
for (auto e : v) 
{
    cout << e << " ";
}

如果要在遍历时修改元素值,记得用引用 !

cpp 复制代码
for (auto& e : v) 
{
    e *= 2;  // 每个元素翻倍
}

7. vector 存放复杂类型与二维数组

7.1 存放 string

cpp 复制代码
vector<string> v1;
string s1("xxxx");
v1.push_back(s1);          // 拷贝一份存入
v1.push_back("yyyyyy");    // 直接传入字符串字面值

// 遍历时强烈建议用 const 引用,避免不必要的拷贝!
for (const auto& e : v1) 
{
    cout << e << " ";
}

7.2 二维 vector(重点!)

二维数组经常用来表示矩阵、棋盘等。用 vector 创建二维数组非常简单:

cpp 复制代码
vector<int> v(5, 1);                 // 一维,5个1
vector<vector<int>> vv(10, v);       // 二维,10行,每行是一个 v

vv[2][1] = 2;  // 修改第3行第2列的元素

// 遍历输出
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;
}

这里 vv 就是一个 10 行 5 列的矩阵,每个元素初始值为 1。访问方式就是 vv[行][列],和原生二维数组一模一样。

这里我们看一个题:
ど°0°う♡ 传送门

解答示例:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        //创建一个二维vector,有numRows行,每行初始为空
        vector<vector<int>> vv(numRows);
        for(size_t i = 0;i<numRows;++i)
        {
            vv[i].resize(i+1,1);//将第i行的元素个数改为i+1,新增元素全部初始化为1
        }

        for(int i = 2;i<vv.size();++i)//从第二行开始计算
        {
            for(int j = 1;j<vv[i].size()-1;++j)//不需要计算每行的首尾了,它们已经是1了
            {
                vv[i][j] = vv[i-1][j] + vv[i-1][j-1];
            }
        }
        
        return vv;
    }
};

结语:

今天的内容到这里就结束了,希望你能有所收获~

代码无bug,学习不迷路,我们下篇再见! (•̀ᴗ•́)و

相关推荐
渡我白衣2 小时前
触类旁通——迁移学习、多任务学习与元学习
人工智能·深度学习·神经网络·学习·机器学习·迁移学习·caffe
NQBJT2 小时前
[特殊字符] VS Code + Markdown 从入门到精通:写论文、技术文档的超实用指南
开发语言·vscode·c#·markdown
草莓熊Lotso2 小时前
Linux 线程同步与互斥(一):彻底搞懂线程互斥原理、互斥量底层实现与 RAII 封装
linux·运维·服务器·开发语言·数据库·c++
逻辑驱动的ken2 小时前
Java高频面试场景题07
java·开发语言·面试·职场和发展·求职招聘·春招
j_xxx404_2 小时前
力扣算法题:字符串(最长公共前缀|最长回文子串)
c++·算法·leetcode
承渊政道2 小时前
【递归、搜索与回溯算法】(穷举vs暴搜vs深搜vs回溯vs剪枝:一文讲清概念与用法)
数据结构·c++·算法·决策树·深度优先·剪枝·宽度优先
敢敢のwings2 小时前
NVIDIA Isaac GR00T与Cosmos:重塑机器人学习的合成数据革命
学习·机器人
承渊政道2 小时前
【递归、搜索与回溯算法】(综合练习:一网打尽常见题型分类总结与方法归纳)
c++·算法·决策树·分类·深度优先·哈希算法·宽度优先
我不是懒洋洋2 小时前
【数据结构】栈和链表基本方法的实现
c语言·开发语言·数据结构·c++·链表·青少年编程·ecmascript