例11.8写一函数建立一个有3名学生数据的单向动态链表。 
 先考虑实现此要求的算法(见图11.12)。
设3个指针变量:head、p1、p2,它们都是用来指向struct student类型数据的。先用malloc函数开辟第一个结点,并使p1、p2指向它。然后从键盘读入一个学生的数据给p1所指的第一个结点。我们约定学号不会为零,如果输入的学号为0,则表示建立链表的过程完成,该结点不应连接到链表中。先使head的值为NULL(即等于0),这是链表为“空”时的情况(即head不指向任何结点,链表中无结点),以后增加一个结点就使head指向该结点。
如果输入的p1->num不等于0,则输入的是第一个结点数据(n=1),令head=p1,即把p1的值赋给head,也就是使head也指向新开辟的结点(图11.13 )。p1所指向的新开辟的结点就成为链表中第一个结点。然后再开辟另一个结点并使p1指向它,接着输入该结点的数据(见图11.14(a))。如果输入的p1->num≠0,则应链入图11.13第2个结点(n=2),由于n≠1,则将p1的值赋给p2->next,此时p2指向第一个结点,因此执行“p2->next=p1” 就将新结点的地址赋给第一个结点的next成员,使第一个结点的next成员指向第二个结点(见图11.14(b))。接着使p2=p1,也就是使p2指向刚才建立的结点,见图11.14(c)。接着再开辟一个结点并使p1指向它,并输入该结点的数据(见图11.15(a)),在第三次循环中,由于n=3(n≠1),又将p1的值赋给p2->next,也就是将第3个结点连接到第2个结点之后,并使p2=p1,使p2指向最后一个结点(见图11.15(b))。
再开辟一个新结点,并使p1指向它,输入该结点的数据(见图11.16(a))。由于p1->num的值为0,不再执行循环,此新结点不应被连接到链表中。此时将NULL赋给p2->next,见图11.16(b)。建立链表过程至此结束,p1最后所指的结点未链入链表中,第3个结点的next成员的值为NULL,它不指向任何结点。虽然p1指向新开辟的结点,但从链表中无法找到该结点。
建立链表的函数可以如下:
#define NULL 0
#define LEN sizeof(struct student)
struct student
{long num;
float score;
struct studentnext;
};
int n; /*n为全局变量,本模块中各函数均可使用它*/
struct student *creat(void)/*定义函数。此函数带回一个指向链表头的指针*/
{struct studenthead;
struct studentp1,*p2;
n=0;
p1=p2=( struct student*) malloc(LEN); /*开辟一个新单元*/
scanf("%ld,%f",&p1->num,&p1->score);
head=NULL;
while(p1->num!=0)
 {n=n+1;
if(n==1)head=p1;
else p2->next=p1;
p2=p1;
p1=(struct student)malloc(LEN);
scanf("%ld,%f",&p1->num,&p1->score);
}
p2->next=NULL;
return(head);
}
函数首部在括弧内写void,表示本函数没有形参,不需要进行数据传递。
可以在main函数中调用creat函数:
main()
{…
creat();/*调用creat函数后建立了一个单向动态链表*/
}
调用creat函数后,函数的值是所建立的链表的第一个结点的地址(请查看return语句)。
请注意:
(1) 第1行为#define命令行,令NULL代表0,用它表示“空地址”。第2行令LEN代表struct student类型数据的长度,sizeof是“求字节数运算符”。
(2) 第9行定义一个creat函数,它是指针类型,即此函数带回一个指针值,它指向一个struct student类型数据。实际上此creat函数带回一个链表起始地址。
(3) malloc(LEN)的作用是开辟一个长度为LEN的内存区,LEN已定义为sizeof(struct student),即结构体struct student的长度。malloc带回的是不指向任何类型数据的指针(void *类型)。而p1、p2是指向struct student类型数据的指针变量,因此必须用强制类型转换的方法使指针的基类型改变为struct student类型,在malloc(LEN)之前加了“(struct student *)”,它的作用是使malloc返回的指针转换为指向struct student类型数据的指针。注意“*”号不可省略,否则变成转换成struct student类型了,而不是指针类型了。
(4) 最后一行return后面的参数是head(head已定义为指针变量,指向struct student类型数据)。因此函数返回的是head的值,也就是链表的头地址。
(5) n是结点个数。
(6) 这个算法的思路是让p1指向新开的结点,p2指向链表中最后一个结点,把p1所指的结点连接在p2所指的结点后面,用“p2->next=p1”来实现。
我们对建立链表过程做了比较详细的介绍,读者如果对建立链表的过程比较清楚的话,对下面介绍的删除和插入过程也就比较容易理解了。