spiceserver代码中很多地方都用到了链表,刚开始看代码的时候会因为这些链表,或者有关这些链表的宏而感到困惑,造成阅读上的困难,其实这些代码我们可以单独将其提取出来,写一些demo进行验证和测试,这样就能很快对这些链表机制进行熟悉,我们可以用windows上的vs或者linux上的gcc进行编译,本次我将使用VS2013来开发。
常规链表
我们在学C语言的时候会学到链表,比如我们要写一个管理学生成绩的链表,我们就可以把数据结构定义成:
typedef struct Student
{
char szName[16];/* 姓名 */
char szNo[16];/* 学号 */
int nScore;/* 成绩 */
Student *next;
}Student;
如果我们要管理张三、李四、王五、赵六四个学生的成绩,我们可以在初始化的时候直接把这个链表建好,实现如下:
void AppendList(Student *pHead, const char *szName, const char *szNo, int nScore)
{
Student *pStudent = pHead;
while (pStudent != NULL && pStudent->next != NULL){
pStudent = pStudent->next;
}
/* 增加节点 */
Student *pItem = (Student *)calloc(1, sizeof(Student));
if (pItem == NULL){
return;
}
strcpy_s(pItem->szName, szName);
strcpy_s(pItem->szNo, szNo);
pItem->nScore = nScore;
pStudent->next = pItem;
pItem->next = NULL;
}
Student *InitList()
{
Student *pHead = (Student *)calloc(1, sizeof(Student));/* 链表头 */
if (pHead == NULL){
return NULL;
}
AppendList(pHead, "张三", "0001", 90);
AppendList(pHead, "李四", "0002", 88);
AppendList(pHead, "王五", "0003", 92);
AppendList(pHead, "赵六", "0004", 87);
return pHead;
}
我们要遍历打印所有学生信息,实现为:
void PrintList(Student *pHead)
{
Student *pStudent = pHead->next;
while (pStudent != NULL){
printf("name:%s No.:%s score:%d\n", pStudent->szName, pStudent->szNo, pStudent->nScore);
pStudent = pStudent->next;
}
}
最后是清理链表,释放内存,实现如下:
void DestroyList(Student *pHead)
{
Student *pStudent = pHead;
Student *pTemp;
while (pStudent != NULL){
pTemp = pStudent;
pStudent = pStudent->next;
free(pTemp);
}
}
我们在主函数调用一般是这样的:
int _tmain(int argc, _TCHAR* argv[])
{
Student *pHead = InitList();
if (pHead){
PrintList(pHead);
DestroyList(pHead);
}
return 0;
}
这是一个比较常规的链表,我这里写的比较简单,还有诸如插入、移除等操作我没有写,因为这不是我讲的重点。
Spiceserver链表
Spiceserver中的链表不是采用以上的方式,如果我用它提供的链表来实现上述需求,那么Student的结构就应该定义为:
typedef struct Student
{
char szName[16];/* 姓名 */
char szNo[16];/* 学号 */
int nScore;/* 成绩 */
Ring item;
}Student;
我们看到了节点的连接是通过Ring这个结构体,它的定义很简单,在Spiceserver源码中有一个Ring.h的文件,我们可以把这个文件拷到我们的工程中,删除掉一些影响我们编译的东西,如spice_assert之类的,对功能不会造成什么影响,Ring的定义如下:
typedef struct Ring RingItem;
typedef struct Ring {
RingItem *prev;
RingItem *next;
} Ring;
我把上述代码用这种方式重新写一遍:
// Containerof.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdlib.h>
#include <string.h>
#include "ring.h"
#define SPICE_OFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define SPICE_CONTAINEROF(ptr, type, member) (type *)( (char *)ptr - SPICE_OFFSETOF(type,member) )
typedef struct Student
{
char szName[16];/* 姓名 */
char szNo[16];/* 学号 */
int nScore;/* 成绩 */
Ring item;
}Student;
void AppendList(Student *pHead, const char *szName, const char *szNo, int nScore)
{
Student *pItem = (Student *)calloc(1, sizeof(Student));
if (pItem == NULL){
return;
}
strcpy_s(pItem->szName, szName);
strcpy_s(pItem->szNo, szNo);
pItem->nScore = nScore;
ring_item_init(&pItem->item);
if (ring_is_empty(&pHead->item)){
/* 如果是空链表,直接加在后面,因为ring_get_tail会返回NULL */
ring_add(&pHead->item, &pItem->item);
}
else{
Ring *pTailItem = ring_get_tail(&pHead->item);
ring_add(pTailItem, &pItem->item);
}
}
Student *InitList()
{
Student *pHead = (Student *)calloc(1, sizeof(Student));
if (pHead == NULL){
return NULL;
}
strcpy_s(pHead->szName, "head");
ring_init(&pHead->item);/* 初始化环 */
AppendList(pHead, "张三", "0001", 90);
AppendList(pHead, "李四", "0002", 88);
AppendList(pHead, "王五", "0003", 92);
AppendList(pHead, "赵六", "0004", 87);
return pHead;
}
void PrintList(Student *pHead)
{
RingItem *link;
//RING_FOREACH_REVERSED(link, &pHead->item){/* 逆序遍历 */
RING_FOREACH(link, &pHead->item){
Student *pStudent = SPICE_CONTAINEROF(link, Student, item);
printf("name:%s No.:%s score:%d\n", pStudent->szName, pStudent->szNo, pStudent->nScore);
}
}
void DestroyList(Student *pHead)
{
RingItem *link;
RingItem *next;
/* 这里不能使用RING_FOREACH,因为会释放,需要提前记录next值,如果用RING_FOREACH会崩溃 */
RING_FOREACH_SAFE(link, next, &pHead->item){
Student *pStudent = SPICE_CONTAINEROF(link, Student, item);/* 通过结构体成员变量的地址,获取结构体首地址 */
printf("Destroy %s\n", pStudent->szName);
ring_remove(link);
free(pStudent);
}
free(pHead);/* RING_FOREACH_SAFE遍历无法遍历到头结点 所以要单独释放 */
}
int _tmain(int argc, _TCHAR* argv[])
{
Student *pHead = InitList();
if (pHead){
PrintList(pHead);
DestroyList(pHead);
}
return 0;
}
上述代码有用到2个宏,这算是阅读代码最困难的地方吧,两个宏的定义:
#define SPICE_OFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define SPICE_CONTAINEROF(ptr, type, member) (type *)( (char *)ptr - SPICE_OFFSETOF(type,member) )
我们通过上一个结构体可以访问下一个结构体的主要原因是上一个结构体记住了下一个结构体成员item的地址,而这两个宏的作用就是通过成员变量的地址,反推结构体的地址,知道了结构体的地址我们就能访问结构体的所有成员了。