转载:https://www.cnblogs.com/xiaoyi115/p/3721922.html
直接逼入正题。 Standard Template Library简称STL。STL可分为容器(containers)、迭代器(iterators)、空间配置器(allocator)、配接器(adaptors)、算法(algorithms)、仿函数(functors)六个部分。
迭代器和泛型编程的思想在这里几乎用到了极致。模板或者泛型编程其实就是算法实现时不指定具体类型,而由调用的时候指定类型,进行特化。在STL中,迭代器保证了STL中算法的连续性和紧密型,使得各算法不需要考虑具体的数据结构。
仿函数的实质就是在结构体中重载operator ()、<、>等操作符。仿函数实际上更多的是一种概念和思想性的东西,而非一种物质上的实际提升。
容器和算法是STL中最为核心的部件,一切工作的铺垫(例如迭代器、空间配置器等)都是根据他们而展开的。下面概要的讲一下容器和算法的主要内容。
容器:
1、vector
vector其实是一种顺序性的容器,当然这里的顺序不是说排好序,而是说操作的时候按照一定的次序关系。另外,vector和数组array是非常相似的,最大的不同是array是静态的,而vector是动态,这就是为什么大部分时候使用vector的原因了,因为我们往往不知道数组开多大比较合适,开大了浪费,开小了保存不下来,选择vector是一个比较优的选择。其实vector本质上就是一个array,只不过它是一个变化的array,那么一个固定大小的array怎么变成vector的呢?我们看一下它的数据结构或许会更加明白一些。
1 template<class T,class Alloc=alloc>
2 class vector{
3 public:
4 typedef T value_type;
5 typedef value_type* point;
6 typedef value_type* iterator;
7 typedef value_type& reference;
8 typedef size_t size_type;
9 ...
10 protected:
11 iterator start;//目前使用空间的头部
12 iterator finish;//目前使用空间的尾
13 iterator end_of_storage;//能够使用空间的尾
14 ...
15 };
我们注意到vector中的start,finish,end_of_storage。
好了,有了这三个变量,一切就有了答案。比如我们初始化vector的大小为10,那么end_of_storage=start+10了。我们可以通过维护finish这个变量来使得它看起来是动态的,比如我们插入5个值,那么通过计算finish与start的差值就能得到size()为5了。我们还可以继续插入,只不过此时需要finish向后移动一位就OK。但是,随之而来的问题又来了,当finish等于end_of_storage之后怎么办呢?显然,如果要继续插入的话,不能进行操作啊。
莫慌,请别怀疑程序猿是智商最高的那一类,我们新建一个array,这次申请的空间比上一个大那不就OK了吗?同时把旧的array复制到newarray中去,同时delete掉old array。这样就保证了还可以继续添加。但是有时候申请资源会不成功哦,所以插入操作并不总是OK的啦。程序猿的世界,bug总是无处不在。这个时候需要重回old array,同时delete 掉新申请的array.
我们已经解决掉了怎么使一个固定大小的array变成一个动态vector的问题,就是建一个更大的array。那么更大到底是多大呢?STL中把这个更大定义为两倍的当前SIZE.这个可能是根据经验得到的数据,两倍会是一个比较优的解。
vector中提供了insert,erase,pushback还有重载操作符等方法。其实insert就是在后面添加一个值,vector容量不够了先扩容再insert.
erase也就是擦掉某个值的操作其实就是删除某个值,然后后面的值前移,vector中erase效率极其低下,如果需要频繁的erase操作,建议还是换一个数据结构会比较好。
还有例如begin(),end(),size().这些其实都是通过维护start,finish,end_of_storage得到的。
2、list
在STL中list表示的是一种环状的双向链表(带头),相比vector需要维护start finish end_of_storage,list只需要维护一个node指针结点(头结点)就可以了。
双向环状链表的初始过程如下:
1 node=getNode();//新建一个结点
2 node->next=node;//后一结点是node
3 node->pre=node;//前一结点是node
其他插入、删除、取值等操作只需要了解链表的操作都是非常easy了。
3、deque(双端队列,两边都可进可出)
deque在这里其实也是一个array,只不过是分段而已,所谓分段在这里的意思就是。deque可能连续第一组有连续十个空间存放值,第二组也有十个空间存放值,但是这两组空间不是连续的,也就是说第一组空间的尾+1,不等于第二组空间的头。这两组空间的组织和管理是通过链表(在STL中称为map,(这个map翻译成地图更合适,而非数据结构中的map))的方式形成的。
4、stack(先进后出的数据结构),queue(先进先出的数据结构)
他们的底层实现都是list,只是根据List做一些量身定制而已。就比如都是一件衣服,不同人穿的时候,稍微在外表上做点修饰,本质不改变。
上面说的都是顺序性的容器,当然顺序性容器中海油优先队列,slist等,它们其实都是大同小异,不具体阐述了。
5、set、map、multiset、multimap
set和map的最大不同就是set是单值,而map是键值对,当然set也可以理解成键值对相等的特殊键值对。这样讲的话,我们大概就知道了,它们的底层实现就是一个东西。那么multiset和set有什么区别呢?主要值能不能重复的问题,如果可以保存多个相同值的话就使用multiset否则就使用set。因为插入的时候,它们分别调用的是insert和unique_insert;同理,map和multimap也是这个意思。
map有排序的功能,并且能够快速的插入和查找,甚至删除。它是怎么做到的呢?
这就要看的底层实现了。它们的底层都是基于红黑树,请看博主这篇文章:map,set的底层实现:红黑树[多图,手机慎入]
博文已经讲得非常清楚了,这里不再累述。
6、hashtable、hashset、hashmap、hash_multiset,hash_multimap
其实这里面主要是讲了一个东西,就是hashtable,同样hashset和hashmap的问题是是否是键值对的问题。而mullti..是是否是多值的问题。
hash的最重要的功能就是插入和查找相当迅速,我们使用hash更多的是看中他查找的时间复杂度几乎是O(1).那么hash是怎么实现如此高效的查找呢?
我们需要看他的数据结构,其实hash是一个array,通过某个合理的方式把值映射到array某个array[i]中。这就是hash的最核心思想了。说的简单,我们这里有几个问题需要解决:
a、如何才是高效的映射?
b、如果两个不同的元素映射到同一个array[i]中又怎么办?
c、数据量太小,array太大,会造成浪费;如果数据量太大,array太小,查找效率会非常低下,怎么解决?
我们一个一个来看。首先会到a.这里映射关系需要用到某个合适哈希函数,实验说明质数是一个非常好的选择,比如(value)mod(某个质数)。
b,提到的其实就是冲突处理的问题,冲突一般有以下方式,线性探测:冲突之后把元素放到下一个位置,下一个也有元素继续后面。二次探测:即一个哈希函数冲突了之后,用下一个哈希函数;开链法:就是在该array[i]下用一个链表表示。冲突了就放到链表中。STL中选择的是开链法。
对于问题c,STL中的解决方法是,选择某个质数K作为N,当K小于hash中的元素个数之后,用更大的质数代替N,同时对之前的每一个元素重构hash.
其他算法:
其他例如find,accumulate...等算法,其实就是维护迭代器,一般都是fistIterator,lastIterator,operatorValue.这种架构。
从始至终,迭代器和泛型在几乎每一个STL容器或算法中都有出现、这才是精髓!
空间配置器
这里在STL中说的空间配置器更多的是指内存的申请和释放。在STL中,一般拥有两级空间配置器,其中第一级配置器更像我们传统意义上的申请;而第二级配置器是一个池,一般称为内存池。
这里对空间配置器进行一个简短的综述,空间配置器管理存储资源的申请和释放的策略是当申请的资源较大时,这里说的较大时大于126BYTES时,调用第一级配置器,采用malloc和free的方式申请和释放;当小于126BYTES时,调用第二级配置器,第一级配置器实际上是由一个链表进行维护,但freeList中找到了合适大小的内存,直接给申请者使用,如果实在不够的话,就只能用部分,甚至再用堆申请一部分,即用malloc申请。释放的时候加入到freeList中。
采用二级配置器的一个利好就是,能够较大程度上减少碎片化,提高内存内部的使用率,减少资源浪费。
参考文献:STL源码剖析
《STL源码剖析》读书笔记的更多相关文章
-
STL源码剖析读书笔记之vector
STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...
-
STL源码剖析读书笔记--第四章--序列式容器
1.什么是序列式容器?什么是关联式容器? 书上给出的解释是,序列式容器中的元素是可序的(可理解为可以按序索引,不管这个索引是像数组一样的随机索引,还是像链表一样的顺序索引),但是元素值在索引顺序的方向 ...
-
Stl源码剖析读书笔记之Alloc细节
阅读基础: Foo *pf = new Foo; 执行了两个步骤: 1)::operator new 向系统申请内存. 2) 调用Foo::Foo()构造函数构造实例. ==> 申请内存,构造 ...
-
STL源码剖析读书笔记--第6章&;第7章--算法与仿函数
老实说,这两章内容还蛮多的,但是其实在应用中一点点了解比较好.所以我决定这两张在以后使用过程中零零散散地总结,这个时候就说些基本概念好了.实际上,这两个STL组件都及其重要,我不详述一方面是自己偷懒, ...
-
STL源码分析读书笔记--第二章--空间配置器(allocator)
声明:侯捷先生的STL源码剖析第二章个人感觉讲得蛮乱的,而且跟第三章有关,建议看完第三章再看第二章,网上有人上传了一篇读书笔记,觉得这个读书笔记的内容和编排还不错,我的这篇总结基本就延续了该读书笔记的 ...
-
(原创滴~)STL源码剖析读书总结1——GP和内存管理
读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...
-
c++ stl源码剖析学习笔记(一)uninitialized_copy()函数
template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy ...
-
重温《STL源码剖析》笔记 第三章
源码之前,了无秘密. --侯杰 第三章:迭代器概念与traits编程技法 迭代器是一种smart pointer auto_Ptr 是一个用来包装原生指针(native pointer)的对象,声明狼 ...
-
重温《STL源码剖析》笔记 第六、七、八章 next_permutation (字典序)
源码之前,了无秘密 ——侯杰 第六章算法 next_permutation 比如:01342 -> 01423 -> 01432 方法:从尾端开始往前寻找两个相邻的元素,令第一个元素为* ...
-
重温《STL源码剖析》笔记 第五章
源码之前,了无秘密 ——侯杰 序列式容器 关联式容器 array(build in) RB-tree vector set heap map priority-queue multiset li ...
随机推荐
-
谈谈rem
用rem已久但是对于它的理解似乎一直都有偏差,使用的时候多采用的是html的font-size:62.5%;然后按照1rem=10px这样来使用.所以我一直不明白,这个rem到底哪里是相对单位了,也不 ...
-
MYSQL中创建存储过程实现向表中循环插入数据
首先在test数据库中先创建一个表test: CREATE TABLE test( ID INT PRIMARY KEY AUTO_INCREMENT ,test_name VARCHAR(20),t ...
-
sql 存储过程中top 后面跟参数的问题
之前存储过程中有top的情况,都是拼接sql,然后通过exec执行,进行查询结果,很不方便. 今天研究了,原来top后面是可以直接写参数的. 只需要top 后面的参数加上小括号就好了 eg: TOP ...
-
WebBrowser脚本错误的完美解决方案
原文:WebBrowser脚本错误的完美解决方案 当IE浏览器遇到脚本错误时浏览器,左下角会出现一个黄色图标,点击可以查看脚本错误的详细信息,并不会有弹出的错误信息框.当我们使用WebBrowse ...
-
git上传本地文件到gitlab
The repository for this project is empty If you already have files you can push them using command l ...
-
Scrapy 1.4 文档 03 Scrapy 教程
在本教程中,我们假设您已经安装了Scrapy.如果没有,请参阅安装指南. 我们将要抓取 quotes.toscrape.com,一个列出著名作家的名言(quote)的网站. 本教程将引导您完成以下任务 ...
-
javascript ES6 新特性之 扩展运算符 三个点 ...
对于 ES6 新特性中的 ... 可以简单的理解为下面一句话就可以了: 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中. 作用类似于 Object.assign() ...
-
Spring整合Shiro
apache shiro 是一个安全认证框架,和 spring security 相比,在于他使用了比较简洁易懂的 认证和授权方式.其提供的 native-session(即把用户认证后的授权信息保存 ...
-
线程间的通信方式3--Handler
Android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因 ...
-
bzoj2212
题解: 线段树合并 比较一下哪一种方案的逆序对少 代码: #include<bits/stdc++.h> using namespace std; ; typedef long long ...