第11课-C++ vector功能探究

1:C++ vector 容器简介

1.1 C++ STL 容器概述

C++ 提供了丰富的标准模板库 (STL),包括 顺序容器(如 vector)、关联容器(如 map、set)等。vector 是最常用的 STL 顺序容器之一,它的特点是支持 动态数组,可以在运行时自动扩展容量,提供高效的随机访问。

1.2 为什么使用 vector

与传统的 C 风格数组(T array[N])相比,vector 具有以下优势:

动态调整大小,无需手动管理内存;
提供了丰富的接口,支持插入、删除、查找等操作;
内置内存管理机制,防止越界访问。
例如,使用 C 风格数组的代码:

int arr[5] = {1, 2, 3, 4, 5};

与之相比,使用 vector 的方式更加灵活:

#include <vector>
using namespace std;vector<int> v = {1, 2, 3, 4, 5};  // 自动管理内存和大小

1.3 vector 的优缺点

优点:动态扩展、支持随机访问、效率高。
缺点:尾部操作快,但头部插入和删除较慢(涉及到元素移动)。

2:vector 的构造方法

2.1 常见构造函数

C++ vector 提供了多种构造方法,可以创建不同初始状态的 vector 对象。

构造函数    功能
vector()    构造一个空的 vector
vector(size_type n, const T& val)    构造包含 n 个元素值为 val 的 vector
vector(const vector& v)    拷贝构造 vector,与已有 vector 一致
vector(initializer_list<T>)    使用初始化列表构造 vector

2.1.1不同构造方法

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v1;                    // 空 vectorvector<int> v2(5, 100);            // 5个100的元素vector<int> v3(v2);                // 拷贝构造vector<int> v4 = {1, 2, 3, 4, 5};  // 使用初始化列表for (int i : v4) {cout << i << " ";  // 输出: 1 2 3 4 5}return 0;
}

输出:

1 2 3 4 5

3:vector 容量与大小操作

3.1 容量管理接口

C++ vector 提供了多种方法管理容器的容量与大小。

方法名    功能描述
size()    返回当前元素个数
capacity()    返回分配的存储空间大小
empty()    判断容器是否为空
resize(n)    将容器大小调整为 n,多出的部分用默认值填充
reserve(n)    预留存储空间,避免多次扩容
shrink_to_fit()    收缩 capacity 以匹配 size() 的大小

3.1.1 示例:容量与大小操作

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v = {1, 2, 3, 4, 5};cout << "Size: " << v.size() << endl;         // 当前元素个数cout << "Capacity: " << v.capacity() << endl; // 当前容量v.resize(10, 100);                            // 调整大小到10cout << "After resize: " << v.size() << endl;v.reserve(20);                                // 预留空间cout << "Capacity after reserve: " << v.capacity() << endl;v.shrink_to_fit();                            // 收缩容量cout << "Capacity after shrink_to_fit: " << v.capacity() << endl;return 0;
}

输出:

Size: 5
Capacity: 5//说明还没扩容
After resize: 10
Capacity after reserve: 20
Capacity after shrink_to_fit: 10

3.2 动态扩展与性能问题

当 vector 超过当前容量时,会自动扩展存储空间,通常是翻倍。虽然扩展是自动的,但涉及到内存重新分配,因此建议提前使用 reserve() 预留空间,减少不必要的性能开销。

补充:

capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2
倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是
根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

// 测试vector的默认扩容机制
void TestVectorExpand()
{size_t sz;vector<int> v;sz = v.capacity();cout << "making v grow:\n";for (int i = 0; i < 100; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}

vs:运行结果:vs下使用的STL基本是按照1.5倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141

g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128

reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代
价缺陷问题。
resize在开空间的同时还会进行初始化,影响size。

4:vector 元素访问与修改

4.1 元素访问方法

方法名    功能
operator[]    通过下标访问元素,不进行边界检查
at(n)    访问指定位置元素,进行越界检查
front()    返回第一个元素
back()    返回最后一个元素
data()    返回指向数组头部的指针

4.1.1 元素访问

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v = {1, 2, 3, 4, 5};cout << "First element: " << v.front() << endl; // 访问第一个元素cout << "Last element: " << v.back() << endl;   // 访问最后一个元素try {cout << v.at(10);  // 越界访问} catch (out_of_range& e) {cout << "Exception: " << e.what() << endl;}//异常捕获return 0;
}

