深入理解C++ STL中的 vector
文章目录
- 1. vector 的概述
- 1.1 vector 是什么?
- 1.2 vector 的优点
- 1.3 vector 的缺点
- 2. vector 的基本使用
- 2.1 vector 的定义
- 2.2 基本操作
- 2.3 示例
- 2.4 迭代器的使用
- 3. vector 的内部实现原理
- 3.1 动态数组的实现
- 3.2 内存管理
- 3.3 内存扩展策略
- 3.4 元素的插入与删除
- 3.4.1 尾部插入与删除
- 3.4.2 中间或头部插入与删除
- 4. vector 高级功能
- 4.1 复制与赋值
- 4.2 移动语义与 std::move
- 4.3 vector 的初始化列表(C++11)
- 4.4 emplace_back() 与 push_back() 的区别
- 4.5 shrink_to_fit() 减少容量浪费
- 5. vector 的常见应用场景
- 5.1 动态数组
- 5.2 堆栈
- 5.3 动态队列
- 5.4 2D 矩阵的实现
- 6. vector 的复杂度分析
- 6.1 时间复杂度
- 6.2 空间复杂度
- 7. vector 的常见问题和陷阱
- 7.1 容量浪费问题
- 7.2 迭代器失效(最需要注意的地方)
- 7.3 元素的析构
前言: C++ Standard Template Library (STL) 是一个强大且灵活的库,提供了许多有用的数据结构和算法,其中vector 是最常用的容器之一。
vector是动态数组的封装,可以在运行时自动调整大小,提供了数组的效率以及更多的功能和灵活性。
在本文中,我们将深入讨论 vector的特性、使用方法、底层实现及其复杂性分析。
1. vector 的概述
vector文档的链接:http://www.cplusplus.com/reference/vector/vector/
1.1 vector 是什么?
vector 是 C++ STL 中一种顺序容器(sequence container),其底层实现基于动态数组。与普通数组不同的是,vector 可以根据需要动态扩展其大小,即它能够存储任意数量的元素,而不需要在创建时指定一个固定的大小。此外,vector 提供了丰富的成员函数,可以方便地对元素进行插入、删除、遍历、查找等操作。
1.2 vector 的优点
动态扩展:vector 能够自动调整大小,避免了固定大小数组带来的内存不足问题。
高效的随机访问:由于 vector 底层是连续的内存块,因此它可以像数组一样通过索引进行快速的随机访问。
灵活的插入和删除:vector 支持高效的尾部插入和删除操作,且提供了多种插入、删除方式。
STL 兼容性:vector 是 STL 容器,支持 STL 的算法和迭代器,可以与其他 STL 容器和算法无缝结合。
1.3 vector 的缺点
头部和中间的插入、删除效率低:由于 vector 使用连续的内存块,因此在中间或头部插入或删除元素时,需要移动大量元素,时间复杂度为 O(n)。 内存浪费:为了提高扩展效率,vector 通常会预留比实际需要更多的内存,这可能导致内存浪费。2. vector 的基本使用
在使用 vector 时,我们需要包含头文件 。下面是一些 vector 的基本使用方法和常见操作:
2.1 vector 的定义
#include <vector>std::vector<int> vec; // 定义一个存储整数的空vector
std::vector<int> vec(10); // 定义一个包含10个元素的vector,元素值默认初始化为0
std::vector<int> vec(10, 5); // 定义一个包含10个元素的vector,元素值为5
std::vector<int> vec2 = vec; // 定义一个新vector,并通过拷贝构造函数初始化
2.2 基本操作
push_back():在 vector 的末尾插入元素。
pop_back():删除 vector 末尾的元素。
size():返回 vector 中元素的数量。
capacity():返回 vector 当前的容量,即它在不重新分配内存的情况下最多可以容纳的元素数。
clear():清空 vector 中的所有元素。
empty():判断 vector 是否为空。
resize():调整 vector 的大小。
reserve():为 vector 预留一定的容量,避免频繁的重新分配内存。
2.3 示例
#include <iostream>
#include <vector>int main() {std::vector<int> vec;// 向vector中添加元素vec.push_back(1);vec.push_back(2);vec.push_back(3);// 输出vector的元素for (int i = 0; i < vec.size(); ++i) {std::cout << vec[i] << " ";}std::cout << std::endl;// 删除最后一个元素vec.pop_back();// 检查是否为空if (!vec.empty()) {std::cout << "The vector is not empty!" << std::endl;}return 0;
}
2.4 迭代器的使用
迭代器是一种用于遍历 vector 元素的工具。vector 提供了多种迭代器,包括 begin() 和 end()。
std::vector<int> vec = {1, 2, 3, 4, 5};// 使用迭代器遍历
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";
}
std::cout << std::endl;
也可以使用 C++11 的范围 for 循环来遍历:
for (int val : vec) {std::cout << val << " ";
}
3. vector 的内部实现原理
3.1 动态数组的实现
vector 的底层实现基于动态数组。当需要插入新元素时,如果当前容量不足,vector 会自动分配更大的内存块,并将原来的元素拷贝到新的内存块中。这种动态扩展策略的时间复杂度较低,因为 vector 的容量在每次扩展时通常是当前容量的两倍。
std::vector<int> vec;
vec.push_back(1); // 第一次插入,分配内存
vec.push_back(2); // 插入,内存足够,不需要重新分配
vec.push_back(3); // 插入,内存足够,不需要重新分配
// 当容量不足时,vector 会重新分配内存,通常是原来容量的两倍。
3.2 内存管理
vector 通过 capacity 来控制当前分配的内存大小,而 size 表示实际存储的元素数量。capacity 总是大于或等于 size。当 size 超过 capacity 时,vector 会重新分配内存,并将所有现有元素拷贝到新的内存地址。
std::vector<int> vec;
vec.reserve(10); // 预先分配10个元素的内存
vec.push_back(1); // 不会触发内存重新分配
vec.push_back(2); // 不会触发内存重新分配
通过使用 reserve(),可以避免多次内存重新分配,从而提高效率。
3.3 内存扩展策略
当 vector 的容量不足时,它通常会以指数倍(通常是 2 倍)的方式扩展。每次扩展都会重新分配一块新的内存,并将旧的元素拷贝到新的位置。这种方式虽然在内存重新分配时会有较大的开销,但由于扩展的频率较低,总体上这种策略是高效的。
3.4 元素的插入与删除
3.4.1 尾部插入与删除
vector 尾部的插入和删除操作是最为高效的,时间复杂度为 O(1),因为它们不需要移动其他元素。使用 push_back() 和 pop_back() 可以高效地操作 vector 末尾的元素。
3.4.2 中间或头部插入与删除
在 vector 中间或头部插入和删除元素时,需要将插入位置之后的所有元素向后移动,这样才能为新元素腾出空间。这使得这些操作的时间复杂度为 O(n)。
vec.insert(vec.begin() + 1, 10); // 在第二个位置插入10,后面的元素都需要向后移动
vec.erase(vec.begin() + 2); // 删除第三个元素,后面的元素都需要向前移动
4. vector 高级功能
4.1 复制与赋值
当一个 vector 被赋值或复制时,会创建一个新对象,并将所有元素进行深拷贝。这意味着修改新对象不会影响原来的 vector。
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = vec1; // 深拷贝
vec2[0] = 10;
std::cout << vec1[0]; // 输出1,vec1不受影响
4.2 移动语义与 std::move
C++11 引入了移动语义,允许将 vector 的资源直接转移到另一个对象,而不需要进行深拷贝。这可以极大地提高性能,尤其是在处理大型对象时。
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = std::move(vec1); // 使用std::move,vec1的资源转移给vec2
在上面的例子中,std::move 将 vec1 的所有资源(如内存和元素)直接转移到 vec2 中,而不需要进行深拷贝。这种操作后,vec1 将处于“空”状态,不再拥有原来的数据,size() 将返回0。
4.3 vector 的初始化列表(C++11)
C++11 引入了初始化列表,可以直接通过大括号初始化 vector:
std::vector<int> vec = {1, 2, 3, 4, 5}; // 初始化列表
这种方式非常方便,不需要手动调用 push_back() 来插入每个元素。
4.4 emplace_back() 与 push_back() 的区别
emplace_back() 是 C++11 新增的功能,它允许直接在容器的末尾构造对象,而无需先构造对象再拷贝到 vector。相比之下,push_back() 会进行额外的拷贝操作,因此在某些情况下,emplace_back() 会比 push_back() 更高效。
class Person {
public:Person(const std::string& name, int age) : name(name), age(age) {}
private:std::string name;int age;
};// 使用push_back
std::vector<Person> people;
people.push_back(Person("John", 30)); // 先构造Person对象,然后拷贝到vector中// 使用emplace_back
people.emplace_back("Jane", 25); // 直接在vector内部构造Person对象,避免拷贝
使用 emplace_back() 时,参数直接传递给对象的构造函数,从而减少了不必要的拷贝或移动操作。
4.5 shrink_to_fit() 减少容量浪费
由于 vector 通常会预留比实际所需更多的内存空间(capacity()),可能会造成内存浪费。shrink_to_fit() 函数用于释放未使用的内存,使得 vector 的容量等于其大小。
std::vector<int> vec = {1, 2, 3};
vec.reserve(10); // 预留10个元素的空间
std::cout << vec.capacity(); // 输出10vec.shrink_to_fit();
std::cout << vec.capacity(); // 输出3,容量被调整为实际使用的大小
需要注意的是,shrink_to_fit() 并不保证一定会减少容量,但在支持该操作的系统上可以显著减少内存占用。
5. vector 的常见应用场景
5.1 动态数组
vector 最常见的应用场景就是作为动态数组使用。当程序中需要动态调整数组大小时,vector 提供了极大的方便。
std::vector<int> vec;
int n;
std::cin >> n;
for (int i = 0; i < n; ++i) {int x;std::cin >> x;vec.push_back(x); // 动态添加元素
}
5.2 堆栈
vector 可以模拟堆栈的功能,因为它提供了高效的尾部插入(push_back())和删除(pop_back())操作。虽然 C++ STL 中已经有 stack 容器,但使用 vector 实现堆栈也是完全可行的。
std::vector<int> stack;
stack.push_back(1); // 入栈
stack.push_back(2);
stack.pop_back(); // 出栈
5.3 动态队列
虽然 C++ STL 提供了 queue 容器,但 vector 同样可以用来实现队列。通过在 vector 的末尾插入元素并从头部移除元素,我们可以模拟队列的行为。
std::vector<int> queue;
queue.push_back(1); // 入队
queue.push_back(2);
queue.erase(queue.begin()); // 出队,删除第一个元素
5.4 2D 矩阵的实现
vector 可以轻松实现二维或多维数组。通过将 vector 嵌套使用,可以构建动态调整大小的二维数组或矩阵。
std::vector<std::vector<int>> matrix(3, std::vector<int>(3)); // 创建3x3的矩阵
matrix[0][0] = 1;
matrix[1][1] = 2;
matrix[2][2] = 3;
这个方法可以处理动态大小的矩阵,使得二维数组的尺寸不需要在编译时确定。
6. vector 的复杂度分析
6.1 时间复杂度
随机访问:由于 vector 是连续的内存块,随机访问某个元素的时间复杂度为 O(1)。
尾部插入和删除:尾部插入(push_back())和尾部删除(pop_back())的平均时间复杂度为 O(1),但在某些情况下(如扩容时)插入操作的复杂度可能会暂时达到 O(n)。
中间或头部插入和删除:由于插入或删除会导致大量元素的移动,因此这些操作的时间复杂度为 O(n)。
6.2 空间复杂度
vector 使用连续的内存块来存储元素,其空间复杂度与存储的元素个数成正比,即 O(n)。由于 vector 会在扩展时预留更多的内存,因此有时它的实际内存使用量会超过其存储的元素量。
为了减少内存浪费,可以使用 shrink_to_fit() 来回收未使用的空间。
7. vector 的常见问题和陷阱
7.1 容量浪费问题
如前所述,vector 的容量通常大于其实际存储的元素数量。如果程序中频繁进行插入操作且对内存使用敏感,可以使用 shrink_to_fit() 来减少浪费。
7.2 迭代器失效(最需要注意的地方)
在 vector 中进行插入或删除操作时,所有指向 vector 元素的迭代器、指针或引用可能会失效。这是因为插入或删除操作可能导致 vector 重新分配内存,从而改变所有元素的地址。
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.push_back(4); // 可能导致内存重新分配
std::cout << *it; // 迭代器可能失效,行为未定义
解决方法是,在插入或删除操作后,尽量避免使用之前的迭代器或指针。
7.3 元素的析构
当 vector 中的对象被删除时,会调用对象的析构函数。因此,如果 vector 存储的是指针类型,在删除 vector 或清空元素时需要特别小心,确保不会引发内存泄漏。
std::vector<int*> vec;
vec.push_back(new int(10));
vec.clear(); // 仅删除了指针,但没有释放内存,可能导致内存泄漏
解决方案是在删除元素之前手动释放内存,或者使用智能指针。
相关文章:
深入理解C++ STL中的 vector
文章目录 1. vector 的概述1.1 vector 是什么?1.2 vector 的优点1.3 vector 的缺点 2. vector 的基本使用2.1 vector 的定义2.2 基本操作2.3 示例2.4 迭代器的使用 3. vector 的内部实现原理3.1 动态数组的实现3.2 内存管理3.3 内存扩展策略3.4 元素的插入与删除3.4…...
MySQL 安装与配置详细教程
MySQL 安装与配置详细教程 MySQL 是一款流行的关系型数据库管理系统,广泛应用于 Web 应用和应用程序中。在本文中,我们将提供一份详细的 MySQL 安装与配置教程,帮助初学者快速上手。 ## 1. 安装 MySQL 首先,我们需要从 MySQL 官…...

