baizy77 发表于 2018-10-19 15:48:28

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

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

版权声明---------------------------------------------------------------------------------------------------------------------该文章原创于Qter开源社区(www.qter.org)作者: 女儿叫老白转载请注明出处!---------------------------------------------------------------------------------------------------------------------课程目录: 【独家连载】《Qt入门与提高-GUI产品开发》目录
网页版课程源码 提取码:1uy7
引言----------------------------------------------------------------------------------------------------------------------上一节我们讲了数组,本节我们给大家介绍Qt的链表处理类QList。链表数据结构的特点是内存并不连续,但是增加、删除链表节点非常方便、高效,因为只需要在增加或删除节点的位置进行修改而不会影响其他节点。正文----------------------------------------------------------------------------------------------------------------------    如果要使用QList,需要包含其头文件。按我们之前讲过的规则,这个头文件同类名一致,所以,代码应该这样写#include <QList>    同样的,我们也提供三种编程场景作为案例:    1.向QList添加成员并遍历。    2.使用自定义类对象    3.使用自定义类对象指针    下面我们分别看一下这三种场景。场景1. 向QList添加成员并遍历。    此处,我们也使用了quint16作为成员对象的类型。/**
* @brief向QList添加成员并遍历。
* @return 无
*/
void example01(){   
    //添加成员
    QList<quint16>lstObj;
    lstObj.push_back(2011);
    lstObj.push_back(2033);
    lstObj.push_back(2033);
    lstObj.push_back(2042);
    lstObj.push_back(2045);
    //push_front
    lstObj.push_front(2046);    向链表中添加成员也用到了push_back()。接下来,我们使用不同的方法对链表进行遍历。为了方便,我们把遍历并打印链表的代码封装为几个接口,因为在其他案例中也要用到。//遍历成员-使用下标
    printByIndex(lstObj);
    //遍历成员-使用迭代器(正序)
    printByIterator(lstObj);
    //遍历成员-使用迭代器(倒序)
    printByIteratorReverse(lstObj);    下面是这几个接口的实现,同QVector的用法类似,因此不再赘述。// 遍历成员-使用迭代器(正序)
void printByIterator(constQList<quint16>& lstObj) {
    cout<< endl << "-------------- QList ---------------"<< endl;
    cout<< "print members using iterator......" << endl;
    QList<quint16>::const_iteratoriteList = lstObj.begin();
    for(iteList = lstObj.begin(); iteList != lstObj.end(); iteList++) {
      cout<< "    " <<*iteList << endl;
    }
}
// 遍历成员-使用迭代器(倒序)
void printByIteratorReverse(constQList<quint16>& lstObj){
    cout<< endl << "-------------- QList ---------------"<< endl;
    cout<< "print members using iterator reverse......" <<endl;
    QList<quint16>::const_reverse_iteratoriteList;
    for(iteList = lstObj.rbegin(); iteList != lstObj.rend(); iteList++) {
      cout<< "    " <<*iteList << endl;
    }
}

// 遍历成员-使用下标
void printByIndex(constQList<quint16>& lstObj){
    cout<< endl << "-------------- QList ---------------"<< endl;
    cout<< "print members using idx......" << endl;
    intidxList = 0;
    for(idxList = 0; idxList < lstObj.size(); idxList++) {
      cout<< "    lstObj["<< idxList << "] =" << lstObj <<endl;
    }
}    定义迭代器时,使用的语法是: 链表对象的类型::iterator。即上述代码中的:       QList<quint16>::iterator     用来判断迭代器是否已经遍历完毕的方法同QVector类似:iteList!= lstObj.end()场景2.使用自定义类对象    我们使用CMyClass类对象,并将其添加到链表。方法同QVector类似。/**
* @brief使用自定义类对象
* @return 无
*/
void example02(){
    //添加成员
    QList<CMyClass>lstObj;
    CMyClassmyclass1(2011, "lisa");
    CMyClassmyclass2(2012, "mike");
    CMyClassmyclass3(2012, "mike");
    CMyClassmyclass4(2013, "john");
    CMyClassmyclass5(2013, "ping");
    CMyClassmyclass6(2025, "ping");
   //如果想让下面的语句编译通过并且按照预期执行,需要为CMyClass类提供拷贝构造函数
    lstObj.push_back(myclass1);
    lstObj.push_back(myclass2);
    lstObj.push_back(myclass3);
    lstObj.push_back(myclass4);
    lstObj.push_back(myclass5);
    lstObj.push_back(myclass6);

    //遍历成员
    cout<< endl << "-------------- QList ---------------"<< endl;
    cout<< "print members using idx......" << endl;
    intidxList = 0;
    for(idxList = 0; idxList < lstObj.size(); idxList++) {
      cout<< "    lstObj["<< idxList << "] : id = " << lstObj.getId()<< ", name = " <<lstObj.getName().toLocal8Bit().data() << endl;
    }
   
    //查找
    cout<< endl << "-------------- QList ---------------"<< endl;
    cout<< "begin find member in QList......" << endl;
    CMyClassmyclassx(2013, "john");
    QList<CMyClass>::iteratoriteList = std::find(lstObj.begin(), lstObj.end(), myclassx);
    if(iteList != lstObj.end()) {
      cout<< "find myclassx in list." << endl;
    }
    else{
      cout<< "cannot find myclassx in list" << endl;
    }
}    上面需要注意的是lstObj.push_back(myclass1)。这句代码要求CMyClass类提供拷贝构造函数。其实如果我们不去编写CMyClass类的拷贝构造函数,程序也能编译通过,因为编译器会因为push_back()的调用为CMyClass类提供默认的拷贝构造函数。大家可以尝试封掉我们的拷贝构造函数做一下测试。封掉CMyClass的拷贝构造函数后,push_back()可以成功调用,但是程序却会运行异常。为什么呢?因为编译器提供的拷贝构造函数仅仅执行位拷贝,也就是会将对象的成员变量的值一对一拷贝赋值,而CMyClas类的成员中有指针,如果按位拷贝那么就不会重新申请内存而是指向同一块内存,结果在CMyClass析构时会出现将同一块内存多次delete的问题,因此引发异常。所以还是养成习惯,应该自己动手为类提供拷贝构造函数。另外,因为在实现查找功能时用到了std::find()算法,所以仍然需要为CMyClass类重载operator==操作符。大家可以尝试封掉类CMyClass的operator==的重载定义和实现,看看会有什么结果。哈哈,编译器告诉我们;    errorC2678: 二进制“==”: 没有找到接受“CMyClass”类型的左操作数的运算符(或没有可接受的转换)编译器主动告诉我们需要为CMyClass提供==的定义。所以重载的代码是不可少的。场景3.使用自定义类对象指针    使用指针的情形与QVector类似。/**
* @brief使用自定义类指针
* @return 无
*/
void example03() {
    //添加成员
    QList<CMyClass*>lstObj;
    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类提供拷贝构造函数
    lstObj.push_back(pMyclass1);
    lstObj.push_back(pMyclass2);
    lstObj.push_back(pMyclass3);
    lstObj.push_back(pMyclass4);
    lstObj.push_back(pMyclass5);
    lstObj.push_back(pMyclass6);   
    //遍历成员
    cout<< endl << "-------------- QList ---------------"<< endl;
    cout<< "print members in custom defined class using idx......"<< endl;
    intidxList = 0;
    for(idxList = 0; idxList < lstObj.size(); idxList++) {
      cout<< "    lstObj["<< idxList << "] : id = " <<lstObj->getId() << ", name = " <<lstObj->getName().toLocal8Bit().data() << endl;
    }
    //退出前要释放内存
    //方法1,使用下标遍历
    cout<< endl << "-------------- QList ---------------"<< endl;
    cout<< "desctruct members before exit......" << endl;
    idxList= 0;
    for(idxList = 0; idxList < lstObj.size(); idxList++) {
      cout<< "    deleting "<< idxList << ", id = " <<lstObj->getId() << ", name = " <<lstObj->getName().toLocal8Bit().data() << endl;
            deletelstObj;
    }

    //方法2,使用迭代器遍历
    //QList<CMyClass*>::iteratoriteList = lstObj.begin();
    //for(iteList = lstObj.begin(); iteList != lstObj.end(); iteList++, idxList++) {
    //if (NULL != *iteList) {
    //      delete *iteList;
    //}
    //}
    lstObj.clear();
}
结语----------------------------------------------------------------------------------------------------------------------   本节我们介绍了链表类QList的用法,它跟QVector有很多相似的地方。唯一不同的地方是数据结构本身所代表的含义。当我们希望通过下标快速访问连续内存对象时,可以使用数组(QVector);当我们希望频繁高效的增删节点时,就要用到链表。这两个类各有各自的特点和应用场景,互相不可替代。下一章节我们将为大家介绍映射,让我们共同期待。
上一节:KS04-05   常用Qt类-QVector下一节:KS04-07   常用Qt类-QMap
页: [1]
查看完整版本: 【独家连载】Qt入门与提高:KS04_06 常用Qt类-QList