baizy77的个人主页

http://www.qtcn.org/bbs/u/88608  [收藏] [复制]

baizy77

小白

  • 2

    关注

  • 8

    粉丝

  • 1

    访客

  • 等级:新手上路
  • 总积分:0
  • 男,1977-11-27

最后登录:2021-01-06

更多资料

日志

4.5 案例9 使用QVector处理数组

2020-12-24 14:55

本案例对应的源代码目录:src/chapter04/ks04_05。
   在软件研发过程中,数据结构是个无论如何也不能回避的话题。包括STL在内,很多第三方库都对常用数据结构进行了模板化封装。从本节课开始,将介绍Qt封装的几个常用数据结构模板类,本节先介绍数组类QVector。学过C++的人员都知道数组的成员在内存中是连续存放的,因此使用下标访问数组成员时效率是非常高的。但是当扩大数组容量时,有可能需要重新申请内存,然后将旧的数组成员复制到新内存中,这会损失一些性能。所以如果经常对数据集进行插入、删除元素的操作,那么不建议使用数组。OK,回归正题,本节设计了3个场景来介绍QVector。
(1)向QVector中添加成员并遍历。
(2)向QVector中添加自定义类的对象。
(3)向QVector中添加自定义类对象的指针。
首先简单说一下课程附件代码。因为要用到命令行来接受用户输入,因此需要在pro中修改CONFIG参数项。

CONFIG += console

因为要用到QVector,所以请在文件开头增加对<QVector>的包含语句。

#include <QVector>

下面分别看一下这3个场景的案例。
1.向QVector中添加成员并遍历
在代码清单4-28中,将int类型的数据添加到QVector。如果把int换成Qt的类,代码也类似。
代码清单4-28

/**
* @brief  向QVector添加成员并遍历。
* @return
*/
void example01(){                        
    // 添加成员
    QVector<quint32> vecId;
    vecId.push_back(2011);
    vecId.push_back(2033);
    vecId.push_back(2033);
    vecId.push_back(2042);
    vecId.push_back(2045);
    // push_front
    vecId.push_front(2046);
    ...
}

代码清单4-28中,建立了一个成员类型为quint32的数组。这是一个整数数组,数组名称为vecId。调用push_back()接口可以向数组中添加成员。push_back()的功能是将新加入的成员添加到数组的尾部,而push_front()接口则负责将成员添加到数组的首部,也就是下标为0的位置。可以将数组的成员打印到终端来印证这一点,见代码清单4-29。
代码清单4-29

// example01()
// 遍历成员-使用下标
cout << endl << "-------------- QVector ---------------" << endl;
cout << "print members using index ......" << endl;
int idxVec = 0;
for (idxVec = 0; idxVec < vecId.size(); idxVec++) {
    cout << "    vecId[" << idxVec << "] =" << vecId[idxVec] << endl;
}

在代码清单4-29中,使用下标遍历数组的成员。语法同访问C++中的普通数组成员一样:数组名[下标]。另外,通过正序迭代器(简称迭代器)来访问数组成员也非常方便,见代码清单4-30。
代码清单4-30

// example01()
// 遍历成员-使用迭代器(正序)
cout << endl << "-------------- QVector ---------------" << endl;
cout << "print members using iterator......" << endl;
QVector<quint16>::iterator iteVec = vecId.begin();                              
idxVec = 0;
for (iteVec = vecId.begin(); iteVec != vecId.end(); iteVec++, idxVec++) {        
    cout << "    " << *iteVec << endl;                                          
}

代码清单4-30展示了正序访问数组的方法,即从下标0开始遍历到数组最后一个成员。为了使用迭代器遍历,需要先定义迭代器,并将其初始化为指向数组的开头(第一个成员),见标号①处。定义迭代器的语法是:数组类型::iterator,即QVector<quint16>::iterator。用来判断迭代器是否已经遍历完毕的代码是:iteVec != vecId.end(),见标号②处。其中vecId.end()表示数组的结尾(并非数组的最后一个成员,而是最后一个成员的下一个位置,是真正的结尾)。当需要用迭代器访问数组成员时,可使用“*迭代器”的语法,比如标号③处的*iteVec。还可以通过倒序遍历数组成员,见代码清单4-31。
代码清单4-31

// example01()
// 遍历成员-使用迭代器(倒序)
cout << endl << "-------------- QVector ---------------" << endl;
cout << "print members using reverse iterator......" << endl;
QVector<quint32>::reverse_iterator reverseIteVec = vecId.rbegin();                  
for (reverseIteVec = vecId.rbegin(); reverseIteVec != vecId.rend();reverseIteVec++) {
    cout << "    " << *reverseIteVec << endl;                                        
}

