C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览
文章目录
- STL六大部件
- 容器的使用
- Array
- vector
- list
- deque
- mutiset
- multimap
- unordered_multiset/set
使用一个东西,却不明白它的道理,不高明。
STL六大部件
- 容器 Containers 用来存放数据
- 分配器 Allocators 为容器内的数据分配存储空间
- 算法 Algorithms 计算数据
- 迭代器 Iterators 访问数据的式(算法使用其访问容器内大数据)
- 适配器 Adapters
- 仿函数 Functors 类似于函数的功能
六大组件之间的关系示意图如下:
容器和容器之间的实现关系图如下(这个图是候捷老师总结的):
大体意思是,左侧的序列式容器中,heap相关的算法实现使用的是上一层的vector,而priority_queue优先级队列的实现使用的是上一层的heap。
缩进的容器代表的是被实现的容器,之上未缩进的代表的是底层结构。像stack和queue(缩进)了容器适配器底层是用deque实现的。
同时对于关联式容器,set,map,mutiset,multimap(缩紧)的底层实现都是rb_tree(未缩进);且hash_set,hash_map等容器的底层是hashtable。
图中显示的是C++11 版本之前的容器内容,当C++11推出之后,slist就变成了forward_list,hash_set和hash_map就变成了unordered_set和unordered_map;hash_multiset 和 hash_multimap就变成了unordered_multiset和unordered_multimap
举例如下代码:
核心功能是计算数组arr中大于40的元素的个数
所有的容器区间都是 前闭后开区间[ ),所以遍历容器的形态可以如下
Containter<T> c;
...
Container<T>::iterator ite = c.begin();
for(; ite != c.end(); ++ ite)...
C++11 的新特性,提供range-based for statement,来简化以上的容器遍历形态
for ( dec1 : coll ) {statement
}
for ( int i : {2,3,4,5,7,8}) {std::cout << i << std::endl;
}
std::vector <double> vec;
...
for ( auto elem : vec) {std::cout << elem << std::endl;
}for (auto &elem :vec) {elem *= 3;
}
容器的使用
Array
静态数组,空间大小是直接分配好的
基本接口如下
- size()
- front() 返回第一个元素
- back() 返回最后一个元素
- data() 返回第一个元素的首地址
#include <iostream>
#include <array>using namespace std;int main()
{array<int,100> arr_test;for(int i = 0;i < 100; ++i) {arr_test[i]=i;}cout << arr_test.size() << endl;cout << arr_test.data() << endl;return 0;
}
vector
基本接口如下:
- push_back()
- size()
- front() 第一个元素
- back() 最后一个元素
- data() 内存中的起始地址
vector的内存增长是两倍的方式,即 push_back 第一个元素时发现内存空间不够,allocator分配器会分配能够存放2个vector元素的内存空间;放第三个元素的时候,又会分配4个元素的内存空间;放第五个的时候又会分配 8个元素的存储空间。。。
同时每次分配新的大小的内存空间的时候会做一个元素的整体拷贝,即将之前的空间元素整体拷贝到新的扩容后的内存空间。
利用data()函数验证如下:
#include <vector> // vector
#include <iostream>using namespace std;int main()
{vector<int> test_arr;for(int i = 0; i<100;++i) {test_arr.push_back(i);if(i % 2 == 0) cout << test_arr.data() << endl;} return 0;
}
可以看到最后的输出,当分配空间之后内存中的起始地址都会发生变化:
0x1885050 #两个元素空间
0x1885050
0x1885090 #四个元素空间
0x1885090
0x18850c0 #八个元素空间
0x18850c0
0x18850c0
0x18850c0
...
后续会进行相关的源码实现的分析,为什么要这样做?
list
基本操作如下
- push_back()
- size()
- max_size() 存放最多的元素的个数,只要内存足够,就能够一直分配
- front()
- back()
deque
双端队列
基本操作如下:
- push_back()
- size()
- front()
- back()
- max_size()
内存分配的示意图如下(元素内存中 的存放方式并不是连续的):
deque内部每一个区域存储一个指针,指针指向的是内存一段buffer区域,buffer里面存储的是一个个具体元素。当需要扩容的时候,会分配一个新的指针区域,并为这个指针区域分配对应的buffer。
mutiset
multiset和set的区别就是 muti允许元素重复,来体现multi的作用。
- insert() 插入元素
- size()
- max_size()
底层数据结构是红黑树的实现
这里它和multimap的基本是一致的,只是multimap容器的对外使用是允许key-value不同,而multiset的key-value是一样的。且元素插入本身有序,因为底层红黑树的结构就是有序的。
一些测试代码如下:
#include <iostream>
#include <string>
#include <ctime>
#include <set>
#include <algorithm>#define MAX_LEN 1000000
using namespace std;int main()
{multiset<string> c;for(int i = 0; i<MAX_LEN ;++i) {try {c.insert(to_string(i));}catch(exception &p) {cout << "i = " << i << " " << p.what() << endl;abort();}} cout << "c.max_size: " << c.max_size() << endl; cout << "c.size: " << c.size() << endl; string target = to_string(23456);cout << "find target is: " << target << endl; /*对比全局的find函数和mutiset自带的函数查询效率*/clock_t timestart = clock();auto item = ::find(c.begin(),c.end(),target);cout << "::find,milli-seconds: " << clock() - timestart << endl;if (item != c.end()) cout << "found " << endl;else cout << "not found" << endl;timestart = clock();auto pItem = c.find(target);cout << "c.find,milli-seconds: " << clock() - timestart << endl;if(pItem != c.end()) cout << "found" << endl;else cout << "not found" << endl; return 0;
}
输出如下,结果非常明显了:
c.max_size: 288230376151711743
c.size: 1000000
find target is: 23456
::find,milli-seconds: 6629
found
c.find,milli-seconds: 5
found
multimap
基本接口如下:
- insert() 插入元素
- size()
- max_size()
- multiset 不允许使用 [ ] 进行insert
同样使用红黑树实现,底层数据按key有序。
简单应用代码:
#include <iostream>
#include <string> //to_string()
#include <ctime> //clock()
#include <map>
#include <algorithm> // find()#define MAX_LEN 1000000
using namespace std;int main()
{multimap<long, string> c;for(int i = 0; i<MAX_LEN ;++i) {try {c.insert(make_pair(i,to_string(i*10)));}catch(exception &p) {cout << "i = " << i << " " << p.what() << endl;abort();}} cout << "c.max_size: " << c.max_size() << endl; cout << "c.size: " << c.size() << endl; long target = 23456;cout << "find target is: " << target << endl; clock_t timestart = clock();auto pItem = c.find(target);cout << "c.find,milli-seconds: " << clock() - timestart << endl;if(pItem != c.end()) cout << "found" << endl;else cout << "not found" << endl; return 0;
}
输出如下:
c.max_size: 256204778801521550 #怀疑这里的max_size与系统内存大小有关系
c.size: 1000000
find target is: 23456
c.find,milli-seconds: 5
found
unordered_multiset/set
基本操作接口:
- insert()
- size()
- max_size()
- bucket_count()
- load_factor()
- max_load_factor()
- max_bucket_count()
数据结构如下:
unordered_multiset/set 以及unordered_multimap/map的底层实现都是hash,为了解决hash冲突,也使用hash 链。为了防止链过长,使用链hash bucket的方式,当链的元素个数达到一定程度会分配一个双倍的存储空间,但是该链上的元素会打rehash重新分配。
简单应用代码:
#include <iostream>
#include <string> // to_string()
#include <ctime> // clock()
#include <unordered_set> // unordered_multiset
#include <algorithm> //::find()#define MAX_LEN 1000000
using namespace std;int main()
{unordered_multiset<string> c;for(int i = 0; i<MAX_LEN ;++i) {try {c.insert(to_string(i));}catch(exception &p) {cout << "i = " << i << " " << p.what() << endl;abort();}} cout << "c.max_size: " << c.max_size() << endl; cout << "c.size: " << c.size() << endl; cout << "c.bucket_count: " << c.bucket_count() << endl; cout << "c.load_factor(): " << c.load_factor() << endl; cout << "c.max_load_factor(): " << c.max_load_factor() << endl; cout << "c.max_bucket_count: " << c.max_bucket_count() << endl; string target = to_string(23456);cout << "find target is: " << target << endl; /*对比全局::find函数提供的查找效率和容器本身提供的find函数查找效率的差异*/clock_t timestart = clock();auto item = ::find(c.begin(),c.end(),target);cout << "::find,milli-seconds: " << clock() - timestart << endl;if (item != c.end()) cout << "found " << endl;else cout << "not found" << endl;timestart = clock();auto pItem = c.find(target);cout << "c.find,milli-seconds: " << clock() - timestart << endl;if(pItem != c.end()) cout << "found" << endl;else cout << "not found" << endl; for(int i = 0;i < 20; ++i) cout << "bucket " << i << "'s element is: " << c.bucket_size(i) << endl;return 0;
}
输出如下:
c.max_size: 384307168202282325
c.size: 1000000
c.bucket_count: 1056323
c.load_factor(): 0.94668
c.max_load_factor(): 1
c.max_bucket_count: 384307168202282325
find target is: 23456
::find,milli-seconds: 75920
found
c.find,milli-seconds: 2
found
bucket 0's element is: 0
bucket 1's element is: 1
bucket 2's element is: 0
bucket 3's element is: 1
bucket 4's element is: 2
bucket 5's element is: 2
bucket 6's element is: 3
bucket 7's element is: 2
bucket 8's element is: 2
bucket 9's element is: 2
bucket 10's element is: 1
bucket 11's element is: 3
bucket 12's element is: 0
bucket 13's element is: 1
bucket 14's element is: 1
bucket 15's element is: 3
bucket 16's element is: 1
bucket 17's element is: 0
bucket 18's element is: 1
bucket 19's element is: 0
以上的结果中bucket_count竟然比size的元素个数多,这个原理就是前面说到unordered_multiset的实现过程中对单个hash链上的元素个数超过一定的限制之后进行rehash,同时分配double的空间导致的。且bucket_count统计的是分配链空间的bucket,即使它没有存放实际的元素也会被统计到该数值之内。
后续会从源码层面分析以上容器各个操作接口及对应存储结构的外在表现,一群顶尖的工程师用优秀的代码风格开发了这样一批使用简单,内部逻辑复杂有趣的STL,学习无止境,向大佬看齐!
相关文章:

Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8570428 通过前面几篇文章的学习,我们知道了在 Android系统中,无论是普通的Activity窗口,还是特殊的输入法窗口和壁…

