c语言实现一个链表
一、基础研究
我们在这里要理解和实现一种最基本的数据结构:链表。首先看看实现的程序代码:
List .h:
事实上我们观察list.h发现前面一部分是数据结构的定义和函数的声明,后面一部分是函数的实现。我们仅仅观察前面一部分就可以知道这个链表的结构是怎么实现的了。
程序将处理的对象分成了三类:线性表、结点和元素,分别定义了它们的数据类型和操作函数,对线性表有创建、撤销、清空操作,对元素有追加、加入、删除、取操作,对结点有取、遍历、创建操作,每一个操作都用一个子函数来实现。它们全部被封装进了头文件list.h,这是对共性的封装。
我们用m.c对list.h进行测试:
执行结果如下:
m.c首先创建了一个字符数组来装载要存入线性表中的元素,再定义了显示线性表的函数showlist和显示单个元素的函数putelement。在主函数中首先调用CreateList函数创建一个线性表,如果创建失败会提示错误并返回,如果成功则调用ListAppend函数将字符数组里的内容放进线性表中,再调用showlist函数显示字符串。之后我们调用ListInsert函数向链表中插入一个元素结点并显示,再调用ListDelete函数删除之前插入的元素,并显示字符串。其中CreateList函数、ListInsert函数、ListDelete函数都是在list.h中的函数,是有关链表本身的操作,是共性,而showlist函数和putelement函数是在c文件中实现的,它们的功能是个性,是需求。showlist函数是调用TraverseList函数遍历链表,并对每个元素用putelement函数进行处理,而putelement函数是将该元素打印出来。为什么在TraverseList函数里要将遍历链表和处理函数分开呢?这里也是将共性和个性分离开,很多时候我们都需要遍历链表,但是不一定每一次都要用同一个函数来处理。那么就把个性也用函数封装起来。
将list.h的第一个语句typedef char EleType改为typedef int EleType,再用m1.c测试:
运行结果为:
这里把链表元素由字符型改成整形,只需要再在m.c里进行极小的改动,就可以实现相关功能。
再将list.h的第一个语句typedef char EleType改为typedef struct{char a;int b;} EleType,再用m2.c测试:
执行结果为:
这里要处理的链表元素为结构体,所以我们要定义一个结构体变量,并进行初始化,之后再插入链表中,然后做一些修改,则可以实现相关功能。
我们可以发现List里只有一个数据项“ChainNode *head”,为什么还要定义这个数据类型?同样地,我们用typedef char EleType定义了线性表存储的元素类型,其实只是将char取名为EleType而已,为什么要取这个别名而不是直接用char呢?我们在编写程序的过程中,需要一些符号来帮助我们认识、理解、记忆变量的名字,这些符号最好是有特殊含义、能让我们联想起它的功能的,如果元素的类型就用char表示,那么在定义和使用元素时很容易把它与别的变量弄混,会造成程序的可读性降低。而且如果链表的元素变成了int型,我们只需要将typedef char EleType改成typedef int EleType就可以了,这样使程序易于修改和扩展。同样地,List里只有一个数据项“ChainNode *head”,但是我们还要将它封装在一个List数据类型中,也是考虑到了程序的扩展性和可读性。而且如果我们在这里只定义一个头指针的话,表达不出定义线性表的意思,是线性表里面包括头结点,这个结点可以用一个头指针指向,所以头指针可以代表一个线性表,但是它们不是一个层次的东西,我们要将线性表的属性都封装起来才能更好的对它进行操作,这个属性是我们抽象出来的,我们同样可以抽象出更多的线性表的属性添加进来以方便实现更多功能。现在我们向线性表中添加一个tail指针,使它指向链表的最后一个结点,那么首先要修改线性表的定义:
修改创建线性表的函数CreateList,因为创建线性表后只有一个头结点,所以head和tail指针都指向这个结点:
撤销线性表时要将头尾两个指针都释放:
因为我们要提高ListAppend的速度,而加入元素是在线性表尾端加入的,所以我们用tail指针加入会更快:
这样我们不用改动线性表的程序m.c就可以实现了,因为这里我们把共性和个性分离开了,使每一个函数的功能单一,独立性高,与外部的隔绝性好。也就是我们从外部看,不用管一个函数的功能是怎么实现的,而只需要知道它的参数是什么,功能是什么,返回值是什么,这样就保证了我们要改动程序只需要改动较小的部分。
为什么要使用一个头结点呢?因为线性表有为空的情况,这时如果没有头结点,我们加入元素就没有地方存放结点的地址,而且我们写函数时还要专门对第一个元素进行处理。这样容易出错,也会使程序变得更加复杂。
程序中实现的链表里的元素类型都是固定的,怎么实现一个链表使它的元素类型为任意类型呢?要在链表里结点的数据空间存放任意类型的数据是不可能的,因为每个节点定义时的大小都是固定的。我们可以这样实现:在结点里的数据空间存放指针,指针指向每一个元素处的空间,这个空间的大小可以是任意的,根据我们定义的数据类型而改变,用malloc函数动态分配内存。
但是现在的问题是我们不知道用户传入的数据大小是多少,像printf函数一样用类型说明符只能实现基本数据类型而不能实现用户自定义类型,而用户用结构体定义的自定义大小可以为任意大小,甚至理论上是无穷大的。之前我以为要实现链表的每一个元素的类型都可以是不一样的,但是后来发现应该实现的是元素类型都是一样的,但是这个类型是由用户决定的而不是提前先规定好的。
因为不知道数据大小,所以我们要在线性表中加入一个数据项int datasize以表示数据大小,并在main函数中创建线性表时用sizeof计算数据大小并传给datasize;
我们将ListAppend函数、ListInsert函数实现为不定函数,这样它们接受的参数类型就没有限制了:
因为我们传入ListAppend函数的链表数据是一个局部变量,保存在栈段中,并且在函数返回后会被释放,所以要另外开辟空间来存储它。这里&lp表示传入的线性表lp在栈中的地址,&lp+1表示下一个参数,即我们要添加的数据在栈中的地址。我们用malloc函数创建一个传入数据大小的空间并将它的地址赋给指针target。然后用memcpy函数将数据从战中转移到target指向的我们动态开辟的空间中。Memcpy函数的原型为:void *memcpy( void *dest, const void *src, size_t count);即从指针src指向的空间拷贝count个字节到指针dest指向的空间里。
之后修改NewChainCode函数、GetElement函数、CreateList函数就可以了,这也体现了各个函数的独立性,否则我们可能就要修改整个程序了。
这里一定要注意的是,我们在一个指针进行赋值之后,一定要对它进行判断,如果是0则返回,这样可以使程序更安全、更容易调试。
现在我们就可以在c文件里定义数据结构而不用更改头文件的内容了。我们用m1.c进行测试:
结果是正确的,注意在用CreateList创建线性表时一定要先用sizeof计算传入的数据大小。
二、扩展研究
1、这个程序有什么特色?表现了一种什么样的程序设计思想?
答:这个程序将共性抽象开并封装到头文件里,我们可以很清楚地看到头文件list.h里封装的都是链表的数据结构和方法,我们在c文件里只需要将数据传入并用自定义的方法(比如输出)来进行操作就可以了。这个程序的结构非常清楚:共性的抽象、个性的实现,每一个函数实现一个功能,函数与函数之间没有联系,这样就可以保证一个函数出问题不会影响到其它函数。这个list.h头文件完全可以看成一个模块,调用它就能实现链表的相关功能,这是结构化的思想。
三、研究总结
程序设计需要综合的能力和视野。这个程序头文件里的函数其实和java里的类很像,每一个需求都是由专门的函数来实现的,函数与函数之间没有联系,只与调用的函数传递数据,这样我们只需要考虑单个函数的功能怎么实现就够了。而这样首先要把问题细化为一个个小需求来实现,这需要我们在程序设计时先对问题有清楚的认识和深度的思考分析。确定每一个函数的功能、参数、返回值,然后再来实现函数,这时就是编程的细节问题了,相对程序设计要简单得多。我们要更多地思考怎么来进行程序设计,而不是具体的技术细节。
c语言实现一个链表的更多相关文章
- 一步一步教你从零开始写C语言链表---构建一个链表
版权声明:本文为博主原创文章,如有需要,请注明转载地址:http://blog.csdn.net/morixinguan.若是侵权用于商业用途,请联系博主,否则将追究责任 https://blog ...
- C语言实现单链表-03版
在C语言实现单链表-02版中我们只是简单的更新一下链表的组织方式: 它没有更多的更新功能,因此我们这个版本将要完成如下功能: Problem 1,搜索相关节点: 2,前插节点: 3,后追加节点: 4, ...
- C语言实现单链表-02版
我们在C语言实现单链表-01版中实现的链表非常简单: 但是它对于理解单链表是非常有帮助的,至少我就是这样认为的: 简单的不能再简单的东西没那么实用,所以我们接下来要大规模的修改啦: Problem 1 ...
- 使用C语言描述静态链表和动态链表
静态链表和动态链表是线性表链式存储结构的两种不同的表示方式. 静态链表的初始长度一般是固定的,在做插入和删除操作时不需要移动元素,仅需修改指针,故仍具有链式存储结构的主要优点. 动态链表是相对于静态链 ...
- 「C语言」单链表/双向链表的建立/遍历/插入/删除
最近临近期末的C语言课程设计比平时练习作业一下难了不止一个档次,第一次接触到了C语言的框架开发,了解了View(界面层).Service(业务逻辑层).Persistence(持久化层)的分离和耦合, ...
- leetcode:Sort List(一个链表的归并排序)
Sort a linked list in O(n log n) time using constant space complexity. 分析:题目要求时间复杂度为O(nlogn),所以不能用qu ...
- C语言数据结构-创建链表的四种方法
结点类型: typedef int datatype; typedef struct NODE{ datatype data; struct NODE *next; }Node,*LinkList; ...
- C语言实现单链表,并完成链表常用API函数
C语言实现单链表,并完成链表常用API函数: 1.链表增.删.改.查. 2.打印链表.反转打印.打印环形链表. 3.链表排序.链表冒泡排序.链表快速排序. 4.求链表节点个数(普通方法.递归方法). ...
- C语言实现通用链表初步(一)
注意:本文讨论的是无头单向非循环链表. 假设不采用Linux内核链表的思路,怎样用C语言实现通用链表呢? 一种常用的做法是: typedef int element_t; struct node_in ...
随机推荐
- shell 二元操作符 =~
17:14 [logc@a005.client.hadoop.qingdao.youku]$ a=1 17:14 [logc@a005.client.hadoop.qingdao.youku]$ if ...
- AABB包围盒、OBB包围盒、包围球的比較
1) AABB 包围盒: AABB 包围盒是与坐标轴对齐的包围盒, 简单性好, 紧密性较差(尤其对斜对角方向放置的瘦长形对象, 採用AABB, 将留下非常大的边角空隙, 导致大量不是必需的包围盒相交測 ...
- lnux内核的malloc实现(Oracle的cache buffer影子)
lnux内核的malloc实现(Oracle的cache buffer影子) 本文原创为freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/artic ...
- Linux 监控CPU 温度
安装测试系统: 硬件:普通PC机, 软件:redhat linux as 4 2.6 .9 , 安装系统自带的lm_sensors-2.8.7-2.i386 你也可以从[url]http://w ...
- IOS内存等信息
1. 获取IOS APP占用的内存 #import <mach/mach.h> // ... void report_memory(void) { struct task_basic_in ...
- scala中常用但其他语言不常见的符号含义
本文旨在介绍Scala在其他语言中不太常见的符号含义,帮助理解Scala Code. 随着我对Scala学习的深入,我会不断增加该篇博文的内容. 修改记录 ----2016.11.23 新增scal ...
- 《Android开发艺术探索》读书笔记 (11) 第11章 Android的线程和线程池
第11章 Android的线程和线程池 11.1 主线程和子线程 (1)在Java中默认情况下一个进程只有一个线程,也就是主线程,其他线程都是子线程,也叫工作线程.Android中的主线程主要处理和界 ...
- 2015 UESTC Training for Search Algorithm & String - M - Palindromic String【Manacher回文串】
O(n)的复杂度求回文串:Manacher算法 定义一个回文值,字符串S是K重回文串,当且仅当S是回文串,且其长度为⌊N/2⌋的前缀和长度为⌊N/2⌋的后缀是K−1重回文串 现在给一个2*10^6长度 ...
- SPOJ 3943 - Nested Dolls 最长不下降子序列LIS(二分写法)
现在n(<=20000)个俄罗斯套娃,每个都有宽度wi和高度hi(均小于10000),要求w1<w2并且h1<h2的时候才可以合并,问最少能剩几个. [LIS]乍一看跟[这题]类似, ...
- C#学习第三天
经过这几天的学习,真的有点觉得以前C学的太不好现在学C#也不顺畅,虽然很多东西都似曾相识,但是就是还得看好几遍才能记得住,而且现在都是简单的东西,还没有看到重载等稍微难点的地方.应该好好努力了,昨天忙 ...