C++ STL - vector/list讲解及迭代器失效

devtools/2024/11/28 4:02:18/

vector 使用

vector 是一个动态数组.

构造/拷贝构造/赋值重载函数

int main()
{// 是一个模板, 在实例化的时候, 需要指明类型std::vector<int> first; // 一个空的数组std::vector<int> second (4,100); // 设置初始空间大小为 4 个int, 全部初始化为 100std::vector<int> third (second.begin(),second.end()); // 通过迭代器构造std::vector<int> fourth (third); // 直接复制 third, 拷贝构造 std::vector<int> fifth;fifth = fourth;  // 复制重载return 0;
}

空间函数

1. size/capacity

size: 返回 vector 当前存储的元素数量
capacity: 返回 vector 最大存储元素数量

size_type size() const noexcept;
size_type capacity() const noexcept;int main()
{vector<int> v1(10, 1);cout << v1.size() << " " << v1.capacity() << endl;return 0;
}

2. resize/reserve

resize: 修改 vector 对象的 size

reserve: 修改 vector 的容量 capacity

void resize (size_type n);
void resize (size_type n, const value_type& val);void reserve (size_type n);int main()
{vector<int> v1;v1.resize(10, 0);// n < size 则数据丢失, n > capacity 则发生扩容, 用 val 填充扩容的空间v1.reserve(20);// 如果当前容量小于 n,vector 会重新分配内存以增加容量。// 如果当前容量已经大于或等于 n,则 reserve() 不会改变容量。return 0;
}

 reserve修改的只是capacity, 而不是size, 这是由区别的.

int main()
{vector<int> v1;v1.reserve(10);v1[1] = 10; // 这是错误的, reserve(10) 只是开辟了 10 个空间, 这 10 个空间还没有给我们使用v1.resize(10);v1[1] = 10; // 设置size = 10, 让v1分配了 10 个空间使用权给我们
}

3. empty

empty: 检查 vector 是否为空, 为空返回 true, 否者返回 false

bool empty() const noexcept;int main()
{vector<int> v1;if(v1.empty){cout << "数组为空" << endl;}else{cout << "数组不为空" << endl;}
}

访问 

1. operator[] / at

这两个函数都是用于访问 vector 中指定索引的元素

int main()
{vector<int> v1(10);for(int i = 0; i < 10; i++){v1[i] = i;}v1.at(5) = 50;cout << v1[2] << endl;cour << v1.at(5) << endl;
}

区别: operator[] 不会进行边界检查, 如果访问位置超出范围, 行为是未定义的

         at 则会进行边界检查, 访问位置超出范围, 则抛出异常

2. front / back

front: 返回数组头部的元素

back: 返回数组末尾的那个元素

int main()
{vector<int> v1(10);for(int i = 0; i < 10; i++){v1[i] = i;}cout << v1.front() << endl;cout << v1.back() << endl;return 0;
}

3. data

data: 返回一个指向 vector 内部数组的指针, 允许直接访问底层数组

int main()
{vector<int> v1(10);for(int i = 0; i < 10; i++){v1[i] = i;}int* arr = v1.data();cour << arr[5] << endl;return 0;
}

修改

1. push_back / pop_back

push_back: 向 vector 的末尾插入一个元素

pop_back: 删除 vector 末尾的一个元素

int main()
{vector<int> v1;v1.push_back(2);cout << v1return 0;
}

2. insert / erase

insert: 向指定的位置 (位置是一个迭代器) 后面插入一个元素, 并返回指向插入元素的迭代器

erase: 删除指定位置的元素, 返回删除位置的后一个元素的迭代器

#include <iostream>
#include <vector>int main ()
{std::vector<int> myvector (3,100);std::vector<int>::iterator it;it = myvector.begin();it = myvector.insert ( it , 200 );cout << *it << endl;it = myvector.erase(it);std::cout << *it << std::endl;return 0;
}

3. swap / clear

swap: 交换两个 vector 的值

clear: 清空 vector 中的元素

