Qt学习之路(36): Qt容器类之遍历器和隐式数据共享

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介:
前面说过,Qt容器类提供了两种遍历器:Java风格的和STL风格的。前者比较容易使用,后者则可以用在一些通过算法中,功能比较强大。
 
对于每一个容器类,都有与之相对应的遍历器:只读遍历器和读写遍历器。只读遍历器有QVectorIterator<T>,QLinkedListIterator<T>和QListIterator<T>三种;读写遍历器同样也有三种,只不过名字中具有一个Mutable,即QMutableVectorIterator<T>,QMutableLinkedListIterator<T>和QMutableListIterator<T>。这里我们只讨论QList的遍历器,其余遍历器具有几乎相同的API。
 
Java风格的遍历器的位置如下图所示(出自C++ GUI Programming with Qt4, 2nd Edition):
 
可以看出,Java风格的遍历器,遍历器不指向任何元素,而是指向第一个元素之前、两个元素之间或者是最后一个元素之后的位置。使用Java风格的遍历器进行遍历的典型代码是:
 
QList< double> list; 
// ... 
QListIterator< double> i(list); 
while (i.hasNext()) { 
        doSomethingWith(i.next()); 
}
 
这个遍历器默认指向第一个元素,使用hasNext()和next()函数从前向后遍历。你也可以使用toBack()函数让遍历器指向最后一个元素的后面的位置,然后使用hasPrevious()和previous()函数进行遍历。
 
这是只读遍历器,而读写遍历器则可以在遍历的时候进行增删改的操作,例如:
 
QMutableListIterator< double> i(list); 
while (i.hasNext()) { 
         if (i.next() < 0.0) 
                i.remove(); 
}
 
当然,读写遍历器也是可以从后向前遍历的,具体API和前面的几乎相同,这里就不再赘述。
 
对应于Java风格的遍历器,每一个顺序容器类C<T>都有两个STL风格的遍历器:C<T>::iterator和C<T>::const_iterator。正如名字所暗示的那样,const_iterator不允许我们对遍历的数据进行修改。begin()函数返回指向第一个元素的STL风格的遍历器,例如list[0],而end()函数则会返回指向 最后一个之后的元素的STL风格的遍历器,例如如果一个list长度为5,则这个遍历器指向list[5]。下图所示STL风格遍历器的合法位置:
 
 
如果容器是空的,begin()和end()是相同的。这也是用于检测容器是否为空的方法之一,不过调用isEmpty()函数会更加方便。
 
STL风格遍历器的语法类似于使用指针对数组的操作。我们可以使用++和--运算符使遍历器移动到下一位置,遍历器的返回值是指向这个元素的指针。例如QVector<T>的iterator返回值是 T * 类型,而const_iterator返回值是 const T * 类型。
 
一个典型的使用STL风格遍历器的代码是:
 
QList< double>::iterator i = list.begin(); 
while (i != list.end()) { 
        *i = qAbs(*i); 
        ++i; 
}
 
对于某些返回容器的函数而言,如果需要使用STL风格的遍历器,我们需要建立一个返回值的拷贝,然后再使用遍历器进行遍历。如下面的代码所示:
 
QList< int> list = splitter->sizes(); 
QList< int>::const_iterator i = list.begin(); 
while (i != list.end()) { 
        doSomething(*i); 
        ++i; 
}
 
而如果你直接使用返回值,就像下面的代码:
 
// WRONG 
QList< int>::const_iterator i = splitter->sizes().begin(); 
while (i != splitter->sizes().end()) { 
        doSomething(*i); 
        ++i; 
}
 
这种写法一般不是你所期望的。因为sizes()函数会返回一个临时对象,当函数返回时,这个临时对象就要被销毁,因此调用临时对象的begin()函数是相当不明智的做法。并且这种写法也会有性能问题,因为Qt每次循环都要重建临时对象。因此请注意, 如果要使用STL风格的遍历器,并且要遍历作为返回值的容器,就要先创建返回值的拷贝,然后进行遍历。
 
