QT 常用数据结构整理

  • Post author:
  • Post category:其他



目录


QString篇


QByteArray 篇


顺序容器


QList


QLinkedList


QQueue


QVector


QStack


关联容器


QMap


QMultiMap


迭代器


QString篇

    //初始化
    bool bOk = false;
    QString str = "sd";
    QString strTemp(str);
    str = QString("%1,%2").arg("11").arg("-gg");
    qDebug()<<str;
    str.sprintf("%s %d","ni",1);
    qDebug()<<str;
    qDebug()<<str.toStdWString().data();

    qDebug()<< str.sprintf(("%.3f"),3.1415926);
    //需要注意 C asprintf是内存分配,失败返回-1好像,但是要free
    qDebug()<< str.asprintf(("%2f"),3.1415926); //不建议使用官方说的

    //is null "\0"不为空
    str = "\0";
    bOk = str.isEmpty();
    qDebug()<<bOk;
    bOk = str.isNull();
    qDebug()<<bOk;

    // 添加 也可以用运算符+
    str = "sd";
    str.append("89");
    qDebug()<< str;
    str.push_back("11");
    str.push_front("A");
    qDebug()<<str;
    //移除
    str.remove("sd");
    qDebug()<<str;
    //附加
    str.prepend("-->");
    qDebug()<<str;

    // 计算长度
    str = "sd\n";
    qDebug()<<str.count();
    qDebug()<<str.size();
    qDebug()<<str.length();
    QString* pStr = &str;
    qDebug()<<pStr->count();
    qDebug()<<pStr->size();
    qDebug()<<pStr->length();

    // 去空格
    str = "  1 sd\n   ";
    qDebug()<< str.trimmed();    //去首
    qDebug()<< str.simplified(); //首尾

    //截取
    str = "12345678";
    qDebug()<< str.left(3);
    qDebug()<< str.right(3);
    qDebug()<< str.mid(2,5);
    qDebug()<< str.remove(2,5);

    // 有规律截取
    QString csv = "forename,middlename,surname,phone";
    QString path = "/usr/local/bin/myapp"; // First field is empty
    QString::SectionFlag flag = QString::SectionSkipEmpty;
    QString data = "forename**middlename**surname**phone";

    str = data.section("**", 2, 2); // str == "surname"
    str = data.section("**", -3, -2); // str == "middlename**surname"
    str = csv.section(',', 2, 2);   // str == "surname"
    str = path.section('/', 3, 4);  // str == "bin/myapp"
    str = path.section('/', 3, 3, flag); // str == "myapp"

    //切割字符
    str = "q/w/e/r/t/y/u";
    qDebug()<< str.split('/');

    //超出用其他代替
    str = "qwertyu";
    qDebug()<< str.leftJustified(20,'.',true).toStdString().data();

    //查询包含特定字符包含大小写(参数2区分大小写)
    bOk = str.contains("llo Wor",Qt::CaseSensitive);
    qDebug()<< bOk;

    //以某个字符开头
    bOk = str.startsWith("llo Wor",Qt::CaseInsensitive);
    qDebug()<< bOk;

    //以某个字符结尾
    bOk = str.endsWith("llo Wor",Qt::CaseSensitive);
    qDebug()<< bOk;

    //替换字符
    str.replace("l","2");
    qDebug()<< str;

    // 与QByteArray 相互转换
    //我们都是在使用QString。QString存储了16位unicode码,很容易用来存储非ASCII或是非Lantin1的编码,另外QString在所有的QtAPI中都是通用的。
    //有两种情况下会比较适合使用QByteArray,第一就是你要存储一般的位数据,第二种情况就是在内存资源很珍贵的情况下,例如像Qt for EmbeddedLinux
    QByteArray byteArray = "Hello World";
    str = QString::fromUtf8(byteArray);

    str = "Hello World";
    byteArray = str.toUtf8();

    //转换进制
    str = "-120";
    int dec = str.toInt(&bOk);
    qDebug()<< dec << bOk;
    dec = str.toUInt(&bOk,16);
    qDebug()<< dec << bOk;

    str = "-120.5";
    double db = str.toDouble();
    qDebug()<< db;

    float f = str.toFloat();
    qDebug()<< f;

