本帖最后由 baizy77 于 2019-7-2 20:32 编辑
版权声明--------------------------------------------------------------------------------------------------------------------- 作者: 女儿叫老白 转载请注明出处! --------------------------------------------------------------------------------------------------------------------- 引言 ---------------------------------------------------------------------------------------------------------------------- 在软件研发过程中,数据结构是无论如何也不能回避的话题,包括stl在内,很多第三方库都对常用数据结构进行了模板化封装,从本节课开始,我们要给大家介绍Qt封装的几个常用数据结构模板类。今天先介绍数组类QVector。
正文 ---------------------------------------------------------------------------------------------------------------------- 我们都知道数组的成员在内存中是连续存放的。当使用下标访问数组成员时效率是非常高的。但是当我们扩大数组容量时,很有可能需要重新申请内存,然后将旧的数组成员拷贝到新内存中,这时可能会损失一些性能。OK,回归正题,我们今天设计了3个场景给大家介绍QVector: 1. 向QVector添加成员并遍历。 2. 使用自定义类对象 3. 使用自定义类对象指针 首先简单说一下课程附件代码:因为要用到命令行来接受用户输入,因此需要使用: 因为我们要用到QVector,所以请在文件开头的include区域增加: 下面我们分别看一下这3个示例: 示例1. 向QVector添加成员并遍历 在这个示例中,我们用的是int。如果用到Qt的类,代码也类似。 - /**
- * @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);
- ……
- }
复制代码 上述代码中,我们建立了一个成员为quint32的数组。这是一个整数数组,数组名称为vecId。紧接着我们调用push_back()接口向数组中添加成员。push_back()的功能是将新加入的成员放到数组的尾部,而push_front()接口则负责将成员追加到数组的首部,也就是下标为0的位置。我们可以通过将数组的成员打印到终端来印证这一点: - // 遍历成员-使用下标
- cout << endl << "-------------- QVector ---------------" << endl;
- cout << "print members using idx......" << endl;
- int idxVec = 0;
- for (idxVec = 0; idxVec < vecId.size(); idxVec++) {
- cout << " vecId[" << idxVec << "] =" << vecId[idxVec] << endl;
- }
复制代码 在上面的代码中,我们使用下表来便利成员,方法是:数组名[下标],这是C++的语法。当然,我们还可以通过迭代器来访问,也非常方便: - // 遍历成员-使用迭代器(正序)
- cout << endl << "-------------- QVector ---------------" << endl;
- cout << "print members using iterator......" << endl;
- QVector<uint>::iterator iteVec = vecId.begin();
- idxVec = 0;
- for (iteVec = vecId.begin(); iteVec != vecId.end(); iteVec++, idxVec++) {
- //cout << " vecId[" << idxVec << "] =" << *iteVec << endl;
- cout << " " << *iteVec << endl;
- }
复制代码
上述代码是通过正序访问,相当于从下标0开始到最后一个成员。为了使用迭代器遍历,我们需要先定义迭代器,并将其初始化为指向数组的开头(第一个成员): - QVector<uint>::iterator iteVec = vecId.begin();
复制代码 用来判断迭代器是否已经遍历完毕的方法是: 其中vecId.end()表示数组的结尾(并非数组的最后一个成员,而是最后一个成员的下一个位置,因此是真正的结尾)。 当我们需要用迭代器操作数组成员并输出时,可使用*迭代器的语法,比如上面代码中的*iteVec。 我们还可以倒序遍历数组成员: - // 遍历成员-使用迭代器(倒序)
- 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;
- }
复制代码 从上述代码可以看出,倒序迭代器与正序迭代器并非同一个类型。倒序迭代器的类型为: QVector<quint32>::reverse_iterator,并且它被初始化指向数组的倒数第一个成员vecId.rbegin(): - QVector<quint32>::reverse_iterator reverseIteVec = vecId.rbegin();
复制代码 倒序迭代器的取值方法同正序迭代器一样,都是采用*迭代器的语法: - cout << " " << *reverseIteVec << endl;
复制代码 下面,我们到数组中查找某个成员,方法是使用stl的算法模板类algorithm,因此需要包含其头文件: 我们看一下查找一个对象并且在它的前面插入另一个对象怎么实现: - // 查找&插入
- iteVec = std::find(vecId.begin(), vecId.end(), 2042);
复制代码 std::find()是altorithm中的搜索算法,它需要3个参数,前两个分别表示要查找的范围,这里我们取数组的开头和结尾;最后一个参数表示要查找的对象,这个例子中我们查找2042这个数字。 - if (iteVec != vecId.end()) {
- vecId.insert(iteVec, 10000); // insert before
- cout << endl << "-------------- QVector ---------------" << endl;
- cout << "insert 10000 before 2042 in vector." << endl;
- }
-
复制代码 上述代码表示,如果找到了则在找到的位置之前插入一个数值:10000。insert()接口的功能是在指定迭代器之前插入一个成员。iteVec != vecId.end()表示迭代器不等于数组的结尾,就表示找到了我们需要查找的成员。 下面的代码用来展示查找与删除功能: - // 查找&删除
- iteVec = std::find(vecId.begin(), vecId.end(), 2042);
- if (iteVec != vecId.end()) { // 找到了
- cout << "erase 2042 from vector." << endl;
- vecId.erase(iteVec);
- }
复制代码 其中,查找功能是一样的。删除的功能由erase()接口提供,它需要一个迭代器参数,所以我们提供了查找到的迭代器。 有时候数组中存在多个相同成员,如果我们希望把它们都找到并从数组中删除该怎么做呢? - // 查找&删除
- for (iteVec=vecId.begin(); iteVec != vecId.end(); ) {
- if ((*iteVec) == 2033) {
- cout << "find 2033 in vector." << endl;
- iteVec = vecId.erase(iteVec); // erase()接口会返回删除后的下一个迭代位置
- } else {
- iteVec++;
- }
- }
复制代码 上述代码实现了我们需要的功能,当它找到2033这个数时,调用erase()接口将迭代器删除,然后把返回值赋给迭代器iteVec,这时iteVec就指向删除2033后的下一个位置了,因此无需再执行++操作,如果没找到2033,我们才对迭代器执行++操作。 其实,如果这行代码正处于某一个类的const接口中,而且迭代器需要访问类的成员数组,那么就需要使用const迭代器,并且初始化时要使constBegin(): - QVector<quint32>::const_iterator iteVec = vecId.constBegin();
复制代码 对const迭代器进行判断也要使用constEnd(): - if (iteVec != vecId.constEnd())
复制代码示例2. 使用自定义类对象 下面我们看一下在数组中使用自定义类对象作为成员: - /**
- * @brief 使用自定义类对象
- * @return 无
- */
- 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);
- ……
- }
复制代码 首先我们定义了几个CMyClass对象将他们并初始化,然后调用push_back()将它们添加到数组。因为push_back()接口需要传入类对象的拷贝而非引用或指针,所以编译器会调用对象的拷贝构造函数。因此,我们需要为类CMyClass提供拷贝构造函数,以免编译器默认生成的拷贝构造函数无法满足要求: myclass.h: - // 拷贝构造函数
- CMyClass(const CMyClass& right);
复制代码
然后,我们遍历数组。首先使用下标遍历: - // 遍历成员,使用下标
- 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;
- }
复制代码 使用下标遍历数组跟前面的方法类似。 然后使用迭代器遍历: - // 遍历成员,使用迭代器
- 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)->的语法调用对象的接口。 下面我们来查找某个对象: - // 查找
- 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;
- }
复制代码 在上面的代码中,我们定义一个被查找对象myclassx,同样也是使用std::find()来查找。这需要我们的自定义类CMyClass重载operator==操作符,否则编译器会报错。 myclass.h: - // 重载操作符operator==
- bool operator==(const CMyClass& right);
复制代码示例3. 使用自定义类对象指针 最后,我们使用类对象的指针来演示数组的使用。 - /**
- * @brief 使用自定义类对象指针
- * @return 无
- */
- 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);
- ……
- }
-
复制代码
我们先new出数组对象,然后将这些对象指针添加到数组。然后,我们遍历数组的成员: - // 遍历成员
- cout << endl << "-------------- QVector ---------------" << endl;
- cout << "print members in custom defined class 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;
- }
-
复制代码 遍历数组成员的代码与前面的示例类似。 与将对象添加到数组有所不同,当我们将指针添加到数组时,退出程序前需要将这些指针占用的内存进行释放,否则将导致内存泄漏: - // 退出前要释放内存
- // 方法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中的对应类,让我们开始练习吧。
|