Qt入门学习之容器类

1. 容器类的简介和特性

I.容器类简介
存储容器(containers)有时候也被称为集合(collections),是能够在内存中存储其它特定类型的对象,通常是一些常用的数据结构,一般是通用模板类的形式。C++ 提供了一套完整的解决方案,作为标准模板库(Standard Template Library)的组成部分,也就是常说的 STL
Qt 提供了另外一套基于模板的容器类。相比 STL,这些容器类通常更轻量、更安全、更容易使用。如果你对 STL 不大熟悉,或者更喜欢 Qt 风格的 API,那么你就应该选择使用这些类。当然,你也可以在 Qt 中使用 STL 容器,没有任何问题。

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

2. 容器类分类及各类详解

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

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

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

II.各个容器类详解
1)顺序容器类

  • QList:这是至今为止提供的最通用的容器类。它将给定的类型 T 的对象以列表的形式进行存储,与一个整型的索引关联。QList在内部使用数组实现,同时提供基于索引的快速访问。我们可以使用 QList::append()和QList::prepend()在列表尾部或头部添加元素,也可以使用QList::insert()在中间插入。相比其它容器类,QList专门为这种修改操作作了优化。QStringList继承自QList。如下例所示:
    QLisiaList;//使用Qlist定义一个字符串列表的容器

     **aList.append("Monday");
     aList.append("Tuesday");
     aList.append("Wednesday");
     QString str=aList[0];**
    

Qlist 以下标索引的方式对数据项进行访问。QList用于添加,插入,替换,移除和删除数据的函数有insert()、replace()、removeAt()、move()、swap()、append()、prepend()、removeFirst()、removeLast()。QList的isEmpty()函数在数据项为空时返回true,size()函数返回数据项个数。Qlist容器类还可以作为函数参数进行传递。

  • QLinkedList:类似于 QList,除了它是使用遍历器进行遍历,而不是基于整数索引的随机访问。对于在中部插入大量数据,它的性能要优于QList。同时具有更好的遍历器语义(只要数据元素存在,QLinkedList的遍历器就会指向一个合法元素,相比而言,当插入或删除数据时,QList的遍历器就会指向一个非法值)。

  • QVector:用于在内存的连续区存储一系列给定类型的值。在头部或中间插入数据可能会非常慢,因为这会引起大量数据在内存中的移动。

  • QStack:这是QVector的子类,提供了后进先出(LIFO)语义。相比QVector,它提供了额外的函数:push(),pop()和top()。

      **QStack<int>stack;
      stack.push(10);
      stack.push(20);
      stack.push(30);
      while(!stack.isEmpty())
      		cout<<stack.pop()<<endl;**
      程序输出30,20,10
    
  • QQueue:这是QList的子类,提供了先进先出(FIFO)语义。相比QList,它提供了额外的函数:enqueue(),dequeue()和head()。

      QQueue<int>queue;
      queue.enqueue(10);
      queue.enqueue(20);
      queue.enqueue(30);
     while(!queue.isEmpty())
     		cout<<queue.dequeue()<<endl;
     程序输出10,20,30
    

2)关联容器类

  • QSet:是基于散列表的集合模板类,他的存储顺序是不一定的,查找值的速度非常快。QSet内部是用QHash实现的。
    定义QSet容器和输入数据的例子如下:

      Qset<QString>set;
      set<<"dog"<<"cat"<<"tiger";
    

测试一个值是否包含在这个集合,用contains()函数,操作如下:

 if(!set.contains("cat")){………………}
  • QMap<Key, T>:提供了字典数据结构(关联数组),将类型 T 的值同类型 Key 的键关联起来。通常,每个键与一个值关联。QMap以键的顺序存储数据;如果顺序无关,QHash提供了更好的性能。
    定义QMap<QString,int>类型变量和赋值的实例如下:

     QMap<QString,int>map;
     map["one"]=1;  //one是键值,1是键值关联的那个值
     map["two"]=2;
     map["three"]=3;
    