QByteArray 篇

提供字节数组。QByteArray 可用于存储原始字节(包括“0”)和传统的 8 位“0”终止字符串。使用 QByteArray 比使用 const char * 方便得多。在后台,它始终确保数据后面跟着“0”终止符,并使用隐式共享(写入时复制)来减少内存使用量并避免不必要的数据复制。同时QByteArray也支持容器操作append、prepend、remove、contains等操作

//初始化 QByteArray 的一种方法是简单地将 const char * 传递给其构造函数。
//例如,以下代码创建一个大小为 5 的字节数组,其中包含数据“Hello”:
    QByteArray ba("hello");

    // or 用下表赋值
    ba.resize(5);
    ba[0] = 0x3c;
    ba[1] = 0xb8;
    ba[2] = 0x64;
    ba[3] = 0x18;
    ba[4] = 0xca;

    // 需要注意判断空,因为isNull指向空字节数组"0"字符(不是空指针)
    QByteArray("").isNull();        // returns false
    QByteArray("").isEmpty();       // returns true

    //也支持 append/clear/compare/indexOf/insert/push_back/count/chop等操作,这里不一一介绍可以查看帮助文档
    QByteArray x("free");
    QByteArray y("dom");
    x.append(y);
    // x == "freedom"

    //back()操作 等同于at(size() - 1)
    qDebug()<<x.back();

    //转char*
    QByteArray array("Hello world");
    char *data = array.data();
    while (*data) {
         qDebug() << "[" << *data << "]";
         ++data;
     }

    // or
    QString tmp = "test";
    QByteArray text = tmp.toLocal8Bit();
    char *pdata = new char[text.size() + 1];
    strcpy(pdata, text.data());
    delete [] pdata;

    // or
    const char* pTemp = text.constData();
    qDebug() << pTemp;

    //转QByteArray
    QString strTemp = QString(pTemp);
    qDebug() << strTemp;

    // 参数2故意写错 返回 "test\x00" 5
    QByteArray byte(QByteArray::fromRawData(pTemp,5));
    qDebug() << byte << byte.size();

    byte = QByteArray(pTemp);
    qDebug() << byte << byte.size();

    // 进制转换
    int n = 63;
    qDebug() << QByteArray::number(n);              // returns "63"
    qDebug() << QByteArray::number(n, 16);          // returns "3f"
    qDebug() << QByteArray::number(n, 16).toUpper();  // returns "3F"

    //unsigned char转换
    char test[10240] = "hello Wold";
    unsigned char test2[1024] = "123456";
    QByteArray array1(test);
    QByteArray array2 = QByteArray::fromRawData(reinterpret_cast<const char*>(test2), sizeof(test2));
    QByteArray array3 = QString::fromUtf8(reinterpret_cast<const char*>(test2)).toUtf8();

看完上面来看看这个避坑实例

    QByteArray byte;
    byte[0] = 'A';
    byte[1] = '\0';
    byte[2] = '\0';
    byte[3] = '\0';
    QString strName = QString("%1%2%3%4").arg(byte[0]).arg(byte[1]).arg(byte[2]).arg(byte[3]);
    QString nam = strName + "1";
    qDebug()<< nam;    //输出"A\u0000\u0000\u00001" 有的编译器调式输出A
    nam = QString::fromUtf8(byte) + "1";
    qDebug()<< nam;    //输出"A1"

问题的原因在于

QString

的构造和显示方式。

在代码中,

byte[0] = 'A'

将字符’A’的ASCII码值65存储在

byte[0]

中。然后,通过

arg

方法将

byte

的每个元素转换为

QString

并合并在一起,得到

strName

的值”A”。

接下来,将字符串”A”与字符串”1″拼接,得到新的字符串”A1″,并存储在

nam

中。

最后,使用

qDebug()

输出

nam

的值。在输出时,由于