理解智能合约:区块链在Web3中的运作机制
随着区块链技术的不断发展,“智能合约”这一概念变得越来越重要。智能合约是区块链应用的核心之一,正在推动Web3的发展,为数字世界带来了前所未有的自动化和信任机制。本文将深入探讨智能合约的基本原理、运作机制,以及它在Web3生…...

QT工程概述
在Qt中,创建 "MainWindow" 与 "Widget" 项目的主要区别在于他们的用途和功能范围: MainWindow:这是一个包含完整菜单栏、工具栏和状态栏的主窗口应用程序框架。它适合于更复 杂的应用程序,需要这些额外的用户…...
redis安装 | 远程连接
1.redis的安装 在Ubuntu下安装redis【网址】使用root账号使用apt来安装。使用apt安装比较的方便,但是安装的版本可能就不是最新的版本。 $ su root $ apt list --installed | grep redis # 查看是否安装 $ apt search redis # 查看apt中的redis版本 $ apt install…...

性价比高的宠物空气净化器应该怎么挑?有哪几款推荐?
前几年和朋友住在一起之后就一起养了两只猫,没想到刚开始还好,到后期之后,我和朋友都苦不堪言,有泪都流不出。 主要是猫咪掉毛实在是太严重了,下班回去之后,发现朋友在打扫家里,又是擦又是扫的…...

Golang | Leetcode Golang题解之第466题统计重复个数
题目: 题解: func getMaxRepetitions(s1 string, n1 int, s2 string, n2 int) int {n : len(s2)cnt : make([]int, n)for i : 0; i < n; i {// 如果重新给一个s1 并且s2是从第i位开始匹配 那么s2可以走多少位(走完了就从头开始走p1, p2 :…...
设计模式 - 行为模式
行为模式 观察者模式,策略模式,命令模式,中介者模式,备忘录模式,模板方法模式,迭代器模式,状态模式,责任链模式,解释器模式,访问者模式 保存/封装 行为/请求…...

InstructGPT的四阶段:预训练、有监督微调、奖励建模、强化学习涉及到的公式解读
1. 预训练 1. 语言建模目标函数(公式1): L 1 ( U ) ∑ i log P ( u i ∣ u i − k , … , u i − 1 ; Θ ) L_1(\mathcal{U}) \sum_{i} \log P(u_i \mid u_{i-k}, \dots, u_{i-1}; \Theta) L1(U)i∑logP(ui∣ui−k,…,ui−1;Θ…...
没有HTTPS 证书时,像这样实现多路复用
在没有 HTTPS 证书的情况下,HTTP/2 通常不能直接通过 HTTP 协议使用。虽然 HTTP/2 协议的规范是可以支持纯 HTTP 连接(即通过 http:// 协议),但大多数主流浏览器(如 Chrome、Firefox)都 强制要求 HTTP/2 必须在 HTTPS 上运行。这是出于安全和隐私的考虑。 因此,如果你没…...

2.1.ReactOS系统NtReadFile函数的实现。
ReactOS系统NtReadFile函数的实现。 ReactOS系统NtReadFile函数的实现。 文章目录 ReactOS系统NtReadFile函数的实现。NtReadFile函数的定义NtReadFile函数的实现 NtReadFile()是windows的一个系统调用,内核中有一个叫NtReadFile的函数 NtReadFile函数的定义 NTS…...

2020-11-06《04丨人工智能时代,新的职业机会在哪里?》
《香帅中国财富报告25讲》 04丨人工智能时代,新的职业机会在哪里? 1、新机会的三个诞生方向 前两讲我们都在说,人工智能的出现会极大地冲击现有的职业,从2020年开始,未来一二十年,可能有一半以上的职业都会…...

TensorRT-LLM七日谈 Day5
模型加载 在day2, 我们尝试了对于llama8B进行转换和推理,可惜最后因为OOM而失败,在day4,我们详细的过了一遍tinyllama的推理,值得注意的是,这两个模型的推理走的是不同的流程。llama8b需要显式的进行模型的转换,引擎的…...

使用Java Socket实现简单版本的Rpc服务
通过如下demo,希望大家可以快速理解RPC的简单案例。如果对socket不熟悉的话可以先看下我的上篇文章 Java Scoket实现简单的时间服务器-CSDN博客 对socket现有基础了解。 RPC简介 RPC(Remote Procedure Call,远程过程调用)是一种…...

P2P 网络 简单研究 1
起因, 目的: P2P 网络, 一道题。题目描述, 在下面。 P2P 网络,我以前只是听说过,并不深入。如果我有5台电脑的话,我也想深入研究一下。 P2P 简介: P2P(Peer-to-Peer)网络是一种分…...

RAG(检索增强生成)面经(1)
1、RAG有哪几个步骤? 1.1、文本分块 第一个步骤是文本分块(chunking),这是一个重要的步骤,尤其在构建与处理文档的大型文本的时候。分块作为一种预处理技术,将长文档拆分成较小的文本块,这些文…...

卫爱守护|守护青春,送出温暖
2024年10月10日,艾多美爱心志愿者来到校园。艾多美“卫艾守护”项目于吉林省白山市政务大厅会议室举办了捐赠仪式,东北区外事部经理黄山出席了捐赠仪式仪式,为全校女同学捐赠了青春关爱包。 此次捐赠,面向吉林省自山市第十八中学、…...

ubuntu-24.04.1 系统安装
使用VMware虚拟机上进行实现 官网下载地址: https://cn.ubuntu.com/download https://releases.ubuntu.com 操作系统手册: https://ubuntu.com/server/docs/ (里面包含安装文档) 安装指南(详细):…...
华为OD机试真题---生成哈夫曼树
华为OD机试中关于生成哈夫曼树的题目通常要求根据给定的叶子节点权值数组,构建一棵哈夫曼树,并按照某种遍历方式(如中序遍历)输出树中节点的权值序列。以下是对这道题目的详细解析和解答思路: 一、题目要求 给定一个…...

小红书新ID保持项目StoryMaker,面部特征、服装、发型和身体特征都能保持一致!(已开源)
继之前和大家介绍的小红书在ID保持以及风格转换方面相关的优秀工作,感兴趣的小伙伴可以点击以下链接阅读~ 近期,小红书又新开源了一款文生图身份保持项目:StoryMaker,是一种个性化解决方案,它不仅保留了面部的一致性&…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

EEG-fNIRS联合成像在跨频率耦合研究中的创新应用
摘要 神经影像技术对医学科学产生了深远的影响,推动了许多神经系统疾病研究的进展并改善了其诊断方法。在此背景下,基于神经血管耦合现象的多模态神经影像方法,通过融合各自优势来提供有关大脑皮层神经活动的互补信息。在这里,本研…...