在使用Java风格的只读遍历器时,我们不需要这么做,因此系统会自动为我们创建这个拷贝,所以,我们只需很简单的按下面的代码书写:
 
QListIterator< int> i(splitter->sizes()); 
while (i.hasNext()) { 
        doSomething(i.next()); 
}
 
这里我们提出要建立容器的拷贝,似乎是一项很昂贵的操作。其实并不然。还记得我们上节说过一个隐式数据共享吗?Qt就是使用这个技术,让拷贝一个Qt容器类和拷贝一个指针那么快速。如果我们只进行读操作,数据是不会被复制的,只有当这些需要复制的数据需要进行写操作,这些数据才会被真正的复制,而这一切都是自动进行的,也正因为这个原因,隐式数据共享有时也被称为“写时复制”。隐式数据共享不需要我们做任何额外的操作,它是自动进行的。隐式数据共享让我们有一种可以很方便的进行值返回的编程风格:
 
QVector< double> sineTable()    
{    
                QVector< double> vect(360);    
                 for ( int i = 0; i < 360; ++i)    
                                vect[i] = std::sin(i / (2 * M_PI));    
                 return vect;    

// call 
QVector< double> v = sineTable();
 
Java中我们经常这么写,这样子也很自然:在函数中创建一个对象,操作完毕后将其返回。但是在C++中,很多人都会说,要避免这么写,因为最后一个return语句会进行临时对象的拷贝工作。如果这个对象很大,这个操作会很昂贵。所以,资深的C++高手们都会有一个STL风格的写法:
 
void sineTable(std::vector< double> &vect)    
{    
                vect.resize(360);    
                 for ( int i = 0; i < 360; ++i)    
                                vect[i] = std::sin(i / (2 * M_PI));    

// call 
QVector< double> v; 
sineTable(v);
 
这种写法通过传入一个引用避免了拷贝工作。但是这种写法就不那么自然了。而隐式数据共享的使用让我们能够放心的按照第一种写法书写,而不必担心性能问题。
 
Qt所有容器类以及其他一些类都使用了隐式数据共享技术,这些类包括QByteArray, QBrush, QFont, QImage, QPixmap和QString。这使得这些类在参数和返回值中使用传值方式相当高效。
 
不过,为了正确使用隐式数据共享,我们需要建立一个良好的编程习惯。这其中之一就是, 对list或者vector使用at()函数而不是[]操作符进行只读访问。原因是[]操作符既可以是左值又可以是右值,这让Qt容器很难判断到底是左值还是右值,而at()函数是不能作为左值的,因此可以进行隐式数据共享。另外一点是,对于begin(),end()以及其他一些非const容器,在数据改变时Qt会进行深复制。为了避免这一点, 要尽可能使用const_iterator, constBegin()和constEnd().
 
最后,Qt提供了一种不使用遍历器进行遍历的方法:foreach循环。这实际上是一个宏,使用代码如下所示:
 
QLinkedList<Movie> list; 
Movie movie; 
... 
foreach (movie, list) { 
         if (movie.title() ==  "Citizen Kane") { 
                std::cout <<  "Found Citizen Kane" << std::endl; 
                 break
        } 
}
 
很多语言,特别是动态语言,以及Java 1.5之后,都有foreach的支持。Qt中使用宏实现了foreach循环,有两个参数,第一个是单个的对象,成为遍历对象,相当于指向容器元素类型的一个指针,第二个是一个容器类。它的意思很明确:每次取出容器中的一个元素,赋值给前面的遍历元素进行操作。需要注意的是, 在循环外面定义遍历元素,对于定义中具有逗号的类而言,如QPair<int, double>,是唯一的选择

本文转自 FinderCheng 51CTO博客,原文链接:
http://blog.51cto.com/devbean/247353

相关文章
|
3月前
|
移动开发 HTML5 容器
Twaver-HTML5基础学习(21)网元管理容器(ElementBox)
本文介绍了Twaver HTML5中的网元管理容器(ElementBox),包括如何监听网元属性变化、容器属性变化、网元元素变化以及数据层次变化。文章通过示例代码展示了如何使用不同的事件监听方法来响应这些变化,并通过控制台输出相关的事件信息。
45 4
Twaver-HTML5基础学习(21)网元管理容器(ElementBox)
|
3月前
|
移动开发 前端开发 HTML5
Twaver-HTML5基础学习(23)页管理容器(TabBox)、选中模型(SelectionModel)
本文介绍了Twaver HTML5中的页管理容器(TabBox)和选中模型(SelectionModel)。文章解释了如何使用TabBox来管理Tab页,并通过示例代码展示了SelectionModel的多种功能,包括追加选中元素、设置选中元素、选中所有元素、移除元素选中状态、清除所有选中状态等。此外,还介绍了如何监听选中状态的变化事件以及如何设置不同的选中模式,如多选、单选和不可选。
37 2
Twaver-HTML5基础学习(23)页管理容器(TabBox)、选中模型(SelectionModel)
|
2月前
|
Kubernetes Linux 持续交付
docker容器学习
【10月更文挑战第1天】
39 1
|
2月前
|
Kubernetes 应用服务中间件 nginx
k8s学习--k8s集群使用容器镜像仓库Harbor
本文介绍了在CentOS 7.9环境下部署Harbor容器镜像仓库,并将其集成到Kubernetes集群的过程。环境中包含一台Master节点和两台Node节点,均已部署好K8s集群。首先详细讲述了在Harbor节点上安装Docker和docker-compose,接着通过下载Harbor离线安装包并配置相关参数完成Harbor的部署。随后介绍了如何通过secret和serviceaccount两种方式让Kubernetes集群使用Harbor作为镜像仓库,包括创建secret、配置节点、上传镜像以及创建Pod等步骤。最后验证了Pod能否成功从Harbor拉取镜像运行。
135 0
|
3月前
|
定位技术 Go 开发工具
dynamic-situational-awareness-qt学习记录
本文是作者yantuguiguziPGJ关于dynamic-situational-awareness-qt学习记录的分享,介绍了在Qt学习过程中发现的qml资源丰富的代码仓库,并提供了资源路径和相关的安装、配置步骤,涉及的内容有数字地球、GIS纹理等,同时提供了相关链接和git命令来克隆代码仓库和ArcGIS Runtime SDK for Qt的安装说明。
|
3月前
|
Kubernetes API Docker
跟着iLogtail学习容器运行时与K8s下日志采集方案
iLogtail 作为开源可观测数据采集器,对 Kubernetes 环境下日志采集有着非常好的支持,本文跟随 iLogtail 的脚步,了解容器运行时与 K8s 下日志数据采集原理。
|
3月前
|
移动开发 数据管理 HTML5
Twaver-HTML5基础学习(22)层管理容器(LayerBox)、告警管理容器(AlarmBox)、列管理容器(ColumnBox)、属性管理容器(PropertyBox)
本文介绍了Twaver HTML5中的多种管理容器:层管理容器(LayerBox)、告警管理容器(AlarmBox)、列管理容器(ColumnBox)和属性管理容器(PropertyBox)。文章解释了这些容器的作用、如何获取它们,并提供了一些基本的操作方法。这些容器分别用于管理图层、告警、表格列和属性对象,是TWaver中数据管理和组织的重要部分。
39 1
|
2月前
|
Linux 应用服务中间件 Shell
docker学习--docker容器镜像常用命令大全(简)
本文档详细介绍了Docker中的镜像命令与容器管理命令。镜像命令部分涵盖了镜像搜索、下载、上传等操作;容器管理命令则包括了容器的创建、启动、停止、删除及日志查看等功能。通过具体示例,帮助用户更好地理解和使用Docker相关命令。
172 0
|
4天前
|
监控 NoSQL 时序数据库
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
《docker高级篇(大厂进阶):7.Docker容器监控之CAdvisor+InfluxDB+Granfana》包括:原生命令、是什么、compose容器编排,一套带走
123 77
|
13天前
|
监控 Docker 容器
在Docker容器中运行打包好的应用程序
在Docker容器中运行打包好的应用程序