C++ 容器

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

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::
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值