1. 认识headers、版本、重要资源
c++标准库大于stl,stl占标准库70%,stl由6大部件构成,编译器已经带着标准库,标准库开发团队把所写的标准库的东西放到一个namespace里,叫std,即standard,例如vector全名是std::vector,using namespace std表示把std这个命名空间全部打开,后面再使用标准库的东西就不用加上std::,或者写using std::cout,以后写cout就代表std::cout
不同的编译器,开发平台,所带的标准库的用法写法是一样的,
2. STL体系结构基础介绍
容器把内存的问题解决了,程序员看不到内存,分配器是来支持容器的,帮助分配内存,容器是一个模板类,generic programming模板编程的思想,
第二个模板参数是分配器可以不写,在容器的源代码里用默认的分配器,有默认值,分配器本身也是一个模板,必须告诉它每次分配的是什么东西,此处每次分配的内存是一个int,要搭配,vi是object 对象,迭代器是泛化的指针,
count_if,count是计算,if指带一个条件,所以此算法能帮助计算给它的条件之下符合这个条件的元素有几个,
predicate谓语,判断一个东西是真或假,传回真或假,此处条件为大于等于40,
begin指向第一个元素,end指向最后一个元素的下一个位置,即前闭后开,
auto elem,elem的类型其实是iterator,本来auto要写成container::iterator,所以elem就会取出后面vec里面每一个元素出来,auto加引用取出来的每个元素都是引用,修改后会影响原来的值,
3. 容器之分类与各种测试
容器分两种:序列式容器,sequence containers,关联式容器,associative containers,关联式容器的元素有key和value,所以这种容器适合做大量快速的查找,
c++11新出现的容器unordered container,不定序,其实底部是用哈市table做的容器,
c和c++本来就有数组,是语言的一部分,而在c++11把array也变成一个class,红框的都是c++11增加的东西,array是把语言的数组包装成一个class,
序列式容器:
array无法在前后扩充,
vector后面可以自动扩充,分配器负责,
deque双向队列,前后都可以扩充,念dek,
标准库提供的list是双向链表,实际是双向环状链表,
c++11新增了forward list单向链表,
关联式容器:
set 集合,map,内部是用红黑树做的,红黑树是高度平衡二分树,左右差不多平衡,在标准库里并没有规定set或map应该用什么来实现,但是由于红黑树很好,所以编译器所带的标准库都用红黑树做set和map,map的每一个节点有两个东西,一个是key,一个是value,而set的key就是value,value就是key,set要求所放的元素不能重复,map是元素的key不能重复,但是multiset或者multimap表示key可以重复,
hash table 的最好的做法是separate chaining,冲突的用链表连起来,每一个就是一个篮子bucket,每个篮子就是连一个链表,
array.data()会传回这个数组在内存里的起点的地址,
大部分容器都有push_back函数,从尾部放入元素,vector只能往后扩展,vector是两倍增长,size是真正元素的个数,capacity是空间有多大,一个string字符串大小是一个指针的大小,data函数得到整个vector的起始地址,
find是一个模板函数,但是在用模板函数的时候和一般函数用法一样,前面加::表示是全局的函数,所有的算法都是全局的模板函数,find返回一个iterator,
全局有个sort,某些容器也有自己的sort,此时用自己的sort比较快,
forward_list单向链表有push_front只允许放在头部,
slist是gcc有,和forward_list单向链表一样,引入头文件是放在ext目录文件夹里的,extension扩充,其余用法概念一样,
一段是一个buffer,一个buffer可以放很多个元素,此处一个段落放了8个元素,deque就是由一段一段构成的, 分段连续,但是让使用者感觉整个都是连续的,五个指针指向五个buffer,每个buffer的元素都有次序性,deque的每次扩充是一个buffer,即一段,deque没有自己的sort,要用全局的sort,list和forwardlist有自己的sort,用自己的sort,
stack和queue这两种容器的源代码其实就是拥有一个deque,因为deque功能最强大,能够支持写出堆和栈,由于这两种容器里面其实没有自己的数据结构,是借用deque,所以从技术上很多人不把这两个叫做容器,而叫做容器的adapter,即容器适配器,
stack是先进后出,所以不会提供函数可以得到一个iterator泛化的指针,如果提供这种函数,就有可能用这个iterator去改变某个元素内容,或者删除插入,会破坏容器的独特性质先进后出,所以没有find,queue也一样,
multiset里面是红黑树所形成的一个底层结构,这个容器没有push_back也没有push_front,安插元素用insert,这个元素不是安插到头或尾,元素落在树里的位置有一定的规则,自己有find函数,比全局的find快,
mutimap的*pitem是那个pair,
unordered_multiset不是用红黑树s支持,而是用hash table,是一种separate chaining分裂的串,以前叫hash_multiset,也用insert插入元素, bucket_count是篮子的数量,bucket_size(i)第i个篮子挂了几个元素,篮子的数量一定比元素数量多,因为长度不能太长,load_factor装载因子,max_load_factor固定为1,
map放入元素可以用c[i]=string[buf],会把i和buf组成一个pair,
4 分配器之测试
不同标准库下分配器不同,gnu和vc不同,
分配器就是一个class,提供两个函数,一个是allocate,一个是deallocate,不应该直接用分配器,因为还要记住当初要了多少个单元,allocate(1)的1就是一个元素,此处元素是声明allocator里面的int,要还,所以应该去使用容器。如果有小量的容器需求就用new搭配delete或malloc搭配free,不要直接用分配器,
5 OOP (面向对象编程) vs. GP (泛型编程)
容器和算法sort两者的关系要借助iterator,当要对vector和deque容器排序的时候,调用::sort这个全局函数,并且把要操作的范围告诉它,
list无法像vector和deque一样使用标准库的全局的sort算法,标准库的sort所用到的迭代器要有一定的条件,而这个条件是链表list提供的迭代器不能满足的,因为需要随机访问迭代器,而list链表是一个一个节点用指针串起来,并不是一个连续空间,所以它所具备的迭代器是不能跳来跳去的,只能往前进一个,或者后退一个,不能随意加几个,
如果有一些容器自己带着sort,就必须用里面自己的sort去排,如果没有带才用全局的sort去排,
6 技术基础:操作符重载and模板(泛化, 全特化, 偏特化)
,type被绑定为int,而type被抽出来了,所以template里就是空的,是特化的形式,
黄色的部分是一个typedef,会被转换成template<>,即看到template<>,就表示要特化,此图的泛化下面有七个特化版本,即hash这个类模板当接收特别的类型的时候,有独特的设计,本来最上面是空的
一个类模板是allocator,可以接收任意的_Tp,但是如果这个_Tp是void,就有独特的特化的设计,特化又成为full specialization全特化,完整的特化,
有两个模板参数,现在变成一个了,把第一个模板参数绑定为bool,即vector本来可以接收任意类型作为元素,但如果指定那个元素是bool值0和1真假值,那么标准库就说有个特别的设计,效率比较高,此处的局部是数量上是局部,是局部特化,即偏特化,还有一种是范围的局部特化,偏特化,比如之前接收任意类型,现在接收pointer to T,泛化的如果是一个pointer,就有独特的设计,const T*是指针指向const T,
7 分配器
operator new 函数会调用malloc,malloc所给的大小比要的还多了很多的东西,要的越大,附加的东西的比例占的越小,要的越小,附加的东西占比越大,附加的东西可以叫overhead开销,
allocator这个class一定是个模板,每一个分配器最重要的两个函数一个是allocate一个是deallocate,allocate函数会调用Allocate函数,而Allocate函数又会调用函数operator new,operator new函数又会调用malloc函数,所以在vc提供的分配器,分配内存的时候调用的是malloc,会得到所需的内存大小以及一些overhead额外的开销,回收内存的时候调用deallocate,会调用operator delete,operator delete函数会调用free函数,
allocate本来是类模板,加上参数int后变成allocator类名称,直接加小括号,一个typename后面直接加小括号形成一个object,临时对象,还的时候还要记住之前是多少个,不方便,但是容器使用没有问题,
所以vc的分配器没有任何独特设计,就是调用c的malloc和free分配和释放内存,且接口设计不方便使用,容器使用没有问题,
vc bc gcc三者一样,他们的分配器最终就是使用malloc和free分配和释放内存,因此会带来大量的额外开销,通常区块小,额外开销大,所以gcc不用,gcc用的另外的分配器是alloc,
所有容器在gcc下使用的分配器是alloc,额外开销里,上下两个cookie,记录整块的大小,因为malloc拿内存,malloc拿到一个指针,还的时候只还这个指针,不需要告诉大小,大小记录在cookie里,所以free函数能找到cookie,就知道回收多大的内存,由于malloc给各式各样的需求使用,有大有小,所以需要cookie记录,但是容器的元素的大小一样,所以可以不需要把每个元素的大小都记录,所以alloc分配器尽量减少malloc的次数,不要cookie,设计了16条链表,某一条链表负责的某个特定大小的区块用链表串起来,第0条链表负责的是8个字节的大小,第7条链表负责的是78=56个字节的大小,第15条负责的每一个区块是168=128字节,所有的容器需要内存的时候向分配器要内存,容器的元素大小会被调整到8的倍数,比如50调整到56,此时分配器就会看是某个链表负责,里面有没有挂内存块,如果没有再向操作系统要即malloc,要一大块,然后切割为小块,给出一块,切出来的用单向链表串起来,所以切出来的每一块都不带cookie,
cookie在很多c++平台上都是上下都有,因此消耗8个字节,所以选择一个容器,元素放100万个,由于这100万个元素都不带cookie ,所以可以节省800万字节,
gcc4.9分配器又改为vc,bc 的样子,之前gcc2.9的还存在,但是不是std的命名空间里的,是放在gnu_cxx