Qt 学习之路:存储容器

存储容器(containers)有时候也被称为集合(collections),是能够在内存中存储其它特定类型的对象,通常是一些常用的数据结构,一般是通用模板类的形式。C++ 提供了一套完整的解决方案,作为标准模板库(Standard Template Library)的组成部分,也就是常说的 STL。

Qt 提供了另外一套基于模板的容器类。相比 STL,这些容器类通常更轻量、更安全、更容易使用。如果你对 STL 不大熟悉,或者更喜欢 Qt 风格的 API,那么你就应该选择使用这些类。当然,你也可以在 Qt 中使用 STL 容器,没有任何问题。

本章的目的,是让你能够选择使用哪个容器,而不是告诉你这个类都哪些函数。这个问题可以在文档中找到更清晰的回答。

 

Qt 的容器类都不继承QObject,都提供了隐式数据共享、不可变的特性,并且为速度做了优化,具有较低的内存占用量等。另外一点比较重要的,它们是线程安全的。这些容器类是平台无关的,即不因编译器的不同而具有不同的实现;隐式数据共享,有时也被称作“写时复制(copy on write)”,这种技术允许在容器类中使用传值参数,但却不会出现额外的性能损失。遍历是容器类的重要操作。Qt 容器类提供了类似 Java 的遍历器语法,同样也提供了类似 STL 的遍历器语法,以方便用户选择自己习惯的编码方式。相比而言,Java 风格的遍历器更易用,是一种高层次的函数;而 STL 风格的遍历器更高效,同时能够支持 Qt 和 STL 的通用算法。最后一点,在一些嵌入式平台,STL 往往是不可用的,这时你就只能使用 Qt 提供的容器类,除非你想自己创建。顺便提一句,除了遍历器,Qt 还提供了自己的 foreach 语法(C++ 11 也提供了类似的语法,但有所区别,详见这里的 foreach 循环一节)。

Qt 提供了顺序存储容器:QListQLinkedListQVectorQStackQQueue。对于绝大多数应用程序,QList是最好的选择。虽然它是基于数组实现的列表,但它提供了快速的向前添加和向后追加的操作。如果你需要链表,可以使用QLinkedList。如果你希望所有元素占用连续地址空间,可以选择QVectorQStackQQueue则是 LIFO 和 FIFO 的。

Qt 还提供了关联容器:QMapQMultiMapQHashQMultiHashQSet。带有“Multi”字样的容器支持在一个键上面关联多个值。“Hash”容器提供了基于散列函数的更快的查找,而非 Hash 容器则是基于二分搜索的有序集合。

另外两个特例:QCacheQContiguousCache提供了在有限缓存空间中的高效 hash 查找。

我们将 Qt 提供的各个容器类总结如下:

  • QList<T>:这是至今为止提供的最通用的容器类。它将给定的类型 T 的对象以列表的形式进行存储,与一个整型的索引关联。QList在内部使用数组实现,同时提供基于索引的快速访问。我们可以使用 QList::append()QList::prepend()在列表尾部或头部添加元素,也可以使用QList::insert()在中间插入。相比其它容器类,QList专门为这种修改操作作了优化。QStringList继承自QList<QString>
  • QLinkedList<T>:类似于 QList,除了它是使用遍历器进行遍历,而不是基于整数索引的随机访问。对于在中部插入大量数据,它的性能要优于QList。同时具有更好的遍历器语义(只要数据元素存在,QLinkedList的遍历器就会指向一个合法元素,相比而言,当插入或删除数据时,QList的遍历器就会指向一个非法值)。
  • QVector<T>:用于在内存的连续区存储一系列给定类型的值。在头部或中间插入数据可能会非常慢,因为这会引起大量数据在内存中的移动。
  • QStack<T>:这是QVector的子类,提供了后进先出(LIFO)语义。相比QVector,它提供了额外的函数:push()pop()top()
  • QQueue<T>:这是QList的子类,提供了先进先出(FIFO)语义。相比QList,它提供了额外的函数:enqueue()dequeue()head()
  • QSet<T>:提供单值的数学上面的集合,具有快速的查找性能。
  • QMap<Key, T>:提供了字典数据结构(关联数组),将类型 T 的值同类型 Key 的键关联起来。通常,每个键与一个值关联。QMap以键的顺序存储数据;如果顺序无关,QHash提供了更好的性能。
  • QMultiMap<Key, T>:这是QMap的子类,提供了多值映射:一个键可以与多个值关联。
  • QHash<Key, T>:该类同QMap的接口几乎相同,但是提供了更快的查找。QHash以字母顺序存储数据。
  • QMultiHash<Key, T>:这是QHash的子类,提供了多值散列。

