stl源码分析知识点总结

1。泛化编程GP是将数据和数据处理函数分离(好处就是 二者可以闭门造车互不关联(只要有接口就行))。而面向对象OOP是将数据和数据处理函数结合在一起。

2.链表之所以不能用::sort是因为::sort的需要 RandomAccessIterator,而链表本身提供的迭代器不能满足RandomAccessIterator所需要的必要条件

3.模板分为三大类 :1、类模板 2、函数模板 3、成员模板
其中 类模板在调用的时候需要用"<>"先指定类型,比如:map<int,int>
而函数模板会有编译器自动推出类型 直接调用即可
成员模板:https://blog.csdn.net/yy19961124/article/details/88562009
4.特化和偏特化
特化:
template
struct iterator_traits{

}
//特化可以理解为将T抽出为int
template<>
struct iterator_traits{

}
偏特化:范围偏特化 和个数偏特化
范围偏特化:
template
struct iterator_traits{

}
//范围偏特化 可以理解为对T进行进一步限制为另一种(比如 指针 或者const修饰)
template
struct iterator_traits<T *>{

}
//个数偏特化

template<typename T,typename T2>
struct iterator_traits{
...
}
//对其中一个形参进行特化 。注意前缀少了一个typename!!!
template<typename T2>
struct iterator_traits<bool,T2>{
...
}
  1. VC6的allocator 只是以::operator new 和::operator delete完成allocte()和deallocate()
    int *p=allocator().allocate(512,(int *)0);//这里的()是生成一个临时对象(存放于栈区,而int *p=new 调用无参构造函数)
  2. STL中默认的分配器一般默认都是 alloc而不是allocator(因为alloc是先分成大块进行切割,而allocator只是改头换面)
    template<class T, class Alloc=alloc,size_t BufSiz=0>
    class deque{

    }
  3. 两个cookie各占四个字节,而且 cookie的最后一位是标示位,方便两个块的合并,之所以是两个cookie是方便和上面的块合并以及和下面的块合并

8.gnu2.9中alloc的缺点就是deallocate完全没有调用free()和delete(),也就是应该释放的内存都串在链表中但不会释放。

9.stl分类:()表示衍生关系:不是继承只是复合关系
1.sequence container:array vector(heap,priority_queue) list deque(queue stack)
2.associate container: set mapp .基础是红黑树
3.Unordered_Containers: Unordered_map/Multimap unorder_set/Multiset
(3中unorder_set/Multiset二者的区别: Multiset是允许数字重复,而set是不允许重复,但都是基于hashtable+bucket)

10.注意参数和返回值
重载 i++ self operator++(int);
重载++i self& operator++(){…};
为什么第二个是引用呢是为了保证下式
++++i可以
i++++不可以
//前置–
–i;
Test & operator–(Test &obj)
//后置–
i–;
Test operator–(Test &obj,int)
11.萃取是为了保证算法访问传入参数的类型,但是为了保证算法可以从传入的每一种iterator都可以获得特定的类型,比如一个普通指针,就需要萃取出特定的类型,(利用偏特化,普通的类就可以直接由萃取机再问类本身,而不普通指针会由萃取机自动封装返回给算法)

12.vector包含三个指针 start ,finish, end_of storage. (32位操作系统中是占用12字节),当vector空间不足时呈二倍增长(当push_back是无空间,就要调用 insert_aux()。而且insert_aux中有

const size_type old_size=size();
const size_type len=old_size!=0?2*oldsize:1

这条代码 可以证明这个说法)
侯捷老师视屏截图
13.

typedef typename RefBase::weakref_type weakref_type;

之所以要加typename是为了告诉编译器 RefBase::weakref_type是个类型,因为不加的话编译器不知道RefBase::weakref_type到底是个静态成员变量还是一个静态成员函数。不加的话会产生编译错误。