QString

默认使用UTF-16编码,所以字符’A’的UTF-16编码的高字节部分为65,低字节部分为0,而字符’1’的UTF-16编码的高字节部分为49,低字节部分也为0。因此,当

qDebug()

输出

nam

时,它实际上输出了四个字符的UTF-16编码的高字节部分,即65、0、0、0和49

顺序容器

关于容器,首先要提一下模板template <typename T>,因为容器的实现方式就有模板这个概念。

关于容器储存数据要求:

1.须有构造函数(无参)

2.须有拷贝构造函数

3.赋值运算

需要注意:

QObject及其子类(如QWidget和Qdialog等)是不可以存储在容器中的,但QObject及其子类的指针可以存储在容器中。


因为它们不支持拷贝构造函数和赋值运算符。QObject及其子类采用了对象树的概念,它们之间存在所有权关系。当一个QObject对象被销毁时,它会自动销毁其所有的子对象。然而,QObject及其子类的指针可以存储在容器中,因为指针只是存储了对象的地址,并没有涉及对象本身的复制。容器内的指针使用完要手动释放

QList

QList内存方式使用连续的内存块来存储其元素,这些元素按照它们在列表中的顺序依次排列。当添加新的元素时,QList会根据需要自动扩展内存空间,以容纳更多的元素。如果列表中的元素被移除,QList会自动收缩内存空间(这里引出一个概念动态内容分配),同时也支持快速的随机访问,QList基于指针数组实现。即QList内部维护着一个指针数组,每个指针指向实际存储的元素。这样可以通过索引快速访问特定位置的元素。

QList<int> list;

    //添加元素
    list.append(1);
    list.append(2);
    list.push_front(-2);
    list.push_back(3);
    qDebug()<< list;

    list.insert(0,-1);
    list.insert(1,-1);
    list.insert(2,-1);
    qDebug()<< list;    // (-2, -1, 1, 2, 3)

    //删除 并释放内存控件 包含clear
    list.removeAll(1);
    qDebug()<< list;
    list.removeOne(-1);
    qDebug()<< list;

    //并不释放内存控件
    list.removeAt(0);
    qDebug()<< list;    // (-1, 1, 2, 3)
    list.removeFirst();
    qDebug()<< list;
    list.removeLast();
    qDebug()<< list;
    list.pop_front();
    qDebug()<< list;
    list.pop_back();
    qDebug()<< list;

    list.clear();
    qDebug()<< list;

    //大小
    list.append(1);
    list.append(2);
    int lenght = list.size(); // or lenght() or count

    //访问
    qDebug()<< list.at(0);  // 效率高,不能当左值,但是序号不对容易崩溃
    qDebug()<< list[0];
    qDebug()<< list.value(1);
    qDebug()<< list.value(3,-1); // 越界就返回设置值
    
    //遍历 这里不要remove 当你删除一个元素后,迭代器会失效
    // 遍历列表并删除某些元素 
    for(auto it = list.begin(); it != list.end();)
    {
        if(*it == 0)
        {
            // 删除当前元素并得到指向下一个元素的新迭代器
            it = list.erase(it);
        }
        else
        {
            // 继续遍历下一个元素
            ++it;
        }
    }
    
    // 遍历列表并在某些位置插入新元素
    for(auto it = list.begin(); it != list.end(); ++it)
    {
        if(*it == 5)
        {
            // 在当前位置插入新元素,并得到指向新插入元素的迭代器
            it = list.insert(it, 10);
            // 更新迭代器以指向正确位置
            ++it;
        }
    }
    
    
    //遍历 C++里的
    
    for each (auto var in list)
    {
        qDebug()<< var;
    }

QLinkedList

