• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

【Example】C++STL常用容器概述

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

前排提醒:

由于 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》

 

头文件:

#include <vector>

 

构造语法:

// 空初始化
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的副本替换该向量中的元素。

 

最简单示例:

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;
}

扩展阅读:

【Example】C++ Vector 内存预分配的良好习惯

【Example】C++ Vector 的内存强制回收

 

 

std::list

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

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

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

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

 

头文件:

#include <list>

 

构造语法:

// 默认
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中的元素。

 

最简单示例:

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 是单项链表,它的头文件是:

#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 元素的引用。

 

回归正题

头文件:

#include <deque>

 

构造语法:

// 默认空
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 的副本。

 

最简单示例:

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

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。

 

头文件:

#include <array>

 

构造语法:

// 默认空
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[] 访问指定位置处的元素。

 

最简单示例:

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 则允许。两者同样都会根据键值大小进行升序排序。

 

构造语法:

// 默认
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= 将一个集中的元素替换为另一个集的副本。

 

最简单示例:

int main()
{
    std::set<int> s3{4, 3, 2, 
                      

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
[C++]C风格、C++风格和C++11特性的线程池发布时间:2022-07-13
下一篇:
C语言的面向对象技术(转)-Panderen发布时间:2022-07-13
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap