本帖最后由 baizy77 于 2019-7-2 20:34 编辑
版权声明--------------------------------------------------------------------------------------------------------------------- 作者: 女儿叫老白 转载请注明出处! --------------------------------------------------------------------------------------------------------------------- 引言 ---------------------------------------------------------------------------------------------------------------------- 上一节我们讲了数组,本节我们给大家介绍Qt的链表处理类QList。链表数据结构的特点是内存并不连续,但是增加、删除链表节点非常方便、高效,因为只需要在增加或删除节点的位置进行修改而不会影响其他节点。 正文 ---------------------------------------------------------------------------------------------------------------------- 如果要使用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(const QList<quint16>& lstObj) {
- cout << endl << "-------------- QList ---------------" << endl;
- cout << "print members using iterator......" << endl;
- QList<quint16>::const_iterator iteList = lstObj.begin();
- for (iteList = lstObj.begin(); iteList != lstObj.end(); 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.rbegin(); iteList != lstObj.rend(); iteList++) {
- cout << " " << *iteList << endl;
- }
- }
-
- // 遍历成员-使用下标
- void printByIndex(const QList<quint16>& lstObj){
- cout << endl << "-------------- QList ---------------" << endl;
- cout << "print members using idx......" << endl;
- int idxList = 0;
- for (idxList = 0; idxList < lstObj.size(); idxList++) {
- cout << " lstObj[" << idxList << "] =" << lstObj[idxList] << endl;
- }
- }
复制代码 定义迭代器时,使用的语法是: 链表对象的类型::iterator。即上述代码中的: 用来判断迭代器是否已经遍历完毕的方法同QVector类似: 场景2. 使用自定义类对象 我们使用CMyClass类对象,并将其添加到链表。方法同QVector类似。 - /**
- * @brief 使用自定义类对象
- * @return 无
- */
- 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;
- }
- }
复制代码 上面需要注意的是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;
- 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();
- }
复制代码
结语 ---------------------------------------------------------------------------------------------------------------------- 本节我们介绍了链表类QList的用法,它跟QVector有很多相似的地方。唯一不同的地方是数据结构本身所代表的含义。当我们希望通过下标快速访问连续内存对象时,可以使用数组(QVector);当我们希望频繁高效的增删节点时,就要用到链表。这两个类各有各自的特点和应用场景,互相不可替代。下一章节我们将为大家介绍映射,让我们共同期待。
|