STL之序列式容器(Vector/Deque/List)

序列式容器

序列式容器包括:静态数组 array 、动态数组 vector 、双端队列 deque 、单链表 forward_ list 、双链表 list 。这五个容器中,我们需要讲解三个 vector deque list 的使 用,包括:初始化、遍历、尾部插入与删除、头部插入与删除、任意位置进行插入与 删除、元素的清空、获取元素的个数与容量的大小、元素的交换、获取头部与尾部元素等。

头文件

#include <vector>
template<class T,class Allocator = std::allocator<T>
> class vector;#include <deque>
template<class T,class Allocator = std::allocator<T>
> class deque;#include <list>
template<class T,class Allocator = std::allocator<T>
> class list;

初始化容器对象

对于序列式容器而言,初始化的方式一般会有五种。

初始为空

vector<int> number;
deque<int> number;
list<int> number;

初始为多个相同的值

vector<int> number(10, 1);
deque<int> number(10, 1);
list<int> number(10, 1);

使用迭代器范围

int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
vector<int> number(arr, arr + 10);//左闭右开区间
//vector可以直接替换为deque与list

使用大括号

vector<int> number = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//vector可以直接替换为deque与list

拷贝构造或者移动构造

vector<int> number1 = {1, 2, 4, 6};
vector<int> number2(number1);
vector<int> number3(std::move(number1));
//vector可以直接替换为deque与list

遍历容器中的元素

就是访问容器中的每个元素,一般可以采用下标或者迭代器的方式进行遍历。

//1、使用下标进行遍历(要求容器必须是支持下标访问的,list不支持下标,所以就不适用)
for(size_t idx = 0; idx != number.size(); ++idx)
{cout << number[idx] << " ";
}//2、使用未初始化的迭代器进行遍历
vector<int>::iterator it;
for(it = numbers.begin(); it != numbers.end(); ++it)
{cout << *it << " ";
}//3、使用初始化迭代器进行遍历
vector<int>::iterator it = number.begin();
for(; it != number.end(); ++it)
{cout << *it << " ";
}//4、使用for加上auto进行遍历
for(auto &elem : number)
{cout << elem << " ";
}

在容器的尾部插入与删除

可以向容器的尾部插入一个元素或者将容器的最后一个元素删除。

//尾部插入一个元素(注意:是拷贝一份副本到尾部)
void push_back( const T& value);
void push_back( T&& value);
//尾部删除
void pop_back();

在容器的头部插入与删除

可以向容器的头部插入一个元素或者将容器的第一个元素删除。

//头部插入
void push_front( const T& value);
void push_front( T&& value);
//头部删除
void pop_front();
对于 deque list 而言,是支持这两个操作的, 但是对于vector没有提供这两个操作。 vector 不支持在头部进行插入元素与删除元素。 这是从效率方面进行的考虑。

vector的原理

vector 头部是固定的,不能进行插入与删除,只提供了在尾部进行插入与删除的操作,所以如果真的要在头部插入或者删除,那么其他的元素会发生移动,这样操作就比较复杂。
探讨 vector 底层实现 ,三个指针:
_ M _ start :指向第一个元素的位置;
_ M _ finish :指向最后一个元素的下一个位置;
_ M _ end _ of _ storage :指向当前分配空间的最后一个位置的下一个位置;
那么这三个指针是怎么来的呢,我们可以从其源码中获取答案(这里可以阅读 vector的源码)


void test()
{
vector<int> number = {1, 2, 3, 4};
&number;//error,只是获取对象栈上的地址,也就是_M_start的地址
&number[0];//ok
&*number.begin();//ok
int *pdata = number.data();//ok
cout << "pdata = " << pdata << endl;//使用printf,思考一下printf与cout打印地址的区别
}

deque的原理

探索 deque 底层实现
deque 是由多个片段组成的,片段内部是连续的,但是片段之间不连续的、分散的,多个片段被一个称为中控器的结构控制,所以说deque 是在物理上是不连续的,但是逻辑上是连续的。
我们依旧可以从其源码中获取答案(这里可以阅读 deque 的源码)
从继承图中可以看到,中控器其实是一个二级指针,由 _M_map 表示,还有一个表示中控器数组大小的 _M_map_size deque 的迭代器也不是一个简单类型的指针,其迭代器是一个类类型,deque 有两个迭代器指针,一个指向第一个小片段,一个指向最后一个小片段。其结构图如下:

list的原理

list是双向链表,其实现如下:

在容器的任意位置插入

三种序列式容器在任意位置进行插入的操作是insert函数,函数接口如下

