《STL基础之vector、list、deque》

【vector、list、deque导读】vector、list、deque这三种序列式的容器,算是比较的基础容器,也是大家在日常开发中常用到的容器,因为底层用到的数据结构比较简单,笔者就将他们三者放到一起做下对比分析,介绍下基本用法,对比下三者的性能。

     

1. vector特性和原理   

     vector是个很基础的容器,其内部也就是一段连续的内存空间,具有动态扩容的能力,支持随机访问容器中的元素,查找元素的时间复杂度是O(1),插入、删除元素(除开尾部,而且vector还有备用空间的情况)会引起内存的拷贝,存在性能问题。vector提供常用的元素操作接口有:push_back、pop_back、erase、clear、insert。还有获取vector大小的size()接口、容量的capacity()接口。

    下面给出一些示例,演示vector是如何去操作元素的?

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;class A
{
public:A() {cout << "A()" << endl;}~A(){cout << "~A()" << endl;}A(const A& other){cout << "A(const A& other)" << endl;}A& operator=(const A& other){cout << "A& operator=(const A& other)" << endl;}A(const A&& other){cout << "A(const A&& other)" << endl;}A& operator= (const A&& other){cout << "A& operator= (const A&& other)" << endl;}
};int main()
{A aa;vector<A> iv(2, aa);std::cout << "size: " << iv.size() << " capacity: " << iv.capacity() << endl;std::cout << "after push back" << std::endl;iv.push_back(aa);std::cout << "size: " << iv.size() << " capacity: " << iv.capacity() << endl;return 0;
}

运行结果如下:

图片

    iv初始化的时候,往容器中插入了两个A类对象,调用了A的拷贝构造函数两次,此时iv元素个数和容量大小都是2,随后又往iv尾部插入一个A类对象,因为iv没有多余的剩余空间,那么此时vector另外寻找了个新的空间,大小为3,并把之前的两个A类对象拷贝到新的空间中去。为啥动态扩容之后,vector的大小变成了3,而不是原来大小的两倍?很炸裂,那我们就调试最新的STL源码。

图片

    很震惊,STL做了优化和改进,动态扩容不再是两倍的扩充了,而是根据元素的实际个数来扩充,所以以前老旧的观念需要改正。

std::cout << "after pop back" << std::endl;
iv.pop_back();
iv.pop_back();
std::cout << "size: " << iv.size() << " capacity: " << iv.capacity() << endl;
 

这个时候,我们在尾部弹出两个元素,那么此时又是一种什么结果?

图片

     弹出两个元素,引起A类对象的析构,元素个数变成了1,容量的大小依然是3。

     vector的删除接口erase,有按照范围删除、也有删除指定位置的元素,这两个接口的源码如下:


iterator erase(iterator first, iterator last)
{iterator i = copy(last, finish, first);destory(i, finish);finish = finish - (last - first);return first;
}iterator erase(iterator position)
{if (position + 1 != end())copy(position + 1, finish, position);--finish;destory(finish);return position;      
}

        可以看出无论是删除指定范围的元素还是删除指定位置的元素,都会涉及到元素的拷贝或者移动赋值;以下示例程序也能验证我们的结论。

std::cout << " after erase " << std::endl;
iv.erase(iv.begin(), iv.begin() + 1);
std::cout << "size: " << iv.size() << " capacity: " << iv.capacity() << endl;

图片

    从上述运行结果可以看到,删除iv容器中首个元素,引起了后面两个元素的移动,也即第二个元素挪到第一个位置去,第三个元素挪到第二个位置去。

2、 list特性和原理

     list背后的数据结构是环状双向链表,支持元素的双向遍历查找,因此list容器在元素查找上的时间复杂度为O(n),但是插入元素、删除元素的时间复杂度始终为O(1)。list支持的元素操作有push_front、push_back、erase、pop_front、pop_back、remove、unique、merge、reverse、sort,其实这些操作,无非就是对底层的链表进行头部插入、尾部删除、翻转、排序、合并等操作。


#include <iostream>
#include <list>int main()
{list<A> li;std::cout << li.size() << endl;A a;li.push_back(a);li.push_back(a);li.insert(li.begin(), a);li.erase(li.begin());return 0;
}

  运行结果:

图片

    可以看出往list容器中push元素或者insert元素,都会引起元素的拷贝构造。

3、 deque特性和原理

    deque 是由一段一段定量连续的空间构成,一旦需要在deque的前面或者尾端增加新空间,此时只需申请一段定量的连续空间,串接在deque的头部或者尾端。deque的整体架构图如下:

图片

       map并不是键值对map,而是一个指针数组,里面存储的是一个个指针,里面每个指针指向一段段连续的内存空间,这些分段的内存分别用来存储数据。虽然内存是分段的,但是给外部的表象是连续的内存空间,原因在于deque的迭代器设计的很巧妙。