13.array 是包含三个类型 一个T value_type ,T* pointer ,T* iterator 。以及value_type _M_instance[_NM?_NM:1]
在这里插入图片描述
14.deque
deque对象本身就占四十个字节
虽然使用者使用起来感觉是来连续区间,但其本身是以来链表来指明
迭代器是一个类包含 cur first,last node 四个成员,其中 first和last指向两个边界,cur指向当前正在遍历的数据,而node类似一个控制中心负责指针的跨段加减
在这里插入图片描述
dequeue中的insert函数会自动判定要插入的数据离开始指针近还是离结尾指针近,因为哪一个短就要把该方向的数据往这个方向推(类似数组向后滑动)
在这里插入图片描述
重载++和–操作 需要先判断 curlast 和curfirst如果为true那么进行set_node()函数,进行跳段操作,也就是重新设置first,last和node。
而重载+=就是通过先判断加上n之后判断是否跨过缓冲区,如果不跨过,则直接cur+=n;否则那么先进行直接除得到一个偏移量k,通过set_node函数来设置缓冲区,最后在将first+offset-k*size(size就是单个缓冲区大小)即可。细节见截图:
在这里插入图片描述
值得一提的是这里的重载-=就是把n变成负数即可,因为+=-n就等于-=n;
一个字:妙
dequeue的扩展也是成二倍增长,但是为了保证可以向前和向后同时增长,所以在二倍增长时,会将数据拷贝到中间,(vector是直接拷贝到新空间的头部)
15 queue和stack默认底部是dequeue,但是也可以选择为list或者dequeue,而且stack比queue多了一个默认容器vector,是因为pop时queue需要popfront。而vector支持popback()。所以stack支持vector。(但是如果你的程序没有用到pop,queue当然也可以把vector作为底层,手动狗头)

16容器rb_tree (红黑树) :平衡二叉搜索树。特征:排列规则有利search和insert,并保持速度平衡---->无任何节点过深
在这里插入图片描述
rb_tree大小为12个字节是因为 node_count 和header为4个字节,而compare为函数,但是因为默认设置大小最小为1,所以目前大小为9但是因为空间上调,所以上升为12个字节

17.setinsert是调用rb_tree的 insert_unique();
在这里插入图片描述
set无法用迭代器修改元素,是因为迭代器加了const修饰
在这里插入图片描述
18:map和multimap:
map底层会对user指定的key_type设为const防止user对key进行修改(rb_tree可以改key但是不建议改,所以map底层就直接设置为const类型防止修改)
map的insert底层调用rb_tree的insert_unique();multimap的insert底层调用rb_tree的insert();
map的它特殊点在于他重载了 [] 所以可以使用类似下表访问的方法,但是它底部是二分查找(lower_bound函数)

19:
lower_bound(key)返回一个迭代器,指向键不小于(大于等于)key的第一个元素
upper_bound(key)返回一个迭代器,指向键大于key的第一个元素
(就是区别于有没有等于)

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<vector>
using namespace std;
int main()
{
  int arr[10]={1,2,3,4,5,6,7,8,9,10};
  int inde1=lower_bound(arr,arr+10,3)-arr;
    int inde12=upper_bound(arr,arr+10,3)-arr;
    cout<<inde1<<endl;///输出2
    cout<<inde12<<endl;///输出3
    return 0;
}

20:hashtable
当某个链表的元素数大于篮子数,会将篮子数乘以二将数据重新打散。(没有数学理论支持,经验之谈)
在这里插入图片描述

21:算法:
算法是一种函数模板,尽管他不和容器直接操作,但是可以通过询问迭代器来获得对应的参数,而迭代器必须能够回答算法的所有需求,要不然就无法实现适配算法和迭代器的适配。

22.typename 的用法(对13的增强):
首先在声明模板的时候,可以用 ,还有一种用法就是:

template<class T>
class job{
typedef typename T::value_Type val;///这里必须加typename否则编译过不去
};

这里加typename的原因是,因为这是模板,编译器无法得知value_Tyoe是不是一个类型,所以你加上typename关键字,就等于告诉编译器这是一种类型。而且,T中必须含有value_Type ,而且value_Type必须是一个类型,这时候也就体现除一种询问的关系。否则最后代码生成时就会报错。
在这里插入图片描述
23.
事实上算法对迭代器的询问还会经过萃取机(traits),因为调用算法的时候不一定传入的就是迭代器,但是为了保证普通指针也可以使用算法,那么就会使用萃取机,
以下是对该过程的大致模拟:
首先 迭代器本身定义

template<class T>
class Iterator{
    public:
typedef T value_Type;
};

萃取机:

template<class T>
class Iterator_traits{
public:
typedef typename T::value_Type val;///问传入的迭代器
};

算法:

template <class Iterator >
void dojob(Iterator ite)
{
typename Iterator_traits<Iterator >:: val value;///问萃取机
return ;
}

,但是这里并没有体现出的萃取机的作用,那么这样的话呢:

template<class T>
class Iterator_traits<T *>{///这里对T进行偏特化,也就是假如传入的是一个普通指针,那么也可以正确回答算法对迭代器问的问题
typedef  T val;
};

,所以traits是一种重要的编程思想(类似中间层)。

24.算法对迭代器问的几个问题(经常获取的几种类型):

  1. value type :这是迭代器所指对象的类型(int ,double,struct …)任何一个和算法完美搭配的迭代器都要定义自己的内嵌valuetype
  2. difference type: 用来表示两个迭代器之间的距离 ,所以一般用来表示STL中的最大容量,如果是连续性容器,那么头尾之间的距离就是其最大容量。
  3. reference type :迭代器分为两种:不允许改变“所指对象的内容”,称为constant iterators,例如 const int * pic;和允许改变“所指对象的内容”,称为mutable iterators,例如 int *pi; 当我们对muitable iterators进行操作时,获得的不应该是一个右值,而应该是一个左值,因为右值不允许赋值操作。在c++中,函数如果想反回左值,那么都是以by reference的形式返回,所以当p是一个mutable iterators 时,如果其value type是T,那么 *p的类型不应该是T而应该是 T&.
    左值右值大佬博客
  4. pointer type: pointer和reference在c++中reference 和pointer有非常密切的关系。如果**“传回一个左值,代表p所指之物"是可能的 ,那么"传回一个左值,令他代表p所指之物的地址”**也一定可以。也就是说可以传回一个pointer指向迭代器所指之物。
  5. iterator_category: 这是指的代码地迭代器的分类,
    1:input iterator:这种迭代器所指的对象不允许外界改变,只读(readonly)
    2: output iterator: 只写(write only)
    3: Forward iterator :允许“写入型”算法(列如:replace())在此种迭代器所形成的区间上进行读写。
    4:Bidrectional iterator: 可双向移动,某些算法需要逆向走访某个迭代器区间(双向)
    5: Random Access Iterator:前四种迭代器都只供应一部分指针算术能力(前三种支持operator++,第四种再加上operator–)第五种则涵盖所有指针算术能力(p+n,p-n,p[n],p1-p2,p1<p2);

当然记住这么多会很麻烦,所以STL规定迭代器必须继承,iterator class这个类。
在这里插入图片描述

25.五种iterator category的关系:
在这里插入图片描述
26.不同的迭代器category对算法有很大影响:
在这里插入图片描述
首先根据萃取出来的迭代器类型,生成临时对象,并作为参数传入被多次重载的函数,由编译器确定具体的算法。

27.仿函数():就是结构体内部重载()从而就可以达到**“类名+()”**的效果,和函数调用类似。
在这里插入图片描述

