STL容器的本质

本文详细介绍了标准模板库(STL)中的各种容器,包括序列容器如vector、string、deque和list,以及关联容器如set、map等。同时对比了不同容器的特点和适用场景,并提供了《Effective STL》一书中关于容器使用的建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.sina.com.cn/s/blog_4d3a41f40100eof0.html

最近在学习unordered_map里面的散列函数和相等函数怎么写.学习过程中看到了一个好帖子.学习学习

 

标准STL序列容器:vector、string、deque和list
标准STL关联容器:set、multiset、map和multimap

非标准序列容器slist和rope
非标准关联容器hash_set、hash_multiset、hash_map和hash_multimap

rope是绳子的意思,string是线的意思,rope就是一个重量级的string

STL不同容器的实现是差别很大的,但它们都有一个数据结构原型,都具备各自的特性,最明显的是同一方法在不同容器中的效率差别很大。因此认清STL常用容器的本质,你才不会在你的应用中选错容器。

std::list的本质是链表
它的优势是在头和尾巴的插入和删除很快速
它的缺点就是随机访问能力差,因为它是挨个访问元素的
如果你的应用对随机访问效率有较高要求,那list不是你的首选。最夸张的是list的size()是从头到尾挨个数一遍的

std::vector的本质是红黑树(一种特殊的平衡二叉树)
它的优点是随机访问能力强,红黑树的原因
它的缺点是非最后一个元素的插入和删除速度慢,为了保证随机访问速度,插入和删除对会对红黑树做调整
一般情况下我们把vector当成一个数组使用,只不过这个数组的大小可以增加,所以不要贪心只用这些功能就够了。贪心你就会造成效率的降低。
vector容量的增长也是有开销的,如果你频繁的push_back(),还不如先一次性的多分配一些reserve()。注意reserve()和resize()是不一样的,一个调整预留空间,一个是调整元素个数。

(此部分会继续更新)

此外《Effective+STL》为我们提供了一些使用原则:

  1. 你需要“可以在容器的任意位置插入一个新元素”的能力吗?如果是,你需要序列容器,关联容器做不到
  2. 你关心元素在容器中的顺序吗?如果不,散列容器就是可行的选择。否则,你要避免使用散列容器。
  3. 必须使用标准C++中的容器吗?如果是,就可以除去散列容器、slist和rope。
  4. 你需要哪一类迭代器?如果必须是随机访问迭代器,在技术上你就只能限于vector、deque和string,但你也可能会考虑rope(关于 rope的更多信息在条款50)。如果需要双向迭代器,你就用不了slist(参见条款50)和散列容器的一般实现(参见条款25)。
  5. 当插入或者删除数据时,是否非常在意容器内现有元素的移动?如果是,你就必须放弃连续内存容器(参见条款5)。
  6. 容器中的数据的内存布局需要兼容C吗?如果是,你就只能用vector(参见条款16)。
  7. 查找速度很重要吗?如果是,你就应该看看散列容器(参见条款25),排序的vector(参见条款23)和标准的关联容器——大概是这个顺序。
  8. 你介意如果容器的底层使用了引用计数吗?如果是,你就得避开string,因为很多string的实现是用引用计数(参见条款13)。你也不能用 rope,因为权威的rope实现是基于引用计数的(参见条款50)。于是你得重新审核你的string,你可以考虑使用vector <char>。
  9. 你需要插入和删除的事务性语义吗?也就是说,你需要有可靠地回退插入和删除的能力吗?如果是,你就需要使用基于节点的容器。如果你需要多元素插入 (比如,以范围的方式——参见条款5)的事务性语义,你就应该选择list,因为list是唯一提供多元素插入事务性语义的标准容器。事务性语义对于有兴 趣写异常安全代码的程序员来说非常重要。(事务性语义也可以在连续内存容器上实现,但会有一个性能开销,而且代码不那么直观。要了解这方面的知识,请参考 Sutter的《Exceptional C++》的条款17[8]。)
  10. 你要把迭代器、指针和引用的失效次数减到最少吗?如果是,你就应该使用基于节点的容器,因为在这些容器上进行插入和删除不会使迭代器、指针和引用失效(除非它们指向你删除的元素)。一般来说,在连续内存容器上插入和删除会使所有指向容器的迭代器、指针和引用失效。
  11. 你需要具有有以下特性的序列容器吗:1)可以使用随机访问迭代器;2)只要没有删除而且插入只发生在容器结尾,指针和引用的数据就不会失效?这个 一个非常特殊的情况,但如果你遇到这种情况,deque就是你梦想的容器。(有趣的是,当插入只在容器结尾时,deque的迭代器也可能会失 效,deque是唯一一个“在迭代器失效时不会使它的指针和引用失效”的标准STL容器。) 
  12. 这些问题几乎不是事情的完结。比如,它们没有关注不同的容器类型使用不同的内存配置策略(条款10和14讨论了这些策略的一些方面)。但是,它们 已经足够是你信服了,除非你对元素顺序、标准的一致性、迭代器能力、内存布局和C的兼容性、查找速度、因为引用计数造成的行为不规则、事务性语义的轻松实 现和迭代器失效的条件没兴趣,你得在容器操作的算法复杂度上花更多的考虑时间。当然这样的复杂度是重要的,但这离整个故事很远。

在程序的世界里面,也是有得必有失,没有完美的东西,你所要决定的是在开发效率和运行效率间找一个平衡。而这个的前提是你要熟悉你所用的东西。

转载于:https://www.cnblogs.com/silentNight/p/5594448.html

### C++ STL Stack 容器的使用方法和特性 #### 基本概念 `std::stack` 是 C++ 标准模板库(STL)中的一个适配器容器,它提供了后进先出(LIFO, Last In First Out)的数据结构。为了使用 `std::stack`,需要包含头文件 `<stack>`。 #### 创建初始化 创建一个栈可以通过指定存储元素的数据类型来完成: ```cpp #include <stack> std::stack<int> myStack; ``` 这行代码声明了一个名为 `myStack` 的整数类型的栈[^1]。 #### 主要操作函数 - **push(element)**: 向栈顶添加新元素。 ```cpp myStack.push(10); ``` - **pop()**: 移除栈顶元素,不返回任何值。 ```cpp myStack.pop(); ``` - **top()**: 返回栈顶元素而不移除此元素;如果调用此成员之前未检查栈是否为空,则可能导致未定义行为。 ```cpp int topElement = myStack.top(); ``` - **empty()**: 判断栈是否为空,当且仅当栈中没有任何元素时返回 true。 ```cpp bool isEmpty = myStack.empty(); ``` - **size()**: 获取当前栈内的元素数量。 ```cpp size_t countElements = myStack.size(); ``` 这些基本的操作使得 `std::stack` 成为处理具有 LIFO 特性的场景的理想选择,例如表达式求值、括号匹配等问题[^5]。 #### 底层实现细节 实际上,`std::stack` 并不是一个独立的容器,而是一个基于其他序列化容器(如 vector 或 deque,默认情况下是 deque)构建起来的容器适配器。这意味着它的内部仍然依赖于某个具体的底层容器来进行实际的数据管理。因此,在某些特定的应用场合下可以选择不同的基础容器以优化性能表现[^3]。 #### 遍历方式 由于栈的本质决定了其不适合直接遍历访问所有元素,通常的做法是从顶部逐个取出直到清空整个栈为止。下面给出了一段简单的例子展示如何遍历并打印栈的内容: ```cpp while (!myStack.empty()) { std::cout << myStack.top() << ' '; myStack.pop(); } ``` 这段程序会依次输出栈里的每一个元素直至全部弹出[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值