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

news2025/4/13 0:45:50

序列式容器

序列式容器包括:静态数组 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;//9


cout << 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;//18

cout << 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;//18

numbers.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.coloradmin.cn/o/2333463.html

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

相关文章

小试牛刀-抽奖程序

编写抽奖程序 需求&#xff1a;设计一个抽奖程序&#xff0c;点击抽奖按钮随机抽取一个名字作为中奖者 目标&#xff1a;了解项目结构&#xff0c;简单UI布局&#xff0c;属性方法、事件方法&#xff0c;程序运行及调试 界面原型 ​ 待抽奖&#xff1a; 点击抽奖按钮&#x…

从 MySQL 切换到国产 YashanDB 数据库时,需要在数据库字段和应用连接方面进行适配 ,使用总结

YashanDB | 崖山数据库系统 - 崖山科技官网崖山数据库系统YashanDB是深圳计算科学研究院完全自主研发设计的新型数据库系统&#xff0c;融入原创理论&#xff0c;支持单机/主备、共享集群、分布式等多种部署方式&#xff0c;覆盖OLTP/HTAP/OLAP交易和分析混合负载场景&#xff…

【学习笔记】头文件中定义函数出现重复定义报错

目录 错误复现原因解决方案inlinestatic 扩展参考 错误复现 现在有一个头文件 duplicate_define.h 和两个源文件 duplicate_define_1.cpp 和 duplicate_define_2.cpp。 两个源文件都引入了头文件 duplicate_define.h&#xff0c;且在各自的函数中调用了定义在头文件中的全局函…

游戏开发中 C#、Python 和 C++ 的比较

&#x1f3ac; Verdure陌矣&#xff1a;个人主页 &#x1f389; 个人专栏: 《C/C》 | 《转载or娱乐》 &#x1f33e; 种完麦子往南走&#xff0c; 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持&#xff01;❤️ 摘要&#xff1a; 那么哪种编程语言最适合游戏开发…

DeepSeek 都开源了哪些技术?

DeepSeek作为中国领先的人工智能企业,通过开源策略推动了全球AI技术的普及与创新。以下是其官方公布的主要开源项目及其技术内容、应用场景和社区反馈的详细分析: 1. FlashMLA 技术描述:专为Hopper架构GPU优化的高效MLA(Multi-Layer Attention)解码内核,针对可变长度序列…

P8754 [蓝桥杯 2021 省 AB2] 完全平方数

题目描述 思路 一看就知道考数学&#xff0c;直接看题解试图理解(bushi) 完全平方数的质因子的指数一定为偶数。 所以 对 n 进行质因数分解&#xff0c;若质因子指数为偶数&#xff0c;对结果无影响。若质因子指数为奇数&#xff0c;则在 x 中乘以这个质因子&#xff0c;保证指…

ADGaussian:用于自动驾驶的多模态输入泛化GS方法

25年4月来自香港中文大学和浙大的论文“ADGaussian: Generalizable Gaussian Splatting for Autonomous Driving with Multi-modal Inputs”。 提出 ADGaussian 方法&#xff0c;用于可泛化的街道场景重建。所提出的方法能够从单视图输入实现高质量渲染。与之前主要关注几何细…

0501路由-react-仿低代码平台项目

文章目录 1 react路由1.1 核心库&#xff1a;React Router安装 1.2 基本路由配置路由入口组件定义路由 1.3 导航方式使用 <Link> 组件编程式导航 1.4 动态路由参数定义参数获取参数 1.5 嵌套路由父路由配置子路由占位符 1.6 重定向与404页面重定向404页面 1.7 路由守卫&a…

OpenAI即将上线新一代重磅选手——GPT-4.1

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【蓝桥杯】赛前练习

1. 排序 import os import sysn=int(input()) data=list(map(int,input().split(" "))) data.sort() for d in data:print(d,end=" ") print() for d in data[::-1]:print(d,end=" ")2. 走迷宫BFS import os import sys from collections import…

Windows 系统下用 VMware 安装 CentOS 7 虚拟机超详细教程(包含VMware和镜像安装包)