int main()
{vector<int> v1(3, 200);vector<int> v2(5, 100);v1.swap(v2);cout << v1[1] << endl;v1.clear();if(v1.empty()){cout << "v1为空" << endl;}return 0;
}

list 使用

list 底层是一个双向链表, 链表擅长的就是插入删除操作.

构造list

int main()
{std::list<int> first;                                // empty list of intsstd::list<int> second (4,100);                       // four ints with value 100std::list<int> third (second.begin(),second.end());  // iterating through secondstd::list<int> fourth (third);return 0;
}

容量

empty / size

和上面 vector 中的函数作用相同
STL 容器中的函数名称非常相似, 功能也相似, 熟悉完 vector 之后, list 使用也很简单

empty: 判断 list 是否为空

size: 返回链表的长度.

int main()
{list<int> l1(3, 10);cout << "l1 的长度为: " << l1.size() << endl;if(l1.empty()){cout << "l1 为空" << endl;}return 0;
}

修改

list 提供了非常多的插入删除操作, 因为相比于 vector, 双向链表插入删除效率更高

push_back / push_front / pop_back / pop_front

push_back: 在 list 尾部插入一个元素

push_front: 在 list 头部插入一个元素

pop_back: 在 list 尾部删除一个元素

pop_front: 在 list 头部删除一个元素

int main()
{list<int> l1(2, 10);l1.push_back(20);l1.push_frong(5);// 在 string 中说过, 迭代器的使用方法和指针差不多std::list<int>::iterator it = l1.begin(); // 获取 l1 首元素的迭代器while(it != l2.end()){cout << *it << " ";}l1.pop_back();l1.pop_front();while(it != l2.end()){cout << *it << " ";}return 0;
}

insert / erase

insert: 向指定的位置 (位置是一个迭代器) 的前面插入一个元素, 并返回指向插入元素的迭代器

erase: 删除指定位置的元素, 返回删除位置的后一个元素的迭代器

int main ()
{std::list<int> mylist;std::list<int>::iterator it;// set some initial values:for (int i=1; i<=5; ++i) mylist.push_back(i); // 1 2 3 4 5it = mylist.begin();++it;       // it points now to number 2           ^mylist.insert (it,10);                        // 1 10 2 3 4 5// "it" still points to number 2                      ^mylist.insert (it,2,20);                      // 1 10 20 20 2 3 4 5mylist.erase(it);it = mylist.begin();while(it != mylist.end()){cout << *it << " ";}
}

迭代器失效问题

迭代器失效: 指的是由于容器(如std::vectorstd::list等)的某些操作导致之前获取的迭代器不再指向容器中的有效元素,或者不再指向任何元素,从而不能被安全使用的现象。

可以理解为迭代器就是指针, 那么指针什么时候会失效: 所指向的空间是错误的, 那么哪些操作会导致指针指向的空间发生变化.

vector 迭代器失效情况

在 vector 中, 有以下情况会导致迭代器失效

  1. 插入操作:当使用 push_backinsert 插入元素时,如果 vector 的容量不足以容纳新元素,它可能会重新分配内存,这会导致所有迭代器失效。即使不重新分配内存,insert 操作也会使插入点之后的迭代器失效。

  2. 删除操作:使用 erasepop_back 删除元素时,指向被删除元素的迭代器会失效,同时,指向被删除元素之后的所有迭代器也会失效。

  3. 容量调整:调用 resizereserve 调整容量时,如果新容量大于当前容量,可能会导致重新分配内存,从而使所有迭代器失效

 再看这段代码

int main()
{vector<int> v1 = {2, 3, 4, 4, 5};std::vector<int>::iterator it = v1.begin();while(it != v1.end()){if(*it % 2 == 0){v1.erase(it);}++it;}return 0;
}

看似每次检查完当前元素之后, it 向后走一步, 检查下一个.
实际上, 因为进行了删除操作, 数据进行了挪动,
导致后面的元素顺序发生变化, it 所指向的内容就变成了未定义行为.

解决方法

int main()
{vector<int> v1 = {2, 3, 4, 4, 5};std::vector<int>::iterator it = v1.begin();while(it != v1.end()){if(*it % 2 == 0){it = v1.erase(it);}}return 0;
}