所有的容器都可以嵌套。例如,QMap<QString, QList<int> >是一个映射,其键是QString类型,值是QList<int>类型,也就是说,每个值都可以存储多个 int。这里需要注意的是,C++ 编译器会将连续的两个 > 当做输入重定向运算符,因此,这里的两个 > 中间必须有一个空格。

能够存储在容器中的数据必须是可赋值数据类型。所谓可赋值数据类型,是指具有默认构造函数、拷贝构造函数和赋值运算符的类型。绝大多数数据类型,包括基本类型,比如 int 和 double,指针,Qt 数据类型,例如QStringQDateQTime,都是可赋值数据类型。但是,QObject及其子类(QWidgetQTimer等)都不是。也就是说,你不能使用QList<QWidget>这种容器,因为QWidget的拷贝构造函数和赋值运算符不可用。如果你需要这种类型的容器,只能存储其指针,也就是QList<QWidget *>

如果要使用QMap或者QHash,作为键的类型必须提供额外的辅助函数。QMap的键必须提供operator<()重载,QHash的键必须提供operator==()重载和一个名字是qHash()的全局函数。

作为例子,我们考虑如下的代码:

作为 struct,我们当做纯数据类使用。这个类没有额外的构造函数,因此编译器会为我们生成一个默认构造函数。同时,编译器还会生成默认的拷贝构造函数和赋值运算符。这就满足了将其放入容器类存储的条件:

Qt 容器类可以直接使用QDataStream进行存取。此时,容器中所存储的类型必须也能够使用QDataStream进行存储。这意味着,我们需要重载operator<<()operator>>()运算符:

根据数据结构的相关内容,我们有必要对这些容器类的算法复杂性进行定量分析。算法复杂度关心的是在数据量增长时,容器的每一个函数究竟有多快(或者多慢)。例如,向QLinkedList中部插入数据是一个相当快的操作,并且与QLinkedList中已经存储的数据量无关。另一方面,如果QVector中已经保存了大量数据,向QVector中部插入数据会非常慢,因为在内存中,有一半的数据必须移动位置。为了描述算法复杂度,我们引入 O 记号(大写字母 O,读作“大 O”):

  • 常量时间:O(1)。如果一个函数的运行时间与容器中数据量无关,我们说这个函数是常量时间的。QLinkedList::insert()就是常量时间的。
  • 对数时间:O(log n)。如果一个函数的运行时间是容器数据量的对数关系,我们说这个函数是对数时间的。qBinaryFind()就是对数时间的。
  • 线性时间:O(n)。如果一个函数的运行时间是容器数据量的线性关系,也就是说直接与数量相关,我们说这个函数是限行时间的。QVector::insert()就是线性时间的。
  • 线性对数时间:O(n log n)。线性对数时间要比线性时间慢,但是要比平方时间快。
  • 平方时间:O(n²)。平方时间与容器数据量的平方关系。

基于上面的表示,我们来看看 Qt 顺序容器的算法复杂度:

 查找插入前方添加后方追加
QLinkedList<T>O(n)O(1)O(1)O(1)
QList<T>O(1)O(n)统计 O(1)统计 O(1)
QVector<T>O(1)O(n)O(n)统计 O(1)

上表中,所谓“统计”,意思是统计意义上的数据。例如“统计 O(1)”是说,如果只调用一次,其运行时间是 O(n),但是如果调用多次(例如 n 次),则平均时间是 O(1)。

下表则是关联容器的算法复杂度:

 查找键插入
平均最坏平均最坏
QMap<Key, T>O(log n)O(log n)O(log n)O(log n)
QMultiMap<Key, T>O(log n)O(log n)O(log n)O(log n)
QHash<Key, T>统计 O(1)O(n)O(1)统计 O(n)
QSet<Key, T>统计 O(1)O(n)O(1)统计 O(n)

QVectorQHashQSet的头部添加是统计意义上的 O(log n)。然而,通过给定插入之前的元素个数来调用QVector::reserve()QHash::reserve()QSet::reserve(),我们可以把复杂度降到 O(1)。我们会在下面详细讨论这个问题。

QVector<T>QStringQByteArray在连续内存空间中存储数据。QList<T>维护指向其数据的指针数组,提供基于索引的快速访问(如果 T 就是指针类型,或者是与指针大小相同的其它类型,那么 QList 的内部数组中存的就是其实际值,而不是其指针)。QHash<Key, T>维护一张散列表,其大小与散列中数据量相同。为避免每次插入数据都要重新分配数据空间,这些类都提供了多余实际值的数据位。

我们通过下面的代码来了解这一算法:

我们创建了一个字符串,每次动态追加一个字符。假设我们需要追加 15000 个字符。在算法运行过程中,当达到以下空间时,会进行重新分配内存空间,一共会有 18 次:4,8,12,16,20,52,116,244,500,1012,2036,4084,6132,8180,10228,12276,14324,16372。最后,这个 out 对象一共有 16372 个 Unicode 字符,其中 15000 个是有实际数据的。

上面的分配数据有些奇怪,其实是有章可循的:

  • QString每次分配 4 个字符,直到达到 20。
  • 在 20 到 4084 期间,每次分配大约一倍。准确地说,每次会分配下一个 2 的幂减 12。(某些内存分配器在分配 2 的幂数时会有非常差的性能,因为他们会占用某些字节做预订)
  • 自 4084 起,每次多分配 2048 个字符(4096 字节)。这是有特定意义的,因为现代操作系统在重新分配一个缓存时,不会复制整个数据;物理内存页只是简单地被重新排序,只有第一页和最后一页的数据会被复制。

QByteArrayQList<T>实际算法与QString非常类似。

对于那些能够使用memcry()(包括基本的 C++ 类型,指针类型和 Qt 的共享类)函数在内存中移动的数据类型,QVector<T>也使用了类似的算法;对于那些只能使用拷贝构造函数和析构函数才能移动的数据类型,使用的是另外一套算法。由于后者的消耗更高,所以QVector<T>减少了超出空间时每次所要分配的额外内存数。

QHash<Key, T>则是完全不同的形式。QHash的内部散列表每次会增加 2 的幂数;每次增加时,所有数据都要重新分配到新的桶中,其计算公式是qHash(key) % QHash::capacity()QHash::capacity()就是桶的数量)。这种算法同样适用于 QSet<T>QCache<Key, T>。如果你不明白“桶”的概念,请查阅数据结构的相关内容。

对于大多数应用程序。Qt 默认的增长算法已经足够。如果你需要额外的控制,QVector<T>QHash<Key, T>QSet<T>QStringQByteArray提供了一系列函数,用于检测和指定究竟要分配多少内存:

  • capacity():返回实际已经分配内存的元素数目(对于QHashQSet,则是散列表中桶的个数)
  • reserve(size):为指定数目的元素显式地预分配内存。
  • squeeze():释放那些不需要真实存储数据的内存空间。

如果你知道容器大约有多少数据,那么你可以通过调用reserve()函数来减少内存占用。如果已经将所有数据全部存入容器,则可以调用squeeze()函数,释放所有未使用的预分配空间。

转载于:https://www.cnblogs.com/lvdongjie/p/4809348.html

爆改YOLOv8 | PSA注意力机制融合入YOLOv8 yolov8,PSA注意力,即插即用 阅读详情

相关推荐

AVDepthData、LiDAR 深度数据人像模式全解析:iOS 真实景深图采集处理实战

在 iOS 高端影像系统中,真实景深信息的采集利用已成为区分旗舰设备基础设备的重要能力。Apple 自 iPhone 7 Plus 引入双摄虚化以来,逐步发展出基于 AVDepthData、TrueDepth LiDAR 的多维深度感知技术,并开放接口供开发者获取高精度深度图,构建背景虚化、人像分割、3D 重建等多种功能。本文将深入解析 AVDepthData 的结构使用方式,系统讲解 LiDAR 采集架构、人像模式的景深融合逻辑,以及如何在实际项目中采集、预览并处理深度图数据,实现具备工程可用性

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 1825

C++ tuple元组的基本用法(总结)

1,元组简介     tuple是一个固定大小的不同类型值的集合,是泛化的std::pair。我们也可以把他当做一个通用的结构体来用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。std::tuple理论上可以有无数个任意类型的成员变量,而std::pair只能是2个成员,因此在需要保存3个及以上的数据时就需要使用tuple元组了。     tuple(元组)在c++11中开始引用的。tuple看似简单,其实它是简约而不简单,可以说它是c++11中一个既简单又复杂的东东,关于它简单的一面是它很容易使用,复杂的一面是它内部隐藏了太多细节,要揭开它神秘的面纱时又

qt-sample-scripts:xTuple 桌面客户端的扩展,包含脚本功能的示例

qt-sample-scripts 这个存储库包含 xTuple 桌面客户端的扩展,其中包含脚本可以做什么的示例。 这些示例不会组合成一个完整的功能,但它们显示了许多关键功能: 推荐的扩展目录结构 package.xml文件的格式 如何打开不同类型的窗口 - QMainWindow、QDialog 和 QWidget 如何修改现有的应用程序窗口 如何为您的扩展创建全新的窗口 如何使用 XTreeWidget 如何允许翻译您的扩展 和更多。

