【STL和泛型编程-侯捷】---学习笔记(上)

1 STL体系结构基础介绍

1    第一讲
(1)Generic Programming(GP,泛型编程),使用template模板为主要工具来编写程序。
(2)STL是泛型编程GP最成功的作品。
1.1 C++ Standard Library VS Standard Template Library
(1)C++标准库(C++ Standard Library),include的头文件,标准库中70%都是STL;C++标准库.h文件,编译器自带,可以看到源代码。
(2)Standrad Template Library,STL,标准模板库
(3)新式头文件封装在std名称空间中,using namespace std;
1.2 重要网页
(1)cplusplus.com;(2)CppReference.com;(3)gcc.gnu.org
2 第二讲 STL体系结构基础介绍
1.3 STL六大部件:

(1)容器(Container);(2)分配器(Allocators);(3)算法(Algorithms);
(4)迭代器(Iterators);(5)适配器(Adapters);(6)仿函式(Functors)
 
(1)迭代器是泛化的指针;

(2)容器的第二个参数是分配器,每个容器要有分配器来负责内存管理,默认值,可以不写;分配器也是一个模板,要<>类型,类型要与容器保持一致。
(3)less<int>是仿函数,bind2nd是函数适配器,绑定第二个参数,即只接收第一个参数,not1是一个函数适配器,反义,大于等于40
(4)复杂度:Complexity,Big-oh
 
(5)前闭后开区间:
 
(6)C++基于STL的for循环
 
注意:要改变值的时候使用auto引用才可以
(7)auto关键词,C++11新增,自动判断类型;

2 容器分类之测试

3.1 结构分类
序列性                      关联性               C++11新增—不定序

注意:红色是C++11新增的
(1)一个指针占用4个字节在32位电脑上,选择单向链表选择Forward-List更划算;
(2)关联性容器底部一般都是红黑树;左右高度平衡二分树;
(3)set中键值不分,是一样的;map分开的,键值类型不同;
(4)set不可以插入重复数据,而multiset可以;set插入数据的同时会返回插入结果,表示插入是否成功;multiset不会检测数据,因此可以插入重复数据;
(5)下图解决有多个数据放在同一个“篮子”,干脆用链表,指针,注意某个链表不可以太长;
 
3.2 array容器
(1)二分查找之前排序,两次调用clock();timeStart=clock();
(clock()-timeStart)
(2)将程序放在一个独立的名称空间中,在名称空间之前添加需要的头文件声明,多次添加同一头文件也没事,因为有保护机制,不会重复引入同一个头文件。
 
3.3 容器vector

(1)每次Push.back申请的空间以两倍增长,会申请预留空间;size是大小,capacity是申请的内存空间大小;是另外找一个两倍大的空间。
(2)try和catch抓取异常,

(3)find是算法,是模板函数;所有算法都是全局模板函数;
3.4 容器list

(1)    使用默认分配器;
(2)    List每次成长是在空间中找一个节点大小;
(3)    list有max_size;deque也有;
(4)    这里使用的是自己的list.sort();不能随机访问迭代器;
3.5 forward_list

(1)forward_list.push_front(),只提供从前面往里放;
(2)::find()全局函数,函数模板
3.6 容器deque

(1)vector扩充会有浪费;

3.7 容器适配器stack---先进后出
注意:关联性容器查找都非常快

(1)    一个deque其实是涵盖了一个stack;
3.8 容器适配器queue---先进先出

(2)一个deque其实是涵盖了一个queue;
Stack和queue在代码是实现上是用的deque,因此由于其没有自己的数据结构,因此不把stack和queue作为容器,而作为容器适配器;
不同提供insert操作,stack先进后出,queue先进先出
3.9 容器multiset---关联容器查找都很快

(1)    允许重复元素;
(2)    自动排序,底层结构是红黑树;
(3)    multiset.find()自己有函数;
(4)    set和multiset都是key和value相同;
3.10 容器multimap

(1)multimap.inset(pair<int,string>),要自己组合pair<int,string>元素放进去;pair<>也是标准库中的模板类;pair.first()和pair.second();
(2)multimap.find()也是自己的成员函数;
3.11 容器unordered_multiset---无序的

(1)unordered_multiset.bucker_count()篮子数量,篮子数量可能比现有大小还多,如上图,有的篮子是空的;篮子一定比元素多!
(2)c.bucket_size()可以看篮子里有多少数量;
3.12 容器set

