C++ 容器

本文详细介绍了C++中的容器,包括顺序容器和关联容器,并重点讲解了迭代器的概念、类型和使用方法。同时,文章讨论了标准库中的容器特性,如无序关联容器,并给出了在不同编译器下容器操作的注意事项和性能差异。

1.什么是容器

容器就是一些特定类型对象的集合。

2.容器的分类

容器可以分为顺序容器和关联容器。

  • 顺序容器:为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。也就是说,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
  • 关联容器:关联容器是按关键字来保存和访问元素。

3.容器操作的利器—迭代器

3.1 什么是迭代器

迭代器是一种更通用的用于访问容器中各个元素的机制。它是泛型编程的产物。它没有统一的实现方式,有些容器的迭代器可能是指针,而有些容器的迭代器则可能是对象。但是不管是何种实现,它们都提供所需的操作,如*和++等操作。

3.2 迭代器的类型

一般来说,我们不需要知道迭代器的精确类型(比如插入迭代器insert_iterator、流迭代器istream_iterator、反向迭代器、随机迭代器等)。而在标准库的容器中,使用iterator和const_iterator来表示迭代器的类型。如:

std::vector<int>::iterator iter;
std::vector<int>::const_iterator citer;

其中,const_iterator和常量指针差不多,能读取大不能修改它所指的元素值。而iterator的对象可读也可写。

3.4 使用迭代器

3.4.1 获取容器的迭代器

每种标准库容器都提供了begin()和end()这两个方法来获取指向容器中的元素的迭代器对象。其中,begin()返回的是指向容器中第一个元素的迭代器对象,而end()返回的是指向容器中最后一个元素的下一个位置的迭代器对象。也就是说,end()返回的其实是一个指向空位置的迭代器对象。

另外,begin()和end()返回的迭代器类型由容器对象是否是常量决定。如果容器对象是常量,begin()和end()返回const_iterator;否则返回iterator。如果确定要使用const_iterator,C++11新标准中引入了cbegin()和cend()这两个函数。

3.4.2 通过迭代器访问元素成员

对迭代器进行解引用操作,就可以获得迭代器器所指的对象。如:

int main(int argc, char **argv)
{
    std::vector<int> intVec;
    intVec.push_back(1);
    intVec.push_back(2);
    auto iter = intVec.begin();
    std::cout << *iter << std::endl;
    std::cin.get();
    return 0;
}

如果迭代器所指的对象的类型是个类,就可能需要通过迭代器来访问这个对象的成员。访问方法是先对迭代器解引用,然后使用.运算符,也可以直接使用->运算符。如:

int main(int argc, char **argv)
{
    std::vector<std::string> strVec;
    strVec.push_back("hello");
    strVec.push_back("world");
    auto strIter = strVec.begin();
    if (!(*strIter).empty())
    {
        std::cout << *strIter << std::endl;
    }
    strIter++;
    if (!strIter->empty())
    {
        std::cout << *strIter << std::endl;
    }

    std::cin.get();
    return 0;
}

4.标准库中的容器

4.1 顺序容器

