Spiceserver中链表的使用

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的地址,而这两个宏的作用就是通过成员变量的地址,反推结构体的地址,知道了结构体的地址我们就能访问结构体的所有成员了。

时间: 2024-12-17 11:19:18

Spiceserver中链表的使用的相关文章

linux 内核 中链表list

这个结构从list.h 移到了types.h, 可见内核对循环链表的重视 include/linux/types.h中定义 struct list_head {        struct list_head *next, *prev;}; include/linux/list.h 中的宏 初始化 一个叫name的链表节点 #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \        s

(转)为什么HashMap中链表长度超过8会转换成红黑树

原博地址:https://blog.csdn.net/xingfei_work/article/details/79637878 HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树:若桶中元素小于等于6时,树结构还原成链表形式. 原因: 红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要:链表长度如果是小于等于6,6/2=3,虽然速度也很

为什么HashMap中链表长度超过8会转换成红黑树

HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树:若桶中元素小于等于6时,树结构还原成链表形式. 原因: 红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要:链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短. 还有选择6和8的原因是: 中间有个差值7可以防止链表和树之间频繁的转换.假设一下

Linux内核中链表的学习

一.自己学习链表 数组的缺点:(1)数据类型一致:(2)数组的长度事先定好,不能灵活更改. 从而引入了链表来解决数组的这些缺点:(1)结构体解决多数据类型(2)链表的组合使得链表的长度可以灵活设置. 基本概念: 头结点: 这个节点是为了便于管理链表的节点,这个节点并不保存数据:虽然和其他节点一样,但是这个头结点是指向首节点的节点. 首节点: 第一个保存有效数据的节. 尾节点: 最后一个保存有效数据的节点 头指针: 头指针是指向头节点的指针. 单链表: 链表节点的数据结构定义: typedef s

驱动中链表的使用

链表是驱动开发中经常遇到的一个数据结构,主要是双向循环链表:要使用链表,需要用到一个LIST_ENTRY的结构,其定义如下: typedef struct _LIST_ENTRY {    struct _LIST_ENTRY  *Flink;    // 指向下一个节点(后继)    struct _LIST_ENTRY  *Blink;    // 指向前一个节点(前驱)} LIST_ENTRY, *PLIST_ENTRY; 在实际的编程中,我们需要自己定义链表的节点,并把节点的第一个成员设

C语言中链表怎么删除结点?

第一个方法: /*根据姓名删除链表的中的学生记录*/ void deleteByName(struct STUDENT * head) { struct STUDENT *p,*q; char name[20]; if(head==NULL) { printf("链表为空.\n"); return; } printf("请输入要删除的学生的姓名:"); scanf("%s",name); for(p=head->next,q=head;p!

关于leetcode中链表操作的的几道题。

1.Remove Nth Node From End of List 第一道题就是经典的只扫描一遍链表就删除倒数第n个节点的链表操作. 思路就是,用两个指针,第一个指针先走n步,然后第二个指针开始,当第一个指针走到最后,那么第二个指针会走到倒数第n+1个点. 但是这里有几个边界需要考虑: 第一个就是,可能删除的是第一个节点,即"正数第一个",这个时候需要判断,判断的方法是第一个指针已经为空了. 当然,OJ上没有处理的是,可能删除的倒数第n个点超过了链表总的长度,或者n是负值等. /*-

C语言中链表任意位置怎么插入数据?然后写入文件中?

链表插入示意图:(图是个人所画)因为链表指针指来指去,难以理解,所以辅助画图更加方便. 定义的结构体: struct student { char ID[11]; //学生学号 char name[20]; //学生姓名 struct student *next; //next 指针 指向 struct student 类型的变量 }stu; 看我写的代码,代码中有详细解释: /*************** 函数功能: 插入出勤学生 返回:指向链表表头的指针 /***************/

C语言中链表节点的实现,以及如何实现泛型

1.C语言中的struct是纯粹的结构体,没有访问权限的概念 2.C语言中用void* 来实现泛型编程,也是C++类和模板底层实现的基础,就是用void*来实现的 #include<stdio.h> //struct LinkNode //{ // int num;//数据域 // struct LinkNode* pnext;//C语言中struct仅仅是结构体,所有成员都是公有的,没有访问权限的概念,且不能省略struct关键字 //}; struct LinkNode { void* p