从代码清单4-31可以看出,倒序迭代器与正序迭代器并非同一个类型。倒序迭代器的类型为QVector<quint32>::reverse_iterator。在初始化时,倒序迭代器指向数组的倒数第一个成员vecId.rbegin(),见标号①处。用来判断倒序迭代器是否已经遍历完毕的代码是:reverseIteVec!=  vecId. rend(),见标号②处。其中vecId.rend()表示数组倒序的结尾(并非数组的第一个成员,而是指向其前一个位置)。当需要用倒序迭代器访问数组成员时,可使用“*迭代器”的语法,比如标号③处的*reverseIteVec。接下来,要到数组中查找某个成员,方法是使用STL的算法模板类algorithm,因此需要包含其头文件<algorithm>。
下面看一下查找某个数组成员并且在它的前面插入另一个成员该怎么实现,如代码清单4-32所示。标号①处的std::find()是<altorithm>中的搜索算法,它需要3个参数。前两个参数分别表示要查找的范围,比如数组的开头和结尾,最后一个参数表示要查找的对象,比如2042这个数字。
代码清单4-32

// example01()
...
// 查找&插入
// 在2042之前插入数字:10000.
iteVec = std::find(vecId.begin(), vecId.end(), 2042);                              
if (iteVec != vecId.end()) {          // 找到了,此时iteVec指向2042。
    vecId.insert(iteVec, 10000);      // 在2042之前插入数字10000。
    cout << endl << "-------------- QVector ---------------" << endl;
    cout << "insert 10000 before 2042 in vector." << endl;
}

代码清单4-32表示,如果找到了则在找到的位置之前插入数字10000。insert()接口的功能是在指定迭代器之前插入一个成员。iteVec!= vecId.end()表示迭代器未指向数组结尾,其含义是找到了需要查找的数字。代码清单4-33用来演示查找与删除数组中指定成员功能。
代码清单4-33


//  example01()
...
// 查找&删除
iteVec = std::find(vecId.begin(), vecId.end(), 2042);                              
if (iteVec != vecId.end()) { // 找到了
    cout << "erase 2042 from vector." << endl;
    vecId.erase(iteVec);
}

  

代码清单4-33中的查找功能跟代码清单4-32一样。删除数组中指定成员的功能由erase()接口提供,该接口需要一个迭代器参数,可以用标号①处用查找接口返回的迭代器。有时候数组中存在多个相同成员,如果希望把它们都找到并从数组中删除该怎么做呢?见代码清单4-34。
代码清单4-34

// example01()
...
// 查找&删除
for (iteVec=vecId.begin(); iteVec != vecId.end(); ) {                              
    if ((*iteVec) == 2033) {
        cout << "find 2033 in vector." << endl;
        // erase()接口会返回删除后的下一个迭代位置
        iteVec = vecId.erase(iteVec);                                               ②
    } else {
        iteVec++;
    }
}

代码清单4-34中,当找到2033这个数字时,调用erase()接口将找到的迭代器从数组中删除,然后把返回值重新赋给迭代器iteVec,见标号②处。这时iteVec就指向删除2033后的下一个位置了,因此无须再执行迭代器自加操作,如果没找到2033,才对迭代器执行自加操作,在for循环设置步长时应采取空操作,见标号①处。
2.向QVector中添加自定义类的对象
向QVector中添加自定义类的对象作为成员,见代码清单4-35。
代码清单4-35

void example02(){
    // 添加成员
    QVector<CMyClass> vecObj;                                                      
    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类提供拷贝构造函数
    vecObj.push_back(myclass1);
    vecObj.push_back(myclass2);
    vecObj.push_back(myclass3);
    vecObj.push_back(myclass4);
    vecObj.push_back(myclass5);
    vecObj.push_back(myclass6);          
    ...
}

在代码清单4-35中,首先定义了几个CMyClass对象并将它们初始化,然后调用vecObj.push_back()将它们添加到数组中。因为vecObj的类型是QVector<CMyClass>,所以push_back()接口需要传入对象的备份而非引用或指针,也就意味着编译器会调用对象的拷贝构造函数。因此,需要为类CMyClass编写拷贝构造函数(见代码清单4-36),以免编译器默认生成的拷贝构造函数无法满足要求,拷贝构造函数的实现请见本节的源代码。
代码清单4-36

// 拷贝构造函数
CMyClass(const CMyClass& right);

然后使用下标遍历数组,见代码清单4-37。
代码清单4-37

// example02()
// 遍历成员,使用下标
cout << endl << "-------------- QVector ---------------" << endl;
cout << "print members using idx......" << endl;
int idxVec = 0;
for (idxVec = 0; idxVec < vecObj.size(); idxVec++) {
   cout << "    vecObj[" << idxVec << "] : id = "<< vecObj[idxVec].getId() << ", name = " <<vecObj[idxVec].getName().toLocal8Bit().data() << endl;
}

然后使用迭代器遍历数组,见代码清单4-38。
代码清单4-38

// 遍历成员,使用迭代器
QVector<CMyClass>::iterator iteVec = vecObj.begin();
for (iteVec = vecObj.begin(); iteVec != vecObj.end(); iteVec++) {
   cout << "    vecObj[" << idxVec << "] : id = "<< (*iteVec).getId() << ", name = " <<(*iteVec).getName().toLocal8Bit().data() << endl;
}

在使用迭代器时,也是用(*iteVec)的方法来访问数组成员。因为数组里存放的是对象,所以可以使用“.”操作符来调用对象的接口。如果数组里存放的是指针,就要用(*iteVec)->的语法调用对象的接口。 下面来查找某个对象,见代码清单4-39。
代码清单4-39

// example02()
// 查找
cout << endl << "-------------- QVector ---------------" << endl;
cout << "begin find member in QVector......" << endl;
CMyClass myclassx(2013, "john");                                                    
QVector<CMyClass>::iterator iteVec = std::find(vecObj.begin(), vecObj.end(), myclassx);
if (iteVec != vecObj.end()) {
    cout << "find myclassx in vector." << endl;
}
else {
    cout << "cannot find myclassx in vector" << endl;
}

在代码清单4-39中,在标号①处定义一个被查找对象myclassx,在标号②处同样也是使用std::find()来查找该对象。如代码清单4-40所示,这需要为自定义类CMyClass重载operator==操作符,否则编译器会报错。CMyClass::operator==()的实现见本节配套的源代码。
代码清单4-40

class CMyClass {
    ...
    // 重载操作符operator==
    bool operator==(const CMyClass& right);
    ...
}

3.向QVector中添加自定义类的对象的指针
使用类对象的指针来演示QVector用法的代码见代码清单4-41。
代码清单4-41

void example03() {
    // 添加成员
    QVector<CMyClass*> vecObj;
    CMyClass* pMyclass1 = new CMyClass(2011, "lisa");
    CMyClass* pMyclass2 = new CMyClass(2012, "mike");
    CMyClass* pMyclass3 = new CMyClass(2012, "mike");
    CMyClass* pMyclass4 = new CMyClass(2013, "john");
    CMyClass* pMyclass5 = new CMyClass(2013, "ping");
    CMyClass* pMyclass6 = new CMyClass(2025, "ping");
    // 无须为CMyClass类提供拷贝构造函数
    vecObj.push_back(pMyclass1);
    vecObj.push_back(pMyclass2);
    vecObj.push_back(pMyclass3);
    vecObj.push_back(pMyclass4);
    vecObj.push_back(pMyclass5);
    vecObj.push_back(pMyclass6);
    ...
}

代码清单4-41中,先new出一些对象,然后将这些对象指针添加到数组。接着遍历数组的成员,如代码清单4-42所示。
代码清单4-42

// example03()
// 遍历成员
cout << endl << "-------------- QVector ---------------" << endl;
cout << "print members in custom defined class using index......" << endl;
int idxVec = 0;
for (idxVec = 0; idxVec < vecObj.size(); idxVec++) {
    cout << "    vecObj[" << idxVec << "] : id = "
         << vecObj[idxVec]->getId() << ", name = "
         << vecObj[idxVec]->getName().toLocal8Bit().data() << endl;
}

与将对象添加到数组有所不同,将指针添加到数组时,退出程序前需要将这些指针指向的内存进行释放,否则将导致内存泄漏,如代码清单4-43所示。代码清单4-43给出了两种方法来遍历数组成员并释放数组成员所指向的内存。因为不能重复释放同一块内存,所以把用迭代器遍历的代码封掉。可以尝试一下把用下标遍历数组的代码封掉,把用迭代器遍历数组的代码解封。
代码清单4-43

// example03()
// 退出前要释放内存
// 方法1,使用下标遍历
cout << endl << "-------------- QVector ---------------" << endl;
cout << "desctruct members before exit......" << endl;
idxVec = 0;
for (idxVec = 0; idxVec < vecObj.size(); idxVec++) {
    cout << "    deleting " << idxVec << ", id = "
         << vecObj[idxVec]->getId() << ", name = "
         << vecObj[idxVec]->getName().toLocal8Bit().data() << endl;
          delete vecObj[idxVec];
}
// 方法2,使用迭代器遍历
//QVector<CMyClass*>::iterator iteVec = vecObj.begin();
//for (iteVec = vecObj.begin(); iteVec != vecObj.end(); iteVec++, idxVec++) {
//    if (NULL != *iteVec) {
//        delete *iteVec;
//    }
//}
vecObj.clear();

在使用QVector进行编程时,很多情况下都是将自定义的类放进QVector中,所以需要掌握两个知识点:
(1)为自定义类编写拷贝构造函数。
(2)为自定义类重载operator=操作符,以便能够使用std::find()接口在数组中查找成员。
----------------------------------------------------------------------------------------------------------------------------------------------
《Qt 5/PyQt 5实战指南》目录
分类:Qr入门与提高|回复:0|浏览:359|全站可见|转载
 

Powered by phpwind v8.7 Certificate Copyright Time now is:05-02 19:19
©2005-2016 QTCN开发网 版权所有 Gzip disabled