模板类类型能力/特性
vector可变大小的动态数组* 以动态数组的方式来管理元素;
* 支持随机访问,只要知道位置,就可以在常量时间内访问任何一个元素。 提供随机访问迭代器,所以适用于任何STL算法;
* 在尾部插入或删除元素较快,在其他位置则较慢;
* 支持主动改变容器的容量(即重新分配内存);
* vector的元素分布于连续的空间中,因此可以将vector当成C风格的数组使用。使用data()函数可以获取这个数组的首地址。其作用类似于&a[0]。(a是一个vector对象)
* 元素可重复,且不会对元素进行自动排序。
deque双端队列* 以动态数组的方式来管理元素 ;
* 支持随机访问,只要知道位置,就可以在常量时间内访问任何一个元素。 提供随机访问迭代器,所以适用于任何STL算法。并有着与vector几乎一摸一样的接口;
* 在头尾两端都可以进行快速安插和删除,而在其他位置则相对较慢;
* 元素的访问和迭代器的动作比vector要稍稍慢一些;
* 不支持主动改变容器的容量(即不支持对容量和内存重新分配时机的控制),所以deque没有capacity()这样与容量相关的成员函数;
* 除头尾两端外,在其他任何位置安插或删除元素,都会导致指向deque元素的reference、pointer和iterator失效。在头尾两端插入时,只有iterator会受影响;
* 重新分配内存时不需要复制所有元素,所以在内存重新分配方面,效率优于vector;
* deque会释放不再使用的内存区块,因此deque的容量是可缩减,但具体实现取决于平台;
* 元素可重复,且不会对元素进行自动排序
list双向链表* 使用双向串连列表来管理元素;
* 不提供随机访问。因此没有at()函数,也不支持操作符[]。访问除头尾两端以外的元素会比较慢;
* 提供双向迭代器,所以所有用来操作元素顺序的算法(比如排序算法,list有自己的sort()函数)都不能用于list;
* 在任何位置上安插和删除元素都非常快,始终都是常量时间内完成;
* 安插和删除动作并不会造成指向其他元素的各个pointer、reference和iterator失效;
* 没有容量和空间重新分配等操作函数,每个元素都有自己的内存,在元素被删除前一直有效;
* 使用list自己的用于移动和移除元素的函数要比通用算法提供的函数更快速;
* 元素可重复,且不会对元素进行自动排序
stack* 是一个后进先出的特殊容器;
* 默认情况下使用deque来作为stack内部存放元素的实际容器。可以通过指定stack的第二个模板参数来修改它,前提是这个类型的容器提供back()、push_back()和pop_back()函数。
* 元素可重复。
queue队列* 是一个先进先出的特殊容器;
* 默认情况下使用deque来作为queue内部存放元素的实际容器。可以通过指定queue的第二个模板参数来修改它,前提是这个类型的容器提供front()、back()、push_back()和pop_front()函数。
* 元素可重复。

4.2 关联容器

模板类类型能力/特性
set/multiset键和值相同的集合* 会根据特定的排序准则自动将元素排序,排序准则可通过第二个模板参数指定,默认是less;
* multiset允许元素重复而set不允许;
* 通常以平衡二叉树来实现,查找元素时拥有良好效能;
* 不能直接改变元素值,要改变元素值,必须先删除旧元素,再插入新元素;
* 提供双向迭代器,所以所有用来操作元素顺序的算法都不能用于set和multiset;
* 所有元素都被视为常量,所以也不能对set和multiset调用任何更易型算法(如remove()),要对其执行更易型操作,只能使用其自身提供的成员函数;
map/multimap键和值不相同的映射* 将键-值对当作元素进行管理;
* 根据key的排序准则自动为元素排序,排序准则可通过第三个模板参数指定,默认是less;
* multimap允许键重复,map不允许键重复,如果有重复的键安插,则会安插不成功;
* 键和值的类型都必须是可复制的或者可移动的;
* 通常以平衡二叉树来实现,通过key来查找元素时拥有良好效能,而通过value来查找元素时,效率比较差;
* 不可以直接改变元素的key,要修改元素的key,必须先移除拥有该key的元素,然后插入拥有新key-value的元素;
* 元素的value可以直接修改,前提是value是非const类型;
* 提供双向迭代器,所以所有用来操作元素顺序的算法都不能用于map和multimap;
* 所有元素的key都被视为常量,因此元素的实质类型是pair<const key, T>。所以不能对map和multimap调用任何更易型算法;
* 对于非const的map,可以通过[key]来直接访问容器中这个key所对应的value的reference。如果容器中不存在这个key,则返回值类型对应的默认值。还可以通过m[key]=value的形式来直接向容器m中插入元素。另外无论是非const的map还是const的map都可以通过at(key)函数来访问实参key所对应的value的reference。

4.3 无序关联容器

无序关联容器通常以hash table为基础,其内的元素没有清晰明确的次序。如果拥有良好的hash策略和实现,在安插、删除和查找元素时,无序关联容器能够获得摊还常量时间。