输出:

First element: 1
Last element: 5
Exception: vector::_M_range_check: __n (which is 10) >= this->size() (which is 5)

4.2 修改元素

通过 operator[] 或 at() 直接修改元素内容:

v[0] = 10;
v.at(2) = 20;


 5:vector 的迭代器与遍历

5.1 迭代器

vector 提供了多种迭代器类型,便于对元素进行遍历、修改或访问。

迭代器类型    功能
begin()    返回指向容器第一个元素的迭代器
end()    返回指向容器末尾的迭代器
rbegin()    返回指向容器最后一个元素的反向迭代器
rend()    返回指向容器第一个元素之前位置的迭代器
cbegin()    常量迭代器,无法修改元素
cend()    常量迭代器,返回指向末尾的常量迭代器

5.1.1使用迭代器遍历 vector

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v = {1, 2, 3, 4, 5};// 使用正向迭代器遍历for (auto it = v.begin(); it != v.end(); ++it) {cout << *it << " ";}cout << endl;// 使用反向迭代器遍历for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {cout << *rit << " ";}cout << endl;return 0;
}

输出:

1 2 3 4 5 
5 4 3 2 1

5.2 使用 for_each() 函数遍历

for_each() 是一种 STL 提供的便捷函数,用于对容器中的每个元素执行指定的操作。5.2.1 示例:使用 for_each() 遍历并输出元素

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;void print(int val) {cout << val << " ";
}int main() {vector<int> v = {1, 2, 3, 4, 5};for_each(v.begin(), v.end(), print);  // 使用for_each输出return 0;
}

输出:

1 2 3 4 5
5.3 vector 迭代器失效问题(重点)

5.3.1 迭代器失效的原因与后果

vector 迭代器失效的根本原因在于底层内存的重新分配或元素的移除,导致迭代器指向的内存不再有效。当发生迭代器失效时,继续使用该迭代器可能会引发未定义行为,如程序崩溃或访问错误数据。

5.3.2 常见导致迭代器失效的操作

扩容相关操作:当 vector 需要扩展容量时,会分配新的内存空间并将原有元素搬移到新的位置。此时,所有的迭代器将会失效。

resize()
reserve()
insert()
push_back()
assign()

删除操作:删除操作会使指向被删除元素及其后续元素的迭代器失效。

5.3.3 扩容操作导致迭代器失效

示例:扩容导致迭代器失效

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v{1, 2, 3, 4, 5, 6};auto it = v.begin();// 扩容相关操作导致迭代器失效v.resize(100, 8);  // 扩容并填充元素// v.reserve(100);  // 扩容但不增加元素// v.push_back(7);  // 末尾插入可能引发扩容// v.assign(100, 8);  // 重新赋值并扩容// 扩容后需要重新获取迭代器it = v.begin();while (it != v.end()) {cout << *it << " ";++it;}cout << endl;return 0;
}

说明:在每次扩容操作后,vector 可能会分配新的内存空间,并释放原来的内存区域。这意味着之前的迭代器已指向失效的内存,因此在扩容操作后,必须重新获取迭代器。

5.3.4 删除操作导致迭代器失效

删除 vector 中的某些元素时,指向被删除元素及其后续元素的迭代器会失效。示例:删除导致迭代器失效

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int main() {vector<int> v{1, 2, 3, 4};// 查找元素3的迭代器auto pos = find(v.begin(), v.end(), 3);// 删除元素3v.erase(pos);// 迭代器失效,继续使用将导致程序崩溃或未定义行为cout << *pos << endl;  // 非法访问return 0;
}

说明:删除某个元素后,指向该元素及其后续元素的迭代器会失效。在删除操作后应重新获取有效的迭代器,以避免出现非法访问或程序崩溃。

5.3.5 删除偶数时的正确和错误写法

错误的删除写法在删除元素后没有正确更新迭代器,会导致迭代器失效,引发未定义行为。

错误示例:

