找回密码
 立即注册
收起左侧

【独家连载】Qt入门与提高: KS04_05 常用Qt类-QVector

0
回复
6417
查看
[复制链接]
累计签到:41 天
连续签到:1 天
来源: 原创 2018-10-18 17:04:00 显示全部楼层 |阅读模式

马上注册,查看详细内容!注册请先查看:注册须知

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本帖最后由 baizy77 于 2019-7-2 20:32 编辑

版权声明
---------------------------------------------------------------------------------------------------------------------
该文章原创于Qter开源社区(www.qter.org
作者: 女儿叫老白
转载请注明出处!
---------------------------------------------------------------------------------------------------------------------
引言
----------------------------------------------------------------------------------------------------------------------
   在软件研发过程中,数据结构是无论如何也不能回避的话题,包括stl在内,很多第三方库都对常用数据结构进行了模板化封装,从本节课开始,我们要给大家介绍Qt封装的几个常用数据结构模板类。今天先介绍数组类QVector。

正文
----------------------------------------------------------------------------------------------------------------------
   我们都知道数组的成员在内存中是连续存放的。当使用下标访问数组成员时效率是非常高的。但是当我们扩大数组容量时,很有可能需要重新申请内存,然后将旧的数组成员拷贝到新内存中,这时可能会损失一些性能。OK,回归正题,我们今天设计了3个场景给大家介绍QVector:
    1.  向QVector添加成员并遍历。
    2.  使用自定义类对象
    3.  使用自定义类对象指针
    首先简单说一下课程附件代码:因为要用到命令行来接受用户输入,因此需要使用:
  1.   CONFIG += console  
复制代码
    因为我们要用到QVector,所以请在文件开头的include区域增加:
  1.   #include <QVector>  
复制代码
    下面我们分别看一下这3个示例:
示例1.  向QVector添加成员并遍历
    在这个示例中,我们用的是int。如果用到Qt的类,代码也类似。
  1. /**
  2. * @brief  向QVector添加成员并遍历。
  3. * @return 无
  4. */
  5. void example01(){        
  6.         // 添加成员
  7.         QVector<quint32> vecId;
  8.         vecId.push_back(2011);
  9.         vecId.push_back(2033);
  10.         vecId.push_back(2033);
  11.         vecId.push_back(2042);
  12.         vecId.push_back(2045);
  13.         // push_front
  14.         vecId.push_front(2046);
  15. ……
  16. }
复制代码
    上述代码中,我们建立了一个成员为quint32的数组。这是一个整数数组,数组名称为vecId。紧接着我们调用push_back()接口向数组中添加成员。push_back()的功能是将新加入的成员放到数组的尾部,而push_front()接口则负责将成员追加到数组的首部,也就是下标为0的位置。我们可以通过将数组的成员打印到终端来印证这一点:
  1. //  遍历成员-使用下标  
  2.     cout  << endl << "-------------- QVector ---------------"  << endl;  
  3.     cout  << "print members using idx......" << endl;  
  4.     int  idxVec = 0;  
  5.     for  (idxVec = 0; idxVec < vecId.size(); idxVec++) {  
  6.         cout  << "    vecId["  << idxVec << "] =" << vecId[idxVec] <<  endl;  
  7.     }
复制代码
    在上面的代码中,我们使用下表来便利成员,方法是:数组名[下标],这是C++的语法。当然,我们还可以通过迭代器来访问,也非常方便:
  1. // 遍历成员-使用迭代器(正序)  
  2.     cout  << endl << "-------------- QVector ---------------"  << endl;  
  3.     cout  << "print members using iterator......" << endl;  
  4.     QVector<uint>::iterator  iteVec = vecId.begin();  
  5.     idxVec  = 0;  
  6.     for  (iteVec = vecId.begin(); iteVec != vecId.end(); iteVec++, idxVec++) {  
  7.         //cout  << "    vecId["  << idxVec << "] =" << *iteVec << endl;  
  8.         cout  << "    " <<  *iteVec << endl;  
  9.     }
复制代码

    上述代码是通过正序访问,相当于从下标0开始到最后一个成员。为了使用迭代器遍历,我们需要先定义迭代器,并将其初始化为指向数组的开头(第一个成员):
  1. QVector<uint>::iterator  iteVec = vecId.begin();
复制代码
    用来判断迭代器是否已经遍历完毕的方法是:
  1. iteVec != vecId.end()
复制代码
    其中vecId.end()表示数组的结尾(并非数组的最后一个成员,而是最后一个成员的下一个位置,因此是真正的结尾)。
    当我们需要用迭代器操作数组成员并输出时,可使用*迭代器的语法,比如上面代码中的*iteVec。
    我们还可以倒序遍历数组成员:
  1. //  遍历成员-使用迭代器(倒序)
  2.     cout  << endl << "-------------- QVector ---------------"  << endl;
  3.     cout  << "print members using reverse iterator......" <<  endl;
  4.     QVector<quint32>::reverse_iterator  reverseIteVec = vecId.rbegin();
  5.     for  (reverseIteVec = vecId.rbegin(); reverseIteVec != vecId.rend();  reverseIteVec++) {
  6.         cout  << "    " <<  *reverseIteVec << endl;
  7. }   
复制代码
    从上述代码可以看出,倒序迭代器与正序迭代器并非同一个类型。倒序迭代器的类型为:
QVector<quint32>::reverse_iterator,并且它被初始化指向数组的倒数第一个成员vecId.rbegin():
  1. QVector<quint32>::reverse_iterator  reverseIteVec = vecId.rbegin();
复制代码
    倒序迭代器的取值方法同正序迭代器一样,都是采用*迭代器的语法:
  1. cout << "    " << *reverseIteVec <<  endl;
复制代码
    下面,我们到数组中查找某个成员,方法是使用stl的算法模板类algorithm,因此需要包含其头文件:
  1. #include <algorithm>
复制代码
    我们看一下查找一个对象并且在它的前面插入另一个对象怎么实现:
  1. // 查找&插入
  2.     iteVec  = std::find(vecId.begin(), vecId.end(), 2042);
复制代码
    std::find()是altorithm中的搜索算法,它需要3个参数,前两个分别表示要查找的范围,这里我们取数组的开头和结尾;最后一个参数表示要查找的对象,这个例子中我们查找2042这个数字。
  1. if  (iteVec != vecId.end())  {
  2.         vecId.insert(iteVec,  10000); // insert before
  3.         cout  << endl << "-------------- QVector ---------------"  << endl;
  4. cout  << "insert 10000 before 2042 in vector." << endl;  
  5.     }
  6.   
复制代码
    上述代码表示,如果找到了则在找到的位置之前插入一个数值:10000。insert()接口的功能是在指定迭代器之前插入一个成员。iteVec != vecId.end()表示迭代器不等于数组的结尾,就表示找到了我们需要查找的成员。
    下面的代码用来展示查找与删除功能:
  1. // 查找&删除
  2.     iteVec  = std::find(vecId.begin(), vecId.end(), 2042);
  3.     if  (iteVec != vecId.end())  { // 找到了
  4.         cout  << "erase 2042 from vector." << endl;
  5.         vecId.erase(iteVec);
  6.     }
复制代码
    其中,查找功能是一样的。删除的功能由erase()接口提供,它需要一个迭代器参数,所以我们提供了查找到的迭代器。
    有时候数组中存在多个相同成员,如果我们希望把它们都找到并从数组中删除该怎么做呢?
  1. // 查找&删除
  2.     for  (iteVec=vecId.begin(); iteVec != vecId.end(); ) {
  3.         if  ((*iteVec) == 2033)  {
  4. cout  << "find 2033 in vector." << endl;
  5.             iteVec  = vecId.erase(iteVec); // erase()接口会返回删除后的下一个迭代位置
  6.         }  else {
  7.             iteVec++;
  8.         }
  9.     }
复制代码
    上述代码实现了我们需要的功能,当它找到2033这个数时,调用erase()接口将迭代器删除,然后把返回值赋给迭代器iteVec,这时iteVec就指向删除2033后的下一个位置了,因此无需再执行++操作,如果没找到2033,我们才对迭代器执行++操作。
其实,如果这行代码正处于某一个类的const接口中,而且迭代器需要访问类的成员数组,那么就需要使用const迭代器,并且初始化时要使constBegin():
  1. QVector<quint32>::const_iterator  iteVec = vecId.constBegin();
复制代码
    对const迭代器进行判断也要使用constEnd():
  1. if (iteVec != vecId.constEnd())
复制代码
示例2.  使用自定义类对象
    下面我们看一下在数组中使用自定义类对象作为成员:
  1. /**
  2. * @brief  使用自定义类对象
  3. * @return 无
  4. */
  5. void example02(){
  6.     //  添加成员
  7. QVector<CMyClass>  vecObj;
  8.     CMyClass  myclass1(2011, "lisa");
  9.     CMyClass  myclass2(2012, "mike");
  10. CMyClass  myclass3(2012, "mike");
  11.     CMyClass  myclass4(2013, "john");
  12.     CMyClass  myclass5(2013, "ping");
  13.     CMyClass  myclass6(2025, "ping");
  14.   
  15.     //  如果想让下面的语句编译通过并且按照预期执行,需要为CMyClass类提供拷贝构造函数
  16.     vecObj.push_back(myclass1);
  17.     vecObj.push_back(myclass2);
  18.     vecObj.push_back(myclass3);
  19.     vecObj.push_back(myclass4);
  20.     vecObj.push_back(myclass5);
  21.     vecObj.push_back(myclass6);
  22. ……
  23. }
复制代码
    首先我们定义了几个CMyClass对象将他们并初始化,然后调用push_back()将它们添加到数组。因为push_back()接口需要传入类对象的拷贝而非引用或指针,所以编译器会调用对象的拷贝构造函数。因此,我们需要为类CMyClass提供拷贝构造函数,以免编译器默认生成的拷贝构造函数无法满足要求:
myclass.h:
  1. // 拷贝构造函数  
  2.     CMyClass(const  CMyClass& right);
复制代码

    然后,我们遍历数组。首先使用下标遍历:
  1. // 遍历成员,使用下标
  2.     cout  << endl << "-------------- QVector ---------------"  << endl;
  3.     cout  << "print members using idx......" << endl;
  4.     int  idxVec = 0;
  5.     for  (idxVec = 0; idxVec < vecObj.size(); idxVec++) {
  6.         cout  << "    vecObj["  << idxVec << "] : id = " <<  vecObj[idxVec].getId() << ", name = " <<  vecObj[idxVec].getName().toLocal8Bit().data() << endl;
  7.     }
复制代码
    使用下标遍历数组跟前面的方法类似。  
然后使用迭代器遍历:
  1. //  遍历成员,使用迭代器
  2.     QVector<CMyClass>::iterator  iteVec = vecObj.begin();
  3.     for  (iteVec = vecObj.begin(); iteVec != vecObj.end(); iteVec++) {
  4.         cout  << "    vecObj["  << idxVec << "] : id = " << (*iteVec).getId()  << ", name = " << (*iteVec).getName().toLocal8Bit().data()  << endl;
  5.     }
复制代码
    使用迭代器时,也是使用(*iteVec)的语法来操作数组成员。因为数组里存放的是对象,所以我们可以使用.操作符来调用对象的接口,如果数组里存放的是对象指针,我们就要用(*iteVec)->的语法调用对象的接口。
    下面我们来查找某个对象:
  1. //  查找
  2.     cout  << endl << "-------------- QVector ---------------"  << endl;
  3.     cout  << "begin find member in QVector......" << endl;
  4.     CMyClass  myclassx(2013, "john");
  5.     QVector<CMyClass>::iterator  iteVec = std::find(vecObj.begin(), vecObj.end(), myclassx);
  6.     if  (iteVec != vecObj.end()) {
  7. cout  << "find myclassx in vector." << endl;
  8.     }
  9.     else  {
  10.         cout  << "cannot find myclassx in vector" << endl;
  11.     }
复制代码
    在上面的代码中,我们定义一个被查找对象myclassx,同样也是使用std::find()来查找。这需要我们的自定义类CMyClass重载operator==操作符,否则编译器会报错。
myclass.h:
  1. //  重载操作符operator==  
  2.   bool  operator==(const CMyClass& right);
复制代码
示例3.  使用自定义类对象指针
    最后,我们使用类对象的指针来演示数组的使用。
  1. /**
  2. *  @brief  使用自定义类对象指针
  3. *  @return 无
  4. */
  5. void  example03() {
  6. // 添加成员
  7.     QVector<CMyClass*> vecObj;
  8.     CMyClass* pMyclass1 = new CMyClass(2011,  "lisa");
  9.     CMyClass* pMyclass2 = new CMyClass(2012,  "mike");
  10.     CMyClass* pMyclass3 = new CMyClass(2012,  "mike");
  11.     CMyClass* pMyclass4 = new CMyClass(2013,  "john");
  12.     CMyClass* pMyclass5 = new CMyClass(2013,  "ping");
  13.     CMyClass* pMyclass6 = new CMyClass(2025,  "ping");
  14. // 无需为CMyClass类提供拷贝构造函数
  15.     vecObj.push_back(pMyclass1);
  16.     vecObj.push_back(pMyclass2);
  17.     vecObj.push_back(pMyclass3);
  18.     vecObj.push_back(pMyclass4);
  19.     vecObj.push_back(pMyclass5);
  20.     vecObj.push_back(pMyclass6);
  21.     ……
  22. }
  23.   
复制代码

   我们先new出数组对象,然后将这些对象指针添加到数组。然后,我们遍历数组的成员:
  1. // 遍历成员
  2.     cout << endl <<  "-------------- QVector ---------------" << endl;
  3.     cout << "print members in  custom defined class using idx......" << endl;
  4.     int idxVec = 0;
  5.     for (idxVec = 0; idxVec <  vecObj.size(); idxVec++) {
  6.         cout << "    vecObj[" << idxVec <<  "] : id = " << vecObj[idxVec]->getId() << ",  name = " << vecObj[idxVec]->getName().toLocal8Bit().data()  << endl;
  7.     }
  8.   
复制代码
   遍历数组成员的代码与前面的示例类似。
与将对象添加到数组有所不同,当我们将指针添加到数组时,退出程序前需要将这些指针占用的内存进行释放,否则将导致内存泄漏:
  1. // 退出前要释放内存
  2.     // 方法1,使用下标遍历
  3.     cout << endl <<  "-------------- QVector ---------------" << endl;
  4. cout << "desctruct members  before exit......" << endl;
  5. idxVec = 0;
  6. for (idxVec = 0; idxVec <  vecObj.size(); idxVec++) {
  7. cout << "    deleting " << idxVec <<  ", id = " << vecObj[idxVec]->getId() << ", name  = " << vecObj[idxVec]->getName().toLocal8Bit().data() <<  endl;
  8. delete vecObj[idxVec];
  9. }

  10.     // 方法2,使用迭代器遍历
  11. //QVector<CMyClass*>::iterator  iteVec = vecObj.begin();
  12. //for (iteVec = vecObj.begin(); iteVec !=  vecObj.end(); iteVec++, idxVec++) {
  13. //  if  (NULL != *iteVec) {
  14. //      delete  *iteVec;
  15. //  }
  16. //}
  17. vecObj.clear();
  18.   
复制代码
    我们给出两种方法进行遍历来释放数组成员所指向的内存。因为不能重复释放,所以我们把迭代器遍历的代码封掉。
结语
----------------------------------------------------------------------------------------------------------------------
   在使用QVector进行编程时,很多时候都是将我们自定义的类放进QVector,所以我们需要掌握两点:
    1.  为自定义类增加拷贝构造函数。
    2.  为自定义类重载operator=操作符,以便能够使用std::find()接口到数组中查找。
    后面的章节我们将介绍链表、映射等数据结构在Qt中的对应类,让我们开始练习吧。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了