无序关联容器有以下特性:

  • 不提供<、>、<=和>=操作符。从C++11开始提供了==和!=;
  • C++标准只保证无序关联容器的迭代器至少是个前向迭代器,所以无序关联容器不提供rbegin()、rend()等函数;
  • 不可以直接改动元素(如unordered_set)或元素的key(如unordered_map);

4.4 注意点

  • 所有STL容器提供的都是“value语义”而非“reference”语义。容器进行元素的安插动作时,内部实施的是copy和/或move动作,而不是管理元素的reference。也就是说,容器中存放的是一个个真实的对象(实体或指针),而不能是引用。因为引用不是对象,它只是一个别名而已。而在访问容器中的元素时,只要容器不是const的,访问元素的函数返回的都是元素的引用。如at()函数返回的就是元素的引用。

  • capacity()、size()和max_size()这三个函数的区别。

    函数含义
    size()当前容器中已放入的元素数量
    capacity()容器在当前所分配的内存下,最大能够存放的元素数量
    max_size()容器所能够存放的最大元素数量。这与是否重新分配的内存无关。其具体值与平台与容器中元素的类型相关。即同一种容器,其元素是int和string时,max_size()返回的结果是不一样的。
  • reserve()和resize()的区别。

    函数含义
    reserve()只改变容器的容量,即影响的是capacity()函数的返回值
    resize()改变容器中现有元素的数量。如果容器中现有元素数量大于其实参,则会删除多出的元素,但不影响容器的容量;如果容器中现有元素数量小于其实参,则会追加新的元素,新追加的元素使用默认值进行初始化,同时容器的容量也会被改变。如果元素类型没有default构造函数,则不能使用resize()函数。
  • 在使用vector时,需要注意容量管理。vector是通过分配比“需要容纳的元素”更多的内存来获取效率上的提升的。所以一旦内存不足,需要重新分配时,与vector元素相关的所有reference、pointer、iterator都会失效。而且内存重新分配很耗时间。如果需要为vector指定内存空间大小,可以使用下面两种方式:

    // 方法一
    std::vector<int> v;
    v.reserve(10); // 这里的10表示是10个元素,而不是指字节
    
    // 方法二
    std::vector<int> v(10);
    

    推荐使用方法一。方法二一是需要元素类型必须提供一个默认构造函数,二是对于复杂的元素类型,即使提供了默认构造函数,初始化动作也很耗时。因为系统会调用元素的default构造函数来一一制造新元素。方法一和方法二的使用对比示例:

    class A {
    public:
        explicit A(int) {};
    };
    
    int main(int argc, char **argv)
    {
    	// 使用方法一
    	std::vector<A> va;
    	std::cout << "va count = " << va.capacity() << std::endl; // 输出0
    	va.reverse(10); 
    	std::cout << "va count = " << va.capacity() << std::endl; // 输出10
    	
    	// 使用方法二
    	std::vector<A> vaa(10); // 执行报错
    	std::cout << "va count = " << va.capacity() << std::endl;
    
    	std::cin.get();
    	return 0;
    }
    

    另外需要注意的是,vector不能使用reserve()函数来缩减容量。如果调用reserve()时指定的实参如果小于当前vector的容量,不会引发任何效果。也正是由于vector不能缩减容量,所以即使删除了vector中的元素,其reference、pointer、iterator也会继续有效,继续指向删除操作发生前的位置。因此,在发生元素删除操作后,再使用之前的reference、pointer、iterator时,有可能会导致不正确的行为。如:

    class A {
    public:
        explicit A(const int age) : m_age(age){};
    
        int age() { return m_age;  }
    private:
        int m_age;
    };
    
    int main(int argc, char **argv)
    {
        std::vector<A> va;
        va.push_back(A(10));
        va.push_back(A(20));
        va.push_back(A(30));
        auto aIter = va.begin();
        A &a1 = *aIter;
        auto a2 = va.at(0);
        std::cout << "====== 删除前 ==========" << std::endl;
        std::cout << "va capacity = " << va.capacity() << std::endl;
        std::cout << "va size = " << va.size() << std::endl;
        std::cout << "a1 age = " << a1.age() << std::endl;
        std::cout << "a2 age = " << a2.age() << std::endl;
        std::cout << "aIter age = " << aIter->age() << std::endl;
        
        va.erase(aIter);
        std::cout << "====== 删除后 ==========" << std::endl;
        std::cout << "va capacity = " << va.capacity() << std::endl;
        std::cout << "va size = " << va.size() << std::endl;
        std::cout << "a1 age = " << a1.age() << std::endl;
        std::cout << "a2 age = " << a2.age() << std::endl;
        // std::cout << "aIter age = " << aIter->age() << std::endl; // 实测时造成程序崩溃
    
        std::cin.get();
        return 0;
    }
    

    visual studio 2015编译运行结果:
    visual studio 2015编译运行结果
    ubuntu20.04上使用gcc编译的运行结果:
    ubuntu20.04上使用gcc编译的运行结果
    示例中,a1是迭代器解引用后的对象的引用,a2是由at()函数直接返回的元素的引用。在删除后,a1指向了新的begin元素,而a2仍是指向被删除的begin元素。另外,在使用erase()删除了aIter位置的元素后,再使用alter时,使用vs2015编译运行时程序崩溃。而在ubuntu上使用gcc编译时,程序能够正常编译,且能够正常运行并输出结果。另外在对容器的容量处理上两者也有差异,前者capacity()返回3,后者返回4。

    如果需要缩减容量,有一种间接的做法,就是交换两个vector的内容。在上面的示例基础上做如下修改:

    std::vector<A> va;
    va.push_back(A(10));
    va.push_back(A(20));
    va.push_back(A(30));
    auto aIter = va.begin();
    A &a1 = *aIter;
    std::cout << "====== 交换前 ==========" << std::endl;
    std::cout << "va capacity = " << va.capacity() << std::endl;
    std::cout << "a1 age = " << a1.age() << std::endl;
    
    //va.erase(aIter);
    //std::cout << "====== 删除后 ==========" << std::endl;
    //std::cout << "va capacity = " << va.capacity() << std::endl;
    //std::cout << "a1 age = " << a1.age() << std::endl;
    
    std::vector<A> vb;
    vb.push_back(A(11));
    vb.push_back(A(12));
    va.swap(vb);
    std::cout << "====== 交换后 ==========" << std::endl;
    std::cout << "va capacity = " << va.capacity() << std::endl;
    std::cout << "a1 age = " << a1.age() << std::endl;
    

    运行结果:
    在这里插入图片描述
    从结果中可以看到,交换后va的容量的确发生了变化。但还需要注意的是,交换后,引用a1的值仍为10。这是因为交换后,原先所有reference、pointer、iterator仍然还是指向原来的位置。也就是说示例中的aIter在交换后虽然仍是va的迭代器,但指向的位置却是在vb中。此时如果直接对aIter进行运算或调用元素自身的操作,都将会导致程序不正确的行为。应当对其重新赋值后再进行其他操作。比如对aIter再次执行aIter=va.begin()操作,这时aIter就指向va中的位置了。

  • 操作符[]、front()、back()都不会检查范围,而at()会检查范围,如果索引超出范围会抛出out_of_range异常。另外在使用操作符[]时,需要确保索引有效。而使用front()和back()时,则必须确保容器中至少含有一个元素。否则都会导致不明确的行为。比如像下面这样的用法:

    std::vector<int> iVec;
    iVec[0] = 10;
    

    使用vs2015编译和gcc编译都能正常编译,但运行时报段错误。如果对上面的程序做如下修改:

    std::vector<int> iVec;
    iVec.reserve(30);
    iVec[0] = 10;
    std::cout << "first element = " << iVec[0] << std::endl;
    

    在vs2015上仍然是可编译但不能运行,而在gcc上则可编译也可正常运行并输出结果。在vs2015上,只有当容器的size()函数返回值大于操作符[]中的索引值时,程序才能正常运行。

  • 一次处理多个元素要比多次处理单个元素更快速。如:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <chrono>
    int main(int argc, char **argv)
    {
        int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
        std::vector<int> iVec;
        std::vector<int> iVec2;
    
        auto t1 = std::chrono::steady_clock::now();
        iVec.insert(iVec.begin(), &a[0], &a[9] + 1);
        auto t2 = std::chrono::steady_clock::now();
        std::cout << "multi run time(ns) = " << std::chrono::duration<double, std::nano>(t2 - t1).count() << std::endl;
        
        auto t3 = std::chrono::steady_clock::now();
        for (auto val : a)
        {
            iVec2.insert(iVec2.end(), val);
        }
        auto t4 = std::chrono::steady_clock::now();
        std::cout << "single run time(ns) = " << std::chrono::duration<double, std::nano>(t4 - t3).count() << std::endl;
    
        std::for_each(iVec.begin(), iVec.end(), [](int item) {
            std::cout << item << "\t";
        });
        std::cout << std::endl;
        std::cout << "===========" << std::endl;
    
        std::for_each(iVec2.begin(), iVec2.end(), [](int item) {
            std::cout << item << "\t";
        });
        std::cout << std::endl;
    
        std::cin.get();
        return 0;
    }
    

    运行结果:
    在这里插入图片描述
    虽然每次运行执行的时间不一样,但是一次插入多个元素的执行时间总是比分多次插入单个元素的时间要快得多。

5.Qt中的容器

容器模板类类型特性
QVector可变大小的动态数组* 内存连续,元素在内存中顺序存放
* 基于索引访问元素
* 元素不会自动排序,顺序与元素的添加顺序相同
* 元素可重复
* 能够像C风格的数组那样使用,见示例5.1。这样很容易将一个vector用于C API。
QList类似于数组的列表。底层使用void *类型的数组来管理数据* 内存分布不连续,且内存会分布于堆上
* 基于索引访问元素
* 元素不会自动排序,顺序与元素的添加顺序相同
* 元素可重复
QLinkedList链表。底层使用自定义的链表结构来管理数据* 内存分布于堆上,不连续
* 基于迭代器来访问元素
* 元素不会自动排序,顺序与元素的添加顺序相同
* 元素可重复
QStack是一个后进先出的特殊容器* 继承自QVector
QQueue是一个先进先出的特殊容器* 继承自QList
QSet键与值相同的关联容器* 底层实现与QHash相似
* 元素的顺序是随机的
* 元素不重复
* 基于迭代器访问元素
QMap存储键-值对的关联容器* 元素按照键的顺序排序
* 默认情况下键不可重复,如果有重复,则会用后添加的覆盖前面的元素(不同于std::map)。不过可以使用insertMulti来添加重复的Key
* 可以通过[key]来获取指定key的值,也可通过这种方式来添加元素
* 键的类型定义中必须含有operator<()以用于键的排序
QMultiMap存储键-值对的关联容器* 是QMap的子类,允许键重复
QHash存储键-值对的关联容器* 元素的顺序是随机的
* 默认情况下键不可重复,如果有重复,则会用后添加的覆盖前面的元素。不过可以使用insertMulti来添加重复的Key
* 可以通过[key]来获取指定key的值,也可通过这种方式来添加元素
* 键的类型定义中必须含有operator==()和一个全局的qHash(Key) 函数
QMultiHash存储键-值对的关联容器* 是QHash的子类,允许键重复

