前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Example】C++ 标准库常用容器全面概述

【Example】C++ 标准库常用容器全面概述

原创
作者头像
芯片烤电池
修改2022-04-28 10:07:21
3.2K0
修改2022-04-28 10:07:21
举报
文章被收录于专栏:C++教程

前排提醒:

由于 Microsoft Docs 全是机翻。所以本文表格是我人脑补翻+审校。

如果有纰漏、模糊及时反馈。

了解每一种容器的特性、知道什么情况下用什么容器就可以。

序列式容器

序列容器是指在逻辑上以线性排列方式存储给定类型元素的容器。

这些容器和数组非常类似,都是在逻辑上连续的(但内存不一定是连续的),与数组不同的是,容器可以非常方便的动态管理,而不是固定元素大小

std::vector

当你需要容器时,就找vector! -- Bjarne Stroustrup

std::vector 差不多是C++当中最常用的容器,它是一个模版类。你可以将它视作传统数组的动态功能增强版本,因此它的泛用性非常高。

当你以局部变量形式创建并初始化 vector 时,对象本身是存储于栈内存当中,但是它所存储的元素却是在堆内存当中连续的一块空间,因此 std::vector 对于随机访问效率会非常高。

vector 的存储是自动管理的,按需扩张收缩。 vector 通常占用多于静态数组的空间,因为要分配更多内存以管理将来的增长。 vector 所用的方式不在每次插入元素时,而只在额外内存耗尽时重分配。分配的内存总量可用 capacity() 函数查询。额外内存可通过对 shrink_to_fit() 的调用返回给系统。 (C++11 起) 重分配通常是性能上有开销的操作。若元素数量已知,则 reserve() 函数可用于消除重分配。 -- 《C++ Reference》

头文件:
代码语言:javascript
复制
#include <vector>
构造语法:
代码语言:javascript
复制
// 空初始化
std::vector<Type> name;

// 带有默认集合
std::vector<Type> name{ value, value, ... };

// 预分配长度
std::vector<Type> name(num);

// 预分配长度与默认值
std::vector<Type> name(num, value);
成员函数:

名称

说明

assign

清除当前vector并将指定的元素复制到该空vector。

at

返回对vector中指定位置的元素的引用。

back

返回对vector中最后一个元素的引用。

begin

返回该vector中起始位置的迭代器。

capacity

返回在不分配更多的内存的情况下vector可以包含的元素数。(当前内存空间)

cbegin

返回指向vector中起始位置的常量迭代器。(const修饰)

cend

返回末尾位置常量迭代器。(非末尾元素)(const修饰)

crbegin

返回一个指向vector中起始位置的常量反向迭代器。(const修饰)

crend

返回一个指向vector中末尾位置的常量反向迭代器。(const修饰)

clear

清除vector的所有元素。(但没有回收内存)

data

返回指向vector中首个元素的指针。

emplace

将元素原位插入到指定位置之前。

emplace_back

将元素原位插入到指定位置之后。

empty

检查vector是否为空。

end

返回指向vector末尾的迭代器。(非末尾元素)

erase

从指定位置删除vector中的一个元素或一系列元素。

front

返回回vector中第一个元素的引用。

get_allocator

将对象返回到vector使用的 allocator 类。

insert

将一个元素或多个元素插入到vector指定位置。

max_size

返回vector的最大长度。

pop_back

删除vector末尾处的元素。

push_back

在vector末尾处追加一个元素。

rbegin

返回起始位置的反向迭代器。

rend

返回末尾位置的反向迭代器。

reserve

重新分配vector的最小存储长度。

resize

为vector指定新的大小。

shrink_to_fit

释放冗余容量(内存)。

size

返回vector中的元素数量。

swap

交换两个vector的元素。

运算符:

名称

说明

operator[]

返回对指定位置的vector元素的引用。

operator=

用另一个vector的副本替换该向量中的元素。

最简单示例:
代码语言:javascript
复制
int main()
{
    std::vector<int> vec = { 0, 1, 2, 3, 4 };
    for (auto &i : vec)
    {
        std::cout << i << std::endl;
    }

    vec.reserve(10);

    vec.push_back(5);
    vec.push_back(6);
    vec.push_back(7);
    vec.push_back(8);
    vec.push_back(9);

    vec.erase(vec.begin() + 2, vec.end() - 3);

    for (auto& i : vec)
    {
        std::cout << i << std::endl;
    }

    vec.clear();
    std::vector<int>().swap(vec);

    return EXIT_SUCCESS;
}

std::list 与 std::forward_list

std::list 是一个模板类,即链表。它的特点是每个元素在逻辑上都以线性连续方式来存储。

它的每个元素内部都有指向前元素及后元素的指针,每次插入与删除都只需更改前后“邻居”的指针,可以做到任何位置高效的插入与删除。

但是,虽然在逻辑上是连续的,然而每个元素在内存当中并不是连续存储的,因此 std::list 无法做到像 std::vector 那样随机读写。(它直接没有 at 函数及 [] 重载)

此外 std::list 对异常的控制是,要么操作成功,出现异常则不进行任何更改。

头文件:
代码语言:javascript
复制
#include <list>
构造语法:
代码语言:javascript
复制
// 默认
std::list<Type> name;

// 预分配长度
std::list<Type> name(num);

// 预分配长度及默认值
std::list<Type> name(num, value);