//1、在容器的某个位置前面插入一个元素
iterator insert( iterator pos, const T& value );
iterator insert( const_iterator pos, const T& value );
number.insert(it, 22);//2、在容器的某个位置前面插入count个相同元素
void insert(iterator pos, size_type count, const T& value);
iterator insert(const_iterator pos, size_type count, const T& value);
number.insert(it1, 4, 44);//3、在容器的某个位置前面插入迭代器范围的元素
template<class InputIt>
void insert(iterator pos, InputIt first, InputIt last);
template<class InputIt>
iterator insert(const_iterator pos, InputIt first, InputIt last);
vector<int> vec{51, 52, 53, 54, 55, 56, 57, 58, 59};
number.insert(it, vec.begin(), vec.end());//4、在容器的某个位置前面插入大括号范围的元素
iterator insert(const_iterator pos, std::initializer_list<T> ilist);
number.insert(it, std::initialiser_list<int>{1, 2, 3});
三种序列式容器的插入示例如下:
//此处list可以换成vector或者deque
list<int> number = {1, 4, 6, 8, 9};
++it;
auto it = number.begin();//1、在容器的某个位置前面插入一个元素
number.insert(it, 22);//2、在容器的某个位置前面插入count个相同元素
number.insert(it, 3, 100);//3、在容器的某个位置前面插入迭代器范围的元素
vector<int> vec{51, 52, 53, 54, 55, 56, 57, 58, 59};
number.insert(it, vec.begin(), vec.end());//4、在容器的某个位置前面插入大括号范围的元素
number.insert(it, {1, 2, 3});
insert 在任意位置进行插入, list 使用起来很好,没有任何问题,但是 deque vector 使用起来可能会出现问题,因为 vector 是物理上连续的 ,所以在中间插入元素会导致插入元素后面的所有元素向后移动,deque 也有类似情况, 可能因为插入而引起底层容量 不够而扩容,从而使得迭代器失效 ( 申请了新的空间,但是迭代器还指向老的空间 ) ,即使没有扩容,插入之后的迭代器也失效了( 不再指向之前的元素了 )

vector的迭代器失效

vector 为例,如果使用 insert 插入元素,而每次插入元素的个数不确定,可能剩余空间不足以存放插入元素的个数,那么insert 在插入的时候 底层就可能导致扩容,从而导 致迭代器还指向老的空间,继续使用该迭代器会出现迭代器失效的问题
void test()
{
vector<int> number = {1, 2, 3, 4, 5, 6, 7, 8, 9};
display(number);
cout << "number.size() = " << numbers.size() << endl;//9
cout << "number.capacity() = " << numbers.capacity() << endl;//9cout << endl << "在容器尾部进行插入: " << endl;
number.push_back(10);
number.push_back(11);
display(number);
cout << "number.size() = " << number.size() << endl;//11
cout << "number.capacity() = " << number.capacity() << endl;//18cout << endl << "在容器vector中间进行插入: " << endl;
auto it = number.begin();
++it;
++it;
number.insert(it, 22);
display(number);
cout << "*it = " << *it << endl;
cout << "number.size() = " << number.size() << endl;//12
cout << "number.capacity() = " << number.capacity() << endl;//18numbers.insert(it, 7, 100);//因为插入个数不确定,有可能底层已经发生了扩容
display(numbers);
cout << "*it = " << *it << endl;
cout << "numbers.size() = " << numbers.size() << endl;//19
cout << "numbers.capacity() = " << numbers.capacity() <<endl;//24//正确办法是重置迭代器的位置
vector<int> vec{51, 52, 53, 56, 57, 59};
numbers.insert(it, vec.begin(), vec.end());//继续使用该迭代器就会出现问题(内存错误)
display(numbers);
cout << "*it = " << *it << endl;
cout << "numbers.size() = " << numbers.size() << endl;
cout << "numbers.capacity() = " << numbers.capacity() << endl;//解决方案:每次在插入元素的时候,可以将迭代器的位置进行重置更新,避免因为底层扩容,迭代器还指向老
//的空间而出现问题
vector<int> vec{51, 52, 53, 56, 57, 59};
it = number.begin();//重新置位
++it;
++it;
numbers.insert(it, vec.begin(), vec.end());//继续使用该迭代器就会出现问题(内存错误)
display(numbers);
cout << "*it = " << *it << endl;
cout << "numbers.size() = " << numbers.size() << endl;
cout << "numbers.capacity() = " << numbers.capacity() << endl;
}
因为 vector push _ back 操作每次只会插入一个元素,所以可以按照统一的形式 2 * capacity() ,但是 insert 的时候,插入的元素个数是不定的,所以就不能一概而论。这里可以分别讨论一下,我们设置capacity () = n , size () = m , insert 插入的元素个数为 t个:
如果 t < n - m ,新插入元素的个数比剩余空间小,这个时候就无需扩容,所以直接插入;
如果 n - m < t < m ,就按照 m 2 倍去进行扩容,新的空间就是 2 * m ;如果n - m < t < n t > m , 就按照 t + m 去进行扩容;
如果 t > n 时,依旧按照 t + m 去进行扩容 ;
这就是vector 进行 insert 扩容的原理(这个原理可以了解一下,主要是为了告诉大家不
是两倍扩容)。