示例5.1:QVector的数组用法

void printInt(int *arr, int size)
{
    for(int i = 0; i < size; i++)
    {
        printf("%d\t", arr[i]);
    }
    printf("\n");
}

int main()
{
	QVector<int> vector(10);
	int *data = vector.data(); // std::vector中也有相似功能的data()函数
	for (int i = 0; i < 10; ++i)
	    data[i] = 2 * i;
	printInt(data, 10);
	return 0;
}

示例5.2:QVector、QList和QLinkedList的iterator以及元素的引用在元素被删除后的有效性

void printList(const QLinkedList<int> &linkList)
{
    std::for_each(linkList.begin(), linkList.end(), [](int item){
        printf("%d\t", item);
    });
    printf("\n");
}

int main()
{
	QVector<int> vec;
    vec.push_back(1);
    vec.push_back(3);
    vec.push_back(2);
    qDebug() << "=========QVector  before delete============";
    qDebug() << vec;
    auto vIter = vec.begin();
    qDebug() <<  "iterator:"<< *vIter;
    auto vecFirst = vec.at(0);
    qDebug() << "reference:" << vecFirst;
    vec.remove(0);
    qDebug() << "=========QVector  after delete============";
    qDebug() << vec;
    qDebug() <<  "iterator:"<< *vIter;
    qDebug() << "reference:" <<vecFirst;

    QList<int> lis;
    lis.push_back(11);
    lis.push_back(33);
    lis.push_back(22);
    qDebug() << "=========int QList  before delete============";
    qDebug() << lis;
    auto lIter = lis.begin();
    qDebug() << "iterator:"<<  *lIter;
    auto listFirst = lis.at(0);
    qDebug() << "reference:" <<listFirst;
    lis.removeAt(0);
    qDebug() << "=========int QList  after delete============";
    qDebug() << lis;
    qDebug() << "iterator:"<<  *lIter;
    qDebug() << "reference:" <<listFirst;

    QList<long long> longList;
    longList.push_back(1111);
    longList.push_back(3333);
    longList.push_back(2222);
    qDebug() << "=========long long QList  before delete============";
    qDebug() << longList;
    auto llIter = longList.begin();
    qDebug() << "iterator:"<< *llIter;
    auto longLstFirst = longList.at(0);
    qDebug() << "reference:" <<longLstFirst;
    longList.removeAt(0);
    qDebug() << "=========long long QList  after delete============";
    qDebug() << longList;
    qDebug() <<  "iterator:"<< *llIter;
    qDebug() <<"reference:" << longLstFirst;

    QLinkedList<int> linkList;
    linkList.push_back(111);
    linkList.push_back(333);
    linkList.push_back(222);
    qDebug() << "=========QLinkedList  before delete============";
    printList(linkList);
    auto linkIter = linkList.begin();
    qDebug() <<  "iterator:"<< *linkIter;
    auto linkFirst = linkList.first();
    qDebug() <<"reference:" << linkFirst;
    linkList.erase(linkIter);
    qDebug() << "=========QLinkedList  after delete============";
    printList(linkList);
    qDebug() <<  "iterator:"<< *linkIter;
    qDebug() <<"reference:" << linkFirst;
	return 0;
}