3.13 容器map

(1)c[i]=string(buf);map可用数组[]索引,Multimap不可以,[]会自动合成Pair格式;
(2)map放了一百万个数据,因为Key不会重复,只有value会重复;
3.14 适用容器
Hash_set;hash_map;hash_multiset;hash_multimap;slist;

3 分配器之测试

容器需要分配器allocator作为支撑;每个容器类都有默认的容器分配器;

不同的分配器,如下allocate之后deallocate不仅需要指针,还需确定申请的大小进行释放;
 
(1)尽量使用容器,小内存申请就用new,delete和allocate,free;不用直接用适配器,因为记忆负担重,还要记住申请内存的大小;直接用并不好用;

7 分配器allocators---给容器用的幕后英雄
7.1 operator new()和malloc()

(1)operator new()底层都会调用到malloc();
(2)绿色是要求的空间大小,但是malloc()实际申请了更多的空间在上下方;
(3)如下图所示,容器的分配器默认为allocator;
 
7.2 分配器allocators

(1)重点在于allocate和deallocate成员函数;allocate调用_Allocate()函数,调用operator new,实际也是底层调用的malloc();deallocate()调用operator delete,operator delete调用C中的free;
(2)VC6+的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊的设计;
(3)直接看着源代码调用分配器allocator如下,需要记住分配大小,不建议直接使用;
 
(4)在malloc()时会产生额外内存开销,VCC和GC都是如此;
(5)实际的运用不是allocator,这样开销太大,还不如直接使用malloc()和new,alloc的实际运作图如下所示,
(6)思想是:尽量减少malloc()次数,就可以减少额外开销;容器内元素类型一致,不需要每次malloc()额外内存存储元素类型,alloc分配器结构如下图所示:
设计有16条链表;单向链表作切割;减少cookie的存在;

(7)G4.9的标准库中allocator的实现(又回到了malloc()重复调用出现额外开销)

(8)原来的alloc就是新的pool_alloc

4 源码分布(VC,GCC)及技术基础

5.1 OOP(Object-Oriented programming) VS.GP(Genic Programming)
(1)标准库中很少有继承关系,因此虚函数很少,关系比较简单;
(2)OOP=数据+操作,OOP是想要把data和method关联在一起打包;
 
(3)GP是将data和method分开来;

(4)Alogorithm是全局函数,左侧是数据容器;容器与算法之间要借助迭代器;
(5)为什么采用GP泛型编程?
A、Containers和Alogorithm可以各自闭门造车,然后以iterator联通既可;
B、Alogorithm通过Iterator确定操作范围,并通过Iterators取用Continer元素;
  


(6)左侧min算法,设置b<a,至于具体<怎么定义,算法不管,有数据本身决定;
因此,在标准库中操作符重载变得至关重要;
(7)为什么list不能使用::sort()排序?

List的迭代器不能跳来跳去,不能随机访问;标准库sort()所用到的随机访问迭代器,而list
迭代器不足以达到要求;
8)所有Algorithms,其内最终设计元素本身的操作,无非就是比大小;
 
    因为find在比等不等,sort在比大小;上图中第二个max(string(),string(),strLonger)
是按照自己定义的函数符作为第三参数,进行比较;
6 技术基础:操作符重载and模板(泛化,全特化,偏特化)
(1)模板分为三大类:类模板,函数模板,成员模板;
(2)类模板特化:template<>
 
 
 
(3)局部特化,Partial Specialization,偏特化,两个模板参数,锁定其中一个
  
如果确定T为一个指针,template<class T> Struct iterator_traits<T*>

 

 5 深度探索容器

8 容器之间的实现关系与分类

(1)标准库中很少有继承,这里并不是继承,而是复合/包含(拥有),has-a关系;
(2)C++11新增,slist改名为forward_list,hash_set,has_map名为unordered_set,
Unordered_map,hash_multiset,has_mulitmap改名为unordered_multiset,unordered_multimap,其新增了容器array;
(3)容器大小,是本身控制的信息占存,与控制的数据多少无关;
9 深度探索list容器

(1)    迭代器不能是指针,指针++回连续找内存,在list中比如内存并不连续,迭代器++就应该指向下一块结点内存;
(2)    容器的iterator迭代器都必须是一个class类,才能成为一个智能指针;所有的容器中都应该有一个typedef将迭代器重名为iterator;

