在第一次的博客中给大家说明了STL源码实现分为好几种,而且各家的实现方法有许多区别,作为初学者的我们,还是要挑软柿子捏,我们从相对来说比较容易理解的PJ版本开始分析,PJ版本一般在VC6.0中可以找到,我在上次的博客中也已经上传,同时还有两本相关的书籍,也都已经上传。
在开始分析源代码之前,我们应该先对STL的组成有一个大概的认识,STL中主要包括六大组件,分别是容器(containers),算法(algorithms),迭代器(iterators),仿函数(functors),空间配置器(allocators),配接器(adapters).
1.容器:保存着各种数据结构,如vector, list, deque, set, map....
2.算法:常用算法,如sort,search,copy,erase.... 算法主要是对容器中保存的数据进行处理。
3.迭代器:是容器和算法之间的胶合剂,就是所谓的“范型指针”,用来访问容器中的元素。
4.仿函数:从实现的角度来说,即是在某一个类中对小括号进行重载,使其对象可以调用小括号,行为类似于函数。
5.空间配置器:负责空间配置和管理,从实现的角度来看,配置器是一个实现了动态空间配置,空间管理,空间释放的class template。
6.配接器:这个我们暂时不会用到,等用到的时候,我们再讨论。
估计大家在看了上面的介绍之后,还是对STL没有什么概念,甚至还可能萌生退意。其实我觉得,上面的STL的六个组件虽然说的很玄乎,但其实只要你以前稍微写过一些链表的基本操作的函数,那对于你来说,STL对于你来说已经不是什么难事了,因为万变不离其宗,不管它的语法多么花哨,名字多么专业,只要我们肯伏下身子,去啃,那没有什么弄不懂的。
接下来,我们就开始分析list.h这个头文件,也即就是STL中的链表,首先需要说明的是,STL中的链表是一个双向循环链表,先给大家一个图来感性的认识一下list。
图画的不好,但是能说明问题就可以了。list的基本结构就如图所示。这里有一个关于命名的小知识,就是一般变量名字前面带下划线的,如_Prev,都说明该变量是一个内部变量,这只是一个大家都墨守成规的,并没有什么强制性要求。
了解了list的大概结构,我们就开始看源代码,当然,如果一开始大家就拿着源代码啃,那肯定是难于上青天,而且效率也满,并不推荐,因为我们也不可能一次性就能把整个list全部分析完,所以今天的任务就是我们先可以创建出一个list,下来,我先贴出我们今天要完成的代码。
#pragma once
#include <malloc.h>
template <class _Type>
class list
{
public :
list() : _Head(_Buynode()), _Size(0){};
protected :
typedef size_t size_type;
struct _Node;
typedef struct _Node* _Nodeptr;
struct _Node
{
_Nodeptr _Prev, _Next;
_Type _Value;
}; //链表的结点
struct _Acc
{
typedef struct _Node*& _Nodepref;
typedef _Type& _Vref;
static _Nodepref _Next(_Nodeptr _P)
{return ((_Nodepref)(*_P)._Next);}
static _Nodepref _Prev(_Nodeptr _P)
{return ((_Nodepref)(*_P)._Prev);}
static _Vref _Value(_Nodeptr _P)
{return ((_Vref)(*_P)._Value);}
};
_Nodeptr _Buynode(_Nodeptr _Next = 0, _Nodeptr _Prev = 0)
{
_Nodeptr _S = (_Nodeptr)malloc(sizeof(_Node));
if(_S == NULL)
return NULL;
_Acc :: _Next(_S) = _Next != 0 ? _Next : _S;
_Acc :: _Prev(_S) = _Prev != 0 ? _Prev : _S;
return _S;
}
_Nodeptr _Head;
size_type _Size;
};
这段代码主要由三部分构成,第一个是结构体_Node,这是构成链表的重要元素,相信这个不用说,第二个是_Acc,这个类的功能就是返回结点的下一个结点,前一个结点,和当前结点的值,最后一个重要的部分就是_Buynode函数,这个函数的功能就是创建一个结点。我们下来会比较详细的对这三个部分进行说明。
1.结点_Node 要创建一个链表,肯定要先有一个结点,链表是由结点构成的,每个结点有三个部分,分别是它的前驱,后继,和值,这和我们平时数据结构里面链表的实现没有任何区别。
2._Acc类,很多人看了这个类,可能会很困惑,为什么要有这个类,这个类的功能就是指定一个结点返回它的前驱,侯继,和值,这些功能上面的_Node结构体完全可以实现阿,你有这些困惑,完全时很正常的,其实,我也很纳闷,这样写代码,确实可以提高美观,但是,难道就仅仅是这个功能吗,这可能就是大牛之间的高明之处吧,不过,如果硬要给个理由的话,我觉得,这可能正体现了一个伟大的设计哲学,就是一段程序只干一件事,大牛们,可能觉得结点结构体的功能就是创建结点,这种操作还是应该交给一个具体的函数来做。
接下来,大家可以看到,_Acc类的每一个成员函数都是static类型的,这是因为我们不需要创建一个_Acc类的对象,就可以调用它的方法。至于函数体里面的内容很简单,就是完成自己相应的功能,然后,返回一个结点指针引用,之所以返回引用,是因为这个结点指针被返回回去,它的值是可以被我们进行修改的。
3._Buynode函数,顾名思义,这个函数的功能就是买一个结点,即就是创建一个你自定义的结点,这个函数有两个参数,一个是你要创建结点的前驱结点,一个是后继结点,里面的实现很有意思,是一个三元表达式,只要大家仔细看,相信大家肯定可以看懂,这个函数的功能相当于是,你可以创建一个结点,这个结点的前驱和后继指针都是你自己可以自定义的,如果你不提供定义,这个函数则默认创建一个前驱和后继都指向自己的结点。
这三个部分明白之后,这段代码也就不是多难了。另外,给大家提供一个主函数来测试自己的代码是否达到了功能,可以用debug来查看自己创建的链表是否成功。
#include "list.h"
int main()
{
list<int> my_list;
return 0;
}