// 从 initlist 创建
std::list<Type> name(initlist);

// 迭代器区间创建
std::list<Type> name(obj.begin(), obj.end());
成员函数:

名称

说明

assign

清空当前list并将指定的元素复制到当前空list。

back

返回对list中最后一个元素的引用。

begin

返回list中指向起始位置的迭代器。

cbegin

返回list中起始的位置的常量迭代器。(const修饰)

cend

返回list中末尾的位置的常量迭代器。(const修饰)

clear

清空list。

crbegin

返回list中起始的常量反向迭代器。(const修饰)

crend

返回list中末尾的常量反向迭代器。(const修饰)

emplace

将元素原位插入到指定位置。

emplace_back

将元素原位插入到末尾位置。

emplace_front

将元素原位插入到起始位置。

empty

判断list是否为空。

end

返回list中指向末尾的迭代器。

erase

从指定位置删除list中的一个元素或一系列元素。

front

返回对list中第一个元素的引用。

get_allocator

返回用于构造list的 allocator 对象的一个副本。

insert

将一个、几个或一系列元素插入list中的指定位置。

max_size

返回list的最大长度。

merge

合并两个已排序list,合并前必须升序或其他指定顺序排序。

pop_back

删除最后元素。

pop_front

删除首个元素。

push_back

从末尾追加元素。

push_front

从起始追加元素。

rbegin

返回起始位置的反向迭代器。

remove

移除满足条件的元素。

remove_if

移除满足谓词条件的元素。

rend

返回list中末尾的反向迭代器。

resize

重新分配长度。

reverse

反转list中元素的顺序。

size

返回list中元素的数目。

sort

按升序或指定其他顺序排列list中的元素。

splice

从另一个list中移动元素。

swap

交换两个list的元素。

unique

删除连续的重复元素。

运算符:

名称

说明

operator=

用另一个list的副本替换当前list中的元素。

最简单示例:
代码语言:javascript
复制
int main()
{
    std::list<int> list(10, 0);

    list.emplace_front(1);
    list.emplace_front(2);
    list.emplace_back(6);
    list.emplace_back(7);

    for (auto& i : list)
    {
        std::cout << i << std::endl;
    }
    std::cout << "------" << std::endl;

    list.sort();
    std::list<int> list_m{1, 2, 3, 4, 5};
    list.merge(list_m);

    for (auto& i : list)
    {
        std::cout << i << std::endl;
    }return EXIT_SUCCESS;
}

std::forward_list 是单项链表,它的头文件是:

代码语言:javascript
复制
#include <forward_list>

它的操作方式和 std::list 基本相同,但是,由于它是单向链表,所以它没有反向迭代器。

也就意味着没有 size() 函数,没有 push_back()、pop_back()、emplace_back() 这些涉及反向操作的函数。

它的优势是空间利用率比 std::list 更高,酌情使用。

它相对于 std::list 多了以下操作函数:

名称

说明

before_begin

返回指向第一个元素之前的迭代器

cbefore_begin

返回指向第一个元素之前的常量迭代器

insert_after

在某个元素后插入新元素

emplace_after

在元素后原位构造元素

erase_after

擦除元素后的元素

std::deque

双端队列,是具有下标与逻辑相邻顺序的容器。它是 std::vector 与 std::list 相结合的方案,既可随机访问、也可高效双端插入删除。

std::vector 之所以随机访问效率高,是因为它在内存当中是连续的空间并且具有下标。

std::list 之所以插入删除效率高,是因为它所进行插入与删除操作时只需更改前后邻居的链接节点指针。

先来看 std::vector 的内存逻辑:【Example】C++ Vector 内存预分配的良好习惯

std::vector 是始终保持每个元素在连续的一块内存上,当 pushback 了新的元素后,如果冗余内存空间不足,需要重新申请一块新内存再将原有数据拷贝入新的内存块并释放旧内存块。

而 std::deque 在面临 pushback 了新元素且已有内存空间面临不足情况时,则新申请一块内存直接存入新数据,再对新旧内存块进行链接。

因此,std::deque 本质是由多个连续内存块组成,在一定程度上兼具了 std::vector 与 std::list 的优点,但却无法单独超越其中一者。

区块,这很 Minecraft! /滑稽 -- ZhouFZ

除此之外,std::deque 还具有以下特点:

1,双端都可以进行数据的增删。

2,不支持内存预分配或其他控制手段,也不支持对容量进行手动修改。

3,deque 会释放冗余的内存区块,时机取决于编译器实现。

4,它的迭代器需要在不同内存区块之间迭代,所以性能不如 std::vector 但优于 std::list 。

需要注意的问题:

迭代器非法化:指的是在 std::deque 逻辑上连续元素的头尾与中间进行插入或删除新的元素而导致的迭代器失效。

特别补充:迭代器失效情况也取决于编译器实现,如果实际操作中存在任何可能原因而导致失效,请采取措施避免。

引发失效的情况:

名称

情况

在头尾插入

可能导致迭代器失效(全部或部分),但指针与引用仍然有效

在头尾删除

其他元素的迭代器不失效

中间插入或删除操作

全部失效

具体原因:

std::deque 是一个同时管理着索引区块与对应数据区块的结构,它通过一个类似于 MAP 的 Key:Value 形式来记录所拥有的内存区块。

