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()函数释放掉上一个元素的内存。
这部分代码,就留个希望学习的朋友自己研究吧。
相关文章推荐
- C语言内存分区_栈区、堆区、全局&静态区、文字常量区、程序代码区
- c++primer plus 编程练习题2
- Effective C++——条款44(第7章)
- 虚函数的实现原理--c++虚函数表解析
- 深入解析C++编程中的静态成员函数
- c++引用与指针的区别(着重理解)
- C语言知识点总结2
- c++11的foreach用法
- C语言小游戏 - 俄罗斯方块
- 浅谈C++中对象的复制与对象之间的相互赋值
- C语言二维数组剖析【元素及元素地址的指针操作】
- c++11基础学习(2)std::bind与std::function特性
- C++ 函数指针
- Python C++ interoperability
- c语言小工具
- Effective C++ ——初始化
- C++ Primer Plus(第6版) 第2章编程练习
- WinRT C++ byte* 转为 Ibuffer^(笔记)
- C++的异常处理
- C++中对象的常引用、动态建立和释放相关知识讲解