baizy77 发表于 2018-10-18 17:04:00

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

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

版权声明---------------------------------------------------------------------------------------------------------------------该文章原创于Qter开源社区(www.qter.org)作者: 女儿叫老白转载请注明出处!---------------------------------------------------------------------------------------------------------------------课程目录: 【独家连载】《Qt入门与提高-GUI产品开发》目录

引言----------------------------------------------------------------------------------------------------------------------   在软件研发过程中,数据结构是无论如何也不能回避的话题,包括stl在内,很多第三方库都对常用数据结构进行了模板化封装,从本节课开始,我们要给大家介绍Qt封装的几个常用数据结构模板类。今天先介绍数组类QVector。
正文----------------------------------------------------------------------------------------------------------------------   我们都知道数组的成员在内存中是连续存放的。当使用下标访问数组成员时效率是非常高的。但是当我们扩大数组容量时,很有可能需要重新申请内存,然后将旧的数组成员拷贝到新内存中,这时可能会损失一些性能。OK,回归正题,我们今天设计了3个场景给大家介绍QVector:    1.向QVector添加成员并遍历。    2.使用自定义类对象    3.使用自定义类对象指针    首先简单说一下课程附件代码:因为要用到命令行来接受用户输入,因此需要使用:CONFIG += console    因为我们要用到QVector,所以请在文件开头的include区域增加:#include <QVector>    下面我们分别看一下这3个示例:示例1.向QVector添加成员并遍历    在这个示例中,我们用的是int。如果用到Qt的类,代码也类似。/**
* @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);
……
}
    上述代码中,我们建立了一个成员为quint32的数组。这是一个整数数组,数组名称为vecId。紧接着我们调用push_back()接口向数组中添加成员。push_back()的功能是将新加入的成员放到数组的尾部,而push_front()接口则负责将成员追加到数组的首部,也就是下标为0的位置。我们可以通过将数组的成员打印到终端来印证这一点: //遍历成员-使用下标
    cout<< endl << "-------------- QVector ---------------"<< endl;
    cout<< "print members using idx......" << endl;
    intidxVec = 0;
    for(idxVec = 0; idxVec < vecId.size(); idxVec++) {
      cout<< "    vecId["<< idxVec << "] =" << vecId <<endl;
    }    在上面的代码中,我们使用下表来便利成员,方法是:数组名[下标],这是C++的语法。当然,我们还可以通过迭代器来访问,也非常方便:// 遍历成员-使用迭代器(正序)
    cout<< endl << "-------------- QVector ---------------"<< endl;
    cout<< "print members using iterator......" << endl;
    QVector<uint>::iteratoriteVec = vecId.begin();
    idxVec= 0;
    for(iteVec = vecId.begin(); iteVec != vecId.end(); iteVec++, idxVec++) {
      //cout<< "    vecId["<< idxVec << "] =" << *iteVec << endl;
      cout<< "    " <<*iteVec << endl;
    }
    上述代码是通过正序访问,相当于从下标0开始到最后一个成员。为了使用迭代器遍历,我们需要先定义迭代器,并将其初始化为指向数组的开头(第一个成员): QVector<uint>::iteratoriteVec = vecId.begin();    用来判断迭代器是否已经遍历完毕的方法是:iteVec != vecId.end()    其中vecId.end()表示数组的结尾(并非数组的最后一个成员,而是最后一个成员的下一个位置,因此是真正的结尾)。    当我们需要用迭代器操作数组成员并输出时,可使用*迭代器的语法,比如上面代码中的*iteVec。    我们还可以倒序遍历数组成员://遍历成员-使用迭代器(倒序)
    cout<< endl << "-------------- QVector ---------------"<< endl;
    cout<< "print members using reverse iterator......" <<endl;
    QVector<quint32>::reverse_iteratorreverseIteVec = vecId.rbegin();
    for(reverseIteVec = vecId.rbegin(); reverseIteVec != vecId.rend();reverseIteVec++) {
      cout<< "    " <<*reverseIteVec << endl;
}       从上述代码可以看出,倒序迭代器与正序迭代器并非同一个类型。倒序迭代器的类型为:QVector<quint32>::reverse_iterator,并且它被初始化指向数组的倒数第一个成员vecId.rbegin():QVector<quint32>::reverse_iteratorreverseIteVec = vecId.rbegin();    倒序迭代器的取值方法同正序迭代器一样,都是采用*迭代器的语法:cout << "    " << *reverseIteVec <<endl;    下面,我们到数组中查找某个成员,方法是使用stl的算法模板类algorithm,因此需要包含其头文件:#include <algorithm>    我们看一下查找一个对象并且在它的前面插入另一个对象怎么实现:// 查找&插入
    iteVec= std::find(vecId.begin(), vecId.end(), 2042);    std::find()是altorithm中的搜索算法,它需要3个参数,前两个分别表示要查找的范围,这里我们取数组的开头和结尾;最后一个参数表示要查找的对象,这个例子中我们查找2042这个数字。if(iteVec != vecId.end()){
      vecId.insert(iteVec,10000); // insert before
      cout<< endl << "-------------- QVector ---------------"<< endl;
cout<< "insert 10000 before 2042 in vector." << endl;
    }
    上述代码表示,如果找到了则在找到的位置之前插入一个数值:10000。insert()接口的功能是在指定迭代器之前插入一个成员。iteVec != vecId.end()表示迭代器不等于数组的结尾,就表示找到了我们需要查找的成员。    下面的代码用来展示查找与删除功能:// 查找&删除
    iteVec= std::find(vecId.begin(), vecId.end(), 2042);
    if(iteVec != vecId.end()){ // 找到了
      cout<< "erase 2042 from vector." << endl;
      vecId.erase(iteVec);
    }    其中,查找功能是一样的。删除的功能由erase()接口提供,它需要一个迭代器参数,所以我们提供了查找到的迭代器。    有时候数组中存在多个相同成员,如果我们希望把它们都找到并从数组中删除该怎么做呢?// 查找&删除
    for(iteVec=vecId.begin(); iteVec != vecId.end(); ) {
      if((*iteVec) == 2033){
cout<< "find 2033 in vector." << endl;
            iteVec= vecId.erase(iteVec); // erase()接口会返回删除后的下一个迭代位置
      }else {
            iteVec++;
      }
    }    上述代码实现了我们需要的功能,当它找到2033这个数时,调用erase()接口将迭代器删除,然后把返回值赋给迭代器iteVec,这时iteVec就指向删除2033后的下一个位置了,因此无需再执行++操作,如果没找到2033,我们才对迭代器执行++操作。 其实,如果这行代码正处于某一个类的const接口中,而且迭代器需要访问类的成员数组,那么就需要使用const迭代器,并且初始化时要使constBegin():QVector<quint32>::const_iteratoriteVec = vecId.constBegin();    对const迭代器进行判断也要使用constEnd():if (iteVec != vecId.constEnd())示例2.使用自定义类对象    下面我们看一下在数组中使用自定义类对象作为成员:/**
* @brief使用自定义类对象
* @return 无
*/
void example02(){
    //添加成员
QVector<CMyClass>vecObj;
    CMyClassmyclass1(2011, "lisa");
    CMyClassmyclass2(2012, "mike");
CMyClassmyclass3(2012, "mike");
    CMyClassmyclass4(2013, "john");
    CMyClassmyclass5(2013, "ping");
    CMyClassmyclass6(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);
……
}    首先我们定义了几个CMyClass对象将他们并初始化,然后调用push_back()将它们添加到数组。因为push_back()接口需要传入类对象的拷贝而非引用或指针,所以编译器会调用对象的拷贝构造函数。因此,我们需要为类CMyClass提供拷贝构造函数,以免编译器默认生成的拷贝构造函数无法满足要求:myclass.h: // 拷贝构造函数
    CMyClass(constCMyClass& right);
    然后,我们遍历数组。首先使用下标遍历:// 遍历成员,使用下标
    cout<< endl << "-------------- QVector ---------------"<< endl;
    cout<< "print members using idx......" << endl;
    intidxVec = 0;
    for(idxVec = 0; idxVec < vecObj.size(); idxVec++) {
      cout<< "    vecObj["<< idxVec << "] : id = " <<vecObj.getId() << ", name = " <<vecObj.getName().toLocal8Bit().data() << endl;
    }    使用下标遍历数组跟前面的方法类似。然后使用迭代器遍历://遍历成员,使用迭代器
    QVector<CMyClass>::iteratoriteVec = vecObj.begin();
    for(iteVec = vecObj.begin(); iteVec != vecObj.end(); iteVec++) {
      cout<< "    vecObj["<< idxVec << "] : id = " << (*iteVec).getId()<< ", name = " << (*iteVec).getName().toLocal8Bit().data()<< endl;
    }    使用迭代器时,也是使用(*iteVec)的语法来操作数组成员。因为数组里存放的是对象,所以我们可以使用.操作符来调用对象的接口,如果数组里存放的是对象指针,我们就要用(*iteVec)->的语法调用对象的接口。    下面我们来查找某个对象://查找
    cout<< endl << "-------------- QVector ---------------"<< endl;
    cout<< "begin find member in QVector......" << endl;
    CMyClassmyclassx(2013, "john");
    QVector<CMyClass>::iteratoriteVec = 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;
    }    在上面的代码中,我们定义一个被查找对象myclassx,同样也是使用std::find()来查找。这需要我们的自定义类CMyClass重载operator==操作符,否则编译器会报错。myclass.h://重载操作符operator==
booloperator==(const CMyClass& right);示例3.使用自定义类对象指针    最后,我们使用类对象的指针来演示数组的使用。/**
*@brief使用自定义类对象指针
*@return 无
*/
voidexample03() {
// 添加成员
    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);
    ……
}

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

    // 方法2,使用迭代器遍历
//QVector<CMyClass*>::iteratoriteVec = 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中的对应类,让我们开始练习吧。
上一节:KS04_04 常用Qt类-qDebug下一节:KS04-06   常用Qt类-QList
页: [1]
查看完整版本: 【独家连载】Qt入门与提高: KS04_05 常用Qt类-QVector