当出现头尾插或者中间插操作时,如果当前所管理的数据内存区块容量不足,而去开辟了新的数据内存区块,自然索引就会增加。

如果存储索引本身的区块内存空间不足,就又要去开辟新的内存去存储更改后的索引区块,已有的迭代器自然就失效了,因为迭代器找不到自己所处数据区块的原有索引在哪了。

(听不懂没事,多琢磨几天。)

《C++ Reference》对迭代器非法化的补充 操作被非法化所有只读操作决不swap 、 std::swap尾后迭代器可能被非法化(实现定义)shrink_to_fit 、 clear 、 insert 、 emplace 、 push_front 、 push_back 、 emplace_front 、 emplace_back始终erase若在起始擦除——仅被擦除元素 若在末尾擦除——仅被擦除元素和尾后迭代器 否则——非法化所有迭代器(包含尾后迭代器)。resize若新大小小于旧者:仅被擦除元素和尾后迭代器 若新大小大于旧者:非法化所有迭代器 否则——不非法化任何迭代器。pop_front仅有指向被擦除元素者pop_back仅有指向被擦除元素者和尾后迭代器此节有仍少量不准确处,更多细节请查看涉及单独成员函数的页面 非法化注意

  • 从 deque 任一端插入时, insert 和 emplace 不会非法化引用。
  • push_front 、 push_back 、 emplace_front 和 emplace_back 不会非法化任何到 deque 元素的引用。
  • 从 deque 任一端擦除时, erase 、 pop_front 和 pop_back 不会非法化到未擦除元素的引用。
  • 以较小的大小调用 resize 不会非法化任何到未擦除元素的引用。
  • 以较大的大小调用 resize 不会非法化任何到 deque 元素的引用。

回归正题

头文件:
代码语言:javascript
复制
#include <deque>
构造语法:
代码语言:javascript
复制
// 默认空
std::deque<Type> name;

// 拷贝构造
std::deque<Type> name(dequeobj);

// 默认分配长度及默认值
std::deque<Type> name(num, value);

// 迭代器区间
std::deque<Type> name(obj.begin(), obj.end());
成员函数:

名称

说明

assign

清空当前deque并将指定的元素复制到当前空deque。

at

返回对deque中指定位置的元素的引用。

back

返回对deque中最后一个元素的引用。

begin

返回指向起始的迭代器。

cbegin

返回指向起始的常量迭代器。(const修饰)

cend

返回指向末尾的常量迭代器。(const修饰)

clear

清空 deque。

crbegin

返回指向起始的逆向常量迭代器。(const修饰)

crend

返回指向末尾的逆向常量迭代器。(const修饰)

emplace

将元素原位插入到指定位置。

emplace_back

将元素原位插入到末尾位置。

emplace_front

将元素原位插入到起始位置。

empty

检查 deque 是否为空。

end

返回指向末尾的迭代器。

erase

从指定位置删除一个或一系列元素。

front

返回第一个元素的引用。

get_allocator

返回用于构造 allocator 的 deque 对象的副本。

insert

将一个、多个或一系列元素插入到指定位置。

max_size

返回可容纳的最大元素数。

pop_back

删除末尾处的元素。

pop_front

删除起始处的元素。

push_back

插入元素到末尾位置。

push_front

插入元素到起始位置。

rbegin

返回指向起始的逆向迭代器。

rend

返回指向末尾的逆向迭代器。

resize

手动改变大小。

shrink_to_fit

释放未使用的内存。

size

返回当前长度。(元素数量)

swap

交换两个deque。

运算符:

名称

说明

operator[]

返回对指定位置的 deque 元素的引用。

operator=

将 deque 的元素替换为另一个 deque 的副本。

最简单示例:

(注意看对迭代器的操作)

代码语言:javascript
复制
int main()
{
    std::deque<int> deque_d(10, 0);

    std::deque<int>::iterator it = deque_d.begin();
    it = deque_d.insert(it, 1);
    it = deque_d.insert(it, 2);
    it = deque_d.insert(it, 3);
    it = deque_d.insert(it, 4);

    std::deque<int>::iterator itf = std::find(deque_d.begin(), deque_d.end(), 3);
    itf = deque_d.emplace(itf, 5);
    itf = deque_d.emplace(itf, 6);
    itf = deque_d.emplace(itf, 7);

    for (auto &i : deque_d) {
        std::cout << i << std::endl;
    }

    return EXIT_SUCCESS;
}

std::array

标准库数组,本质一个模板类,是一个固定长度的容器,不可扩容。在现代C++中,主张使用 std::array 替代传统样式的数组。

std::array 提供的功能也比 std::vector、std::list 更简单。因为,它从设计上的目的,就是对传统数组进行现代化改造。

具体体现在:

1,它拥有和传统数组一样的性能、可访问性。

2,它具有传统数组所没有的容器优点:可获取大小、随机访问迭代器、支持赋值等。

所以,当你需要固定大小的数组时,应首先考虑 std::array。

头文件:
代码语言:javascript
复制
#include <array>
构造语法:
代码语言:javascript
复制
// 默认空
std::array<Type, SizeNum> name;

// 默认值情况下
std::array<Type, SizeNum> name{value, value...};
std::array<Type, SizeNum> name = {value, value...};

成员函数:

名称

说明

array

构造一个数组对象。

at

访问指定位置处的元素。

back

访问最后一个元素。

begin

指定受控序列的开头。

cbegin