是双向链表,每个节点都存储了数据元素和指向下一个节点的指针。内存结构如下:首先,存储了指向第一个节点的指针(头指针)。每个节点包含两个主要部分:数据元素和指针。数据元素是节点中实际存储的数据,可以是任意类型。指针是指向下一个节点的指针,它存储了下一个节点的地址或空指针。因此重复删除或者移动速度会比Qlist快点

    QLinkedList<int> integerList;

    // 添加元素
    integerList << 1 << 2;
    integerList.append(3);
    integerList.insert(0,0);
    integerList.prepend(-1);
    integerList.push_back(4);
    integerList.push_front(5);

    //移除
    integerList.takeFirst();
    integerList.takeLast();

    //删除
    integerList.removeOne(1);
    integerList.removeAll(1);

    //查找
    bool bOk = integerList.contains(2);

    //迭代
    QLinkedList<int>::iterator it;
    for (it = integerList.begin(); it != integerList.end(); ++it) {
        qDebug() << *it;
    }

    //只读迭代
    QLinkedList<int>::const_iterator cit;
    for (cit = integerList.constBegin(); cit != integerList.constEnd(); ++cit) {
        qDebug() << *cit;
    }

QQueue

QQueue 是 QList 的派生类,继承了列表的全部功能,在此基础上新增了dequeue、enqueue、head操作。

 QQueue<int> queue;
 queue.enqueue(1);
 queue.enqueue(2);
 queue.enqueue(3);    //入队操作,添加队尾
 queue.dequeue();       //出队操作,将队头元素移除
 while (!queue.isEmpty())
     cout << queue.dequeue() << endl;

//T &    head()                          //获取队头的读写引用
//const T &    head() const      //获取队头的只读引用

QVector

QVector<T>和其他QT容器、大部分值类型的类(包括 QString)一样,使用隐式共享。但拷贝一个QVector时,内部只是拷贝了一个指针和递增了引用计数,只有修改数据时,才会进行深拷贝,隐式共享也称写时复制。隐式共享是QVector和std::vector<T>的主要区别。QBasicAtomic类用于保存引用计数,支持原子操作(线程安全),确保隐式共享可以跨线程使用。 QT在不同架构下使用汇编语言来实现QBasicAtomic类。为了避免每次容器增长时都分配内存, QVector<T> 会一次分配超过需要的内存。如果参数T是可拷贝类型(可以通过memcpy() or memmove()进行拷贝的对象,例如C++原类型和QT隐式共享类), QVector<T>使用 realloc()一次再分配 4096字节的增量。 这是因为现代操作系统当重新分配一个内存时并不拷贝整块数据,只是物理内存页面简单的重新排序,第一页和最后一页需要进行拷贝。对于不可拷贝数据类型,QVector<T>以每次增长50%的方式,确保在频繁调用append()函数时平均算法复杂度为 O(n)。如果用QVector<T>储存自定义的类型, QT会假定自定义数据类型是不可拷贝类型。对于自定义可拷贝的数据类型,可以使用 Q_DECLARE_TYPEINFO()宏来声明。


//添加 
void    append(const T & value)      //将 value 添加到向量尾部
void    push_back(const T & value)   //将 value 添加到向量尾部,STL风格
void    prepend(const T & value)     //将 value 添加到向量头部    
​​​​​​​void    push_front(const T & value)  //将 value 添加到向量头部,STL风格  
通常情况下,添加到向量尾部的开销比较小,而添加到头部意味着将向量原有的元素都向后平移,开销会很大。
//插入元素
void insert(int i, const T & value)
void insert(int i, int count, const T & value)

第一个 insert() 是将一个 value 插入到序号 i 的位置,原本序号 i 以及后面的元素都平移,然后把 value 复制到序号 i 的位置。

第二个 insert() 是将 count 个 value 值插入到序号 i 的位置,插入后从序号 i 到 i+count-1 序号都是等于 value 值的元素。insert() 函数支持的最大序号是 size() ,即元素总数,如果 i 等于 size() ,那么元素添加到向量末尾。如果 i >size() ,程序会因为访问越界而崩溃。其他带序号 i 的函数,序号范围仅支持 0 ~ size()-1 ,只有 insert() 例外多支持一个添加到末尾。

 //计算大小
