第一次写博客,如有错误还请指正…………
今天下午倒腾了一下c语言的链表,感觉链表就是一个小型的数据库,用于简单的小程序的用户信息存储和管理还是很方便的。
下面为方便起见,以学生信息为例:
链表采用了动态分配的办法为一个结构分配内存空间。每一次分配一块空间可用来存放一个学生的数据,我们可称之为一个结点。有多少个学生就应该申请分配多少块内存空间,也就是说要建立多少个结点。当然用结构数组也可以完成上述工作,但如果预先不能准确把握学生人数,也就无法确定数组大小。而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。用动态存储的方法可以很好地解决这些问题。有一个学生就分配一个结点,无须预先确定学生的准确人数,某学生退学,可删去该结点,并释放该结点占用的存储空间。从而节约了宝贵的内存资源。另一方面,用数组的方法必须占用一块连续的内存区域。而使用动态分配时,每个结点之间可以是不连续的(结点内是连续的)。结点之间的联系可以用指针实现。 即在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员,常把它称为指针域。
可在第一个结点的指针域内存入第二个结点的首地址,在第二个结点的指针域内又存放第三个结点的首地址,如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。
下面给出一个简单的例子,包含链表的创建,
#include<stdio.h>
#include<stdlib.h>
#define LEN sizeof (struct stu)
/*
head为(伪)头指针,pbefore为指向两相邻结点的前一结点的指针变量。pafter为后一结点的指针变量。
*/
//定义学生个数
int n=4;
//定义链表结构体
typedef struct stu{
int num;
int age;
struct stu *next;
}TYPE;
/*定义删除函数:删除所选中的节点*/
void delete(TYPE *head,int number)
{
int i;
TYPE *list=head;
TYPE *tp=NULL;
//这里循环n+1次,是因为包含头节点在内总共有n+1个节点
for(i=0;i<(n+1);i++){
printf("the next:%ld\n",(long)list->next);
if(list->num==number){
if(list->next==0){
tp->next=0;
break;
}else{
tp->next=list->next;
break;
}
}
tp=list;// 临时指针指向的是前一节点
list=list->next; //继续判断下一节点
}
}
/*定义查询函数:显示链表中所有信息*/
void query(TYPE *head)
{
TYPE *list=head;
while(list)
{
printf("%d,%d\n",list->num,list->age);
printf("the next:%ld\n",(long)list->next);
list=list->next;
}
}
int main()
{
//这是定义的真正的头节点
struct stu *head;
head->num=0;
head->age=0;
struct stu *thead,*pbefore,*pafter;
int i;
for(i=0;i<n;i++){
pafter=(TYPE*) malloc(LEN); //定义的pafter也相当于一个临时节点
printf("input Number and Age\n");
scanf("%d%d",&pafter->num,&pafter->age);
if(i==0) pbefore=thead=pafter; //第一次循环,直接设置头节点
else pbefore->next=pafter;
//这里的pbefore其实是上一轮循环的after赋予的,所以是 “前一个节点”
//注意:这里改动的不是pbefore,而是pbefore指向的内存里的数据
pafter->next=NULL; // 将最后的指针域清零,赋给pbefore,即前节点
pbefore=pafter;// 这里pbefore指向了新的内存,也就是下一个节点
// 强调一下,pbefore只是一个指针
}
head->next=thead;
query(head);
int number;
printf("you want to delete:");
scanf("%d",&number);
delete(head,number);
query(head);
return 0;
}
以上程序包含了一个节点数为n+1的链表(n个学生节点和1个头节点,因为头节点没有数据域)。如果没有提前定义头节点,则第一个学生的节点则为头节点,这样就导致这个头节点无法删除。
上图可以看出,如果直接删除了头节点,会发生段错误。
现在我们在链表的尾部增加一个节点
#include<stdio.h>
#include<stdlib.h>
#define LEN sizeof (struct stu)
typedef struct stu{
int num;
int age;
struct stu *next;
}TYPE;
int n=4;
/*增加节点函数:返回新的尾部节点地址*/
TYPE *add(TYPE* tail)
{
TYPE *new;
new=(TYPE*) malloc(LEN);
printf("input the new num and age\n");
scanf("%d%d",&new->num,&new->age);
tail->next=new;
printf("tail:%ld",(long)tail);
new->next=NULL;//缺了这一句,新的节点数据就显示不出了
tail=new;
printf("new tail:%ld",(long)tail);
return tail;
}
/*删除节点函数*/
void delete(TYPE *head,TYPE* tail,int number)
{
int i;
TYPE *list=head;
TYPE *tp=NULL;
for(i=0;i<(n+1);i++){
// printf("the next:%ld\n",(long)list->next);
if(list->num==number){
if(list->next==0){
tp->next=0;
tail=tp;//若尾部节点被删除,则更新尾部节点
break;
}else{
tp->next=list->next;
break;
}
}
tp=list;// 临时指针指向的是前一节点
list=list->next; //继续判断下一节点
}
}
/*查询函数:显示所有节点数据*/
void query(TYPE *head)
{
TYPE *list=head;
while(list)
{
printf("%d,%d\n",list->num,list->age);
//printf("the next:%ld\n",(long)list->next);
list=list->next;
}
}
int main()
{
//初始化头节点以及尾部节点
struct stu *head;
struct stu *tail;
head->num=0;
head->age=0;
struct stu *thead,*pbefore,*pafter;
int i;
for(i=0;i<n;i++){
pafter=(TYPE*) malloc(LEN);
printf("input Number and Age\n");
scanf("%d%d",&pafter->num,&pafter->age);
if(i==0) pbefore=thead=pafter;
else pbefore->next=pafter;
pafter->next=NULL;
pbefore=pafter;
}
tail=pafter;
printf(" tail:%ld",(long)tail);
head->next=thead;
query(head);
int number;
printf("you want to delete:");
scanf("%d",&number);
delete(head,tail,number);
query(head);
tail=add(tail);
query(head);
tail=add(tail);
query(head);
return 0;
}
这里我们需要定义尾部节点,并且增加后要对尾部节点进行更新,方便再次在链表后面插入节点,注意:不要忘了给新的节点分配内存,否则会报段错误,因为你给并没有分配内存的结构体赋值了。
new=(TYPE*) malloc(LEN);
另外不要忘了给新的尾部节点的指针域设置为NULL。
new->next=NULL;
以上是增加了两个节点的例子,运行结果如下
可以看到,增加的节点已经显示出来了。