返回一个随机访问常量迭代器,它指向数组中的第一个元素。

cend

返回一个随机访问常量迭代器,它指向刚超过数组末尾的位置。

crbegin

返回一个指向反向数据中第一个元素的常量迭代器。

crend

返回一个指向反向数组末尾的常量迭代器。

data

获取第一个元素的地址。

empty

测试元素是否存在。

end

指定受控序列的末尾。

fill

将所有元素替换为指定值。

front

访问第一个元素。

max_size

对元素数进行计数。

rbegin

指定反向受控序列的开头。

rend

指定反向受控序列的末尾。

size

对元素数进行计数。

swap

交换两个容器的内容。

运算符:

运算符

说明

array::operator=

赋值替换数组。

array::operator[]

访问指定位置处的元素。

最简单示例:
代码语言:javascript
复制
int main()
{
    std::array<int, 5> arry = {5, 4, 3, 2, 1};
    std::sort(arry.begin(), arry.end());

    for (auto &i : arry)
    {
        std::cout << i << std::endl;
    }
    std::cout << "-------------" << std::endl;

    arry[2] = 10;

    for (auto& i : arry)
    {
        std::cout << i << std::endl;
    }
    std::cout << "-------------" << std::endl;

    arry.fill(0);

    for (auto& i : arry)
    {
        std::cout << i << std::endl;
    }

    return EXIT_SUCCESS;
}

关联式容器

与序列式容器不同的是,关联式容器是采用键值对的方式即 Key : Value 对应的方式来存储数据。

STL 所内置的关联式容器主要使用红黑树来实现,容器内会自动根据 Key 来自动升序排序。

此外还有基于哈希值的无序关联式容器,请照猫画虎使用即可。

名称

头文件

实现

键值对应

允许键重复

键排序

std::set

set

红黑树

Key = Value

No

升序

std::multiset

set

红黑树

Key = Value

Yes

升序

std::unordered_set

unordered_set

哈希表

Key = Value

No

std::unordered_multiset

unordered_set

哈希表

Key = Value

Yes

std::map

map

红黑树

Key : Value

No

升序

std::multimap

map

红黑树

Key : Value

Yes

升序

std::unordered_map

unordered_map

哈希表

Key : Value

No

std::unordered_multimap

unordered_map

哈希表

Key : Value

Yes

红黑树与哈希表不同实现的关联式容器区别:红黑树实现的关联式容器遍历性能更好,哈希表实现的关联式容器基于键的随机访问性能更好。

请记下表格当中的头文件,下文当中不再赘述。

Set

std::set 与 std::multiset 最显著的特点就是键就是值,所以在 Set 当中的值不能直接修改,需要删除旧值再重新建立新值 (即新建立键值,只是对于 set 来说值就是键而已)。

std::set 与 std::multiset 的区别是,std::set 不允许有重复值,std::multiset 则允许。两者同样都会根据键值大小进行升序排序。

构造语法:
代码语言:javascript
复制
// 默认
std::set<Type> name;
std::multiset<Type> sm1;

// 迭代器区间
std::set<Type> name(obj.begin(), obj.end());
std::multiset<Type> name(obj.begin(), obj.end());

// initlist
std::set<int> name{value, value, ...};
std::multiset<int> name{value, value, ...};

// 拷贝构造和移动构造略

// 自定义比较器(C++14)
struct Point { double x, y; };
struct PointCmp {
    bool operator()(const Point& lhs, const Point& rhs) const {
       return std::hypot(lhs.x, lhs.y) < std::hypot(rhs.x, rhs.y);
    }
};
std::set<Point, PointCmp> z = { {2, 5}, {3, 4}, {1, 1} };
成员函数:

名称

说明

begin

返回指向起始的迭代器。

cbegin

返回指向起始的常量迭代器。(const修饰)

cend

返回指向末尾的常量迭代器。(const修饰)

clear

清除所有元素。

contains(c++20)

检查是否存在指定键。仅限C++20。

count

返回匹配特定键的元素数量。

crbegin

返回指向起始的常量逆向迭代器。(const修饰)

crend

返回指向末尾的常量逆向迭代器。(const修饰)

emplace

原位插入元素。

emplace_hint

原位插入元素,且尽可能于 hint(迭代器) 前面位置。

empty

检查是否为空。

end

返回指向末尾的迭代器。

equal_range

返回一对表示范围区间的迭代器,为匹配特定键的元素范围。

erase

从指定位置移除一个元素或元素范围,或者移除与指定键匹配的元素。

find

寻找带有特定键的元素,并返回它所处位置的迭代器。

get_allocator

返回用于构造 allocator 的 set 对象的副本。

insert

将一个元素或元素范围插入到set。

key_comp

返回set内用于比较排序对象(比较器)的副本。

lower_bound

返回指向首个不小于给定键的元素的迭代器。

max_size

返回set的最大长度。

rbegin

返回指向起始的逆向迭代器。

rend

返回指向末尾的逆向迭代器。

size

返回set中的元素数量。

swap

交换两个set。

upper_bound

返回指向首个大于给定键的元素的迭代器。

value_comp

返回用于在value_type类型的对象中比较键的函数。

运算符:

名称

说明

operator=

将一个集中的元素替换为另一个集的副本。

最简单示例:
代码语言:javascript
复制
int main()
{
    std::set<int> s3{4, 3, 2, 1, 0};

    s3.emplace(5);
    s3.emplace(6);
    s3.emplace(7);
    s3.emplace(8);

    for (auto &i : s3) {
        std::cout << i << std::endl;
    }
    std::cout << "-------------" << std::endl;

    s3.erase(s3.find(5));
    s3.emplace(10);

    auto it = s3.equal_range(10);
    if (it.first == s3.end() || it.second == s3.end())
    {
        std::cout << "There are 10 in the set" << std::endl;
    }
 
    for (auto& i : s3) {
        std::cout << i << std::endl;
    }

    return EXIT_SUCCESS;
}

扩展阅读:

std::unordered_set 与 std::unordered_multiset

基于哈希表实现的无序 set 容器,拥有比红黑树所实现的版本更好的随机访问性能,但是遍历性能弱于红黑树实现。

序列由哈希函数弱排序,哈希函数将此序列分区到称为存储桶的有序序列集中。 在每个存储桶中,比较函数确定任何一对元素是否具有等效的排序。 每个元素同时用作排序键和值。 序列以允许查找、插入和移除任意元素的方式表示,并包含与序列中的元素数量无关的多个操作(常量时间),至少在所有存储桶长度大致相等时如此。 在最坏情况下,当所有元素位于一个存储桶中时,操作数量与序列中的元素数量成比例(线性时间)。 插入元素不会使任何 iterator 无效,删除元素只会使指向已删除元素的 iterator 失效。 -- Microsoft Docs

两者相较于 std::set,新增了以下桶操作、哈希策略函数:

名称

说明

bucket_count

返回桶数量

max_bucket_count

返回桶最大数量

bucket_size

返回桶的大小

bucket

返回带有特定键的桶

load_factor

返回每个桶的平均元素数量

max_load_factor

获取或设置每个桶的最大元素数。

rehash

重新生成哈希表,并且为指定数量的桶预留空间。

reserve

重新分配预留元素个数。

hash_function

返回用于存储元素的哈希函数对象。

key_eq

返回用于比较键相等性的函数对象。

Map

与 set 不同的是,map 系列是键值与值对应的形式,即 Key : Value 成对出现。基于红黑树的 map 会根据键的大小自动升序排序,基于哈希表的则无序。

map 可以根据键的映射直接修改元素值。但是,键却是常量无法修改,只能删除已有的键值对再添加新的。

标准库当中 map 系列分为 std::map 和 std::multimap,前者不允许键重复,后者则允许键重复。

构造语法:
代码语言:javascript
复制
// 默认空
std::map<KeyType, ValueType> name;

// initlist
std::map<KeyType, ValueType> name{ {Key, Value}, ...};

// 迭代器范围
std::map<KeyType, ValueType> name(map.begin(), map.end());

// 拷贝构造和移动构造略

// 自定义比较struct
struct Comp
{
    int x, y;
    Comp(int v, int n) : x(v), y(n) {};
    bool operator()(const int& l, const int& r) const {
        return std::hypot(l, r) < std::hypot(x, y);
    };
};
std::map<int, string, Comp> m4(Comp(11, 22));

// 自定义比较lambda
int xx = 11;
int yy = 22;
auto compLambda = [&](const int &x, const int &y) {
    return std::hypot(x, y) < std::hypot(xx, yy);
};
std::map<int, string, decltype(compLambda)> m5(compLambda);
成员函数:

名称

说明

at

查找具有指定键值的元素。(在std::multimap中不提供)

begin

返回一个迭代器,此迭代器指向Map起始位置。

cbegin

返回一个常量迭代器,此常量迭代器指向Map起始位置。(const修饰)

cend

返回一个常量迭代器,此常量迭代器指向Map末尾位置。(const修饰)

clear

清除所有元素。

contains(C++20)

检查Map中是否有具有指定键的元素。(仅限C++20)

count

返回Map中其键与参数中指定的键匹配的元素数量。

crbegin

返回一个常量反向迭代器,此常量反向迭代器指向Map起始位置。(const修饰)

crend

返回一个常量反向迭代器,此常量反向迭代器指向Map末尾位置。(const修饰)

emplace

将原位构造的元素插入到Map中。

emplace_hint

将原位构造的元素插入到Map中,且尽可能于 hint(迭代器) 前面位置。

empty

判断是否为空。

end

返回一个迭代器,此迭代器指向Map末尾位置。

equal_range

返回一对迭代器。第一个迭代器指向Map中其键大于指定键的第一个元素。第二个迭代器指向Map中其键等于或大于指定键的第一个元素。

erase

从指定位置移除Map中的元素或元素范围。

find

寻找带有特定键的元素,并返回它所处位置的迭代器。

get_allocator

返回用于构造 allocator 的 map 对象的副本。

insert

将一个或一系列元素插入到Map中的指定位置。

key_comp

返回Map内用于比较排序对象(比较器)的副本。

lower_bound

返回指向首个不小于给定键的元素的迭代器。

max_size

返回可容纳的最大元素数。

rbegin

返回一个反向迭代器,此反向迭代器指向Map起始位置。

rend

返回一个反向迭代器,此反向迭代器指向Map末尾位置。

size

返回当前Map中的元素数量。

swap

交换两个Map。

upper_bound

返回指向首个大于给定键的元素的迭代器。

value_comp

返回用于在value_type类型的对象中比较键的函数。

运算符:

名称

说明

operator[]

将元素插入到具有指定键值的映射。(在std::multimap中不提供)

operator=

将一个映射中的元素替换为另一映射副本。

最简单示例:
代码语言:javascript
复制
int main()
{
    std::map<int, string> mapObj{ {0, "ABC"}, {1, "DEF"}, {2, "GHI"}};
    for (auto &it : mapObj)
    {
        std::cout << "Key:" << it.first << " Value:" << it.second << std::endl;
    }
    std::cout << "-------------" << std::endl;

    std::vector<std::pair<int, string>> vec{ std::make_pair(3, "OOO"), std::make_pair(4, "PPP"), std::make_pair(5, "QQQ") };
    mapObj.insert(vec.begin(), vec.end());
    
    for (auto& it : mapObj)
    {
        std::cout << "Key:" << it.first << " Value:" << it.second << std::endl;
    }
    std::cout << "-------------" << std::endl;

    auto it = mapObj.find(4);
    if (it != mapObj.end())
    {
        it->second = "***";
    }

    for (auto& it : mapObj)
    {
        std::cout << "Key:" << it.first << " Value:" << it.second << std::endl;
    }

    return EXIT_SUCCESS;
}

扩展阅读:

std::unordered_map 与 std::unordered_multimap

两个基于哈希表实现的 Map,顾名思义一个不允许键重复,另一个则允许。

哈希函数将此序列分区到称为存储桶的有序序列集中。 在每个存储桶中,比较函数将确定任一元素对是否具有等效顺序。 每个元素存储两个对象,包括一个排序键和一个值。 序列以允许查找、插入和移除任意元素的方式表示,并包含与序列中的元素数量无关的多个操作(常量时间),至少在所有存储桶长度大致相等时如此。 在最坏情况下,当所有元素位于一个存储桶中时,操作数量与序列中的元素数量成比例(线性时间)。 此外,插入元素不会使迭代器失效,移除元素仅会使指向已移除元素的迭代器失效。 -- Microsoft Docs

两者相较于 std::map,新增了以下桶操作、哈希策略函数:

名称

说明

bucket_count

返回桶数量

max_bucket_count

返回桶最大数量

bucket_size

返回桶的大小

bucket

返回带有特定键的桶

load_factor

返回每个桶的平均元素数量

max_load_factor

获取或设置每个桶的最大元素数。

rehash

重新生成哈希表,并且为指定数量的桶预留空间。

reserve

重新分配预留元素个数。

hash_function

返回用于存储元素的哈希函数对象。

key_eq

返回用于比较键相等性的函数对象。

std::pair 与 std::tuple

可以同时存储不同数据类型的容器,它们两个都有各自的优势与最佳用途。

std::pair

std:pair 是一个类模板,提供了一个单元存储两个不同类型数据的功能,但也仅限于存储两个数据

但也正是它的优势:拿它可以轻松高效的初始化 std::map。

注意:声明 std::pair 时<>内的类型声明必须和初始化时()内排列的数据类型相对应。 

头文件:
代码语言:javascript
复制
#include <utility>
构造语法:
代码语言:javascript
复制
// 默认空
std::pair<Type1, Type2> name;

// 默认值
std::pair<Type1, Type2> name(Value1, Value2);

// make_pair
auto name3(std::make_pair<Type1, Type2>(Value1, Value2));
成员对象:

名称

说明

first

Value1

second

Value2

成员函数:

名称

说明

operator=

赋值

swap

交换

辅助类:

名称

说明

std::tuple_size<std::pair>

获得大小

std::tuple_element<std::pair>

获得元素类型

最简单示例:
代码语言:javascript
复制
int main()
{
    std::map<int, string> mapObj{ {0, "ABC"}, {1, "DEF"}, {2, "GHI"}};

    std::vector<std::pair<int, string>> vec{ std::make_pair(3, "OOO"), std::make_pair(4, "PPP"), std::make_pair(5, "QQQ") };
    mapObj.insert(vec.begin(), vec.end());

    mapObj.insert(std::make_pair(6, "GGG"));
    mapObj.insert(std::make_pair(7, "EEE"));
    
    for (auto& it : mapObj)
    {
        std::cout << "Key:" << it.first << " Value:" << it.second << std::endl;
    }
    return EXIT_SUCCESS;
}

std::tuple

元组,它是 std::pair 的扩展,是一个类模板。可以将多个不同类型的值汇集在一起,但它的长度只能是固定的。

此外,它还需要配合其头文件内的几个类外部函数来使用。

注意:

1,声明 std::tuple 时 <> 内的类型声明必须和初始化时()内相排列的数据类型对应。 

2,std::tuple 长度是固定的,长度由声明时 <> 内的类型数量而定,此后便不可更改。

头文件:
代码语言:javascript
复制
#include <tuple>
构造语法:
代码语言:javascript
复制
// 默认空
std::tuple<Type1, Type2, Type3, ...> name;

// 默认值
std::tuple<Type1, Type2, Type3, ...> name(Value1, Value2, Value3, ...);

// 使用 std::pair 构造
std::tuple<Type1, Type2> name(std::make_pair<Type1, Type2>(Value1, Value2));

// 使用 std::make_tuple 构造
std::tuple<Type1, Type2, Type3, ...> name(std::make_tuple(Value1, Value2, Value3, ...));
成员函数:

名称

说明

operator=