(3)    iterator要模仿指针,重载运算符操作;
9.2 重载++
(1)iterator重载前置++和后置++,为了区别,operator++(int)为后置++,
Operator++()为前置++;

(2)先分析前置++,是将node解引用的next赋给本身,并返回;在执行后置++时,分为记住原值,进行操作,返回原值,其中进行操作就调用的后置++实现,
(3)在记住原值时,不是对*this解引用,还是先调用的复制构造函数;
(4)前置++引用,后置++返回值,上图左下角,C++不允许后置++两次,因此后置返回的是数值self,而不是引用self&;
9.3 重载*与->

所有容器的iterator都有两大部分:typedef和运算符重载
9.4 版本改进 2.9->4.9


(1)    G2.9版本要传入3个模板参数,G4.9只需要一个;传进来之后在typedef指针和引用类型;
(2)    最右侧不是void*了,而是明确一个指向自己类型的指针;
注意:所有容器都要遵循前闭后开的区间规则,所以list有一个空的;
9.5 G4.9版本

G2.9中大小为一个指向节点结构体的指针,占4字节,而G4.9版本中大小有两个指针,分别是_M_next和_M_prev;因此为8字节;
10 iterator迭代器的设计规则和Iterator Traits的作用与设计
(1)    traits特征,特性;
(2)    iterator是容器与算法之间的桥梁;算法提问题,迭代器回答;

(3)iterator_category()是看++,--,能不能跳来跳去,随机访问;
(4)iterator_difference_type:两个迭代器区间距离的表示类型;(unsigned int)
(5)value_type:元素类型
(6)一共有5种分类,5种typename;另外两种reference和pointer没有在C++标准库中使用过,但要写出来;迭代器需要定义的5个类型,以便回答算法提问;

10.2 Iterator Traits的作用与设计----萃取机
(1)如果iterator不是class,就不能typedef ,例如被视为普通指针;



(2)不能直接问,因为不知道是不是class,就问traits萃取机,作为中间层,加以判断,第二部分都是指针,偏特化指针,类的话第1部分就已经解决了。
(3)利用traits的偏特化,区分指针与类;
(4)加上const之后value_type就不能被赋值,就没有什么用,因此为T;
10.3 完整的iterator_traits写法
 
(1)当是类的时候,执行第一段直接问,当是指针地时候,执行下面的T*和constt T*偏特化版本;算法问iterator_traits,当是指针时,代替回答;
(2)针对iterator提出的iterator_traits萃取机;

 


11 深度探索容器vector
(1)不可以原地扩充,都是重新找一个两倍大的地方;

(2)依靠3个指针;start,finish,end_of_storage;sizeof(vector)就是容器本身的大小,就是三个指针,3*4=12字节,在32位系统上;
(3)finish指向最后一个元素的下一个元素;
11.1 二倍增长---发生在放元素的函数中

(1)    两次检查是否有空间,是因为insert_aux也有可能在别处被调用;

(2)    vector每次生长都会大量调用元素的拷贝构造和析构函数,要注意!
(3)链表节点分离,需要两个指针指来指去,但vector连续内存空间,只要一个指针就可以

 

(3)    public:is-a关系,private:has-a关系
(4)    新版的iterator不再是一个指针,而是一个对象,对应类有typedef回答问题

12 容器array,forward_list深度探索
12.1 array容器

(1)将本来的数组变为容器array?因为要提供迭代器,迭代器就要提供相应的5种类型,以便于连接算法与容器;

12.2 forward_list容器

12.3 deque容器---双向进出,stack和queue的底层实现

(1)map类型T**,map_size是控制器链表的数量,cur是指向任意一个元素的指针;看起来好像连续,实际上是分段的,每个buffer有一定长度,容器需要头和超尾迭代器即start和finish;因此,deque容器本身的大小有两个iterator+map+map_size=2*4*4+4+4=40字节;

 
(3)    deque插入元素值,deque<T>::insert()
首先判断是否为头尾插入,直接调用push_front()和push_back(),然后根据离哪一端比较近,开始复制挪位置;

 
(4)deque如何模拟连续空间?

(5)front()返回第一个buffer中的第一个元素,back()指向的是超尾指针,所以要先退一步然后在解引用,返回最后一个buffer的最后一个元素;
(6)size()是相减操作,迭代器做了-的操作符重载;看两者之间多少个buffer,buffer*大小,然后在分别加上finish本身和start本身的元素;是尾巴迭代器-头迭代器;

