本案例對應的源代碼目錄: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; } } |
向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。
----------------------------------------------------------------------------------------------------------------------------------------------