但是如果只写仿函数就可以融入STL中吗? 不!还差一点,因为就像咱们前面提到的一样,适配器会向functors问问题,也就是必须定义相应的类型。
而且 STL规定 每个AdaptableFunction 都应该选适当者继承之,因为Function adapter会询问一些问题(也就是定一些特定的类型,因为可能会 typedef typename T::val val,所以T中就必须得有val这种类型)。
在这里插入图片描述
28.适配器:
有容器适配器,迭代器适配器,仿函数适配器,
容器适配器:stack,queue(上面提到,可以底层是链表或其他容器)
博主认为,所谓适配器就是,将一个已有的东西改成一个更具体功能的东西,比如一个less()这个仿函数,那么你就可以,通过bind(less(),40),将less修改成一个bind,使比大小变成绑定的第二个。

29.explicit关键字是为了将构造函数取消隐式转换,只能用已经定义的构造函数。此处附一个大佬的连接:
大佬博客

30.X适配器:
istream_iterator:
在这里插入图片描述
博主理解就是,将cin对象用一个指针(in_stream指针,它和cin是一种类型)进行保留,然后重载对应的操作符即可。

任意参数的函数模板

template<typename... type>//c++2.0,允许传入任何个数的参数模板

c++11新特性
32. 一个很有意思的万能哈西:
在这里插入图片描述
注意这里的调用关系,这里会将参数逐个减少,然后最后只有两个参数的时候调用3达到终点(类似于递归调用,但是由于函数模板不能判断参数的个数,只能依靠最佳适配,所以必须得提供一个两个参数的函数,保证调用的正确性)。
妙啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!

31.c++11偏特化疑惑:

template<typename T,typename...Typ>
class fa {};

//template< typename...Typ>
//class fa<T *>{}; //个数偏特化编译出错,

//template< typename T,typename...Typ>
//class fa<T *>{}; //范围偏特化编译出错,

template<typename...Typ >
class fa{
public:
	int a;
};

当使用 c++11的<template…typ>时,会导致偏特化有误。百度了一下也没有理解所以然。留个坑

32.c++11 tuple元组:
以下是一些测试代码:

#include <stdio.h>
#include <iostream>
#include<vector>
#include<tuple>
#include<string>
using namespace std;
int main()
{
	
	cout << sizeof(string) << endl;
	tuple<int, int, string>t1(41, 42, "fasdg");//定义一个tuple对象
	cout << sizeof(t1) << endl;

	auto t2 = make_tuple(1, 2, "fa");//利用 auto定义一个tuple对象。
	cout <<"第二个" << get<2>(t2) << endl;//从tuple取值,<>中的0,1,2类似于数组下标
	cout << sizeof(t2) << endl;

	int a ;
	int b;
	string c = "fas";
	cout << "prec:"<<c<<endl;
	tie(a, b,c) = t2;//从tuple中取值
	cout << "c: " <<c<< endl;

	system("pause");
	return 0;
}


在这里插入图片描述
注意tail函数会因为强转把第一个值给省去。
而且这里利用父类继承,会将数据逐个省去让父类少一个参数,最后总调用空的偏特化版本。

但博主利用vs2017测试并不好使(ORZ):
在这里插入图片描述

template<typename T,typename...Typ >
class fa<T,Typ...> :public fa<Typ...> {
public:
	int a;
};
static_cast<int>(x);//c++中强转 
(int)x;//c中强转

34.cout:
为什么多种类型都可以用cout输出:是因为cout内部对<<进行重载,而且对<<进行多次重载,达到到多种类型都适配的效果。
如果类内部需要达到用cout输出效果,那么就需要类内部对其进行重载:
在这里插入图片描述
34. hashtable中cuount和find的区别:
根据STL源码分析267页,可以得知底层,二者都是先确定bucket,然后对该链表进行遍历,但是find是当它找到该值直接返回,而count函数刚开始定义一个result=0,然后将整个链表全部遍历,每当有一个相同就进行result++,然后将result返回,但是由于,unordered_map不允许重复,所以reult只能是1或0,所以当只判定是否存在时,用find要比count快(博主试验过)。
可能会有很多人对count这样的设计很奇怪,因为既然只有一个那找到后直接返回不得了,但是我们细想一下,count这个单词的本身意义就是计数或者统计,所以遍历一遍也不是没道理(狗头)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值