int main() {vector<int> v{1, 2, 3, 4, 4};auto it = v.begin();// 错误的删除写法,迭代器未更新while (it != v.end()) {if (*it % 2 == 0) {v.erase(it);  // 迭代器失效,++it 导致未定义行为}++it;}return 0;
}

这里去分析一下会发现it和end两个刚好错过了,it就“离家出走”了😂正确示例:

int main() {vector<int> v{1, 2, 3, 4, 4};auto it = v.begin();// 正确的删除写法while (it != v.end()) {if (*it % 2 == 0) {it = v.erase(it);  // 返回新的有效迭代器,指向被删除元素的下一个元素} else {++it;}}for (int num : v) {cout << num << " ";}return 0;
}

输出:

1 3

5.3.6 编译器对迭代器失效的处理差异

不同编译器(如 GCC 和 MSVC)对迭代器失效的处理方式不同。GCC 在某些情况下可能会宽容处理失效迭代器,程序不会立即崩溃,但输出结果不确定;MSVC 则会直接抛出错误并导致程序崩溃。示例:GCC 下的宽松处理

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int main() {vector<int> v{1, 2, 3, 4, 5};auto it = find(v.begin(), v.end(), 3);// 删除元素3v.erase(it);// 虽然迭代器失效,但在 GCC 下程序可能不会崩溃cout << *it << endl;  // 输出不确定return 0;
}

示例:MSVC 下严格处理

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int main() {vector<int> v{1, 2, 3, 4, 5};auto it = find(v.begin(), v.end(), 3);// 删除元素3v.erase(it);// 在 MSVC 下,使用失效迭代器会导致程序崩溃cout << *it << endl;  // 程序崩溃return 0;
}

5.3.7 扩容后的迭代器失效问题

即使扩容后的程序在 Linux 环境下不会立刻崩溃,但输出结果仍然是不可靠的。以下代码展示了 vector 在 reserve() 扩容后的迭代器失效问题。

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v{1, 2, 3, 4, 5};auto it = v.begin();cout << "扩容之前,vector的容量为: " << v.capacity() << endl;// 通过 reserve 扩容v.reserve(100);cout << "扩容之后,vector的容量为: " << v.capacity() << endl;// 迭代器失效,输出结果错误while (it != v.end()) {cout << *it << " ";  // 输出结果可能错误++it;}cout << endl;return 0;
}

输出:

1 2 3 4 5  // 正常输出
扩容之前,vector的容量为: 5
扩容之后,vector的容量为: 100
0 2 3 4 5  // 错误输出

5.3.8 总结与建议

避免迭代器失效:进行可能引发迭代器失效的操作(如扩容、删除等)后,必须重新获取迭代器,以保证程序的稳定性。
最佳实践:对于 erase() 操作,使用函数返回的迭代器继续遍历,以避免出现迭代器失效问题。
编译器差异:不同编译器(如 GCC 和 MSVC)对迭代器失效的处理方式不同,在开发跨平台程序时应尤为注意。

6:vector 的插入、删除与修改

6.1 插入操作

vector 提供多种方法用于向容器中插入元素。

方法名    功能描述
push_back()    在末尾插入一个元素
insert()    在指定位置插入元素
emplace_back()    在末尾直接构造元素,避免不必要的复制开销

6.1.1 使用 push_back() 和 insert() 插入元素

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v = {1, 2, 3};// 在末尾插入v.push_back(4);// 在第二个位置插入元素5v.insert(v.begin() + 1, 5);for (int val : v) {cout << val << " ";}return 0;
}

输出:

1 5 2 3 4

6.1.2 emplace_back() 与 push_back() 的区别

emplace_back() 直接在容器末尾构造元素,减少了不必要的临时对象生成。适用于复杂对象的插入场景。

6.1.3 使用 emplace_back() 插入元素

#include <iostream>
#include <vector>
using namespace std;struct Point {int x, y;Point(int a, int b) : x(a), y(b) {}
};int main() {vector<Point> points;// 直接在末尾构造对象points.emplace_back(1, 2);cout << "Point: " << points[0].x << ", " << points[0].y << endl;return 0;
}

输出:

Point: 1, 2

6.2 删除操作

vector 提供了多种删除元素的方式,包括删除末尾元素和删除指定位置的元素。

方法名    功能描述
pop_back()    删除末尾元素
erase()    删除指定位置的元素或一段范围内的元素
clear()    清空整个 vector

6.2.1 使用 pop_back() 和 erase() 删除元素

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v = {1, 2, 3, 4, 5};// 删除末尾元素v.pop_back();// 删除第一个元素v.erase(v.begin());for (int val : v) {cout << val << " ";}return 0;
}

输出:

2 3 4


6.2.2 使用 clear() 清空 vector

v.clear();
cout << "Vector size after clear: " << v.size() << endl;  // 输出:0

6.3 修改元素

通过迭代器或下标可以直接修改 vector 中的元素。

6.3.1 修改 vector 元素

#include <iostream>
#include <vector>
using namespace std;int main() {vector<int> v = {1, 2, 3, 4, 5};// 修改第二个元素v[1] = 10;for (int val : v) {cout << val << " ";}return 0;
}

输出:

1 10 3 4 5

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1553406.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

多区域OSPF路由协议

前言 之前也有过关于OSPF路由协议的博客&#xff0c;但都不是很满意&#xff0c;不是很完整。现在也是听老师讲解完OSPF路由协议&#xff0c;感触良多&#xff0c;所以这里重新整理一遍。这次应该是会满意的 一些相关概念 链路状态 链路指路由器上的一个接口&#xff0c;链路状…