前言 资源 一、准备工作 &#xff08;一&#xff09;下载 VMware Workstation &#xff08;二&#xff09;下载 CentOS 7 镜像 二、安装 VMware Workstation&#xff08;比较简单&#xff0c;按下面走即可&#xff09; 三、创建 CentOS 7 虚拟机 四、安装 CentOS 7 系统…

五、用例篇

Bug等级&#xff1a;崩溃、严重、一般、次要 bug的生命周期 面试高频考题&#xff1a;跟开发产生争执怎么办&#xff1f; (1)反思自己&#xff0c;是不是bug描述写的不清楚 (2)站在用户思考问题&#xff0c;反问开发人员&#xff1a;“如果你是用户&#xff0c;你能接受这样…

【QT】学习笔记1

QT概述 Qt是一个1991年由QtCompany开发的跨平台C图形用户界面应用程序开发框架。它既可以开发GUI程序&#xff0c;也可用于开发非GUI程序&#xff0c;比如控制台工具和服务器。Qt是面向对象的框架&#xff0c;使用特殊的代码生成扩展&#xff08;称为元对象编译器&#xff08;…

英伟达开源253B语言模型:Llama-3.1-Nemotron-Ultra-253B-v1 模型情况

Llama-3.1-Nemotron-Ultra-253B-v1 模型情况 1. 模型概述 Llama-3.1-Nemotron-Ultra-253B-v1 是一个基于 Meta Llama-3.1-405B-Instruct 的大型语言模型 (LLM)&#xff0c;专为推理、人类对话偏好和任务&#xff08;如 RAG 和工具调用&#xff09;而优化。该模型支持 128K 令…

质检LIMS系统在半导体制造行业的应用 半导体质量革命的现状

在半导体这个“工业皇冠上的明珠”领域&#xff0c;纳米级的精度要求与质量管控如同硬币的两面。随着芯片制程向3nm、2nm演进&#xff0c;传统质检模式已难以满足海量数据、复杂工艺的质量追溯需求。质检LIMS实验室系统作为质量管理的中枢神经&#xff0c;正在重构半导体制造的…

面向对象高级(1)

文章目录 final认识final关键字修饰类&#xff1a;修饰方法&#xff1a;修饰变量final修饰变量的注意事项 常量 单例类什么是设计模式&#xff1f;单例怎么写?饿汉式单例的特点是什么&#xff1f;单例有啥应用场景&#xff0c;有啥好处&#xff1f;懒汉式单例类。 枚举类认识枚…

HTTP 压力测试工具autocannon(AI)

简介 autocannon 是一款基于 Node.js 的高性能 HTTP 压力测试工具&#xff0c;适用于评估 Web 服务的并发处理能力和性能瓶颈。 一、工具特点 高性能‌&#xff1a;利用 Node.js 异步非阻塞机制模拟高并发请求‌。‌实时监控‌&#xff1a;测试过程中动态展示请求统计和性能…

my2sql工具恢复误删数据

一、下载my2sql my2sql下载地址https://github.com/liuhr/my2sql/blob/master/releases/centOS_release_7.x/my2sql 二、my2sql工具注意事项 1. binlog格式必须为row&#xff0c;且binlog_row_imagefull 原因&#xff1a;binlog_row_image 参数决定了 binlog 中是否记录完整的…

【AGI-Eval行业动态】OpenAI 语音模型三连发,AI 语音进入“声优”时代

前言&#xff1a;OpenAI又双叒叕搞事情了&#xff01;这次他们带着三款全新语音模型强势来袭&#xff0c;直接让 AI 语音界卷出新高度&#xff01;无论是语音识别的精准度、还是根据文字生成音频的脑洞&#xff0c;这三款模型都堪称“神仙打架”。 如果你还在用老掉牙的语音助手…

蓝桥杯嵌入式十四届模拟一(eeprom)

一.LED 先配置LED的八个引脚为GPIO_OutPut&#xff0c;锁存器PD2也是&#xff0c;然后都设置为起始高电平&#xff0c;生成代码时还要去解决引脚冲突问题 二.按键 按键配置&#xff0c;由原理图按键所对引脚要GPIO_Input 生成代码&#xff0c;在文件夹中添加code文件夹&#…