//返回list中第index个数据的指针
void* At(List list, int index);
//在begin和end之间查找符合condition的第一个元素,
//比较函数由condition指向,比较的值由data指向
//当第一个参数的值小于第二个参数的值时,返回1,否则返回0
//根据condition函数的不同,可以查找第一个相等、大于或小于data的值
Iterator FindFirst(Iterator begin, Iterator end, void *data,
int (*condition)(const void*, const void*));
//查找list中第一个与data相等的元素的下标,
//equal函数,当第一个参数与第二个参数的值相等时,返回1,否则返回0
int IndexOf(List list, void *data,
int (*equal)(const void*,const void*));
//查找在begin和end之间的最小值,比较函数由less指向
//当第一个参数的值小于第二个参数的值时,返回1,否则返回0
Iterator GetMin(Iterator begin, Iterator end,
int (*less)(const void*, const void*));
//查找在begin和end之间的最大值,比较函数由large指向
//当第一个参数的值大于第二个参数的值时,返回1,否则返回0
Iterator GetMax(Iterator begin, Iterator end,
int (*large)(const void*, const void*));
//获取list的长度
int GetLength(List list);
//若list为空链表,则返回1,否则返回0
int IsEmpty(List list);
//销毁list
void DestroyList(List *list);
//获得list的首迭代器
Iterator Begin(List list);
//获得list的尾迭代器,指向最后一个元素的下一个位置
Iterator End(List list);
//使it指向下一个位置,并返回指向下一个位置后的迭代器
Iterator Next(Iterator *it);
//使it指向上一个位置,并返回指向上一个位置后的迭代器
Iterator Last(Iterator *it);
//通过迭代器it获得数据,相当于*p
void* GetData(Iterator it);
//获取当前迭代器的下一个迭代器,注意,并不改变当前迭代器
Iterator GetNext(Iterator it);
//获取当前迭代器的上一个迭代器,注意,并不改变当前迭代器
Iterator GetLast(Iterator it);
为了更加清楚地表达这个链表的结构,下图所示的,就是该链表的结构:
调用InitList函数后,链表的结构如下:

当向链表中插入一定数量的结点后,链表的结构如下:

三、如何实现隐藏链表的成员变量(即封装)
首先,我们为什么需要封装呢 我觉得封装主要有三大好处。
1)隔离变化,在程序中需要封装的通常是程序中最容易发生变化的地方,例如成员变量等,我们可以把它们封装起来,从而让它们的变化不会影响到系统的其他部分,也就是说,封装的是变化。
2)降低复杂度,因为我们把一个对象是如何实现的等细节封装起来,只留给用户一个最小依赖的接口,从而让系统变量简单明了,在一定程度降低了系统的复杂性,方便了用户的使用。
3)让用户只能按照我们设计好的接口来操作一个对象或类型,而不能自己直接对一个对象进行操作,从而减少了用户的误操作,提高了系统的稳定性。
在面向对象的设计中,如果我们想要隐藏一个类的成员变量,我们可以把这些成员变量声明为私有的,而在C语言中,我们可以怎么实现呢 其实其实现是很简单的,我们在C语言中,当我们要使用一个自己定义的类型或函数时,我们会把声明它的头文件包含(include)过来,只要我们在文件中只声明其类型是一个结构体,而把它的实现写在.c文件中即可。
在本例子中,我把struct list和struct node定义在.c文件中,而在头文件中,只声明其指针类型,即typedef struct node* Iterator和typedef struct list* List;当我们要使用该类型时,只需要在所在的文件中,include该头文件即可。因为在编译时,编译器只要知道List和Iterator是一个指针类型就能知道其所占的内存大小,也就能为其分配内存,所以能够编译成功。而又因为该头文件中并没有该类型(struct list和struct node)的定义,所以我们在使用该类型时,只能通过我们提供的接口来操作对象。例如,我们并不能使用List list; list->data等等的操作,而只能通过已定义的接口GetData来获得。
四、如何实现泛型
泛型,第一时间想起的可能是模板,但是在C语言中却没有这个东西。但是C语言中却有一个可以指向任何类型,在使用时,再根据具体的指针类型进行类型转换的指针类型,它就是void*。
为什么void*可以指向任何类型的数据 这还得从C语言对于数据类型的处理方式来说明。在C语言中,我们使用malloc等函数来申请内存,而从内存的角度来看,数据是没有类型的,它们都是一串的0或1,而程序则根据不同的类型来解释这个内存单元中的数据的意义,例如对于内存中的数据,FFFFFFFF,如果它是一个有符号整型数据,它代表的是-1,而如果它是一个无符号整型数据,它代表的则是2^32-1。进一步说,如果你用一个int的指针变量p指向该内存,则*p就是-1,如果你用unsigned int的指针p指向该内存,则*p = 2^32-1。
而我们使用malloc等函数时,也只需要说明申请的内存的大小即可,也不用说明申请的内存空间所存放的数据的类型,例如,我们申请一块内存空间来存放一个整型数据,则只需要malloc(sizeof(int)),即可,当然你完全可以把它当作一个具有4个单位的char数组来使用。所以我们可以使用void指针来指向我们申请的内存,申请内存的大小由链表中的成员data_size定义,它也是真正的data所占的内存大小。