(7)对迭代器进行前置后置++的赋值运算符重载,注意先定义前置++,后置++还可以调用;后置—调用前置--;前置实现要比后置简单;---大家风范

(8)set_node是将控制器指针回到控制中心后+1,然后在依次改变迭代器中的3个指针;last指向的是超尾元素;
(9)注意,先判断再--
(10)可以随意移动位置,所以还重载了+=和+,先判断会不会跨越缓冲区,计算要跨几个,退回控制中心,调到正确位置之后,在确定接着继续走几步。

(11)-=用+=表示调用;[]重载是将其移动到n个位置;
 
(12)新版本:每个容器有一个基类,基类包含数据类,数据类继承一个allocator基类,数据类还包含一个数据小类;此处public继承allocator怪怪的!

 
(13)deque在容量扩充时,copy会自动在新内存的中端,使得两端扩充区域一致;
12.4 容器queue,stack深度探索---以deque为底层
(1)通过内含deque,然后stack和queue封锁某些功能,来达到其目的功能;

(2)因此会将queue和stack看做为容器适配器;

(3)stack和queue都可选择list或deque作为底层结构;

(4)注意:使用模板时,编译器不会预先帮你做全面性的检测,只是你用到哪里检查到哪里;

(4)    stack和queue都不可选择set或map作为底层结构;
13 RB-tree深度探索(红黑树—高度平衡的二叉树)
序列性容器->关联性容器(小型数据库)
13.1 红黑树---一定会走完左边,再走右边,从最左边开始


Value=key+data;
(1)rb_tree红黑树要知道key,value,以及怎么正确拿到value里的key,因为data可能是一个类,也可以是多个数据的组合数据,即需要知道keyofvalue,知道排序的规则compare,以及适配器;
看容器首先看数据data;
(2)node_count中的节点(元素)数量;header是红黑树节点的指针;key_compare可能是一个函数,仿函数(类);
(3)class rb_tree是源代码中的一个底层类,非公开,本身大小是12->24;

(4)仿函数key_compare理论0字节,实际会占1字节,size_type是uint,所以是4+4+1
(5)其中,最顶层的红色是虚的,不放元素,为了实现方便设置的;

(6)像identity就是一个仿函数,是一个类但是没有实际数据,使用类似函数,称仿函数;

(7)3个指针+1个枚举=->24字节

13.2 容器set,multiset深度探索
 
(1)我们无法使用set/multiset的iterators改变元素值(因为Key有其严谨的排序规则);
Set/multiset的Iterator是其底部的rb-tree的const-iterator,就是为了进制user对元素赋值;
(2)底层set实现是一个rb-tree;

13.3 容器map,multimap深度探索


(1)map独有的[]调用方式,注意,如果不存在,【】会创建一个Key值的元素放入map,并附一个默认值;

14 hashtable(哈希表/散列表)深度探索
 
(1)当空间充足,每个元素都放在自己对应的位置,当空间不足,就放在H%M的位置,此时会发生多个元素放在同一个位置的碰撞,曾使用穿一次二次多次函数将其后来者转移到其他位置放置,但是增加了复杂度;
(2)因此考虑如下图中,采用链表形式,相同的就连在一起放置;
(3)但是要防止其中一个链表过长,影响效率;此处检查,没有太过数学道理,就是根据经验,设定为当元素数量>篮子数量时,将其进行打散;将篮子增加两倍,重新进行计算;不是严格的两倍,选择素数,两倍数量附近的一个素数作为新的篮子数量;

14.1 hashtable的代码实现
(1)hashtable也是内部源码,不对外公开;

(2)模板参数:HashFcn是一个函数/仿函数/函数对象,对应怎么算物体/元素编号;
ExtractKey如何取出Key,EqualKey比较key怎么比大小相等;
(3)容器hashtable的大小,前三个是仿函数,3*1;buckets本身12,num_elements是4个字节,因此总大小为3+12+4=19个字节->20个字节;
(4)hashtable_iterator必须有一个指针有能力回到控制中心,移动当前所在的篮子;
(5)使用hashtable:

(6)hashtable最重要的就是怎么给编号!下面都是偏特化:(如果是数值,就是当成编号)
 

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值