如何编写一个通用的链表
编写通用链表是一项复杂的任务,不可能在这一节中把它阐述清楚,这里我们先考虑三个问题。
存值还是存指针
通用链表首先要做到能够存放任何数据类型的数据,新手常见的做法是定义一个抽象数据类型,需要存放什么,就
定义成什么。如:
typedef int Type;
typedef struct _DListNode
{
struct _DListNode* prev;
struct _DListNode* next;
Type data;
}DListNode;
这样的链表算不上是通用的,因为你存放整数时编译一次,存放字符串时,重新定义Type再编译一次,存放其他类
型同样要重复这个过程。麻烦不说,关键是没有办法同时使用多个数据类型。我们要找到一种同时可以表示不同数据类
型的类型才行,有人说可以用union,但是数据类型是无穷无尽的,不可能在 union中表示它们的全部。
可行的办法有两种。
? 存值
typedef struct _DListNode
{
struct _DListNode* prev;
struct _DListNode* next;
void* data;
size_t length;
}DListNode;
存入时复制一份数据,保存数据的指针和长度。考虑到复制数据会带来性能开销,不合符C语言的风格,而且C语言
中没有构造函数,实现深复制比较麻烦,所以在C语言中以这种方式实现的链表很少见。
? 存指针
typedef struct _DListNode
{
struct _DListNode* prev;
struct _DListNode* next;
void* data;
}DListNode;
只保存指向对象的指针,存取效率高,是C语言中常见的做法。在存放整数时,可以把void*强制转换成整数使用,
以避免内存分配(在现实中,90%以上的情况下,链表都是存放结构的)。
让C++可以调用
这不是一个和本书主旨密切相关的话题,因此在这里只是稍微提一下。C++中允许同名函数存在,所以编译器会对函
数名重新编码。当C++代码包含C语言的头文件时,重新编码的函数名如果与C语言库中的原函数名不一致,就会造成找不
到函数的情况。为了让用C语言实现的函数可以在C++中调用,需要在头文件中加点东西才行。
#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif
它表示如果在C++中调用这里的函数,编译器不能对函数名进行重新编码。
完整的接口
作为一个通用的链表,接口要比较完整才行,否则无法满足各种情况的需要(提供完整的接口并不违背最小接口原则
)。实现具有完整接口的链表不是件容易的事,在这里你只需要先实现插入删除等基本操作就行了,后面我们会一步一步地继续扩展它的功能。