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,学习无止境,向大佬看齐!
C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览相关推荐
- 杜邦接口还有一个跟他相反的叫什么_C++|一个小实例理解STL六大部件
STL主要包含容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adapters,或叫适配器).算法(algorithms).仿函数(functor ...
- STL六大组件:分配器、容器、迭代器、算法、仿函数、适配器
来自 侯捷<STL源码剖析> 转载于:https://www.cnblogs.com/madison/archive/2012/05/23/2515574.html
- STL源码分析之RB-tree关联容器 上
前言 本节将分析STL中难度很高的RB-tree, 如果对红黑树有所认识的那么分析起来的难度也就不是很大, 对红黑树没有太多了解的直接来分析的难度就非常的大了, 可以对红黑树有个了解红黑树之原理和算法 ...
- c++STL标准模板库(关联式容器(set,multiset容器))
关联式容器(associate容器)是STL提供的容器的一种,其中元素与序列容器不同的是它已经排过序,它主要通过关键字的方式来提高查询效率.关联式容器包含set.multiset.map.multim ...
- C++(STL):14--- forward_list比list更高效的容器
forward_list 是 C++ 11 新添加的一类容器,其底层实现和 list 容器一样,采用的也是链表结构,只不过 forward_list 使用的是单链表,而 list 使用的是双向链表(如 ...
- [置顶] 从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)...
首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下: C++ Code 1 2 template < class _Ty, c ...
- set获取元素_C++与STL入门(4):关联容器:集合set
回复set:获取csdn论坛对set集合关联容器的讲解 set使用前需要包含 set的详细用法在CSDN论坛中讲的相当清楚,这里再把常用到的子函数和特性进行展示. set的两个特点: ①set内的元素 ...
- Linux、C/C++、CS-Notes
Linux的很多基础问题,到现在依旧解决的不是很好 ,实践太少都是要背锅的. 目录 Linux: 编译链接: 科学上网: C++ 1.课程 2.STL 3.语言 IDE Qt CS-Note Linu ...
- C++STL与泛型编程__侯捷视频_学习博客_总目录
C++STL与泛型编程 侯捷 (1): c++重要网站相关 C++STL与泛型编程(2) 第一个C++ STL Application: STL六大部件代码示例,容器前闭后开区间,auto关键字的用法 ...
最新文章
- Linux 2.4调度系统分析--转
- 【PP操作手册】试制计划
- oracle移动表所在的表空间,ORACLE移动表空间(原)
- html5大赛是什么,IE9开发大赛为HTML5打了一针兴奋剂
- Python网络爬虫和正则表达式学习总结
- 删数问题(Noip1994)--贪心
- 以太坊DAO之股东协会智能合约
- win10电脑桌面透明便签_win10系统在桌面添加透明便签的操作方法
- 魅族手机在开发调试中无法打印log的解决方法
- 漫谈Anchor-based和Anchor-Free
- 基于autojs7的皮皮虾极速版辅助
- oracle中删除级联方法,Oracle 外键级联删除
- 直播搭建软件开发直播搭建技术流程解决方案
- 主成分分析(PCA)数学原理详解
- 神州租车还有这些新玩法?打造了行业新标杆
- 浏览器同域名请求的最大并发数限制
- 领导力专题︱如何培养与提升领导力
- 目标检测的评价指标P,R,mAP
- ElementUI介绍
- 小精灵家庭理财 下载
热门文章
- Tomcat 服务器的端口号的修改
- xcode 4.3.2 use storyboard创建TableView
- 写了一个PPT,用于公司内部培训
- Gazebo构建小车模型并通过ROS控制
- 《OpenCV3编程入门》学习笔记6 图像处理(七)阈值化
- c语言程序开平方,关于开平方的c程序
- php10-e 豪华版,华为畅享 10e 规格参数
- php 7 显示错误信息,PHP 7 错误处理
- python2的input,关于python2.x input函数的安全隐患
- 使用Python,OpenCV进行去水印,图像修复