template<class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator
{typedef T** map_pointer;  //指向管控中心maptypedef __deque_iterator self;T* cur;  //指向缓冲区当前的元素T* first; //指向缓冲区一个元素T* last; //指向缓冲区最后一个元素map_pointer node; //管控中心的节点
}

      假设我们在遍历元素的时候,走到了第二个缓冲区的末尾节点,此时,应该如何跳转到下一个缓冲区,且看deque的源码。


void set_node(map_pointer new_node)
{node = new_node;//下一个节点的首位元素便是firstfirst = *new_node;last = first + difference_type(buffer_size());
}self& operator++()
{++cur;if (cur == last){//跳转到下一个节点set_node(node + 1);cur = first;}return *this;
}

     好,再验证下deque插入元素,是否会涉及到插入对象的拷贝。


A a;
deque<A> idque(2, a);
idque.push_back(a);
idque.push_front(a);
idque.insert(idque.begin(), a);
idque.insert(idque.end(), a);

图片

      在头部、尾部插入元素,只会拷贝当前的对象,并不会涉及到其它对象的拷贝或者移动。那如果在容器的中间端插入对象呢?


cout << "after insert" << endl;
idque.insert(idque.begin() + 2, a);

图片

    可以清晰看到,在中间部位插入对象,还是会影响到其它元素的移动,现在新版的STL倒是做了优化和改进,使用移动构造或者移动赋值的方式去搬移对象,而不是单纯地拷贝构造或赋值。

4、 性能比对

int main()
{// 获取当前时间作为示例auto start = std::chrono::system_clock::now();A a;deque<A> idque;time_t t1 = time(NULL);for (int i = 0; i < 100 * 10000; ++i){idque.push_front(a);}// 计算差值auto end = std::chrono::system_clock::now();auto duration = end - start;cout << "deque: " << duration.count() << endl;list<A> li;start = std::chrono::system_clock::now();for (int i = 0; i < 100 * 10000; ++i){li.push_back(a);}end = std::chrono::system_clock::now();duration = end - start;cout << "list: " << duration.count() << endl;vector<A> iv;start = std::chrono::system_clock::now();for (int i = 0; i < 100 * 10000; ++i){iv.push_back(a);}end = std::chrono::system_clock::now();duration = end - start;cout << "vector: " << duration.count() << endl;return 0;
}

图片

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

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

相关文章

Windows中本地组策略编辑器gpedit.msc打不开/微软远程桌面无法复制粘贴

目录 背景 解决gpedit.msc打不开 解决复制粘贴 剪贴板的问题 启用远程桌面剪贴板与驱动器 重启RDP剪贴板监视程序 以上都不行&#xff1f;可能是操作被Win11系统阻止 最后 背景 远程桌面无法复制粘贴&#xff0c;需要查看下主机策略组设置&#xff0c;结果按WinR输入…

高精度加法乘法

高精度加法&乘法都是把数字转化成数组进行运算&#xff0c;存储 高精度加法 建议多在纸上画画&#xff0c;梳理思路 代码实现 输入字符串 //初始化数组存储 int a[250]{0}; int b[250]{0}; int c[251]{0}; //定义字符串&#xff0c;输入字符串 string s1,s2; getline(c…

Python 列表思维导图

Python 列表思维导图 腾讯云盘下载连接 https://share.weiyun.com/Ri6bUJed

获取snmp oid的小方法1(随手记)

snmpwalk遍历设备的mib # snmpwalk -v <SNMP version> -c <community-id> <IP> . snmpwalk -v 2c -c test 192.168.100.201 .根据获取的值&#xff0c;找到某一个想要的值的oid # SNMPv2-MIB::sysName.0 STRING: test1 [rootzabbix01 fonts]# snmpwalk -v…

【leetcode练习·二叉树】计算完全二叉树的节点数

本文参考labuladong算法笔记[拓展&#xff1a;如何计算完全二叉树的节点数 | labuladong 的算法笔记] 如果让你数一下一棵普通二叉树有多少个节点&#xff0c;这很简单&#xff0c;只要在二叉树的遍历框架上加一点代码就行了。 但是&#xff0c;力扣第第 222 题「完全二叉树的…

【C++语言】卡码网语言基础课系列----5. A+B问题VIII

文章目录 练习题目AB问题VIII具体代码实现 小白寄语诗词共勉 练习题目 AB问题VIII 题目描述&#xff1a; 你的任务是计算若干整数的和。 输入描述&#xff1a; 输入的第一行为一个整数N&#xff0c;接下来N行每行先输入一个整数M&#xff0c;然后在同一行内输入M个整数。 输出…

《大数据时代“快刀”:Flink实时数据处理框架优势全解析》

在数字化浪潮中&#xff0c;数据呈爆发式增长&#xff0c;实时数据处理的重要性愈发凸显。从金融交易的实时风险监控&#xff0c;到电商平台的用户行为分析&#xff0c;各行业都急需能快速处理海量数据的工具。Flink作为一款开源的分布式流处理框架&#xff0c;在这一领域崭露头…

WebStorm安装及配置迁移

一、安装 官方下载安装包 WebStorm&#xff1a;JetBrains 出品的 JavaScript 和 TypeScript IDE 适用于2024版本及以下 按需安装后重启电脑 运行WebStorm,注意不要选择大陆地区&#xff0c;语言不选择中文&#xff0c;运行激活文件 二、配置迁移 根据已有软件导出相关配置…

ARM内核:嵌入式时代的核心引擎

引言 在当今智能设备无处不在的时代&#xff0c;ARM&#xff08;Advanced RISC Machines&#xff09;处理器凭借其高性能、低功耗的特性&#xff0c;成为智能手机、物联网设备、汽车电子等领域的核心引擎。作为精简指令集&#xff08;RISC&#xff09;的典范&#xff0c;ARM核…

离线大模型-通义千问

关部署离线模型的教程就不写了&#xff0c;百度一搜一大堆 Kamailio介绍 1. Kamailio介绍 user: 您了解kamailio吗&#xff1f;assistant: 是的&#xff0c;我了解Kamailio。Kamailio是一个开源的SIP服务器和代理&#xff0c;用于处理VoIP&#xff08;Voice over Internet…

Spring Boot是什么及其优点

简介 Spring Boot是基于Spring框架开发的全新框架&#xff0c;其设计目的是简化Spring应用的初始化搭建和开发过程。 Spring Boot整合了许多框架和第三方库配置&#xff0c;几乎可以达到“开箱即用”。 优点 可快速构建独立的Spring应用。 直接嵌入Tomcat、Jetty和Underto…

FOC核心原理的C语言实现

概述 应用FOC算法&#xff0c;比如无人机、电动汽车或工业电机控制。因此&#xff0c;除了理论&#xff0c;还需要提供实用的实现步骤、常见问题及解决方案&#xff0c;比如如何获取电机的位置信息&#xff08;编码器或传感器&#xff09;&#xff0c;如何处理电流采样&#x…

前端学习-事件委托(三十)

目录 前言 课前思考 for循环注册事件 语法 事件委托 1.事件委托的好处是什么? 2.事件委托是委托给了谁&#xff0c;父元素还是子元素 3.如何找到真正触发的元素 示例代码 总结 前言 才子佳人&#xff0c;自是白衣卿相 课前思考 1.如果同时给多个元素注册事件&…

缩位求和——蓝桥杯

1.题目描述 在电子计算机普及以前&#xff0c;人们经常用一个粗略的方法来验算四则运算是否正确。 比如&#xff1a;248153720248153720 把乘数和被乘数分别逐位求和&#xff0c;如果是多位数再逐位求和&#xff0c;直到是 1 位数&#xff0c;得 24814>145 156 56 而…

万用表使用

目录 0 万用表表盘符号说明 测量功能符号 其他符号 表盘刻度符号 一、万用表的类型和功能 二、万用表的基本功能 三、万用表的使用步骤 四、万用表的注意事项 万用表是一种多功能、多量程的测量仪表,广泛应用于电子电路、电气设备的检测和维修中。以下是万用表的使用方…

Hypium+python鸿蒙原生自动化安装配置

Hypiumpython自动化搭建 文章目录 Python安装pip源配置HDC安装Hypium安装DevEco Testing Hypium插件安装及使用方法​​​​​插件安装工程创建区域 Python安装 推荐从官网获取3.10版本&#xff0c;其他版本可能出现兼容性问题 Python下载地址 下载64/32bitwindows安装文件&am…

细说机器学习算法之ROC曲线用于模型评估

系列文章目录 第一章&#xff1a;Pyhton机器学习算法之KNN 第二章&#xff1a;Pyhton机器学习算法之K—Means 第三章&#xff1a;Pyhton机器学习算法之随机森林 第四章&#xff1a;Pyhton机器学习算法之线性回归 第五章&#xff1a;Pyhton机器学习算法之有监督学习与无监督…

ROS_noetic-打印hello(√)

笔者创建的路径如下 进入到src&#xff0c; catkin_create_pkg helloworld roscpp rospy std_msgs Helloworld-C hello_C.cpp #include "ros/ros.h" int main(int argc, char *argv[]) { //执行 ros 节点初始化 ros::init(argc,argv,"hello"); //创…

冲刺蓝桥杯之速通vector!!!!!

文章目录 知识点创建增删查改 习题1习题2习题3习题4&#xff1a;习题5&#xff1a; 知识点 C的STL提供已经封装好的容器vector&#xff0c;也可叫做可变长的数组&#xff0c;vector底层就是自动扩容的顺序表&#xff0c;其中的增删查改已经封装好 创建 const int N30; vecto…

Golang 并发机制-2:Golang Goroutine 和竞争条件

在今天的软件开发中&#xff0c;我们正在使用并发的概念&#xff0c;它允许一次执行多个任务。在Go编程中&#xff0c;理解Go例程是至关重要的。本文试图详细解释什么是例程&#xff0c;它们有多轻&#xff0c;通过简单地使用“go”关键字创建它们&#xff0c;以及可能出现的竞…