公众号:
声明:个人所写所有博客均为自己在学习中的记录与感想,或为在学习中总结他人学习成果,但因本人才疏学浅,如果大家在阅读过程中发现错误,欢迎大家指正。
学习LwIP网络协议过程中,内存方面用到了很多链表知识,下一博客打算写关于LwIP中的链表
一、关于顺序存储和链式存储
用数组存储数据元素,数组元素与数据元素之间的关系是顺序映射。这种存储可以概括为:数据对象以结点为单位,连续且等距的存放在一块存储区域。
(1)如果线性表的元素aij存储在Lij位置,那么a1,j+1存储在Li,j+1位置;
(2)如果对列中的第i个数据元素位于其循环存储的表示的位置Li,那么队列的第i+1个数据元素存储在(Li+1)%n的位置;
(3)如果栈顶结点位于LT,那么栈顶之下的节点位置是LT-1;
顺序存储机制缺失可以满足一些操作,如存取线性表中任意结点、入栈、出栈、入列,然而,基于顺序存储的其他操作,如在有序表中插入、删除,可能造成效率低。
采用链式表示可以完全克服效率等低下的缺陷,无论是向表中插入或从表中删除,操作过程都不需要移动数据。与顺序表示方法不同,链表表示的节点不再是连续且等距地存放在一块存储区域,相反,数据项可以存放在存储区的任何位置,存储区中数据元素的顺序无需保持表中元素的顺序,但存储区中的每个结点必须包括下一个结点的存储位置信息。因此,链式表示除存储每个结点的数据项之外,还要设置一个指针域或者链域,用来存放下一个结点的位置。采用链式表示的线性表称为链表,,一般而言,链表中的结点包含零个或多个数据项域,还要附加一个以上的指针域。
指针
C语言提供了对指针强大的支持。实际上,在C语言中,对于任意类型T,都有相应的指针类型,指针类型的取值是一个内存地址。指针类型使用的最重要的两个运算符是:
& 取地址运算符
* 复引用(或间址)运算符
使用指针能够获得更高程度的灵活性和效率。但是,指针也是不安全的。当用C语言进行程序设计时,把所有不指向实际对象的指针设为NULL是一种明智的做法。这样可以减少师徒访问程序范围以外的内存区域,或者没有合法指针指向的区域的可能性。有些机器允许复引用空指针,并且结果为NULL,使得程序得以继续执行。而在另一些机器上,无论0地址上的结果是什么,都产生一个严重错误。
另一个明智的程序设计技巧是在进行指针类型之间互相转换时,显式地使用强制类型转换(type cast)。例如:
pi = malloc(sizeof(int)); /* assign to pi a pointer to int */
pf = (float *)pi; /* casts an int pointer to a float pointer */
另一个值得注意的方面是,在许多系统中,指针的大小和整形一样,因为整形是默认类型说明符,所以,有些程序员在定义函数时,常常省略返回类型,返回类型的默认类型为整形,而后可能当成指针来使用。在某些机器上是一种危险的做法,所以建议应该使用显示定义函数的返回类型。
二、单向链表
1、一个简单的单词链表:
首先定义链表的结点结构,声明各成员域类型,结构中应该含有一个字符数组,以及一个指向下一个结点的指针。
typedef struct listNode *listPointer;
struct listNode {
char data[4];
listPointer link;
};
以上程序包含自引用结构。应注意,指针listPointer定义出现在结构体struct listNode之前,C语言允许一个指针类型的定义出现在该类型定义之前。
结点结构声明之后,马上可以创建一个空表,语句是:
listPointer first = NULL;
就是说,创建的新表称为first。则first中存放的是这个表的起始存储地址。这个新表是空表,现在first的内容是0,表的初始化完成。NULL是C语言的保留字,其值就是0,用于条件判断语句。下面是判断空表的宏定义:
#define IS_EMPTY(first) (!(first))
2、两结点链表:
一个简单的两结点链表:
typedef struct listNode *listPointer;
struct listNode{
int data;
listPointer link;
};
创建一个两结点链表:
如何创建一个两结点链表呢?第一个结点的数据域是10,第二个结点的数据域是20.变量first指向第一个结点,second指向第二个结点。第一个结点的链域指向第二个结点,第二个结点的链域是NULL。并返回变量first,指向表头。
listPointer create2()
{ /* create a linked list with two nodes */
listPointer first, second;
MALLOC(first, sizeof(*first));
MALLOC(second, sizeof(*second));
second->link = NULL;
second->data = 20;
first->data = 10;
first->link = second;
return first;
}
链表插入结点:
在链表中任意位置x之后插入一个结点,结点的数据域是50。函数的参量有两个,first指向表头,如果first为空指针,则返回他的值变成指向数据域为50的指针,由于这个指针的内容可能改变,必须传这个指针的地址,形参声明为listPointer *first。第二个参量值x不会改变,因此不用传它的地址。函数调用语句为 insert(&first, x) ,first是表头,x指向插入的位置。
void insert(listPointer *first, listPointer x)
{ /* insert a new node with data = 50 into the chain first after node x */
listPointer temp;
MALLOC(temp, sizeof(*temp));
temp->data = 50;
if(*first) {
temp->link = x->link;
x->link = temp;
} else {
temp->link = NULL;
*first = temp;
}
}
insert函数在插入时要区分表是否为空。如果是空表,要先置temp为NULL,然后让first指向temp的地址。如果为非空表,则在x与其后继结点之间插入temp指向的结点。
删除链表结点:
在链表中删除给定结点比插入结点麻烦一些,删除结点要考虑结点所在位置。删除操作涉及三个指针。假定first指向表头,x指向待删结点,trail指向x的直接前驱结点。delete要把删除结点的存储空间返还给系统,所以调用了free。
void delete(listPointer *first, listPointer trail, listPointer x)
{ /* delete x from the list, trail is the preceding node and *first is the front of the list */
if(trail)
trail->link = x->link;
else
*first = (*first)->link;
free(x);
}
打印链表:
void printList(listPointer first)
{
printf("The list contains: ")
for( ; first; first = first->link)
printf("%4d", first->data);
printf("\n");
}