oracle非归档模式下如何备份,Oracle之RMAN数据库在非归档模式下的备份和恢复
1.数据库在非归档模式下的备份 SQLgt; archive log list;数据库日志模式 非存档模式自动存档 禁用存档终点 USE_DB_RECOVERY_FIL1.数据库在非归档模式下的备份SQL> archive log list;数据库日志模式 非存档模式自动存档 禁用存档终点 USE_DB_RECOVERY_FILE_DEST最早的联机日…

C# 视频多人脸识别的实现
上一篇内容的调整,提交到git了,https://github.com/catzhou2002/ArcFaceDemo基本思路如下:一、识别线程1.获取当前图片2.识别当前图片的人脸位置,并将结果存入列表3.分别获取人脸的特征值并比对,并将结果存入列表4.如果…

C++ STL: 分配器allocators 源码分析
STL 基本的六大组件作用以及功能如下: 可以看到allocator是数据存储组件container的幕后支持者,负责为其数据存储分配对应的存储空间。 operator::new 在详细介绍alloctor之前,先描述一下new运算符,我们使用C new一个对象的时候…

android xUtils的使用
gethub地址:https://github.com/wyouflf/xUtils/ xUtils简介 xUtils 包含了很多实用的android工具。xUtils 支持大文件上传,更全面的http请求协议支持(10种谓词),拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响...xUitls 最…

oracle 条件反转,Oracle反转倒置函数
Oracle提供了一个反转倒置函数reverse,但此函数不能分组倒置,本文提供了一个即可分组倒置的函数,如下所示:CREATE OR REPLACE FUNCTION REVERSE_F(p_str VARCHAR2, p_delimiter VARCHAR2:)RETURN VARCHAR2 ISv_return VARCHAR2(40…

android读取大图片并缓存
最近开发电视版的云存储应用,要求”我的相册“模块有全屏预览图片的功能,全屏分辨率是1920*1080超清。UI组件方面采用GalleryImageSwitcher组合,这里略过,详情参见google Android API。相册图片预取缓存策略是内存缓存(…

[ZJOI2018]历史
Description: 给定一棵树,定义每个点的操作为把这个点到1号点的路径覆盖上颜色i,每次该点到1号点经过的不同颜色段数会加到答案中,要使所有点按某一顺序操作完后答案最大 给定每个点要执行的操作次数,并给出m次修改,问每次修改后的最大答案 Hint: \(n,m \le 4*10^5\) Solution:…

C++ STL: lower_bound 和 upper_bound
接口声明 以下有两个不同的版本 lower_bound template <class ForwardIterator, class T>ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val);template <class ForwardIterator, class T, class Compare>ForwardItera…

1199: 房间安排
1199: 房间安排 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 1 Solved: 1[Submit][Status][Web Board]Description 2010年上海世界博览会(Expo2010),是第41届世界博览会。于2010年5月1日至10月31日期间,在中国上海市举行。本次世博会也是由中国举办的首届世界…

oracle判断非空并拼接,oracle sql 判断字段非空,数据不重复,插入多跳数据
oracle sql 判断字段非空,数据不重复select distinct(mobile) from wx_user_mobile where active_time is not nullbegininsert into sms_submit(id,gateway_id,source_number,dest_number,message_content)values(sms_system.nextval,1,88…

量产工具介绍(2)
前面介绍了量产工具概念,U盘构造,量产工具获取途径,以及国内的芯片分类,今天,我们从注意事项及常见问题继续介绍量产相关知识注意事项厂家推出的量产工具也是在不断提高版本的,新版本添加有新主控型号驱动。…

C++ STL: 容器vector源码分析
文章目录前言vector的核心接口vector push_back实现vector 的 Allocatorvector 的 push_back总结前言 vector 是我们CSTL中经常使用的一个容器,提供容量可以动态增长,并且随机访问内部元素的数据存储功能。 这个容器我们最常使用,所以也是最…

STM32学习笔记9(SysTick滴答时钟)
我不得不说意法半导体确实有点风骚!甚至有点变态。我对ST文档 STM32F10XXX参考手册的编辑水平真是不敢恭维。手册中好多说明都是含糊不清,甚至将好多对初学者来说很重要的地方都一笔带过,让人着实摸不着头脑。比如前面我说过的关于NVIC嵌套向…

oracle存储空间管理,Oracle存储空间管理
Oracle存储空间管理1.查看每个数据文件的剩余表空间(一个表空间只对应N个数据文件,N一般等于1)主要是利用表dba_free_space(表空间剩余空间状况)和dba_data_files(数据文件空间占用情况)select b.file_id "文件ID",b.tablespace_name "表空间名",b.f…

漫谈Httpclient
引用地址: http://hc.apache.org/httpclient-3.x/ End of life The Commons HttpClient project is now end of life, and is no longer being developed. It has been replaced by the Apache HttpComponents project in its HttpClient andHttpCore modules, whic…

具体数学-扰动法
from scipy.special import combn int (input("n: "))k int (input("k: "))sum1 0for j in range(0, k):for i in range(0, n1):sum1 sum1 (i**j)*comb(k1,j)result ((n1)**(k1) - sum1)/comb(k1,k) print(result) python 中计算排列组合:…

C++ STL: 超详细 容器 deque 以及 适配器queue 和 stack 源码分析
文章目录前言deque 实现deque类_Deque_iterator 类deque 的元素插入 insert函数deque如何模拟空间连续queue 实现stack 的实现前言 C容器中 deque是一个非常有趣的容器结构,我们知道deque本身是一个支持双向扩容的结构,对外表现出连续的存储方式。 本节将…

php好的mvc中index方法,创建一个mvc应用目录架构并创建入口文件index.php
摘要:<?php require vendor/autoload.php;require pig/Base.php;define(ROOT_PATH,__DIR__./);$configrequire pig/config.php;$queryStr$_S<?php require vendor/autoload.php;require pig/Base.php;define(ROOT_PATH,__DIR__./);$configrequire pig/confi…

C语言对mysql数据库的操作
C语言对mysql数据库的操作 原文:C语言对mysql数据库的操作这已经是一相当老的话题。不过今天我才首次使用,把今天的一些体会写下来,也许能给一些新手带来一定的帮助,更重要的是供自己今后忘记的怎么使用而进行查阅的! 我们言归正传…

[转]MCC(移动国家码)和 MNC(移动网络码)
From : http://blog.chinaunix.net/uid-20484604-id-1941290.html 国际移动用户识别码(IMSI) international mobile subscriber identity 国际上为唯一识别一个移动用户所分配的号码。 从技术上讲,IMSI可以彻底解决国际漫游问题。但是由于…

虚拟机cenos 重置密码
许久不用虚拟机,临时想登录看下,结果想不起来密码了,尝试各种可能的密码都登录失败。然后百度查验各种方法,看到一篇文章然后根据上面说的成功解决了问题,贴出链接:https://blog.csdn.net/dannistang/artic…

g-git 相关命令 及其 基本原理探索 (一)
文章目录git 最小配置作用域git 创建本地仓库git log 查看版本演进.git 目录refs目录objectsgit 三种对象类型详解 (commit ,tree,blob)因为工作需求,接下来将从git的使用到其内部工作原理,来避免代码提交或者review或者版本管理上的一些尴尬,…

php表单退出怎么写,PHP提交表单失败后如何保留填写的信息
[导读]本文章来给各位同学介绍PHP提交表单失败后如何保留填写的信息一些方法总结,最常用的就是使用缓存方式了,这种方法如果网速慢是可能出问题的,最好的办法就是使用ajax了。1.使用header头设置缓存控制头Cache-c本文章来给各位同…

lists,tuples and sets of Python
(python2.7.x) Lists 列表 列表是一个有序序列(集合),可变的(可以修改),可以理解为其他语言中的数组类型,但是列表更灵活更强大。 列表由方括号[]来定义的,它的元素可以是任意类型或对象,一个列…

Javascript中使用WScript.Shell对象执行.bat文件和cmd命令
WScript.Shell(Windows Script Host Runtime Library)是一个对象,对应的文件是C:/WINDOWS/system32/wshom.ocx,Wscript.shell是服务器系统会用到的一种组件。shell 就是“壳”的意思,这个对象可以执行操作系统外壳常用…

控件绘制的方法
1处理WM_PAINT 最极端的选择是执行一个 WM_PAINT 处理程序,并且自己完成所有的绘制。这意味着,您的代码将需要进行一些与呈现控件相关的琐事 — 创建适当的设备上下文(一个或多个),决定控件的大小和位置,绘…

google gflags的参数解析,便捷实用
命令行参数解析,一直是我们后段开发人员需要经常使用的一个功能,用来从终端解析接口的输入 ,并做出对应的处理。这里为使用C/python的开发人员推荐一个便捷的命令行解析接口集 gflags。 我们之前使用C的getopt/getopt_long 函数需要自己使用…

linux进程下的线程数,Linux下查看进程线程数的方法
0x01:ps -ef只打印进程,而ps -eLf会打印所有的线程[rootcentos6 ~]# ps -ef | grep rsyslogdroot 1470 1 0 2011 ? 00:01:13 /sbin/rsyslogd -c 4root 29865 28596 0 22:45 pts/5 00:00:00 grep rsyslogd[rootcentos6 ~]# ps…

OpenCV学习(20) grabcut分割算法
在OpenCV中,实现了grabcut分割算法,该算法可以方便的分割出前景图像,操作简单,而且分割的效果很好。算法的原理参见papaer:“GrabCut” — Interactive Foreground Extraction using Iterated Graph Cuts 比如下面的一…