int count() const
int size() const
int length() const
//移除元素
T    takeAt(int i) //卸下序号 i 位置元素,并返回该元素
T    takeFirst()    //卸下向量的头部元素,并返回该元素
T    takeLast()    //卸下向量的尾部元素,并返回该元素
//直接删除
    void    remove(int i)     //删除序号 i 位置元素
    void    removeAt(int i) //删除序号 i 位置元素
    void    remove(int i, int count) //从序号 i 位置开始删除参数里 count 个数的元素
    void    removeFirst()  //删除头部元素
    void    pop_front()    //删除头部元素,STL风格
    void    removeLast() //删除尾部元素
    void    pop_back()    //删除尾部元素,STL风格
//删除多个类似
    bool    removeOne(const T & t)    //如果向量里存在等于 t 的元素就删除并返回 true,否则找不到返回 false
    int    removeAll(const T & t)    //删除向量中所有等于 t 的元素,并返回删除的数量
//迭代
    QVector<int> va = {1, 2, 3};
    QVector<int>::iterator  it = va.begin();
    while(  it != va.end() )
    {
        qDebug()<< (*it);
        it++;   //下一个
    }

    // or
    for (int i = 0; i < vector.size(); ++i) {
         if (vector.at(i) == "Alfonso")
             cout << "Found Alfonso at position " << i << endl;
     }


QStack

Qstack继承QVector,提供后入先出结构,在此基础上增加压栈、出站、返回栈顶

void push(const T & t) //将 t 添加到栈顶

T  pop()    //取出栈顶元素并返回

T &  top()  //获取栈顶元素的读写引用

const T &    top() const  //获取栈顶元素的只读引用

T QStack::pop() //出栈 取出栈顶元素并返回

关联容器

关联存储容器中存储的一般是二元组,即键值对。QT提供两种关联容器类型:QMap<K, T>和QHash<K, T>。

QMap

QMap 模板类通常将数据按照 key – value(键-值) 配对的形式存储,程序中以 key 作为序号来查询对应的 value,比如  map[“红色”] = 0xFF0000 。QMap 通常将一个 key 映射为一个 value,而 QMultiMap 通常将 一个 key 映射为多个 value,这就是单映射和多映射(QMultiMap )的区别。使用红黑树的好处:

  1. 在插入、删除和查找操作上,红黑树可以保持对数时间复杂度(O(log n)),这使得

    std::map

    具有较高的性能。
  2. 二叉搜索树保证了元素的有序性,这意味着我们可以很容易地对

    std::map

    进行顺序访问。
  3. 自平衡特性保证了即使在最坏情况下,树的高度也保持在log(n),这避免了性能下降。
    QMap<QString, int>NameAge = {{"andle",5},{"mell",10},{"ancoll", 8} };
    QMap<QString, int>TempNameAge = std::move(NameAge);
    qDebug()<< NameAge;
    qDebug()<< TempNameAge;

    //添加
    NameAge.insert("mrain",9);
    NameAge["andle"] = 9;
    qDebug()<< NameAge;
    qDebug()<< TempNameAge;


移除和删除函数


从映射对象卸下一个元素,但不释放空间,使用下面函数:

T  take(const Key & key)

返回值 T 是 value 类型的数值。如果映射对象里根本没有指定 key,那么返回值是 value 类型默认构造函数生成的对象。

如果不需要返回值,直接从映射对象删除指定 key 及其 value,使用下面函数:

int  remove(const Key & key)

返回值是删除元素的个数,如果返回值为 0,说明映射里没有该 value;如果为 1 ,说明正好删除了一对 key – value ;如果返回值大于1,说明程序之前使用 insertMulti() 函数为一个 key 添加了多个 value 值(QMap 允许一对多映射,多个 key-value 元素 的 key 值相同,但一般不建议这样做)。

如果希望清空所有元素,那么使用如下函数:

void  clear()


访问和查询函数


查询映射对象内是否包含 key 键:

bool  contains(const Key & key) const

查询映射对象内所有元素数目:

int count() const
int size() const

统计 key 对应的 value 值数量,使用下面函数:

