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

在软件研发过程中,数据结构是个无论如何也不能回避的话题。包括STL在内,很多第三方库都对常用数据结构进行了模板化封装。从本节课开始,将介绍Qt封装的几个常用数据结构模板类,本节先介绍数组类QVector。学过C++的人员都知道数组的成员在内存中是连续存放的,因此使用下标访问数组成员时效率是非常高的。但是当扩大数组容量时,有可能需要重新申请内存,然后将旧的数组成员复制到新内存中,这会损失一些性能。所以如果经常对数据集进行插入、删除元素的操作,那么不建议使用数组。OK,回归正题,本节设计了3个场景来介绍QVector。

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

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

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

首先简单说一下课程附件代码。因为要用到命令行来接受用户输入,因此需要在pro中修改CONFIG参数项。

CONFIG += console

因为要用到QVector,所以请在文件开头增加对<QVector>的包含语句。

#include <QVector>

下面分别看一下这3个场景的案例。

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

在代码清单4-28中,将int类型的数据添加到QVector。如果把int换成Qt的类,代码也类似。

代码清单4-28

/**

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

...

}

代码清单4-28中,建立了一个成员类型为quint32的数组。这是一个整数数组,数组名称为vecId。调用push_back()接口可以向数组中添加成员。push_back()的功能是将新加入的成员添加到数组的尾部,而push_front()接口则负责将成员添加到数组的首部,也就是下标为0的位置。可以将数组的成员打印到终端来印证这一点,见代码清单4-29。

代码清单4-29

// example01()

// 遍历成员-使用下标

cout << endl << "-------------- QVector ---------------" << endl;

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

int idxVec = 0;

for (idxVec = 0; idxVec < vecId.size(); idxVec++) {

cout << "    vecId[" << idxVec << "] =" << vecId[idxVec] << endl;

}

在代码清单4-29中,使用下标遍历数组的成员。语法同访问C++中的普通数组成员一样:数组名[下标]。另外,通过正序迭代器(简称迭代器)来访问数组成员也非常方便,见代码清单4-30。

代码清单4-30

// example01()

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

cout << endl << "-------------- QVector ---------------" << endl;

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

QVector<quint16>::iterator iteVec = vecId.begin();                              ①

idxVec = 0;

for (iteVec = vecId.begin(); iteVec != vecId.end(); iteVec++, idxVec++) {        ②

cout << "    " << *iteVec << endl;                                           ③

}

代码清单4-30展示了正序访问数组的方法,即从下标0开始遍历到数组最后一个成员。为了使用迭代器遍历,需要先定义迭代器,并将其初始化为指向数组的开头(第一个成员),见标号①处。定义迭代器的语法是:数组类型::iterator,即QVector<quint16>::iterator。用来判断迭代器是否已经遍历完毕的代码是:iteVec  !=  vecId.end(),见标号②处。其中vecId.end()表示数组的结尾(并非数组的最后一个成员,而是最后一个成员的下一个位置,是真正的结尾)。当需要用迭代器访问数组成员时,可使用“*迭代器”的语法,比如标号③处的*iteVec。还可以通过倒序遍历数组成员,见代码清单4-31。

代码清单4-31

// example01()

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

cout << endl << "-------------- QVector ---------------" << endl;

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

QVector<quint32>::reverse_iterator reverseIteVec = vecId.rbegin();                   ①

for (reverseIteVec = vecId.rbegin(); reverseIteVec != vecId.rend();reverseIteVec++) { ②

cout << "    " << *reverseIteVec << endl;                                        ③

}

从代码清单4-31可以看出,倒序迭代器与正序迭代器并非同一个类型。倒序迭代器的类型为QVector<quint32>::reverse_iterator。在初始化时,倒序迭代器指向数组的倒数第一个成员vecId.rbegin(),见标号①处。用来判断倒序迭代器是否已经遍历完毕的代码是:reverseIteVec !=  vecId. rend(),见标号②处。其中vecId. rend()表示数组倒序的结尾(并非数组的第一个成员,而是指向其前一个位置)。当需要用倒序迭代器访问数组成员时,可使用“*迭代器”的语法,比如标号③处的* reverseIteVec。接下来,要到数组中查找某个成员,方法是使用STL的算法模板类algorithm,因此需要包含其头文件<algorithm>。

下面看一下查找某个数组成员并且在它的前面插入另一个成员该怎么实现,如代码清单4-32所示。标号①处的std::find()是<altorithm>中的搜索算法,它需要3个参数。前两个参数分别表示要查找的范围,比如数组的开头和结尾,最后一个参数表示要查找的对象,比如2042这个数字。

代码清单4-32

// example01()

...

// 查找&插入

// 在2042之前插入数字:10000.

iteVec = std::find(vecId.begin(), vecId.end(), 2042);                               ①

if (iteVec != vecId.end()) {          // 找到了,此时iteVec指向2042。

vecId.insert(iteVec, 10000);      // 在2042之前插入数字10000。

cout << endl << "-------------- QVector ---------------" << endl;

cout << "insert 10000 before 2042 in vector." << endl;

}

代码清单4-32表示,如果找到了则在找到的位置之前插入数字10000。insert()接口的功能是在指定迭代器之前插入一个成员。iteVec != vecId.end()表示迭代器未指向数组结尾,其含义是找到了需要查找的数字。代码清单4-33用来演示查找与删除数组中指定成员功能。

代码清单4-33

//  example01()

...

// 查找&删除

iteVec = std::find(vecId.begin(), vecId.end(), 2042);                               ①

if (iteVec != vecId.end()) { // 找到了

cout << "erase 2042 from vector." << endl;

vecId.erase(iteVec);

}

代码清单4-33中的查找功能跟代码清单4-32一样。删除数组中指定成员的功能由erase()接口提供,该接口需要一个迭代器参数,可以用标号①处用查找接口返回的迭代器。有时候数组中存在多个相同成员,如果希望把它们都找到并从数组中删除该怎么做呢?见代码清单4-34。

代码清单4-34

// example01()

...

// 查找&删除

for (iteVec=vecId.begin(); iteVec != vecId.end(); ) {                               ①

if ((*iteVec) == 2033) {

cout << "find 2033 in vector." << endl;

// erase()接口会返回删除后的下一个迭代位置

iteVec = vecId.erase(iteVec);                                               ②

} else {

iteVec++;

}

}

代码清单4-34中,当找到2033这个数字时,调用erase()接口将找到的迭代器从数组中删除,然后把返回值重新赋给迭代器iteVec,见标号②处。这时iteVec就指向删除2033后的下一个位置了,因此无须再执行迭代器自加操作,如果没找到2033,才对迭代器执行自加操作,在for循环设置步长时应采取空操作,见标号①处。

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

向QVector中添加自定义类的对象作为成员,见代码清单4-35。

代码清单4-35

void example02(){

// 添加成员

QVector<CMyClass> vecObj;                                                      ①

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

vecObj.push_back(myclass1);

vecObj.push_back(myclass2);

vecObj.push_back(myclass3);

vecObj.push_back(myclass4);

vecObj.push_back(myclass5);

vecObj.push_back(myclass6);

...

}

在代码清单4-35中,首先定义了几个CMyClass对象并将它们初始化,然后调用vecObj.push_back()将它们添加到数组中。因为vecObj的类型是QVector<CMyClass>,所以push_back()接口需要传入对象的备份而非引用或指针,也就意味着编译器会调用对象的拷贝构造函数。因此,需要为类CMyClass编写拷贝构造函数(见代码清单4-36),以免编译器默认生成的拷贝构造函数无法满足要求,拷贝构造函数的实现请见本节的源代码。

代码清单4-36

// 拷贝构造函数

CMyClass(const CMyClass& right);

然后使用下标遍历数组,见代码清单4-37。

代码清单4-37

// example02()

// 遍历成员,使用下标

cout << endl << "-------------- QVector ---------------" << endl;

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

int idxVec = 0;

for (idxVec = 0; idxVec < vecObj.size(); idxVec++) {

cout << "    vecObj[" << idxVec << "] : id = " << vecObj[idxVec].getId() << ", name = " << vecObj[idxVec].getName().toLocal8Bit().data() << endl;

}

然后使用迭代器遍历数组,见代码清单4-38。

代码清单4-38

// 遍历成员,使用迭代器

QVector<CMyClass>::iterator iteVec = vecObj.begin();

for (iteVec = vecObj.begin(); iteVec != vecObj.end(); iteVec++) {

cout << "    vecObj[" << idxVec << "] : id = " << (*iteVec).getId() << ", name = " << (*iteVec).getName().toLocal8Bit().data() << endl;

}

在使用迭代器时,也是用(*iteVec)的方法来访问数组成员。因为数组里存放的是对象,所以可以使用“.”操作符来调用对象的接口。如果数组里存放的是指针,就要用(*iteVec)->的语法调用对象的接口。 下面来查找某个对象,见代码清单4-39。

代码清单4-39

// example02()

// 查找

cout << endl << "-------------- QVector ---------------" << endl;

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

CMyClass myclassx(2013, "john");                                                     ①

QVector<CMyClass>::iterator iteVec = 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;

}

在代码清单4-39中,在标号①处定义一个被查找对象myclassx,在标号②处同样也是使用std::find()来查找该对象。如代码清单4-40所示,这需要为自定义类CMyClass重载operator==操作符,否则编译器会报错。CMyClass::operator==()的实现见本节配套的源代码。

代码清单4-40

class CMyClass {

...

// 重载操作符operator==

bool operator==(const CMyClass& right);

...

}

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

使用类对象的指针来演示QVector用法的代码见代码清单4-41。

代码清单4-41

void example03() {

// 添加成员

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

...

}

代码清单4-41中,先new出一些对象,然后将这些对象指针添加到数组。接着遍历数组的成员,如代码清单4-42所示。

代码清单4-42

// example03()

// 遍历成员

cout << endl << "-------------- QVector ---------------" << endl;

cout << "print members in custom defined class using index......" << endl;

int idxVec = 0;

for (idxVec = 0; idxVec < vecObj.size(); idxVec++) {

cout << "    vecObj[" << idxVec << "] : id = "

<< vecObj[idxVec]->getId() << ", name = "

<< vecObj[idxVec]->getName().toLocal8Bit().data() << endl;

}

与将对象添加到数组有所不同,将指针添加到数组时,退出程序前需要将这些指针指向的内存进行释放,否则将导致内存泄漏,如代码清单4-43所示。代码清单4-43给出了两种方法来遍历数组成员并释放数组成员所指向的内存。因为不能重复释放同一块内存,所以把用迭代器遍历的代码封掉。可以尝试一下把用下标遍历数组的代码封掉,把用迭代器遍历数组的代码解封。

代码清单4-43

// example03()

// 退出前要释放内存

// 方法1,使用下标遍历

cout << endl << "-------------- QVector ---------------" << endl;

cout << "desctruct members before exit......" << endl;

idxVec = 0;

for (idxVec = 0; idxVec < vecObj.size(); idxVec++) {

cout << "    deleting " << idxVec << ", id = "

<< vecObj[idxVec]->getId() << ", name = "

<< vecObj[idxVec]->getName().toLocal8Bit().data() << endl;

delete vecObj[idxVec];

}

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

//QVector<CMyClass*>::iterator iteVec = 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 5/PyQt 5实战指南》目录

4.5 案例9 使用QVector处理数组相关推荐

  1. JS案例:使用对象、对象数组、正则表达式

    JS案例:使用对象.对象数组.正则表达式 1.使用对象 声明对象变量,其实就是将一个json对象赋给一个变量,可以看到json对象就由一些键值对构成. 运行效果: 如果将{id: 3, name: & ...

  2. hive中存Array类型的数据的案例,将字符串的数组格式转成数组的字符串,自定义函数方式处理‘[12,23,23,34]‘字符串格式的数据为array<int>格式的数据。

    1.创建表带有Array的表: create table t_afan_test ( info1 array<int>, info2 array<string> ) ROW F ...

  3. 第19篇 基础(十九)详解QVector(数组)

    1.QVector数组介绍 数组我们都非常熟悉,数据在内存中是连续分布的.这种结构的缺点是当元素很多时,除了在结尾添加一个新元素以及修改某个元素值之外,其他相关的函数(如最开头插入)执行起来会随元素增 ...

  4. JavaScipt30(第七个案例)(主要知识点:数组some,every,findIndex方法)

    承接上文,这是第7个案例,这个案例没什么说的,主要有三个注意点: 附上项目链接: https://github.com/wesbos/JavaScript30 // 1. slice(begin, e ...

  5. JS案例——找出两个数组中的不同元素或对象、数组去重

    一.找出两个数组中的不同元素或对象 1. 数据 var arr1 = ["张瑞淑", "徐海涛", "谢岗岗", "薛鹏" ...

  6. C++_结构体中const使用场景_结构体_毕业设计案例_使用结构体数组_随机数种子---C++语言工作笔记027

    然后我们再看const符号,在结构体中的应用 首先我们新建一个结构体.student 然后定义一个结构体变量,并初始化 然后我们再写个方法,去打印这个结构体变量 可以看到我们用的是传值

  7. 案例:图书管理——补充知识(数组相关API)||补充知识(数组响应式变化)

    案例:图书管理 Vue数组操作 1.变异方法:会影响数组的原始数据的变化. 2.替换数组:不会影响原始的数组数据,而是形成一个新的数组. <!DOCTYPE html> <html ...

  8. Algorithms_基础数据结构(01)_线性表之数组数组的应用案例分析

    文章目录 大纲图 数组的经典面试题目 数据结构三要素 数据逻辑结构(线性结构&非线性结构) 数据存储结构(顺序存储.链式存储.索引存储和散列存储) 顺序存储 链式存储 索引存储 散列存储 数据 ...

  9. c++两个数组对比去掉重复的元素_30 数组案例

    数组案例 案例一:筛选指定数组元素 要求:将数组[2,0,6,1,77,0,52,0,25,7]中的0去掉后,形成一个不包含0的新数组. var arr = [2,0,6,1,77,0,52,0,25 ...

最新文章

  1. 所有类是object的子类,但是又可以继承一个其他类解析
  2. Hibernate一对一映射示例注释
  3. jquery插件dataTables(dataTables在显示表格的时候,果然是个好东西,支持排序/搜索/分页/...)
  4. sql_action
  5. find命令使用案例
  6. 与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误。
  7. 京瓷打印机p5026cdn_京瓷ECOSYS P5026cdn驱动
  8. Gstreamer 命令行
  9. S7-200SMART 模拟量间接寻址 批量处理
  10. 一篇文让你看懂NB-IoT、LoRa、eMTC、Sigfox及ZigBee的应用场景【转】
  11. percentile_approx函数
  12. 微信公众平台最实用的工具和技巧大集合
  13. 使用Python调整图片尺寸(大小)
  14. php面向过程求圆 三角形 长方形计算,求,圆,三角形,长方形,梯形的面积,周长公式...
  15. spring @Autowired用法
  16. Maven中央仓库地址
  17. 记录Python 入门练习题目
  18. Raspberry PI 编译WLan驱动模块, 并配置登录WIFI
  19. 十进制转换为十六进制(转载过来的)
  20. 计算机制造技术飞速发展 使用计算机,机械设计制造及其自动化中计算机技术的应用分析...

热门文章

  1. 真人访谈:不想一辈子打工,我做了个月入 40 余万的 App
  2. Orleans 知多少 | .NET Core 分布式云计算框架(转)
  3. windows串口通信函数API
  4. python新媒体运营工作是什么_运营这个岗位是做什么工作的?
  5. MTK 开发调试方法
  6. php 课文的 单词,英语课文生词的重点处理
  7. 看了林志颖的个人履历 我第一次被他震住了
  8. 使用ceph的对象存储
  9. Google 社招 | 谷歌云最大规模招聘 就是现在!
  10. Win7系统升级Win10提示0x80072F8F-0x20000错误代码的解决方法