版权声明
---------------------------------------------------------------------------------------------------------------------
该文章原创于Qter开源社区(www.qter.org)
作者: 女儿叫老白
转载请注明出处!
---------------------------------------------------------------------------------------------------------------------
课程目录: 【独家连载】《Qt入门与提高-GUI产品开发》目录
引言
----------------------------------------------------------------------------------------------------------------------
上一节我们讲了链表,本节我们给大家介绍Qt的映射处理类QMap。map的数据结构是键值对,也就是给定一个key,可以得到对应的value值,这对于我们访问数据提供了非常方便的手段,比如我们可以把学生的id作为key,把学生对象作为value,这样只要给出一个学员id,就可以方便、快速找到学员对象并访问其信息。
正文
----------------------------------------------------------------------------------------------------------------------
如果要使用QMap,需要包含其头文件。按我们之前讲过的规则,这个头文件同类名一致,所以,代码应该这样写:
- #include <Map>
复制代码
同样的,我们也提供三种编程场景作为案例:
1. 向QList添加成员并遍历、删除。
2. 使用自定义类对象
3. 需要注意的情况
下面我们分别看一下这三种场景。
场景1. 向QList添加成员并遍历、删除。
此处,我们也使用了quint16作为成员对象的类型。
- /**
- * @brief 向QMap添加成员并遍历
- * @return 无
- */
- void example01(){
- // 添加成员
- QMap<quint16, QString> mapObj;
- mapObj[1] = "BeiJing";
- mapObj[2] = "ShangHia";
- mapObj[3] = "GuangZhou";
- mapObj[4] = "ShenZhen";
- mapObj.insert(5, "XiaMen");
- }
复制代码
我们演示了两种向QMap中添加成员的语法。
一种是直接用 map[key]=value的方式,如:
mapObj[1]= "BeiJing";
另一种使用insert(key,value):
mapObj.insert(5,"XiaMen");
接下来,我们封装了printByIterator()接口对QMap进行遍历。
- void printByIterator(const QMap<quint16, QString>& mapObj)
- {
- cout << endl << "-------------- QMap ---------------" << endl;
- cout << "print members using iterator......" << endl;
- QMap<quint16, QString>::const_iterator iteMap = mapObj.begin();
- for (iteMap = mapObj.begin(); iteMap != mapObj.end(); iteMap++) {
- cout << "-- key = " << iteMap.key() << ", value = " << iteMap.value().toLocal8Bit().data() << endl;
- }
- }
复制代码
上述代码中我们使用了一个const迭代器iteMap,并将其初始化指向QMap的头部。
- QMap<quint16, QString>::const_iterator iteMap = mapObj.begin();
复制代码
用来判断迭代器是否已经遍历完毕的方法同QVector类似:
- iteMap = mapObj.begin()
复制代码
使用迭代器访问QMap的数据时,同QVector不太一样。因为QMap有key和value。访问key可以用:
- iteMap.key()
复制代码
访问迭代器的value,可以用:
- iteMap.value()
复制代码
用上述两个接口得到的对象跟QMap的key和value的类型一致,因此可以直接使用这种语法调用对象的接口,比如,如果value是一个QString,那么我们就可以这样编写代码:
- iteMap.value().toLocal8Bit().data()
复制代码
QMap的查找功能通过find()接口实现,比如,我们如果查找QMap中是否存在5这个数据,可以这样写:
- iteMap = mapObj.find(5);
- if (iteMap != mapObj.end()) {
- cout << endl << "-------------- QMap ---------------" << endl;
- cout << "find member where key = 5, and value = " << iteMap.value().toLocal8Bit().data() << endl;
- } else {
- cout << "cannot find member from map where key = 5." << endl;
- }
复制代码
如上述代码所示,判断map中是否找到指定数据的方法也是将迭代器与map.end()进行比较:
- if (iteMap != mapObj.end())
复制代码
QMap的删除操作使用erase()接口,参数是要删除的迭代器:
- // 删除
- cout << endl << "-------------- QMap ---------------" << endl;
- if (iteMap != mapObj.end()) {
- cout << "erase it from map." << endl;
- mapObj.erase(iteMap);
- }
复制代码
场景2. 使用自定义类对象
我们使用CMyClass类对象,并将其添加到QMap。方法同quint16类似。
- /**
- * @brief 使用自定义类对象
- * @return 无
- */
- void example02(){
- // 添加成员
- QMap<CMyClass, uint> mapObj;
- CMyClass myclass1(2011, "lisa");
- CMyClass myclass2(2012, "mike");
- CMyClass myclass3(2012, "mike");
- CMyClass myclass4(2013, "john");
- CMyClass myclass5(2013, "ping");
- CMyClass myclass6(2025, "ping");
- // 如果想让下面的语句编译通过并且按照预期执行,需要为CMyClass类提供拷贝构造函数,并重载operator<操作符。
- mapObj[myclass1] = 1;
- mapObj[myclass2] = 2;
- mapObj[myclass3] = 3;
- mapObj[myclass4] = 4;
- mapObj[myclass5] = 5;
- mapObj[myclass6] = 6;
- }
复制代码
上面需要注意的是mapObj[myclass1]= 1,这句代码要求CMyClass类提供拷贝构造函数。其实如果我们不去编写CMyClass类的拷贝构造函数,程序也能编译通过,因为编译器会因为push_back()的调用为CMyClass类提供默认的拷贝构造函数。大家可以尝试封掉我们的拷贝构造函数做一下测试。
另外,因为在实现查找功能时用到了QMap::find(),我们需要为CMyClass类重载operator<操作符。大家可以尝试封掉类CMyClass的operator<的重载定义和实现,看看会发生什么。
- bool CMyClass::operator<(const CMyClass& right) const {
- if (getId() < right.getId()) {
- return true;
- }
- else if ((getId() == right.getId()) && (getName() < right.getName())) {
- return true;
- }
- else {
- return false;
- }
- }
复制代码
重载自定义类的operator<()接口时需要注意保证逻辑的正确性。也就是说,要保证按照我们的算法运行时,传入a、b两个值时,我们的operator<()接口可以返回不同的结果。如果都返回true或者都返回false,那么我们的算法是错误的,将导致运行时异常。
在QMap中使用自定义类时,查找功能没有什么特别的:
- // 遍历成员
- cout << endl << "-------------- QMap ---------------" << endl;
- QMap<CMyClass, uint>::iterator iteMap;
- for (iteMap = mapObj.begin(); iteMap != mapObj.end(); iteMap++) {
- cout << "key = ("<< iteMap.key().getId() << ", " << iteMap.key().getName().toLocal8Bit().data() << "), value = "<< iteMap.value() << endl;
- }
复制代码
从上述代码可以看出,iteMap.key().getName()这种编码就是对key对象的调用CMyClass::getName()接口,因为key()的类型就是CMyClass.
自定义类对象的查找功能,也需要先定义一个对象,然后调用QMap的find():
- // 查找&删除
- cout << endl << "-------------- QMap ---------------" << endl;
- cout << "begin find member in QMap......" << endl;
- CMyClass myclassx(2013, "john");
- iteMap = mapObj.find(myclassx);
- if (iteMap != mapObj.end()) {
- ……
- }
- else {
- cout << "cannot find myclassx in map" << endl;
- }
复制代码
场景3. 需要注意的情况
如果我们的value是一个QStringList之类的list或者vector等容器,那么有些编译器不允许对未经初始化的value直接采取插入等修改操作,比如:
- // 有的编译器执行如下代码时报错
- std::map<uint, QStringList> mapObj1;
- std::map<uint, QStringList>::iterator iteMap1;
- iteMap1 = mapObj1.find(1);
- if (iteMap1 == mapObj1.end()) {
- cout << "not found!" << endl;
- mapObj1[1].push_back("hello"); // error
- }
复制代码
上文中,mapObj1[1]尚未初始化,如果直接push_back(),将导致错误。
应该改为:
- // 有的编译器不允许用本行的代码直接对未初始化的value操作,
- // 而应该用下面的3行代码:先定义一个QStringList对象lst,把数据插入lst,然后把lst作为value插入到QMap
- QStringList lst;
- lst.push_back("hello");
- mapObj1[1] = lst;
复制代码
也就是需要用一个临时变量lst处理一下。
结语
----------------------------------------------------------------------------------------------------------------------
本节我们介绍了映射类QMap的用法,它跟stl的map有很多相似的地方。我们一般在界面类程序中选择QMap,而且服务类程序中选择stl的map或者其他第三方库的map。当数据量很大时,自己建立数组或链表进行查找性能是很差的,还是要用QMap来处理以提高代码性能。
下一节: KS04-08 编写自己的公共类库