运行结果:
在这里插入图片描述
从示例5.2中可以看出:

  • QVector的iterator在元素删除后会自动刷新,而std::vector的则不会;
  • 元素类型是int和元素类型是long long的QList的iterator处理存在差异,这是因为对于元素是int的QList的数据存储在栈上,而long long类型的是存储在堆上。对于是存储在栈上还是存储在堆上,判别标准是sizeof(T) <=sizeof(void *)且T是通过Q_DECLARE_TYPEINFO定义为Q_MOVABLE_TYPE或者Q_PRIMITIVE_TYPE时,在栈上,否则在堆上;
  • QLinkedList和long long类型的QList的iterator的表现相同,这是因为它们的数据都是存储在堆上;
  • 对于元素的引用,即使这个元素从容器中删除了,该引用仍然有效。

QVector、QList和QLinkedList的各个操作时间对比:

容器访问元素插入在头部添加元素在尾部添加元素
QLinkedListO(n)O(1)O(1)O(1)
QListO(1)O(n)Amort. O(1)Amort. O(1)
QVectorO(1)O(n)O(n)Amort. O(1)

其中的Amort. 表示摊还操作

QSet、QMap、QHash和QMultiMap的各个操作时间对比:

容器访问元素(平均)访问元素(最差的情况)插入(平均)插入(最差的情况)
QMap<Key, T>O(log n)O(log n)O(log n)O(log n)
QMultiMap<Key, T>O(log n)O(log n)O(log n)O(log n)
QHash<Key, T>Amort.O(1)O(n)Amort. O(1)O(n)
QSetAmort.O(1)O(n)Amort. O(1)O(n)

