只有流过血的手指,才能弹出世间的绝唱。 ——泰戈尔
关于链表中的知识
1、前言
关于链表呢,其实有很多种。
当然了,链表也相当于算是数据结构的一种类型,但是在自己在C语言中编写链表,也不会是感觉上的那么简单,并且尤其是其中的一级指针和二级指针的使用问题,如果不能较好的理解这点的关系和区别,那么不仅仅是在编写层面上的问题也更有着在未来对于用户交互之间的问题。举个例子来说,用户是不可能看到在代码层面我们所写的逻辑和结构,如果只是让一个普通人去使用,那么必然会有教程,那如果搞不清楚逻辑问题和指针结构,你在指导用户使用的时候也必然造成繁琐的问题,让用户也要遭受记住不同指针的麻烦。
2、一级指针和二级指针
很显然,对于一级指针也就是相当于是普通的指针可以参考一下了解一下这篇文章一级指针基本介绍。那么二级指针,相对于一级指针也好理解,就相当于一级指向普通,二级指向一级。(哈哈哈,有点像是在数学中的求导,还记得当年的导数大题,几乎是很少做出来,莫名emo)。
3、正文介绍,创建单链表
这是一段关于链表的头文件,不是“.c”文件。
代码语言:javascript复制typedef struct SListNode
{
SLNDataType val;
struct SListNode* next;
}SLNode;
void SLTPrint(SLNode* phead);
void SLTPushBack(SLNode** pphead, SLNDataType x);
void SLTPushFront(SLNode** pphead, SLNDataType x);
void SLTPopBack(SLNode** pphead);
void SLTPopFront(SLNode** pphead);
SLNode* SLTFind(SLNode* phead, SLNDataType x);
SLNode* SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
void SLTErase(SLNode** pphead, SLNode* pos);
void SLTDestroy(SLNode** pphead);
在对于一个普通的不带头的单链表来说,正常也就这几个操作。关于为什么没有创建一个节点的函数呢,那是因为这都是可以随便你自己需不需要的。 下面就都来演示一个是不需要写创建的单链表 不需要的,但是这是在".c"中写的
代码语言:javascript复制void test()
{
SLNode* plist = NULL;
SLTPushBack(&plist, 1);
}
但是这么写的话,也需要注意的是在插入,删除,查找的时候得判断是否为空指针,如果是空的话该怎么处理特殊情况。
3,1单链表创建时出现的问题
在刚刚的代码中,你是否注意到了**&plist**,这是相当关键的一点。还不知道,你们还记不记得,在刚学习C语言的时候,应该都知道的一个错误吧,就是关于自己写一个将两个数值交换的函数吧。
代码语言:javascript复制Swap_int 1(int a,int b);
Swap_int 2(int* a,int* b);
可以考虑一下,在上面的两个方法去命名的时候到底该用哪一个。这就不又得提到一个,这样的知识点了。关于形参和实参的区别,形参是实参的一份临时拷贝。如果是按照第一个方法写的话,最终的结果也就是,什么效果也没有,因为在传回来的时候,已经对a,b这两个临时拷贝的形参删除,不会返回真正的值,导致出现问题。那么关于之前的问题是大抵上回想起来,那我们看看对于单链表是不是也是这样呢? 我们可以写下面两段的测试代码,在运行的时候通过监视器观察一下。
代码语言:javascript复制void test 1 ()
{
SLNode* plist = NULL;
SLTPushBack(&plist, 1);
}
void SLTPushBack 1 (SLNode** pphead, SLNDataType x);
void test 2 ()
{
SLNode* plist = NULL;
SLTPushBack(plist, 1);
}
void SLTPushBack 2 (SLNode* pphead, SLNDataType x);
如果能够很好的理解刚刚的函数数值交换和给个大概讲的以及与二级指针的大概意思的话,应该也就能够选出是第一种的方法才算是能够真正的将数值插入我们所创建的单链表中。但是更细致点的来说其实如果能把空指针的情况排除在外的话,也可以不用二级指针,只有是为什么呢? 那再来举一个例子来说啊 对于数组来说,怎么传值呢?
3、1、1关于传参时候的细节,从数组衍生为单链表中的应用
代码语言:javascript复制#include <stdio.h>
void set_arr(int arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i )
{
arr[i] = -1;
}
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
set_arr(arr, sz);//设置数组内容为-1
return 0;
}
这里的数组在传参的时候,不是一级级指针,那么可以真正的改变吗,其实又是可以的。那么此时又得注意到。 形参操作的数组和实参的数组是同⼀个数组并且数组传参,形参是不会创建新的数组的 那其实是有点疑惑的吧,我当时也是,这怎么一会是一级又一会不是一级指针,而且一会形参可以,一会又不可?! 那其实用一幅图来说明吧
大概就是这样的,虽然是形参,但是只会是数组的首元素的地址的拷贝,或者是单链表头指针的拷贝,但是拷贝归拷贝,所蕴含的,指向下一个地址还是和原来的实参的下一个的地址是一样的,所以总结起来也就是像是就是刚刚那段话的含义。 那么到这里其实也就是可以理解了,链表的头一定是二级指针,有链表头参加(改动的意思),就是必须要使用二级指针,只有通过二级指针的解引用,才能对头的实参进行操作,而不只是在形参上改变,导致进行完了之后没有什么用,随着销毁一起解决了。(其实想要大概了解一下计算机在程序运行的时候在什么地方存储,什么地方改变,可以看一下这篇文章里面有图的大概的介绍)
4、单链表头插和尾插等一系列操作
根据上面的描写,其实也大概是知道怎么去合理的运用指针,多看看关于数组的理解,更好的延伸到单链表中,进行理解。 所以,在正文介绍的那段代码之中。尾插,头茬,尾删,头删,在指定位置之前删除的这一系列操作中。 尾插需要二级指针,是因为要考虑到万一指针指向的地方还没有第一个节点,若没有节点的话,就必须要二级来创建节点,为了避免这种情况下的无法处理,所以就直接是二级指针传入。 我之前一直以为是因为malloc就要用到二级指针。因为我以为是如果用一级指针,malloc不能真正的创建,反而会在函数结束的时候成为危险的地区。其实不然,这里的原因都是因为在进行那些传入二级指针的地方的时候,他们都得需要考虑到空节点的情况。换句话说,真正需要的是找到头结点实参的位置才能去继续访问。
5、总结
其实这一大段也就只是讲了,一级和二级在单链表中的使用罢了,其实没理解的时候真的是一团雾水,理解之后真的会有所帮助。实在不理解,可以多看看第三段和第四段。