STL第一讲
由于这一讲解比较零碎,我记录的也比较的零碎:
标准库和标准模板模板的概念,前者包含了后者,前者还包括了一些零碎的函数库。
这一讲主要讲解了我们STL之间的关系:
STL常见的6大部件就是容器,适配器,分配器,算法,迭代器,仿函数;
解释一下我们其中关系,我们的容器内存管理是由我们的分配器完成,并不需要我们自己进行内存的管理,而其实我们可以跨过容器直接对分配器进行操作,但是其实没有这种必要的,因为我们在利用分配器分配器进行分配操作之后,释放内存需要我们呢记住分配好的内存大小,我们对比一下malloc和free函数free需要我们记住malloc的大小吗?所以这种直接使用不利用内存的管理。
而我们的算法给了我们一些操作容器的手段,那么这些手段操作都需要落实在我们容器上面的每一个元素上面,这就是那么就有了我们呢迭代器的产生。而适配器的作用是将我们输入的东西,通过包装来以便能够顺利的被我们的容器所使用。
首先明白我们有时候在全局有一个算法函数,一般借助::调用,而在容器自己内部也有一个算法函数,一般用用法为st.find(),优先使用我们的自己所带的函数。
deque是一个双向队列,其内部实现的结构如下所所示:
其也是我们stack和queue这两种容器的实现方法,deque包含了其所有的操作方法,而且由于这两种容器特殊的结构,所以不会提供迭代器来进行内部元素的访问,以免产生对结构的破坏;
那么接下来聊一聊我们的hashtable,在hash进行篮子分配的时候,我们的篮子数量一定会大于元素的数量,而当元素的数量到达一定的程度的时候,我们就寻找双倍的篮子大小,那以前篮子的值重新打碎放进篮子里面;还有就是明白 map可以使用[]重载操作符,而mutimap就不可以使用[]重载操作符;
第二讲
OOP:将data和methods放在一起
GP:将data和methods分开来。这样容器和算法的团队就可以各自操作,期间用迭代器来进行联通就可以了。
那么就可以解释一下为什么List为什么不可以使用算法里面sort而是要自己内部写一个sort,就是因为算法团队中使用了迭代器的+5,-4这种连续跳跃操作,而这种操作只用关联容器才可以使用,所以list就没有办法利用迭代器使用算法sort,而是自己在容器里面写了一个。
理解一下什么是泛化什么是特化,和偏特化;特化标志(tempalte<>);
举个例子:
template <class type>
struct __type_traits{
typedef __true_type this_dummy_member_must_be_first;
typedef __false_type has_trivial_default_constructor;
}
-----上面就是模板
template<> struct __type_traits<int>{
typedef __true_type has_trivial_default_constructor;
typedef __ture_type has_trivial_copy_constructor;
}
------上面就是特化 特化成为一个int的版本
特化有什么好处?编译器及时可以根据具体的类型去做更好更精确的事情,比如大家调用这个函数都是输出自己的类型,而我就是想给int搞特殊,让他输出“hello world”;
偏特化其实就是局部特化,并不是所有的参数都是特化;偏特化还可以分为数量的偏特化和范围的偏特化;比如传入的参数个数和参数的类型
泛化
template <class T,class Alloc=alloc>
class vector{
...
};
特化:
template <class Alloc=alloc>
class vector<bool,Alloc>{
...
};
那么以上就是在我们调用vector的时候,在调用bool容器的时候进行一些特殊的操作,但是分配器还是不变的,可以随意传入一种分配器类型;
类型导致的偏特化:
template <class Iterator>
struct iterator_traits{
typedef typename iterator::iterator_category iterator_category;
typedef typename iterator::value_type value_type;
}
下面就是偏特化的版本,只要我们传入的一种参数的指针,那么就进行下面的一种惭怍,而不是上面的那种
template <class T>
struct iterator_traits<T*>{
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
}
理解了上面的一些部分我们开始进行源代码剖析:
分配器allocators:头文件
我们分析一下分配器的源代码会发现,其实allocator是调用了operator new的函数,而继续追根朔源,就会发现其实operator new函数内部就是调用了malloc。那么我们呢来研究一下malloc函数,
当我们调用了malloc之后,会发现返回的空间比我们申请的空间还要多,其中包括了字节对齐和头部尾部记录分配大小所占用的空间。
而当我们调用了deallocate的时候,调用了operator delete()—>free来释放空间,其实并没有任何其它特殊的设计。但是以上分配器都是很烂的,因为其实当我们利用容器分配内存的的时候都是很小的,比如一个int,然后malloc返回的时候,会导致很多额外空间浪费。比如光说100个int,就会浪费800个字节的头部尾部记录空间大小。-------所以说以上的分配器都很烂----
然而在2.9版本alloc 一种特殊的分配器,其可以很好的解决这个问题:我们思考一下容器是否需要进行分配空间大小的记录吗,我们容器里面的类型都是一致的,所以alloc就以这种一大块的方式来进行解决,当我们呢寻求一块大小的时候,我们直接挂在我们的编号下面对应的大小就可以了。
然而到了4.9版本,alloc函数改成了__pool_alloc函数,alloc分配器取消了。这么好的分配器怎么就取消了呢?
迭代器的设计原则和ioterator trails;
trials就是认为设计的一种萃取机
我们知道,迭代器作为我们算法和容器之间的桥梁,算法需要冲迭代器中获取相应的信息;这就是为什么我们呢迭代器设计中一定有五个固定的typdef:
为啥说迭代器是一种泛化的指针?就是因为迭代器在指针的基础上,能够在容器之间完成指针相应的功能,这就要求迭代器能够“智能”,如果list的迭代器++不是只是的单纯的完成指针意义上的++,而是List节点内部找到next并进行指针转移。此外迭代器并且能够提供其他信息以供给算法使用;
那么算法是怎样获得这些信息呢?
其实过程就是算法通过向迭代器进行提问,迭代器完成回答,但是有时候我们的迭代器退化成了指针,指针没有的我们之前所说的那五个typedef,那么算法怎样完成提问呢?也很简单,这时候用一个计算解决问题的常用思想,给其加一个中间层,就是间接提问,这时候的我们萃取机也就出场了:
萃取机通过我们偏特化的效果,来进行实际的区分我们指针和迭代器:
这里的迭代器萃取机只是萃取机中的其中一种,以后还会遇见更多;