int  count(const Key & key) const

如果映射对象不存在 key 键,那么返回值为 0,如果存在一对 key-value ,那么返回值为 1;如果程序之前使用 insertMulti() 函数为一个 key 添加了多个 value,那么返回值是多个 value 值的数量。

判断映射对象是否全空,没有元素,使用下面两个函数都可以:

bool empty() const      //STL风格
bool isEmpty() const   //Qt风格


获取映射中第一个 value 值,使用下面函数:

T &  first()
const T &  first() const

获取映射中第一个 key 键,使用下面函数:

const Key & firstKey() const

获取映射中最后一个 value 值,使用下面函数:

T&  last()
const T&  last() const

获取映射中最后一个 key 键,使用下面函数:

const Key & lastKey() const


根据已知 value 值反查归属的 key 键:

const Key  key(const T & value, const Key & defaultKey = Key()) const

反查耗时比较长,需要逐个遍历元素,注意多个 key 对应的 value 可能一样,所以上面函数只返回第一个匹配的 key。如果找不到就返回默认构造的 Key() 。

如果要根据 value 反查所有匹配的 key 键列表,使用下面函数:

QList<Key>  keys(const T & value) const

如果需要获取映射所有元素的 key 值列表,使用下面函数:

QList<Key>  keys() const

注意 insertMulti() 函数可能导致多个 key-value 元素的 key 值一样,keys() 获取的键值是可能重复的。如果希望获取不重复出现的 key 列表,使用下面函数:

QList<Key>  uniqueKeys() const

根据 key 查询对应 value ,使用下面函数:

const T value(const Key & key, const T & defaultValue = T()) const

如果没有找到 key-value 元素,那么返回 T() 值,就是 value 类型默认构造值。

存在一对多映射的情况下,可以用下面函数获取 key 对应的多个 value 值列表:

QList<T>  values(const Key & key) const

如果要获取映射中所有元素的 value 值列表,使用下面函数:

QList<T>  values() const

QMultiMap

QMultiMap 是 QMap 的派生类,继承了 QMap 绝大多数的功能函数,同时也根据一对多映射(一个key对应多个val)的特性做了改进,将基类的部分函数功能进行了重载

QMultiMap::QMultiMap ()  //默认构造函数

QMultiMap::QMultiMap ( const  QMap<Key, T> & other )  //复制构造函数从QMap到QMultiMap

//也支持移动构造  
QMultiMap<QString, QString> namePhone{
        {"Alice", "10086"},
        {"Alice", "10087"},
        {"Bob", "10010"},
        {"Bob", "10011"}
    };
    qDebug()<<namePhone;
    QMultiMap<QString, QString> namePhone2( namePhone );
    qDebug()<<namePhone2;

    QMultiMap<QString, QString> namePhone3 =  std::move(namePhone);
    qDebug()<<namePhone<<endl<<namePhone3;



/*输出
QMap(("Alice", "10087")("Alice", "10086")("Bob", "10011")("Bob", "10010"))
QMap(("Alice", "10087")("Alice", "10086")("Bob", "10011")("Bob", "10010"))
QMap()
QMap(("Alice", "10087")("Alice", "10086")("Bob", "10011")("Bob", "10010"))

*/

因为QMultiMap是从QMap继承过来的主要说说不同点


添加函数

QMap<Key, T>::iterator QMultiMap::​insert(const Key & key, const T & value)

QMap<Key, T>::iterator QMultiMap::​insert(QMap<Key, T>::const_iterator pos, const Key & key, const T & value)

QMultiMap 的迭代器完全从基类 QMap 继承,因此可以看到参数和返回值的迭代器都是 QMap 的迭代器。但要注意 QMultiMap::​insert() 总是插入新的 key-value 节点,不会对旧节点进行替换,即使新旧节点键值一模一样,也会增加新的节点。

第一个 ​insert() 自动添加新节点到红黑树的排序位置,红黑树只按照 key 排序,同样 key 的多个 value 值不会排序,同样 key 的新节点总是插入到同样 key 旧节点的最前面。