在容器的任意位置删除元素

三种序列式容器的删除操作是erase函数,函数接口如下

//删除指定迭代器位置的元素
iterator erase(iterator position);
//删除一个迭代器范围的元素
iterator erase(iterator first, iterator last);
对于 vector 而言,会导致删除迭代器之后的所有元素前移,从而导致删除元素之后的所有迭代器失效(迭代器的位置没有改变,但是因为元素的移动,导致迭代器指向的不是删除之前的元素,所以失效);deque vector 复杂,要看 pos 前后的元素个数来决定,deque erase 函数可以看 STL 源码,需要看删除位置与 size () 的一半的大小,然后看是挪动前一半还是后一半,尽量减少挪动的次数;list 会删除指向的元素,从而导致指向删除元素的迭代器失效。
这里以 vector erase 为例,看看其删除元素的操作与删除后的效果。
//题意:删除vector中所有值为4的元素。
vector<int> vec = {1, 3, 5, 4, 4, 4, 4, 7, 8,4, 9};
for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)
{if(4 == *it){vec.erase(it);}
}//发现删除后有些4没有删除掉,可以推测出是什么原因吗?是那些4没有删除呢?
//正确解法:
for (auto it = vec.begin(); it != vec.end();)
{ if (4 == *it){vec.erase(it);//此处可以使用it接收erase的结果,更通用一些,即:it = vec.erase(it);}else{++it;}
}

其他操作

//1、清除容器中的所有元素(三个序列式容器都有)
void clear();//2、获取元素个数(三个序列式容器都有)
size_type size() const;//3、获取容量大小(只有vector有)
size_type capacity() const;//4、回收多余的空间,使得元素的个数与容量大小对应,不存在没有使用的空间(vector与deque有这个函数)
void shrink_to_fit();//5、交换两个相同容器中的元素(三个序列式容器都有)
void swap( vector& other);
vector<int> number1 = {1, 2, 3};
vector<int> number2 = {10, 20, 30};
number1.swap(number2);//之后number1中的内容与number2中的内容做了交换//6、更改容器中元素个数(三个序列式容器都有)
//以vector为例,执行resize时候,如果count < size(),就将多余的元素删除;如果count > size(),就在//之前的元素后面执行insert添加元素(没有指定就添加默认值),元素的个数在改变的同时,容量也在发生改变 //(上一次的两倍或者本次元素个数)
void resize( size_type count, T value = T() );
void resize( size_type count);
void resize( size_type count, const value_type& value);//7、获取第一个元素(三个序列式容器都有)
reference front();
const_reference front() const;//8、获取最后一个元素(三个序列式容器都有)
reference back();
const_reference back() const;//9、C++11增加的可变参数模板的几个函数
//在容器的尾部就地构造一个元素
template< class... Args >
void emplace_back( Args&&... args);
vector<Point> vec;
vec.emplace_back(1, 2);//就地将(1, 2)构建为一个对象存放在vector的尾部,减少拷贝或者移动

list的特殊操作

排序函数sort

void sort();//默认以升序进行排序,其实也就是,使用operator<进行排序
template< class Compare >
void sort(Compare comp);//其实也就是传入一个具有比较的类型,即函数对象
template <typename T1, typename T2>
struct Compare
{bool operator()(const T1 &a, const T2 &b) const{return a < b;}
};

移除重复元素u n i q u e

void unique();
size_type unique();
注意使用 unique 的时候,要保证元素 list 是已经排好顺序的,否则使用 unique 是没有用的。

逆置链表中的元素r e v e r s e

void reverse();
void reverse() noexcept;
将链表中的元素逆置。

合并链表的函数m e r g e

//合并两个链表(other既可以是左值也可以是右值)
void merge( list& other );
void merge( list&& other );
template <class Compare>
void merge( list& other, Compare comp );
template <class Compare>
void merge( list&& other, Compare comp );
合并的链表必须是有序的,如果没有顺序,合并没有效果。两个链表合并之后,并且另一个链表就为空了。

从一个链表转移元素到另一个链表s p l i c e

//移动other链表到另一个链表的某个指定位置前面
void splice(const_iterator pos, list& other);
void splice(const_iterator pos, list&& other);//移动other链表中的某个元素到另一个链表的某个指定位置前面
void splice(const_iterator pos, list& other, const_iterator it);
void splice(const_iterator pos, list&& other, const_iterator it);//移动other链表的一对迭代器范围元素到另一个链表的某个指定位置前面
void splice(const_iterator pos,list& other, const_iterator first, const_iterator last);
void splice(const_iterator pos,list&& other,const_iterator first, const_iterator last);

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

swift菜鸟教程6-10(运算符,条件,循环,字符串,字符)

一个朴实无华的目录 今日学习内容&#xff1a;1.Swift 运算符算术运算符比较运算符逻辑运算符位运算符赋值运算区间运算符其他运算符 2.Swift 条件语句3.Swift 循环4.Swift 字符串字符串属性 isEmpty字符串常量let 变量var字符串中插入值字符串连接字符串长度 String.count使用…

泛微ECOLOGY9 记 数据展现集成 自定义开窗测试中对SQL 的IN语法转换存在BUG

背景 搭建流程时&#xff0c;需将明细表1中的合同字段 供明细表2中的合同开窗查询使用。 最终实现如下图&#xff1a; 选择 发票号时&#xff0c;自动带出明细表1中的采购合同号清单&#xff0c;然后在明细表2中开窗采购合同号时&#xff0c;只跳出明细表1中有的采购合同号&am…

(自用)蓝桥杯准备(需要写的基础)

要写的文件 led_app lcd_app key_app adc_app usart_app scheduler LHF_SYS一、外设引脚配置 1. 按键引脚 按键引脚配置如下&#xff1a; B1&#xff1a;PB0B2&#xff1a;PB1B3&#xff1a;PB2B4&#xff1a;PA0 2. LCD引脚 LCD引脚配置如下&#xff1a; GPIO_Pin_9 /* …

PM2 完全指南:Node.js 应用后台启动、关闭与重启详解

文章目录 **PM2 完全指南&#xff1a;Node.js 应用后台启动、关闭与重启详解****1. 什么是 PM2&#xff1f;****2. 安装 PM2****全局安装****验证安装** **3. 使用 PM2 启动 Node.js 应用****基本启动****指定应用名称****集群模式&#xff08;多进程负载均衡&#xff09;****监…

Linux环境变量详解

引言 在Linux系统中&#xff0c;环境变量是一种非常重要的概念&#xff0c;它影响着系统的运行方式和应用程序的行为。无论你是Linux新手还是经验丰富的管理员&#xff0c;深入理解环境变量都能帮助你更高效地使用和管理Linux系统。本文将从基础概念到高级应用&#xff0c;全面…

408 计算机网络 知识点记忆(8)

前言 本文基于王道考研课程与湖科大计算机网络课程教学内容&#xff0c;系统梳理核心知识记忆点和框架&#xff0c;既为个人复习沉淀思考&#xff0c;亦希望能与同行者互助共进。&#xff08;PS&#xff1a;后续将持续迭代优化细节&#xff09; 往期内容 408 计算机网络 知识…

@linux系统SSL证书转换(Openssl转换PFX)

在Linux中&#xff0c;你可以使用OpenSSL工具将PFX/P12格式的证书转换为单独的CRT&#xff08;证书&#xff09;、KEY&#xff08;私钥&#xff09;文件以及提取证书链 1. 提取私钥文件(.key) openssl pkcs12 -in your_certificate.pfx -nocerts -out private.key -nodes系统会…

DAOS系统架构-组件

如上图所示&#xff0c;一个完整的DAOS系统是由管理节点组件、客户端节点组件、服务端节点组件以及网络通信组件四个部分组成。管理节点组件通过管理网络通道&#xff08;蓝色&#xff09;对DAOS服务管理和监控。客户端节点组件通过数据网络通道&#xff08;红色&#xff09;与…

制作一款打飞机游戏教程2:背景滚动

滚动原型开发 接下来&#xff0c;我们开始聚焦滚动原型的开发。我们需要确定游戏关卡的长度以及背景滚动的速度。 地图与精灵空间限制 在开发过程中&#xff0c;我们遇到了地图与精灵空间限制的问题。PICO 8的地图编辑器下半部分与精灵表共享空间&#xff0c;这意味着我们只…

计算机组成原理——CPU与存储器连接例题

计算机组成原理——CPU与存储器连接例题 设CPU共有16根地址线和8根数据线&#xff0c;并用(MREQ) ̅作为访存控制信号&#xff08;低电平有效&#xff09;&#xff0c;(WR) ̅作为读/写命令信号&#xff08;高电平读&#xff0c;低电平写&#xff09;。现有下列存储芯片&#…

GNSS静态数据处理

1 安装数据处理软件&#xff1a;仪器之星&#xff08;InStar &#xff09;和 Trimble Business Center 做完控制点静态后&#xff0c;我们需要下载GNSS数据&#xff0c;对静态数据进行处理。在处理之前需要将相关软件在自己电脑上安装好&#xff1a; 仪器之星&#xff08;InS…

Process Explorer 性能调优实战:精准定位资源泄漏与高负载进程

一、下载与安装 ‌下载地址‌ Process Explorer安装包下载&#xff1a;https://pan.quark.cn/s/950c36ba5364下载后解压压缩包&#xff0c;运行 procexp.exe&#xff08;32 位系统&#xff09;或 procexp64.exe&#xff08;64 位系统&#xff09;‌。 ‌界面概览‌ 主界面以树…

SVMSPro分布式综合安防管理平台-->以S3存储革新,开启智能安防新纪元

SVMSPro分布式综合安防管理平台–>以S3存储革新&#xff0c;开启智能安防新纪元 在数字化转型浪潮下&#xff0c;企业安防管理正面临海量数据存储、跨区域协同以及数据安全的严峻挑战。如何实现高效、弹性、低成本的存储扩容&#xff1f;如何确保关键录像数据万无一失&…

Python 装饰器(Decorator)

文章目录 代码解析1. 装饰器定义 timer(func)2. 应用装饰器 timer **执行流程****关键点****实际应用场景****改进版本&#xff08;带 functools.wraps&#xff09;** 这是一个 Python 装饰器&#xff08;Decorator&#xff09; 的示例&#xff0c;用于测量函数的执行时间。下…

git commit时自动生成Change-ID

创建全局钩子目录&#xff1a; 创建一个全局的Git hooks目录&#xff1a; mkdir -p ~/.githooks 下载并设置commit-msg钩子脚本&#xff1a; 下载Gerrit的commit-msg钩子脚本&#xff0c;并放置在全局钩子目录中(如下载不了&#xff0c;可从本页面附件中下载&#xff0c;“…

最新Ktransformers v0.24(Docker)并发部署DeepSeek-V3-0324模型

一、介绍 KTransformers v0.2.4 发布说明 我们非常高兴地宣布&#xff0c;期待已久的 KTransformers v0.2.4 现已正式发布&#xff01;在这个版本中&#xff0c;我们对整 体架构进行了重大重构&#xff0c;更新了超过 1 万行代码&#xff0c;为社区带来了备受期待的多并发支…

飞牛私有云5大硬核功能实测!

&#x1f4f8; 1. 智能相册&#xff1a;AI搜图原图自由 - 自动备份&#xff1a;手机照片/视频实时同步&#xff0c;支持RAW格式、实况照片无损备份&#xff0c;释放128G手机秒变256G。 - AI黑科技&#xff1a; - 人脸识别&#xff1a;自动归类人物相册&#xff0c;输入「妈妈…

webrtc pacer模块(一) 平滑处理的实现

Pacer起到平滑码率的作用&#xff0c;使发送到网络上的码率稳定。如下的这张创建Pacer的流程图&#xff0c;其中PacerSender就是Pacer&#xff0c;其中PacerSender就是Pacer。这篇文章介绍它的核心子类PacingController及Periodic模式下平滑处理的基本流程。平滑处理流程中还有…

【android bluetooth 协议分析 01】【HCI 层介绍 1】【hci_packets.pdl 介绍】

在 AOSP 的蓝牙协议栈 (Gabeldorsche) 中&#xff0c;hci_packets.pdl 是一个 协议描述语言文件&#xff0c;用于定义 HCI (Host Controller Interface) 层的数据包结构和通信协议。以下是详细解析&#xff1a; 1. 文件作用 system/gd/hci/hci_packets.pdl 协议自动化生成&…

操作系统 4.2-键盘

键盘中断初始化和处理 提取的代码如下&#xff1a; // con_init 函数&#xff0c;初始化控制台&#xff08;包括键盘&#xff09;的中断 void con_init(void) {set_trap_gate(0x21, &keyboard_interrupt); } ​ // 键盘中断处理函数 .globl _keyboard_interrupt _keyboard…