【翻译】Qt容器概述(Qt6)

一、描述 Qt 库提供了一组通用的基于模板的容器类。这些类可用于存储指定类型的项目。 这些容器类被设计为比 STL 容器更轻、更安全且更易于使用。如果您不熟悉 STL,或者更喜欢以“Qt 方式”做事,您可以使用这些类而不是 STL 类。 容器类是隐式共享的,它们是可重入的,并且它们针对速度、低内存消耗和最小的内联代码扩展进行了优化,从而产生更小的可执行文件。此外,在所有用于访问它们的线程将它们用作只读容器的情况下,它们是线程安全的。 二、容器Qt 提供以下顺序容器:QList、QStack 、

友善啊,朋友的博客 2197

QT6 的QList变化

2020年9月30日(星期三)格卢别夫|评论意见 使用Qt 6,许多组件都会发生更改。集装箱也不例外。在这篇博客文章中,我试图捕捉对QList和相关类最重要的更改。 Q向量和QList是统一的 在此之前,Qt为这两个容器提供了非常不同的实现:QVector是一个自然而直观的类似数组的容器,而QList在其实现中非常特殊,以很好地适应Qt本身定义和使用的类型。通过对现有类型的Qt 6更新,在以前版本中已经完成的工作的支持下,类之间的实现差异似乎没有什么好处。此外,在许多情况下,Q向量被证明是一个更好..

晴树的专栏 2405

Qt学习之路(35): Qt容器类之顺序存储容器

本来计划先来说下model/view的,结果发现model/view涉及到一些关于容器的内容,于是就把容器部分提前了。   容器Containers,有时候也被称为集合collections,指的是能够在内存中存储其他特定类型的对象的对象,这种对象一般是通用的模板类。C++提供了一套完整的解决方案,成为标准模板库Standard Template Library,也就是我们常说的STL

flowingFire2012的专栏 508

Qt学习之路(37): Qt容器类之关联存储容器

Qt学习之路(37): Qt容器类之关联存储容器 2009-12-24 13:58:58 标签:C++ 教程 学习 QT教程 Qt 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/248373 今天我们来说说Qt容器类中的关联存储容器。所谓关

carrie0728的专栏 1300

元组(tuple)

tuple和list非常类似,但是tuple一旦初始化就不能修改。 元组中元素不可变,创建后就不能更改其中元素。 一,创建元组 1,创建一个元素的元组: >>> tup=() 2,创建只有一个元素的元组: >>>tup=(1,) #只创建一个元素时,在元素后面加上逗号,否则会被当成其他数据类型. 3. 创建多个元素的元组: >>>tup=(1,2,["a","b","c"],"a") 4.可以通过tuple()将列表,集合,字符串转换为

yangzheng_的博客 1354

Qt下关于tuple用法 ”too many arguments“警告解释

Qt环境下使用tuple#include &lt;QCoreApplication&gt;#include &lt;windows.h&gt;#include &lt;windowsx.h&gt;#include &lt;QDebug&gt;#include &lt;QString&gt;#include &lt;QObject&gt;#include &lt;QStringList&gt;#in

Justice132的博客 4342

tuple元组

tuple 元组是一个固定大小的不同类型值得集合。是泛化的std::pair。可以将其当作一个通用的结构体来用,不需要创建结构体又获取结构体的特征,基本用法:tuple&lt;const cha*,int&gt;tp = make_tuple(sendPack,nSendSize); 等价于 struct A {     char* p;     int len; } 用std::tie,会创建一...

u011140203的专栏 711

tuple定义的两种方式以及两种操作方法

tuple定义的两种方式 定义一个元素的元组 定义空元组的两种方式 tuple类型的两个操作方法的使用 空元组定义(1) 2、第二种 空元组定义(2) 二、操作方法(index,count) 1.index index() 方法查找指定值的第一次出现。 如果未找到该值,index() 方法将引发异常。 代码如下(示例): 这里查找1第一次出现的位置,出现的位置下标是2 2.count count() 方法返回指定值在元组中出现的次数 代码如下(示例): 返回值 2 在元组中出现

m0_63615831的博客 1489

qt几种常用容器的理解和使用

本文整理了对qt几种容器的理解和用法,主要包括:QVector、QList、QLinkedList、QByteArray、QVariant、QSet、QMap、QHash。

linyibin_123的博客 3127

爱普生L6279清零软件+图解N

EPSON 打印机清零软件下载免费版 原厂清零软件 无需解压密码 ,放入任意文件夹就可以使用。需要自己测试。

上一篇: MySQL的IFNULL函数
下一篇: 如何kill掉TaobaoProtect.exe
weixin_30851409
博客等级 码龄11年 136粉丝 0原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值