也可以使用insert()函数赋值,或者remove()移除一个键值对。如下:

map.insert("four",4);
map.remove("two");

要查找一个值,使用[]或者value()函数即可,如下:
int num1=map[“one”];
int num2=map.value(“two”);
注意:如果映射表中没有找到指定的键,会返回一个缺省构造值,如值的类型是字符串,则返回一个空的字符串。
在使用value()函数查找键值时,还可以指定一个缺省的返回值,如下:
timeout=map.value(“TIMEOUT”,30);//没找到键值,返回30。

  • QMultiMap<Key, T>:这是QMap的子类,提供了多值映射(一个键可以与多个值关联),是用于处理多值映射的便利类。QMap正常情况下是不允许多值映射的,除非使用QMap::insertMulti()添加键值对
    QMultiMap是QMap的子类,大多数QMap的函数都可以使用,但是也有几个特殊的,QMultiMap::insert()等效与QMap::insertMulti(),QMultiMap::replace()等效于QMap::insert()。QmultiMap使用实例如下所示:

      QMultiMap<QString,int>map1,map2,map3
      map1.insert("plenty",100);
      map1.insert("plenty",1000);//map1.size()==2
      map1.insert("plenty",100);//map2.size()==1
      map1.insert("plenty",100);//map3.size()=3;
    

QMultiMapn不提供[]操作符号,使用value()函数访问最新插入的键的单个值。如果想要获取一个键对应的所有值,使用value()函数,返回值是QList类型:
Qlistvalue=map.values(“plenty”);

  • QHash<Key, T>:该类同QMap的接口几乎相同,但是提供了更快的查找。QHash以字母顺序存储数据。
  • QMultiHash<Key, T>:这是QHash的子类,提供了多值散列

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

2. 容器类的迭代器

迭代器为访问容器类里的数据项提供了统一的方法,Qt 有两种迭代器类:Java 类型的迭代器和 STL 类型的迭代器。两者比较,Java 类型的迭代器更易于使用,且提供一些高级功能,而 STL 类型的迭代器效率更高。

I.Java 类型迭代器
对于每个容器类,有两个 Java 类型迭代器:一个用于只读操作,一个用于读写操作,各个Java 类型的容器类见表 1。
在这里插入图片描述
QMap 和 QHash 等关联容器类的迭代器用法相冋,QList 和 QLinkedList、QSet 等容器类的用法相同,所以下面只以 QMap 和 QList 为例介绍迭代器的用法。
顺序容器类的迭代器的使用
Java 类型迭代器的指针不是指向一个数据项,而是在数据项之间,迭代器指针位置示意图如图 2 所示。
在这里插入图片描述
下面是遍历访问一个 QList 容器的所有数据项的典型代码:

QList<QString> list;
list << "A" << "B" << "C" << "D";
QListIterator<QString> i (list);//创建一个QList容器类的只读迭代器i
while (i.hasNext())//hasNext()判读在迭代器指针后面是否还有数据
	qDebug () << i.next ();

在这里插入图片描述
如果在遍历过程中要对容器类的数据进行修改,需要使用 QMutableListlterator如下面的示例代码为删除容器中数据为奇数的项:

	QList<int> list;
	list <<1<<2<<3<<4<<5;
	QMutableListIterator<int> i (list);
	while (i.hasNext()) {
	    if (i.next() % 2 != 0)
	        i.remove();
	}

remove() 函数移除 next() 函数刚刚跳过的一个数据项,不会使迭代器失效。setValue() 函数可以修改刚刚跳过去的数据项的值。

关联容器类的迭代器的使用
对于关联容器类 QMap,使用 QMapIterator 和 QMutableMapIterator 迭代器类,它们具有表 3 所示的所有函数,主要是增加了 key() 和 value() 函数用于获取刚刚跳过的数据项的键和值。
例如,下面的代码将删除键(城市名称)里以“City”结尾的数据项:

QMap<QString, QString> map;
map.insert("Paris", "France");
map.insert("New York", "USA");
map.insert("Mexico City", "USA");
map.insert("Moscow", "Russia");
...
QMutableMapIterator<QString, QString> i(map);
while (i.hasNext ()) {
    if (i.next().key().endsWith("City"))
        i.remove();
}

如果是在多值容器里遍历,可以用 findNext() 或 findPrevious() 查找下一个或上一个值,如下面的代码将删除上一示例代码中 map 里值为“USA”的所有数据项:

QMutableMapIterator<QString, QString> i(map);、
while (i.findNext("USA"))
    i.remove();

II.STL类型迭代器
STL 迭代器与 Qt 和 STL 的原生算法兼容,并且进行了速度优化。
在这里插入图片描述
对于每一个容器类,都有两个 STL 类型迭代器:一个用于只读访问,一个用于读写访问。无需修改数据时一定使用只读迭代器,因为它们速度更快。
注意,在定义只读迭代器和读写迭代器时的区别,它们使用了不同的关健字,const_iterator 定义只读迭代器,iterator 定义读写迭代器。此外,还可以使用 const_reverse_iterator 和 reverse_iterator 定义相应的反向迭代器。
STL 类型的迭代器是数组的指针,所以“++”运算符使迭代器指向下一个数据项,运算符返回数据项内容。与 Java 类型的迭代器不同,STL 迭代器直接指向数据项,STL 迭代器指向位置示意图如图 5 所示。
在这里插入图片描述
begin() 函数使迭代器指向容器的第一个数据项,end() 函数使迭代器指向一个虚拟的表示结尾的数据项,end() 表示的数据项是无效的,一般用作循环结束条件。
下面仍然以 QList 和 QMap 为例说明 STL 迭代器的用法,其他容器类迭代器的用法类似。
顺序容器类的迭代器的用法
下面的示例代码将 QList list 里的数据项逐项输出:

QList<QString> list;
list << "A" << "B" << "C" << "D";
QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
	  qDebug() << *i;

constBegin() 和 constEnd() 是用于只读迭代器的,表示起始和结束位置。
若使用反向读写迭代器,并将上面示例代码中 list 的数据项都改为小写,代码如下:

QList<QString>::reverse_iterator i;
for (i = list.rbegin(); i != list.rend(); ++i)
    *i = i->toLower();
}

关联容器类的迭代器的用法
对于关联容器类 QMap 和 QHash,迭代器的操作符返回数据项的值。如果想返回键,使用 key() 函数。对应的,用 value() 函数返回一个项的值。
例如,下面的代码将 QMap<int,int> map 中所有项的键和值输出:

QMap<int, int> map;
...
QMap<int, int>::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
    qDebug () << i.key () << ':' << i.value ();

补充:
如果只想要遍历容器中的所有项,可以使用foreach关键字。foreach是头文件中定义的宏,语法是:foreach(variable,container)
使用foreach的代码比使用迭代器更简洁。如:遍历QLinkedList的实例代码:

QLinkedList<QString>list;
……
QString str;
foreach(str,list)
	qDebug()<<str;

用于迭代的变量也可以在foreach中定义,foreach语句也可以使用花括号,可以使用break退出迭代,如下所示:

QLinkedList<QString>list;
……
foreach(const QString str,list)
{
  if(str.isEmpty()){break;}
  qDebug()<<str;
}    

对于QMap和QHash,foreach会自动访问“键------值”对里的值,无需调用value(),如果需要反问键则调用keys(),如下所示:

QMap<QString,int>map;
……
foreach(const QString &str,map.keys())
	qDebug()<<str<<‘:’<<map.value(str);
对与多重映射,可以使用两重foreach语句,如下所示:
QMultiMap<QString,int >map;
……
foreach(const QString &str,map.uniqueKeys()){
	foreach(int i,map.value(str))
		qDebug()<<str<<':'<<i;
}
注意:foreach关键字遍历一个容器变量是创建了容器的一个副本,所以不
能修改原来容器变量的值。

猜你喜欢

转载自blog.csdn.net/cainiaoxiakexing/article/details/88739939