C++ | Leetcode C++题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; class Solution { public:int findMinArrowShots(vector<vector<int>>& points) {if (points.empty()) {return 0;}sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>&…

【重学 MySQL】五十一、更新和删除数据

【重学 MySQL】五十一、更新和删除数据 更新数据删除数据注意事项 在MySQL中&#xff0c;更新和删除数据是数据库管理的基本操作。 更新数据 为了更新&#xff08;修改&#xff09;表中的数据&#xff0c;可使用UPDATE语句。UPDATE语句的基本语法如下&#xff1a; UPDATE ta…

秒懂Linux之线程

目录 线程概念 线程理解 地址空间&#xff08;页表&#xff0c;内存&#xff0c;虚拟地址&#xff09; 线程的控制 铺垫 线程创建 ​编辑 线程等待 线程异常 线程终止 代码 线程优点 线程缺点 线程特点 线程概念 线程是进程内部的一个执行分支&#xff0c;线程是C…

【转载翻译】消息队列 - ActiveMQ、RabbitMQ、Kafka、ZeroMQ

转载自本人博客&#xff1a;【转载翻译】消息队列 - ActiveMQ、RabbitMQ、Kafka、ZeroMQ 转载自&#xff1a;The System Design Cheat Sheet: Message Queues - ActiveMQ, RabbitMQ, Kafka, ZeroMQ 本文由 Aleksandr Gavrilenko 发布于2023年12月21日 1. 前言 消息队列是异步服…

TypeScript 算法手册 【归并排序】

文章目录 1. 归并排序简介1.1 归并排序定义1.2 归并排序特点 2. 归并排序步骤过程拆解2.1 分割数组2.2 递归排序2.3 合并有序数组 3. 归并排序的优化3.1 原地归并排序3.2 混合插入排序案例代码和动态图 4. 归并排序的优点5. 归并排序的缺点总结 【 已更新完 TypeScript 设计模式…

Java | Leetcode Java题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; class Solution {public int findMinArrowShots(int[][] points) {if (points.length 0) {return 0;}Arrays.sort(points, new Comparator<int[]>() {public int compare(int[] point1, int[] point2) {if (point1[1] > point2[1…

STM32-MPU6050+DAM库源码(江协笔记)

目录 1、MPU6050简介 2、MPU6050参数 3、MPU6050硬件电路 4、MPU6050结构 5、MPU6000和MPU6050的区别 6、MPU6050应用场景 7、MPU6050电气参数 8、MPU6050时钟源选择 9、MPU6050中断源 10、MPU6050的I2C读写操作 11、DMP库移植 1、MPU6050简介 10轴传感器&#xff1…

AS-REP Roasting 实验

1. 实验网络拓扑 kali: 192.168.72.128win2008: 192.168.135.129 192.168.72.139win7: 192.168.72.149win2012:(DC) 192.168.72.131 2. 攻击原理 如果设置了不需要Kerberos预认证&#xff1a; 那么就可以直接发AS_REQ请求TGT票据&#xff0c;由于不要求预身份认证&#xff0…

Golang | Leetcode Golang题解之第453题最小操作次数使数组元素相等

题目&#xff1a; 题解&#xff1a; func minMoves(nums []int) (ans int) {min : nums[0]for _, num : range nums[1:] {if num < min {min num}}for _, num : range nums {ans num - min}return }

awd基础学习

一、常用防御手段 1、改ssh密码 passwd [user] 2、改数据库密码 进入数据库 mysql -uroot -proot 改密码 update mysql.user set passwordpassword(新密码) where userroot; 查看用户信息密码 select host,user,password from mysql.user; 改配置文件 &#xff08;否则会宕机…

信息安全工程师(30)认证概述

前言 认证&#xff0c;作为一种信用保证形式&#xff0c;是通过一系列的程序和标准来确认某人或某物的身份、资格、性能或质量的过程。其重要性不言而喻&#xff0c;是国家规范经济、促进发展的重要手段&#xff0c;也是政府保障产品、生态和人民生命财产安全的关键措施&#…

绑定Rust变量会踩什么坑

讲动人的故事&#xff0c;写懂人的代码 3.2 变量绑定的声明和初始化分开 在3.1.1中提到&#xff0c;变量的声明和初始化可以分开。而这也为程序员挖了一个坑&#xff0c;如代码清单3-4所示。 本书代码下载链接为github.com/wubin28/book_LRBACP。本书所有的代码清单&#xff…

【电路基础 · 2】电阻电路的等效变换(自用)

总览 1.电路的等效变换 1.1 电阻电路 1.2 等效变换是什么 1.3 线性电路和非线性电路 1.4 时变电路和非时变电路 1.5 二端网络&#xff08;一端口网络&#xff09;、四端网络&#xff08;二端口网络&#xff09;、六端网络&#xff08;三端口网络&#xff09; 1.6 两端电路的等…

51c自动驾驶~合集1

我自己的原文哦~ https://blog.51cto.com/whaosoft/11466109 #HTCL 超过所有视觉方案&#xff01;HTCL&#xff1a;分层时间上下文问鼎OCC 本文是对ECCV2024接受的文章 HTCL: 的介绍&#xff0c;HTCL在SemanticKITTI基准测试中超过了所有基于相机的方法&#xff0c;甚至在和…

中安未来 OCR—— 开启文字识别新时代

在数字化的浪潮中&#xff0c;高效准确的文字识别技术正发挥着越来越重要的作用。今天&#xff0c;我要向大家介绍一款令人惊艳的 OCR 解决方案 —— 中安未来 OCR。 一、初识中安未来 OCR 中安未来 OCR 以其强大的功能和卓越的性能&#xff0c;在众多文字识别工具中脱颖而出。…

森林火灾检测数据集 7400张 森林火灾 带标注 voc yolo

森林火灾检测数据集 7400张 森林火灾 带标注 voc yolo 森林火灾检测数据集 名称 森林火灾检测数据集 (Forest Fire Detection Dataset) 规模 图像数量&#xff1a;共7780张图像。类别&#xff1a;仅包含一种类别——火源。 数据划分 训练集 (Train)&#xff1a;通常占总数据…

死锁的成因与解决方案

目录 死锁的概念与成因 栗子 死锁的情况 哲学家问题 如何避免死锁 必要条件 死锁的解决方案 总结 死锁的概念与成因 多个线程同时被阻塞,他们中的其中一个或者全部都在等待某个资源的释放,导致线程无限期被阻塞,程序无法停止 栗子 我和美女a出去吃饺子,吃饺子要醋和酱油…

VScode 自定义代码配色方案

vscode是一款高度自定义配置的编辑器, 我们来看看如何使用它自定义配色吧 首先自定义代码配色是什么呢? 看看我的代码界面 简而言之, 就是给你的代码的不同语义(类名, 函数名, 关键字, 变量)等设置不同的颜色, 使得代码的可读性变强. 其实很多主题已经给出了定制好的配色方案…