赋值

swap

交换两个tuple

非成员辅助函数:

名称

说明

make_tuple

创建一个tuple对象,其类型根据各实参类型定义

tie

创建左值引用的tuple,或将 tuple 解包为独立对象

forward_as_tuple

创建转发引用的 tuple

tuple_cat

通过连接任意数量的元组来创建一个tuple

std::get(std::tuple)

元组式访问指定的元素

辅助类:

名称

说明

tuple_size

获得大小

tuple_element

获得元素类型

ignore

用 tie 解包 tuple 时用来跳过元素的占位符

std::uses_allocator<std::tuple>

特化 std::uses_allocator 类型特征

最简单示例:
代码语言:javascript
复制
int main()
{
    std::tuple<int, string, float> t4(std::make_tuple(0, "str", 3.14));
    
    std::cout << std::get<0>(t4) << std::endl;
    std::cout << std::get<1>(t4) << std::endl;
    std::cout << std::get<2>(t4) << std::endl;

    // 批量拆包
    int i;
    string s;
    float f;

    std::tie(i, s, f) = t4;

    std::cout << i << std::endl;
    std::cout << s << std::endl;
    std::cout << f << std::endl;

    return EXIT_SUCCESS;
}

容器配接器

C + + 标准库定义了三种类型的容器适配器: stack 、 queue 和 priority_queue 。 每种适配器都限制了一些基础容器类的功能,以便对标准数据结构提供精确控制的接口。

