`
izuoyan
  • 浏览: 9224185 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

阅读更多
版权声明: 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://devbean.blog.51cto.com/448512/247353
前面说过,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>,是唯一的选择

本文出自 “豆子空间 ” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/247353

分享到:
评论

相关推荐

    Qt学习之路2

    13. **容器和遍历器**: Qt容器类提供了存储和管理数据集的功能,包括QList、QMap等。文档会讲解这些容器的使用方法和遍历技术。 14. **数据共享**: Qt中的隐式数据共享机制允许在不进行实际数据复制的情况下,共享...

    QT学习之路

    14. "QT容器类之顺序存储容器、遍历器和隐式数据共享、关联存储容器" 则涵盖了Qt提供的数据结构,如QList、QMap等,它们用于存储和管理数据集合。 15. "MODEL-VIEW架构" 这是Qt中用于分离数据显示和数据管理的架构...

    Qt+学习之路+2

    隐式数据共享 - **定义**: Qt中的一种机制,用于优化容器类的性能。 - **应用场景**: 在需要频繁复制数据结构的情况下提高效率。 #### 41. model/view架构 - **定义**: 一种将数据模型与视图分离的设计模式。 - **...

    其实这本PDF就是Qt学习之路第一版

    - **遍历器与隐式数据共享**: 提高容器类的使用效率和性能优化策略。 #### 七、Model-View架构 - **概念**: 解释Model-View架构的基本思想及其在Qt中的实现方式。 - **具体组件**: - **QListWidget**: 显示列表项...

    QT学习之路2 (1~82篇)

    40. 隐式数据共享 41. model/view 架构 42. QListWidget、QTreeWidget 和 QTableWidget 43. QStringListModel 44. QFileSystemModel 45. 模型 46. 视图和委托 47. 视图选择 48. QSortFilterProxyModel 49. 自定义...

    Qt学习之路(1-60)

    学习者将学习如何在Qt中使用这些容器类,以及它们的遍历器和隐式数据共享机制。 ### Model-View架构 Model-View架构是Qt用于分离显示逻辑和数据逻辑的核心设计模式。在Model-View架构部分,学习者将了解到如何使用...

    QT学习笔记

    16. 容器和数据共享:QT使用STL兼容的容器类来管理数据,并且支持隐式数据共享,这样可以更高效地处理大量数据。 17. Model/View架构:QT的Model/View架构用于分离和管理数据模型与数据显示,使得开发数据驱动的...

    Qt5学习资料

    - 隐式数据共享:讨论Qt容器类的数据共享机制。 #### 11. model/view架构 - 介绍model/view架构的工作原理和优势。 - 讲解如何使用该架构分离数据和视图,以及如何使用QAbstractItemModel和QAbstractItemView。 ...

    QT自学的学习资料完整讲义

    在学习的最后部分,讲义引导学习者了解如何进行国际化,以及Qt的容器类,如顺序存储容器、遍历器和隐式数据共享、关联存储容器等,帮助开发者构建和管理数据。MODEL-VIEW 架构是Qt的精髓之一,通过学习QListWidget、...

    Qt+学习之路

    - **隐式数据共享**:一种优化机制,用于提高Qt容器的性能。 #### 16. 结论 - **整体评价**:《Qt学习之路2》是一本非常适合初学者入门Qt的书籍,内容涵盖了Qt的基本概念和技术细节。 - **未来发展**:随着Qt5的...

    qt5 学习资料(pdf)

    - **隐式数据共享**:Qt 中的一种优化机制,通过共享数据减少内存占用。 #### 14. 绘图系统 - **绘图基础**:Qt 提供了强大的绘图功能,支持基本图形的绘制、路径绘制、图像处理等。 - **画笔和画刷**:`QPen` 和 `...

    精通qt4编程(源代码)

    \13.1 Qt容器类 326 \13.1.1 QList、QLinkedList和QVector 327 \13.1.2 QMap、QHash 332 \13.2 QString 334 \13.2.1 隐式共享 335 \13.2.2 内存分配策略 336 \13.2.3 操作字符串 336 \13.2.4 查询字符串数据 337 \...

    精通Qt4编程(第二版)源代码

    \13.1 Qt容器类 326 \13.1.1 QList、QLinkedList和QVector 327 \13.1.2 QMap、QHash 332 \13.2 QString 334 \13.2.1 隐式共享 335 \13.2.2 内存分配策略 336 \13.2.3 操作字符串 336 \13.2.4 查询字符串数据 ...

Global site tag (gtag.js) - Google Analytics