上面介绍 erase 和 insert 的时候说了: 这两个函数会返回一个迭代器.
此时返回的迭代器是有效的. 我们使用返回的迭代器代替已经失效的迭代器.

list 迭代器失效

list 迭代器失效: 

删除操作:使用 erase 删除元素时,只有指向被删除元素的迭代器会失效。其他迭代器,包括指向被删除元素之前和之后的元素的迭代器,仍然有效。

int main()
{std::list<int> lst = {1, 2, 3, 4, 5};std::list<int>::iterator it = lst.begin(); // it 指向第一个元素lst.erase(it); // 仅 it 失效,lst.begin() 之后的迭代器仍然有效++it; // 未定义行为,it 已经失效// it = lst.erase(it);  应该将 it 进行更新
}

所以当指向了可能导致迭代器失效的操作后, 就不要再使用那个旧的迭代器了.
需要更新当前的迭代器.


http://www.ppmy.cn/devtools/136509.html

相关文章

Linux: C语言解析域名

在上一篇博客 Linux: C语言发起 DNS 查询报文 中&#xff0c;自己构造 DNS 查询报文&#xff0c;发出去&#xff0c;接收响应&#xff0c;以二进制形式把响应的数据写入文件并进行分析。文章的最后留下一个悬念&#xff0c;就是写代码解析 DNS answer section 部分。本文来完成…

Spring Boot OA系统:企业资源规划的新选择

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

【UE5】使用基元数据对材质传参,从而避免新建材质实例

在项目中&#xff0c;经常会遇到这样的需求&#xff1a;多个模型&#xff08;例如 100 个&#xff09;使用相同的材质&#xff0c;但每个模型需要不同的参数设置&#xff0c;比如不同的颜色或随机种子等。 在这种情况下&#xff0c;创建 100 个实例材质不是最佳选择。正确的做…

【Next】中间件

概述 Next.js 的 中间件 (Middleware) 是一种在请求完成之前运行的函数&#xff0c;用于对入站请求进行处理和操作。它可以在路由匹配前执行逻辑&#xff0c;用于身份验证、请求重写、重定向、设置响应头等任务。 使用场景 身份验证&#xff1a;在用户访问页面前检查登录状态…

天润融通携手挚达科技:AI技术重塑客户服务体验

业务爆发式增长&#xff0c;但座席服务却跟不上&#xff0c;怎么办&#xff1f; 智能充电领导者的挚达科技就面临过 这样的问题&#xff0c;让我们来看看如何解决。 2010年以来&#xff0c;国内新能源汽车市场进入高速发展期&#xff0c;作为新能源汽车的重要配件&#xff0c…

Java与Kotlin在鸿蒙中的地位

在当今移动操作系统领域&#xff0c;华为推出的鸿蒙系统&#xff08;HarmonyOS&#xff09;正逐渐崭露头角&#xff0c;成为与Android、iOS并驾齐驱的操作系统之一。对于开发者而言&#xff0c;了解如何为鸿蒙系统开发高质量的应用程序变得至关重要。在这篇文章中&#xff0c;我…

ThreadLocal 和 Caffeine 缓存是两种不同的缓存机制,它们在用途和实现上有明显的区别

ThreadLocal 和 Caffeine 缓存是两种不同的缓存机制&#xff0c;它们在用途和实现上有明显的区别&#xff1a; ThreadLocal 缓存&#xff1a; ThreadLocal 提供了线程局部变量的功能&#xff0c;每个线程可以访问自己的局部变量&#xff0c;而不会与其他线程冲突。ThreadLocal …

项目中排查bug的思路案例

bug描述&#xff1a;调用了删除的接口&#xff0c;执行成功了&#xff0c;也删掉了选中的数据&#xff0c;但是不执行删除后的处理操作&#xff0c;会报一个“系统未知错误&#xff0c;请反馈给管理员” 解决&#xff1a; 成功删掉了数据&#xff0c;但删除后的操作没有执行&a…