stack 类支持) 数据结构的后进先出 (后进先出。 可以在脑海中将其类比为一摞盘子。 元素(盘子)只能从堆栈顶部(基容器末尾的最后一个元素)插入、检查或删除。 仅访问顶部元素的限制是使用 stack 类的原因。

queue 类支持先进先出 (FIFO) 数据结构。 可以在脑海中将其类比为排队等候银行柜员的人。 元素(人)可从行的后部添加,并且可以从行的前部删除。 行的前部和后部都可以插入。 仅以这种方式访问前端和后端元素的限制是使用 queue 类的原因。

priority_queue类对其元素进行排序,以便最大的元素始终位于顶部位置。 它支持元素的插入以及顶部元素的检查和删除。 要记住的一个好方法是,人们将其按 age、身高或其他一些标准进行排列。

-- Microsoft Docs

C++ 标准库当中提供了三种容器配接器,分别是 std::stack、std::queue、std::priority_queue。它们并不是容器,而是给予容器功能,比如栈与队列。

首先先讲两个逻辑:

栈:先进后出

队列:先进先出

配接器目的就是给予容器这些抽象的功能。

std::stack

std::stack 类是容器适配器,它给予程序员栈的功能——特别是 FILO (先进后出)数据结构。 该类模板表现为底层容器的包装器——只提供特定函数集合。栈从被称作栈顶的容器尾部推弹元素。 -- 《C++ Reference》

在不指定的情况下,std::stcak 默认基于 std::deque 实现。当然也可手动指定基于 std::vector 或者 std::list 实现。

由于栈本身属于线性概念,所有它不能用于关联式容器。

头文件:
代码语言:javascript
复制
#include <stack>
构造语法:
代码语言:javascript
复制
// 默认
std::stack<Type> name;

// 指定底层容器
std::stack<Type, std::vector<Type>> name;
std::stack<Type, std::list<Type>> name;
std::stack<Type, std::deque<Type>> name;

// 默认值统一初始化
std::stack<Type> name({1, 2, 3});

// 根据其他容器
std::vector<Type> name({1, 2, 3});
std::stack<int, std::vector<Type>> name(vec);

std::list<Type> list({ 1, 2, 3 });
std::stack<Type, std::list<Type>> name(list);

std::deque<Type> deque({ 1, 2, 3 });
std::stack<Type, std::deque<Type>> name(deque);
成员函数:

名称

说明

top

访问栈顶元素

empty

判断是否为空

size

返回当前元素个数

push

向栈顶推入元素

pop

推出(移除)栈顶元素

emplace

原位向栈顶推入元素

swap

交换两个类型相同的栈

运算符:

名称

说明

operator=

赋值

最简单示例:
代码语言:javascript
复制
int main()
{
    std::stack<int, std::vector<int>> st({ 1, 2, 3 });

    st.push(4);
    st.push(4);
    st.push(4);

    st.top() = 5;

    while (!st.empty())
    {
        std::cout << st.top() << std::endl;
        st.pop();
    }

    return EXIT_SUCCESS;
}

std::queue

队列可以给予容器先进先出的功能,就像核酸检测排队一样先到的先做。

和 std::stack 有一个共同点,就是 std::queue 也是默认使用 std::deque 作为默认容器,也可基于 std::vector 和 std::list。

PS:std::queue 支持 std:set 构造,但不支持 std::map。

头文件:
代码语言:javascript
复制
#include <queue>
构造语法:
代码语言:javascript
复制
// 默认
std::queue<Type> name;

// 指定底层容器
std::queue<Type, std::vector<Type>> name;
std::queue<Type, std::list<Type>> name;
std::queue<Type, std::deque<Type>> name;

// 默认值统一初始化
std::queue<Type> name({1, 2, 3});

// 根据其他容器
std::vector<Type> name({1, 2, 3});
std::queue<int, std::vector<Type>> name(vec);

std::list<Type> list({ 1, 2, 3 });
std::queue<Type, std::list<Type>> name(list);

std::deque<Type> deque({ 1, 2, 3 });
std::queue<Type, std::deque<Type>> name(deque);
成员函数:

名称

说明

front

访问首个元素

back

访问末尾

empty

判断是否为空

size

返回当前元素个数

push

向尾部推入元素

pop

移除首个元素

emplace

原位向尾部推入元素

swap

交换两个类型相同的队列

运算符:

名称

说明

operator=

赋值

最简单示例:
代码语言:javascript
复制
int main()
{
    std::queue<int> qu({ 1, 2, 3 });
    
    qu.push(9);
    qu.pop(); // del 1

    std::cout << "Front " << qu.front() << std::endl; // 2
    std::cout << "Back " << qu.back() << std::endl; // 9

    return EXIT_SUCCESS;
}

std::priority_queue

std::priority_queue  与 std::queue 完全不同,它是优先级队列,优先级高的元素先出队列,而并非先进入的元素。

默认情况下,std::priority_queue 会选择值最大的元素作为最高优先级。当然,也可以自定义值最小元素作为最高优先级。

PS:和 std::queue 一样,std::priority_queue 支持 std:set 构造,但不支持 std::map。

头文件:
代码语言:javascript
复制
#include <queue>
构造语法:
代码语言:javascript
复制
// 默认
std::priority_queue<Type> name;

// 指定底层容器
std::priority_queue<Type, std::vector<Type>> name;
std::priority_queue<Type, std::list<Type>> name;
std::priority_queue<Type, std::deque<Type>> name;
std::priority_queue<Type, std::set<Type>> name;

// 自定义比较器
auto comp = [](const Type& v1, const Type& v2){
    return v1 > v2;
};
std::priority_queue<Type, std::vector<Type>, decltype(comp)> name(comp);
成员函数:

名称

说明

top

访问最大优先级元素

empty

判断是否为空

size

返回当前所容纳的元素数量

push

推入元素并排序底层容器

emplace

原位推入元素并排序底层容器

pop

移除优先级最大的元素

swap

交换两个同类型priority_queue

运算符:

名称

说明

operator=

赋值

最简单示例:
代码语言:javascript
复制
int main()
{
    auto comp = [](const int& v1, const int& v2){
        return v1 > v2;
    };
    std::priority_queue<int, std::vector<int>, decltype(comp)> pri_qu(comp);

    pri_qu.push(3);
    pri_qu.push(8);
    pri_qu.push(6);
    pri_qu.push(1);
    pri_qu.push(7);
    pri_qu.push(5);
    pri_qu.push(9);
    pri_qu.push(2);
    pri_qu.push(4);

    while (!pri_qu.empty())
    {
        std::cout << pri_qu.top() << std::endl;
        pri_qu.pop();
    }

    return EXIT_SUCCESS;
}

AirChip

2022-04-08 02:22

====================================

芯片烤电池 C++ Example 2022-Spring Season Pass :

【Example】C++ 标准库常用容器全面概述

【Example】C++ 回调函数及 std::function 与 std::bind

【Example】C++ 运算符重载

【Example】C++ 标准库智能指针 unique_ptr 与 shared_ptr

【Example】C++ 接口(抽象类)概念讲解及例子演示

【Example】C++ 虚基类与虚继承 (菱形继承问题)

【Example】C++ Template (模板)概念讲解及编译避坑

【Example】C++ 标准库 std::thread 与 std::mutex

【Example】C++ 标准库多线程同步及数据共享 (std::future 与 std::promise)

【Example】C++ 标准库 std::condition_variable

【Example】C++ 用于编译时封装的 Pimpl 演示 (编译防火墙 Private-IMPL)

【Example】C++ 单例模式 演示代码 (被动模式、兼容VS2022编译)

====================================

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 序列式容器
    • std::vector
      • 头文件:
      • 构造语法:
      • 成员函数:
      • 运算符:
      • 最简单示例:
    • std::list 与 std::forward_list
      • 头文件:
      • 构造语法:
      • 成员函数:
      • 运算符:
      • 最简单示例:
    • std::deque
      • 头文件:
      • 构造语法:
      • 成员函数:
      • 运算符:
      • 最简单示例:
    • std::array
      • 头文件:
      • 构造语法:
      • 运算符:
      • 最简单示例:
  • 关联式容器
    • Set
      • 构造语法:
      • 成员函数:
      • 运算符:
      • 最简单示例:
      • 扩展阅读:
    • Map
      • 构造语法:
      • 成员函数:
      • 运算符:
      • 最简单示例:
      • 扩展阅读:
  • std::pair 与 std::tuple
    • std::pair
      • 头文件:
      • 构造语法:
      • 成员对象:
      • 成员函数:
      • 辅助类:
      • 最简单示例:
    • std::tuple
      • 头文件:
      • 构造语法:
      • 成员函数:
      • 非成员辅助函数:
      • 辅助类:
      • 最简单示例:
  • 容器配接器
    • std::stack
      • 头文件:
      • 构造语法:
      • 成员函数:
      • 运算符:
      • 最简单示例:
    • std::queue
      • 头文件:
      • 构造语法:
      • 成员函数:
      • 运算符:
      • 最简单示例:
    • std::priority_queue
      • 头文件:
      • 构造语法:
      • 成员函数:
      • 运算符:
      • 最简单示例:
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档