C语言虽然没有泛型,但是拥有对内存的终极解释权限,这个也带给了C语言强大的表达能力。对于复杂数据结构,侵入式链表绝对是一个好的经典实现,Linux Kernel 在 2.1 之后就使用 list_head 的内嵌来实现双向循环链表。而且,glibc也提供了这种实现的标准库 glibc/include/list.h。
Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
– Linus
对于C语言项目,有时候会需要精细规划内存使用。这时设计数据结构时,通常都是存储和控制结构分离。先规划存储,然后规划各种逻辑关系、查找结构。这时候,Data结构里面就会经常的用到侵入式链表。一个Data块被多个链表使用,这种结构在Linux内核、Redis、Nginx都是非常常见。内嵌链表的实现原理也很简单,结构体描述的是一块连续内存的内存布局,知道结构体一个成员的地址,那么就可以偏移到结构体的首地址,标准库里就有offsetof 宏。
举一个例子,一组数据,里面嵌两个链表,一组为正数链表,一组为负数链表。那么就可以在Data中嵌两个链表进去,实现如下:
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
typedef struct list_head {
struct list_head *next;
struct list_head *prev;
} list_t;
/* Define a variable with the head and tail of the list. */
#define LIST_HEAD(name) \
list_t name = { &(name), &(name) }
/* Initialize a new list head. */
#define INIT_LIST_HEAD(ptr) \
(ptr)->next = (ptr)->prev = (ptr)
/* Add new element at the head of the list. */
void list_add(list_t *newp, list_t *head) {
newp->next = head->next;
newp->prev = head;
head->next->prev = newp;
head->next = newp;
}
/* Remove element from list. */
void list_del(list_t *elem) {
elem->next->prev = elem->prev;
elem->prev->next = elem->next;
}
/* Get typed element from list at a given position. */
#define list_entry(ptr, type, member) \
((type *) ((char *) (ptr) - (unsigned long) (&((type *) 0)->member)))
/* Iterate forward over the elements of the list. */
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
typedef struct tagData {
int val;
list_t positive;
list_t negative;
} Data;
int main() {
// 存储Data节点
Data data[10];
memset(data, 0, sizeof(data));
size_t i = 0, len = sizeof(data) / sizeof(data[0]);
LIST_HEAD(positive);
LIST_HEAD(negative);
INIT_LIST_HEAD(&positive);
INIT_LIST_HEAD(&negative);
// 随机初始化Data的val
for (i = 0; i < len; i++) {
data[i].val = (int)random() % 10 - 5;
if (data[i].val > 0) {
list_add(&data[i].positive, &positive);
}
if (data[i].val < 0) {
list_add(&data[i].negative, &negative);
}
}
// print 正数链和负数链
list_t *pos;
printf("positive number: ");
list_for_each(pos, &positive) {
Data *d = list_entry(pos, Data, positive);
printf("%d ", d->val);
}
printf("\n");
printf("negative number: ");
list_for_each(pos, &negative) {
Data *d = list_entry(pos, Data, negative);
printf("%d ", d->val);
}
}
好的数据结构设计可以让程序清晰明了。数据结构设计的越完善,代码流程往往也就越清晰简洁。