4.6 案例10 使用QList处理链表

本案例对应的源代码目录:src/chapter04/ks04_06。

本节介绍Qt的链表处理类QList。链表数据结构的特点是成员的内存并不连续,因此用下标访问链表不太合适。在链表中增加、删除成员非常高效,因为只需要在增加或删除成员的位置进行修改而不会影响其他成员。如果要使用QList,需要包含其头文件<QList>。本案例也设计了三种编程场景对QList的使用进行介绍。

(1)向QList中添加成员并遍历。

(2)向QList中添加自定义类的对象。

(3)向QList中添加自定义类对象的指针。

下面进行详细介绍。

1.向QList中添加成员并遍历

代码清单4-44中介绍了向QList中添加成员并遍历的方法,这里也使用quint16作为成员对象的类型。

代码清单4-44

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);

    ...

}

向QList中添加成员也用到了push_back()。接下来使用几种不同的方法对链表进行遍历。为了方便,把遍历、打印链表的代码封装为几个接口,以便在其他案例中使用,如代码清单4-45所示。在标号①处,因为传入的参数类型是const引用,所以在printByIterator()接口实现代码中的标号②处,需要使用QList<quint16>::const_iterator定义常量迭代器对象iteList来访问链表。为iteList赋值时需要调用lstObj对象的constBegin()接口,在判断链表首尾时使用QList的constBegin()、constEnd()接口。同理,在标号④处的倒序访问接口中,用QList<quint16>::const_reverse_iterator定义迭代器对象,并且在标号⑤处用的是QList的crbegin()、crend()接口获取链表的倒序首尾。

代码清单4-45

void example01(){

    ...

    // 遍历成员-使用下标

    printByIndex(lstObj);

    // 遍历成员-使用迭代器(正序)

    printByIterator(lstObj);

    // 遍历成员-使用迭代器(倒序)

    printByIteratorReverse(lstObj);

    ...

}

// 遍历成员-使用正序迭代器

void printByIterator(const QList<quint16>& lstObj) {                            

    cout << endl << "-------------- QList ---------------" << endl;

    cout << "print members using iterator......" << endl;

    QList<quint16>::const_iterator iteList = lstObj.constBegin();               

    for (iteList = lstObj.constBegin(); iteList != lstObj.constEnd(); iteList++) {

        cout << "    " << *iteList << endl;

    }

}

// 遍历成员-使用倒序迭代器

void printByIteratorReverse(const QList<quint16>& lstObj){

    cout << endl << "-------------- QList ---------------" << endl;

    cout << "print members using iterator reverse......" << endl;

    QList<quint16>::const_reverse_iterator iteList;                               

    for (iteList = lstObj.crbegin(); iteList != lstObj.crend(); iteList++){                                                                       

        cout << "    " << *iteList << endl;

    }

}

// 遍历成员-使用下标

void printByIndex(const QList<quint16>& lstObj){

    cout << endl << "-------------- QList ---------------" << endl;

    cout << "print members using index......" << endl;

    int idxList = 0;

    for (idxList = 0; idxList < lstObj.size(); idxList++) {

        cout << "    lstObj[" << idxList << "] =" << lstObj[idxList] << endl;

    }

}

2.向QList中添加自定义类的对象

向QList中添加自定义类的对象时使用CMyClass类,方法同QVector用法类似,见代码清单4-46。

代码清单4-46

void example02(){

    // 添加成员

    QList<CMyClass> lstObj;

    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类提供拷贝构造函数

    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;

    int idxList = 0;

    for (idxList = 0; idxList < lstObj.size(); idxList++) {

        cout << "    lstObj[" << idxList << "] : id = "

             << lstObj[idxList].getId() << ", name = "

             << lstObj[idxList].getName().toLocal8Bit().data() << endl;

    }

     // 查找

    cout << endl << "-------------- QList ---------------" << endl;

    cout << "begin find member in QList......" << endl;

    CMyClass myclassx(2013, "john");

    QList<CMyClass>::iterator iteList = 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;

    }

}

代码清单4-46中需要注意的是标号①处的lstObj.push_back(myclass1),这句代码要求CMyClass类提供拷贝构造函数。其实如果不写CMyClass类的拷贝构造函数,程序也能构建成功,因为编译器会为CMyClass类提供默认的拷贝构造函数。尝试一下封掉CMyClass的拷贝构造函数,看看是否能将项目构建成功。其实,封掉CMyClass的拷贝构造函数后,push_back()可以成功调用,但是程序却会出现运行时异常。这是为什么呢?因为编译器提供的默认拷贝构造函数仅仅执行按位复制,也就是将对象的成员变量的值一对一复制,而CMyClass类的成员中有指针,如果按位复制那么就不会为指针变量重新申请内存而是将它和被复制对象指向同一块内存。在CMyClass析构时会出现将同一块内存多次delete的问题,导致出现异常。所以,在本案例中应该为类编写显式的拷贝构造函数。

另外,因为在实现查找功能时用到了std::find(),所以仍然需要为CMyClass类重载operator==操作符。尝试封掉类CMyClass的operator==的重载操作符的定义和实现代码,看看会有什么结果。哈哈,编译器会报错。

error C2678: 二进制“==”: 没有找到接受“CMyClass”类型的左操作数的运算符(或没有可接受的转换)。

编译器明确提示需要为CMyClass提供operator==的重载,所以如果要使用std::find(),那么对类的operator==操作符的重载是不可缺少的。

3.向QList中添加自定义类对象的指针

向QList中添加自定义类对象的指针与QVector的案例类似,见代码清单4-47。

代码清单4-47

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;

    int idxList = 0;

    for (idxList = 0; idxList < lstObj.size(); idxList++) {

        cout << "    lstObj[" << idxList << "] : id = "

             << lstObj[idxList]->getId() << ", name = "

             << lstObj[idxList]->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[idxList]->getId() << ", name = "

             << lstObj[idxList]->getName().toLocal8Bit().data() << endl;

        delete lstObj[idxList];

    }

    // 方法2,使用迭代器遍历                                                  ①

    //QList<CMyClass*>::iterator iteList = lstObj.begin();

    //for (iteList = lstObj.begin(); iteList != lstObj.end(); iteList++, idxList++) {

    //    if (NULL != *iteList) {

    //        delete *iteList;

    //    }

    //}

    lstObj.clear();

}

代码清单4-47中的标号①处,方法2的代码被封掉的原因也是因为防止重复析构。此处给出方法2是为了演示用迭代器的方式遍历列表的成员。

本节介绍了链表类QList的用法,它跟QVector有很多相似的地方。不同之处在于,如果对性能要求更高一些,应考虑QVector。

----------------------------------------------------------------------------------------------------------------------------------------------

《Qt 5/PyQt 5实战指南》目录

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章