您的位置:首页 > 编程语言 > C语言/C++

C语言实现单向链表

2015-09-17 17:10 211 查看

首先声明:

笔者刚刚接触C语言,发现C语言与C#相比,太麻烦了。大部分功能都要自己去实现。最明显的例子就是关于可变长度的数组的问题。于是各种百度,找到这个问题的解决方案,就是用链表来实现。关于C语言实现链表,很多教科书上都有示例代码。笔者大致了解了一下基本思想,然后就自己实验出了本文中的代码。

所以,这篇blog不能算是研究成果吧,只能算是我自己的一点学习笔记。有不足的地方,还请各位指教。

1、开篇:

在C#中,如果要实现一个可变长度的数组,随时向数组中添加元素、删除元素,可以使用list类、hashtable类等集合类型即可。但是在C语言中,数组的长度在定义的时候就必须确定,那么数组长度怎么才能实现可变呢?

在数据结构一书中提到了一种数据类型:链表。它的指导思想是在第一个数据块中保存第二个数据块的地址,第二个数据块中又保存第三个数据块的地址。通过层层引用,直到最后一个数据块。如此一来,只要知道第一个数据块的地址,然后就可以通过各数据块中保存的下一个数据库的地址,来实现类似于长度可变得数组的功能。

既然是数据块,那么就必须要保存一种类型的数据。然后它还需要保存下一个数据块的地址,这样的一来,就需要在一种类型中保存两种类型的数据。因此,结构体,是用来做链表的理想类型。因为在结构体中,可以保存任意多种数据类型,当然包括了基本类型,复杂类型等,也包含了自身指针类型。

2、实例:

在本例中,我假设保存学生的信息(暂时只保存“名字”和“年龄”两个信息)。对于一个特定的班级,学生数量是一定的。可以用数组来实现。但是这样写代码,通用行不强,因为同一个学校中有很多个班级,每个班级中学生的数量也是不同的。如果使用数组,就必须把数组定义的足够大,能容纳下学生数量最多的班级的学生信息,但是这样也会造成内存的严重浪费。因此,使用“链表”,可以很方便的随时扩充“数组的元素”。

如此一来,我们就需要一个“学生”类型的结构体,一个指向第一个元素的指针head,一个能向下移动的指针p_student。

3、代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Student  struct STU     //将struct STU 用STU代替
#define Volume sizeof(struct STU)

//定义学生信息结构体
struct STU{
char name[16];
int age;
Student *Next;      //指向同类型的下一个数据;
};

int main()
{
char identify[16] = {0};                  //接收用户输入的姓名
int old = 0;                              //接收用户输入的年龄
int no=0;                                 //用于指示现在接收的第几个数据

Student *head = NULL;                     //<span style="font-family: Arial, Helvetica, sans-serif;">定义链表的头指针head</span>

Student *p_student = NULL;                //<span style="font-family: Arial, Helvetica, sans-serif;">定义接收数据时候,新申请内存单元的指针</span>

//输入数据
do{
printf("please input name: ---->less than 15 bytes ---->press Enter to quit\n");
gets(identify);

//判断输入的姓名是否正常,如果输入的是enter键,则退出
if(identify[0] == '\0'){
break;
}else{
printf("please input age: ---->less than 15 bytes\n");
scanf("%d%*c",&old);

//指示第几次获取用户输入
no++;
//如果是第一次申请内存,则先将申请到的内存首地址赋值给p_student,并将链表的首地址赋值给head;
//否则将申请到的新内存地址,先赋值给p_student->Next,然后再把这个地址赋值给p_student;
if(no ==1){
p_student = malloc(Volume);
head = p_student;
}else{
p_student = p_student->Next = malloc(Volume);
}
strcpy(p_student->name,identify);
p_student->age = old;
p_student->Next = NULL;
}
}while(1);

printf("you have ended input\n");
printf("in total, you have entered %d students\n\n",no);

printf("starting readding all informations\n");
if(head ==NULL){                                //如果head不为空,则表示这不是一个空链表
printf("there is no data in the linker.");
}else{
p_student = head;                           //重置位置指针
no = 0;
do{
no++;
printf("No: %-5d\tName:%-15s\tAge:%-5d\n",no,p_student->name,p_student->age);
if(p_student->Next ==NULL){         //判断元素的.Next成员是否为NULL。如果是,则表示已经读到最后一个元素,如果不为NULL,则表示后面还有元素。
break;
}else{
p_student= p_student->Next;
}
}while(1);
printf("All informations have been displayed here .enjoy yourself.\n");
}
printf("Program is ending...\nProgram has ended.\n");
return 0;
}


4、代码陷阱分析:

4.1 --> gets(identify);
这句代码的作用是从键盘获取输入的字符串。需要注意的是,如果不输入字符,而直接按下回车键的时候,是不会读取任何东西。不知道是我理解有误还是编译器的问题,实际实验的效果就是这样的。所以在下一条语句中,首先判断identity[0]是不是'\0';如果是'\0',就表示输入结束。那么就退出do循环。

4.2 --> scanf("%d%*c",&old);
在句代码的作用是从键盘获取输入的数字,存放到变量old中。但是一定要注意:在输入完以后我们需要在按下回车键,以只是scanf()函数开始读取。这个时候,回车符‘\n’,保存在了输入缓冲区。而scanf()不会自动吸收掉这个回车符。等到下一次读取的时候,又会将这个回车符自动读取,然后造成gets(identify)语句读取错误。所以在这里,使用临时数据格式字符串%*来吸收掉多输入的‘\n’。

4.3 --> if(no ==1)代码块
这里的想法是,在使用malloc()函数申请内存空间的时候,先把申请到的内存的地址赋值给上一个元素的.Next成员,然后把.Next的地址赋值给p_student,这样就可以让上一个元素指向下一个元素,又能很方便的使用一级指针访问新申请的内存空间。
但是问题就出现在第一个元素上。因为在输入循环第一次执行的时候,p_student的值为NULL,即不指向任何内存空间,也就是说没有没有任何内存空间来保存下一元素的地址。所以要先判断一下,是否是第一个元素。如果是,则先保存p_student的地址,如果不是,则就可以放心的把新申请的地址赋值给上一个元素的Next成员了。

4.5 --> if(p_student->Next ==NULL) 代码块
这个代码的功能是判断当前这个元素是否还有下一个元素,如果有,则继续读下一个元素,如果没有,则表示已经读取完成,退出循环。

5、遗留问题:

程序写到这,可以正常运行了。但是先不要高兴的太早。因为这段代码中有一个很容易忽略的问题,那就是当我们使用完这些数据后,比如写入到文件,内存空间中这些数据其实就没用了。但是由于这些内存是我们手动申请的,操作系统是不会帮我们管理的。所以如果在最后我们不去手动释放这些内存的话,这些我们申请的内存空间,就回保持被占用的状态,而操作系统无法使用它。这样一来,内存就泄露了。所以,这个地方千万要注意,释放内存。

至于释放内存的方法,那就是是根据指针,去一个一个释放了。可以用循环来做,定义一个临时的指针来保存下一个元素的地址,然后使用free()函数释放掉上一个元素的内存。

这部分代码,就留个希望学习的朋友自己研究吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: