单向链表的实现及思考
2013-11-30 19:01
120 查看
最近重新开始写C,有一些问题也随之而来。重新写了个单项链表,通过这个链表复习了一些自己差不多快忘了的原理。
关于链表的实现:
1. 用C写了Node的结构体,而链表其实就是指向头结点的指针而非指整张表。所以链表和结点指针是同一回事,这一点和平时的认知可能相驳。
2. 谁来管理链表(即指向头结点的指针)?我们写了3个文件,linklist.h linklist.c main.c。其中main.c文件是调用并测试linklist的文件,剩下两个文件作为头文件和代码文件存在。那么,谁来管理链表呢?如果我们是用JAVA代码写,实现了linklist类之后,可以在main文件中新建一个linklist的对象,然后调用该类的函数。然而,C语言不存在对象这个概念,如果硬要认为那些函数是内部函数,那么所有的都是public的“内部函数”。在这里,我让linklist自己管理链表,即将linklist这个对象创建在linklist而非在main函数中。这样使代码更容易阅读。那么可不可以在main函数中创建那个对象呢?可以,事实上我们可以模仿面向对象的思想,写一个函数作为“构造函数”,将对象返回给main函数即可了。Java也是如何构造对象的,所以其实可以看作一回事。
关于C语言中的一些问题:
1. 局部变量与栈或堆上开辟空间的问题:函数内的局部变量通常是在栈上开辟的,而通过malloc这类语句开辟的空间通常是在堆上。栈上开辟的变量在函数结束后即被释放,而堆上的变量在整个程序的运行周期中依旧存在,相当于可以随时创建的全局变量。在链表的插入函数时,实现该函数时,最好在堆上创建一个结点变量,使其内容和传入的欲添加的结点一致。这样做的意义和好处在于,插入函数结束后该结点不会被释放!并且这个结点不会因为用户释放了传入参数中的那个结点导致链表出现问题!
2.传值还是传地址:实现链表中的函数时,通常需要将结点传给函数,那么传值还是传地址好呢?我们知道在按值传递中,形参是实参的一个拷贝,并不是同一回事。而按地址传递时,形参即是实参。这里我们采用按地址传递的办法,而用传值也能实现,而且也更安全。(安全的意思是指:如果按地址传递,编写插入函数时,可能会直接将参数这个结点插入到链表中,然而这个结点是用户传入的!用户可能重复使用,释放该结点,而这个结点已经是链表中的一个结点,这会导致链表出现很严重的错误!)
2. 参数为结点还是结点地址:C语言中参数传送只有一个机制,那就是按值传送!但是即便是值,我们传送的是指针指向的地址呢还是整个结点信息?这里选择传送指针指向的地址。原因在于C语言按值传送就意味着形参是实参的一个副本,如果行参为整个结点的话,那么复制的时候将比单单复制一个地址耗费更多的资源。
3. 关于别名,赋值等问题,将另起一篇讨论。
单向链表的代码:
linklist.h
linklist.c
main.c
关于链表的实现:
1. 用C写了Node的结构体,而链表其实就是指向头结点的指针而非指整张表。所以链表和结点指针是同一回事,这一点和平时的认知可能相驳。
2. 谁来管理链表(即指向头结点的指针)?我们写了3个文件,linklist.h linklist.c main.c。其中main.c文件是调用并测试linklist的文件,剩下两个文件作为头文件和代码文件存在。那么,谁来管理链表呢?如果我们是用JAVA代码写,实现了linklist类之后,可以在main文件中新建一个linklist的对象,然后调用该类的函数。然而,C语言不存在对象这个概念,如果硬要认为那些函数是内部函数,那么所有的都是public的“内部函数”。在这里,我让linklist自己管理链表,即将linklist这个对象创建在linklist而非在main函数中。这样使代码更容易阅读。那么可不可以在main函数中创建那个对象呢?可以,事实上我们可以模仿面向对象的思想,写一个函数作为“构造函数”,将对象返回给main函数即可了。Java也是如何构造对象的,所以其实可以看作一回事。
关于C语言中的一些问题:
1. 局部变量与栈或堆上开辟空间的问题:函数内的局部变量通常是在栈上开辟的,而通过malloc这类语句开辟的空间通常是在堆上。栈上开辟的变量在函数结束后即被释放,而堆上的变量在整个程序的运行周期中依旧存在,相当于可以随时创建的全局变量。在链表的插入函数时,实现该函数时,最好在堆上创建一个结点变量,使其内容和传入的欲添加的结点一致。这样做的意义和好处在于,插入函数结束后该结点不会被释放!并且这个结点不会因为用户释放了传入参数中的那个结点导致链表出现问题!
2.传值还是传地址:实现链表中的函数时,通常需要将结点传给函数,那么传值还是传地址好呢?我们知道在按值传递中,形参是实参的一个拷贝,并不是同一回事。而按地址传递时,形参即是实参。这里我们采用按地址传递的办法,而用传值也能实现,而且也更安全。(安全的意思是指:如果按地址传递,编写插入函数时,可能会直接将参数这个结点插入到链表中,然而这个结点是用户传入的!用户可能重复使用,释放该结点,而这个结点已经是链表中的一个结点,这会导致链表出现很严重的错误!)
2. 参数为结点还是结点地址:C语言中参数传送只有一个机制,那就是按值传送!但是即便是值,我们传送的是指针指向的地址呢还是整个结点信息?这里选择传送指针指向的地址。原因在于C语言按值传送就意味着形参是实参的一个副本,如果行参为整个结点的话,那么复制的时候将比单单复制一个地址耗费更多的资源。
3. 关于别名,赋值等问题,将另起一篇讨论。
单向链表的代码:
linklist.h
struct Node{ int value; struct Node *next; }; typedef struct Node *Linklist; Linklist linklist; int nodeCount; int addNodeAsc(struct Node *toAddNode); int deleteNode(struct Node *toDeleteNode); void printLinklist(); void createLinklist(); int nodeCmp(struct Node *nodeOne, struct Node *nodeTwo);
linklist.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "linklist.h" #define GREATER 1 #define EQUAL 0 #define SMALLER -1 #define RETURN_ERROR -1 #define RETURN_SUCCESS 0 int addNodeAsc(struct Node *toAddNode){ struct Node *prev,*curr; struct Node *newNode=(struct Node *)malloc(sizeof(struct Node)); memcpy(newNode,toAddNode,sizeof(struct Node)); if(linklist==NULL){ linklist=newNode; linklist->next=NULL; }else{ int cmpResult=nodeCmp(linklist,newNode); if((cmpResult==GREATER)||(cmpResult==EQUAL)){ newNode->next=linklist; linklist=newNode; }else{ for(curr=linklist;;prev=curr,curr=curr->next){ if(curr==NULL){ newNode->next=curr; prev->next=newNode; break; } cmpResult=nodeCmp(curr,newNode); if((cmpResult==GREATER)||(cmpResult==EQUAL)){ newNode->next=curr; prev->next=newNode; break; } }//end for }//end if cmpResult } nodeCount++; return(RETURN_SUCCESS); }//end function addNodeAsc int deleteNode(struct Node *toDeleteNode){ struct Node *prev, *curr; if(nodeCmp(linklist,toDeleteNode)==EQUAL){ curr=linklist; linklist=linklist->next; free(curr); nodeCount--; return(RETURN_SUCCESS); }else{ for(curr=linklist;;prev=curr,curr=curr->next){ if(curr==NULL){ printf("NO SUCH A NODE EXISTS!\n"); return(RETURN_ERROR); } if(nodeCmp(curr,toDeleteNode)==EQUAL){ struct Node *tmp=curr->next; free(curr); prev->next=tmp; nodeCount--; return(RETURN_SUCCESS); } }//end for } } void printLinklist(){ struct Node *curr; for(curr=linklist;curr!=NULL;curr=curr->next){ printf("[%d]\n",curr->value); } } void createLinklist(){ linklist=NULL; nodeCount=0; } int nodeCmp(struct Node *nodeOne, struct Node *nodeTwo){ if(nodeOne->value > nodeTwo->value){ return GREATER; }else if(nodeOne->value == nodeTwo->value){ return EQUAL; }else{ return SMALLER; } }
main.c
#include <stdio.h> #include <malloc.h> #include "linklist.h" int main(){ createLinklist(); printf("TEST CASE: INSERT A NODE INTO NULL LINKLIST\n"); struct Node *node=(struct Node*)malloc(sizeof(struct Node)); node->value=10; node->next=NULL; addNodeAsc(node); printLinklist(); printf("TEST CASE: INSERT A NODE INTO THE HEAD OF LINKLIST \n"); node->value=9; node->next=NULL; addNodeAsc(node); printLinklist(); printf("TEST CASE: INSERT A NODE INTO THE TAIL OF LINKLIST \n"); node->value=20; node->next=NULL; addNodeAsc(node); printLinklist(); printf("TEST CASE: INSERT A NODE INTO THE MIDDLE PART OF LINKLIST \n"); node->value=11; node->next=NULL; addNodeAsc(node); printLinklist(); printf("TEST CASE: DELETE A NODE WHICH IS IN THE MIDDLE PART OF LINKLIST \n"); node->value=11; node->next=NULL; deleteNode(node); printLinklist(); printf("TEST CASE: DELETE THE NODE WHICH IS THE HEAD OF LINKLIST \n"); node->value=9; node->next=NULL; deleteNode(node); printLinklist(); printf("TEST CASE: DELETE THE NODE WHICH IS THE TAIL OF LINKLIST \n"); node->value=20; node->next=NULL; deleteNode(node); printLinklist(); printf("TEST CASE: DELETE THE LAST NODE \n"); node->value=10; node->next=NULL; deleteNode(node); printLinklist(); return 0; }
相关文章推荐
- Python 实现单向链表
- Linux_C练习:单向链表实现通讯录
- Java 实现单向链表
- 单向链表的简单实现
- C++实现单向循环链表
- 【数据结构练习】单向链表实现、链表逆序实现
- 单向不带头结点不带环的链表实现
- 转:四种方式实现--从尾到头输出单向链表(链表逆序打印)
- 大话数据结构(三)——单向循环链表的java实现
- java单向链表基本操作简单实现
- 数组简单实现单向链表
- 数据结构:单向链表的C语言实现
- java 实现单向链表
- 反转单向链表的实现
- 在java实现自定义链表(单向链表)
- STL源码剖析 13 slist 单向链表的实现
- 数据结构之单向链表的实现【C++】
- 单向链表--java实现
- (C语言版)链表(一)——实现单向链表创建、插入、删除等简单操作(包含个人理解说明及注释,新手跟着写代码)
- 使用内部单向链表实现的一个简单堆栈