其中的Amort. 表示摊还操作

6.容器常用操作

容器插入删除元素访问查找排序
vector* push_back
* insert
* emplace
* pop_back
* erase
* clear
* []
* at
* front
* back
* std::find
* std::find_if
* std::sort
deque* push_back
* push_front
* insert
* emplace
* pop_back
* pop_front
* erase
* clear
* []
* at
* front
* back
* std::find
* std::find_if
* std::sort
list* push_back
* push_front
* insert
* emplace
* emplace_back
* emplace_front
* pop_back
* pop_front
* erase
* remove
* remove_if
* clear
* front
* back
* std::find
* std::find_if
* sort:相比于std::sort效率更高
* std::sort
stack* push* top:返回下一个元素,但不移除元素
* pop:移除下一个元素,但不返回被移除的元素
* top--
queue* push* front:返回下一个元素(即最早被放入的元素),不移除元素
* back:返回最后一个元素(即最近被放入的元素,不移除元素
* pop:移除一个最早被放入的元素,但不返回被移除的元素
* front
* back
--
set/multiset* insert
* emplace
* emplace_hint
* erase
* clear
* 通过迭代器遍历
* 使用find函数查找
* find
* std::find
* std::find_if
* 自动排序。可通过第二个模板参数指定自动排序时使用的准则
map/multimap* insert
* emplace
* emplace_hint
* erase
* clear
* 通过迭代器遍历
* 使用find函数查找指定的key
* [key]:仅用于map
* at(key):仅用于map
* find
* std::find
* std::find_if
* 自动排序。可通过第三个模板参数指定自动排序时使用的准则
unordered_set/unordered_map* insert
* emplace
* emplace_hint
* erase
* clear
* 通过迭代器遍历
* 使用find函数查找指定的key
* [key]:仅用于unordered_map
* at(key):仅用于unordered_map
* find
* std::find
* std::find_if
* 自动排序。可通过第三个模板参数指定自动排序时使用的准则
QVector* push_back
* push_front
* insert
* prepend
* append
* pop_back
* pop_front
* erase
* remove
* clear
* []
* at
* first
* front
* last
* back
* value
* takeAt:返回并移除元素
* takeFirst:返回并移除元素
* takeLast:返回并移除元素
QList* push_back
* push_front
* insert
* prepend
* append
* pop_back
* pop_front
* erase
* removeAt
* removeAll
* removeOne
* clear
* []
* at
* first
* front
* last
* back
* value
* takeAt:返回并移除元素
* takeFirst:返回并移除元素
* takeLast:返回并移除元素
QLinkedList* push_back
* push_front
* insert
* prepend
* append
* pop_back
* pop_front
* erase
* removeAll
* removeOne
* removeFirst
* removeLast

* first
* front
* last
* back
* takeFirst:返回并移除元素
* takeLast:返回并移除元素
QStack* push* top:返回元素
* clear
* pop:返回并移除元素
QQueue* enqueue* dequeue
* clear
* head
QSet* insert* erase
* clear
* remove
* 通过迭代器遍历
* 使用find函数
* find
* …
* 不能直接排序,可以转换为QList进行排序
QMap* insert* erase
* clear
* remove
* first
* last
* take:会删除元素
* value
* 通过迭代器遍历
* 使用find函数
* find
* …
* key自动排序。可以修改key类型的operator<()来设置排序准则
QHash* insert* erase
* clear
* remove
* take:会删除元素
* value
* 通过迭代器遍历
* 使用find函数
* find
* …
* 不能直接排序,可以通过keys()函数获取到key的QList,然后对key进行排序

表格中的-表示一般不进行这种操作,…表示可以使用与标准容器相同的做法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值