第二个 insert() 会在建议的迭代器位置 pos 附近插入新节点,但不一定是指定位置,最终还是要按红黑树的 key 进行排序确定位置。


删除函数

需要注意的是remove,是删除所有键为key的value


[]操作

QMap是没有值就添加,有值就覆盖,QmultiMap没有[]操作,添加typename QMap<Key, T>::iterator replace(const Key &key, const T &value)

迭代器

容器类提供了两种风格的迭代器:Java风格和STL风格。Java风格迭代器比较容易使用,STL风格迭代器则可以用在一些通用算法中,功能比较强大

// Java风格

QList<double> list;

// …

QListIterator<double> i(list);

while (i.hasNext()) {

doSomethingWith(i.next());

}

// STL风格

QList<QString> list;

list << “A” << “B” << “C” << “D”;

QList<QString>::iterator i;

for (i = list.begin(); i != list.end(); ++i)

*i = (*i).toLower();


需要注意迭代器的隐式共享和删除失效

    QVector<int> a, b;
    a.resize(100); //向量初始化为100个0
    // i 指向向量第一个元素
    QVector<int>::iterator i = a.begin();
    //隐式共享,b 和 a 指向同一块存储空间
    b = a;
    //隐式共享在一个对象发生变化后,为变化的对象分配新空间,并赋值
    // a 元素修改后,i 迭代器与 a 无关了!!!
    // i 其实指向 b 首元素,因为 b 没有修改,使用旧的内存空间
    a[0] = 5;
    //这时候 *i 是 b 开头的数值 0
    b.clear(); //清空 b,那么迭代器 i 就属于野指针!!!

    // 迭代器的错误示范
    int j = *i; //野指针使用,未知结果

迭代器 i 原本指向 a 的空间,但是隐式共享特性是谁改变数值,就给谁分配新空间,修改 a[0] 之后,迭代器 i 其实已经处于异常状态,已经与 a 无关了。

这时候 i 仍然指向 b,那么一旦 b 清除空间,i 就成了野指针,这是非常危险的,可能导致内存错误,程序异常结束。

因此,使用迭代器时,一定要注意使用最新的迭代器赋值,不要用旧的过期的迭代器。


隐式共享对象元素的增删改都可能导致迭代器失效

,如果不清楚状况,那最好的做法是复制一个常量对象,对常量对象使用只读迭代器:

// OK
const QList<int> sizes = splitter->sizes();
QList<int>::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
    ...

如果没有使用隐式共享,只是一个对象一个存储空间,那么可以放心使用迭代器,迭代过程中删除元素应该使用专门的迭代器函数 erase(), erase() 会返回下一个元素迭代器位置,

迭代循环过程中一般不要使用 remove(),remove() 可能导致之前的迭代器失效



在迭代过程正确删除元素的示范代码如下:

QHash<QString, int>::iterator i = hash.begin();

while (i != hash.end())

{


if (i.key().startsWith(“_”))

i = hash.erase(i);

else

++i;

}

或者保存旧元素的迭代器,提前移动到下一个元素位置,然后删除旧元素迭代器位置:

QHash<QString, int>::iterator i = hash.begin();

while (i != hash.end())

{


QHash<QString, int>::iterator prev = i;

++i;

if (prev.key().startsWith(“_”))

hash.erase(prev);

}


错误示范

上面代码一般情况下不会出错,但是不建议这么写,这段代码的问题在于,当删除list1中的元素时,会导致后面的元素索引发生变化(其实就是size变化了),导致循环遍历不完整或者出现越界访问等问题。

也可以这样写:从后往前遍历list1,这样删除元素时不会对后面的元素造成影响。修改后的代码如下

    QList<int> list1; 
    list1 << 1 << 2 << 3 << 4;
    for(int i = list1.size() - 1; i >= 0; i--)
    { 
        if(list1[i] == 2) 
        { 
            list1.removeAt(i);
        } 
        qDebug() << list1[i] << list1.size();
    }



版权声明:本文为qq_31565379原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。