昨天周末给学妹讲了一些指针的知识,本来我对指针就是似懂非懂的状态,

经过昨天一讲,我对指针的学习就更深刻了果然给别人讲课也是学习的一个方法。

加上最近复习数据结构,发现我的博客里没有链表的博文,所以趁这时候加上一篇。

在此之前,我们先谈一下我要说的一些基本知识:

①函数参数为什么是双指针?

我们先写一下这么一个程序:

# include<stdio.h>
void Gai(int m)
{
m=;
}
int main(void)
{
int a=;
Gai(a);
printf("%d\n",a);
return ;

那么我们可以得知,输出a的值是1,为什么我调用了函数,把a传进去,并没有变成5呢?这就是关键所在。

我总结一下,形参m只是实参a的一个赋值的变量,形参我们都知道是函数调用时候才分配内存单元,

当函数调用完毕后,形参就会被干掉了,所以上面程序可以这么理解:定义一个a变量,它的值为1,

当把a作为实参传进Gai这个函数时,系统会定义一个变量m,并且把a的值“1”赋给了m,然后又执行m=5。

所以,到整个程序结束,m=5,a=1,所以a的值根本就没有发生改变。

所以说,在main函数里,若想通过函数来改变变量的值,那是不可能的。

接下来我们把程序修改一下:

# include<stdio.h>
void Gai(int * m)
{
*m=;
}
int main(void)
{
int a=;
Gai(&a);
printf("%d\n",a);
return ;
}

通过运行后我们可以看到a的值此时变成了“5”。所以,我们可以总结:

若一个变量想通过函数来改变本身的值,将本身作为参数传递是不成功的,

只有传递本身的指针(地址)才能达到这样的效果。

所以后面我们创建链表时,传递的是双指针,这就是为什么参数是双指针的原因。

因为我之前也一直不明白,直到我昨天给学弟学妹们讲课的时候,我才恍然大悟,所以我也算很笨了,

所以在这里给大家总结一下,因为我在别的博客里,看到也有挺多人不理解为什么是双指针,现在希望能够帮助大家更容易理解一些。

②每一个变量的内存单元中都有自己的地址

为了好理解,我画图把它绑在一块,虽然可能物理结构上不是长这样,但逻辑上是长这样的,比如

int a=; 

 int * p = &a;   

所以说,只要是变量它都会有自己的地址(指针),即使是指针变量。

然后,指针它就是用来存地址的,只有两部分,一部分是附带自己的地址,一部分是存别人的地址

③指针就是地址,地址就是指针,指针类型的变量它的值只用来装指针。

为什么我会说这句话呢。因为之前,在昨天为止,我那么久,居然一直都理解错了,也怪我太笨了哈哈。比如说定义了节点类型

typedef struct n
{
int data; //数据域
struct n * next; //指针域
} Node;

然后 Node * L;  我一直以为L是长这样子的

原来不是!!!  它不是!!  害死我了,以前我可纠结了好久了!!!太蠢了哈哈!!

原来我一直以为什么类型的指针就长什么样!!

不是的,其实它是什么类型的指针它就存什么样的地址。

所以L其实是长这样的:

总之这个坑,如果你们已经会的,可以笑一下我,如果也一样像我一样掉坑的,

希望看到这里后能及时填坑。这个大大大大大的坑,嗨呀,气死了。都怪以前没认真学指针。

以上就是今天的预备知识,接下来就开始学习单链表的简单操作了。我会用图来结合,

因为我一直强调图和代码结合,这样才能学好数据结构,这样才能对数据结构有形象的想法,

当然大神都是直接理解的,我就比较菜,就挖掘了自己的学习方法,嘿嘿。

单链表我采用了头指针和头结点的结构。

这次单链表的操作可能有些不一样,但原理都是一样的,或者说,把图理解了,代码也就理解了;

/*
参数:头指针的指针(双指针)
作用:初始化链表,使头指针指向一个新结点,
这个新节点就是头结点
*/
void InitHead(Node * *pHead) /*
参数:头指针,其实也是头指针的拷贝
作用:释放整个链表
*/
void Free_list(Node * pHead) /*
参数:头指针,一个值
作用:往链表的末段追加val
*/
void append(Node * pHead,int val) /*
参数:头指针
作用:遍历输出链表(跳过头结点)
*/
void Showlist(Node * pHead)

(一)初始化链表

void InitHead(Node * *pHead) //为链表生成头结点 使头指针指向头结点
{
*pHead = (Node *)malloc(sizeof(Node));
if(*pHead == NULL)
{
printf("头结点分配失败,程序终止! \n");
exit(-);
}
(*pHead)->next=NULL;
}

在main函数里面定义:  Node * L = NULL; //定义一个指针,指向Node类型,其实也就是整个链表的头指针

然后调用       InitHead(&L);

图解如下:

*pHead = (Node *)malloc(sizeof(Node)); 

其实*PHead就是头指针L的值了,加*号就代表指针的值,也就是图中右边的部分。

malloc会申请一个结点,然后返回结点的首地址,其实这个新生成的结点是没有名字的,

为了方便理解,我们管它叫x  图解如下:

至于PHead?哈哈,等这个函数结束后,它就被会干掉了,所以到头来,它只为他人作了嫁衣,不过这也正是它存在的意义。

如果传递的是单指针的话,pHead作为盗版的头指针指向了那个新生成的结点,

然后函数结束后,它们的状态分别是:L  依然存在,什么也没变化。 pHead,被干掉,彻彻底底的没了,

至于新生成的结点,则是孤零零的在内存区里瑟瑟发抖,等待有缘人来指向它,

所以这就是为什么要用双指针的理由。用了双指针,L指向了新生成的结点,PHead被干掉,皆大欢喜。

(二)释放链表

void Free_list(Node * pHead) //释放链表
{
Node * p;
while(pHead != NULL)
{
p = pHead;
pHead = pHead->next;
free(p);
p = NULL;
}
}

因为在链表中生成的新节点是用malloc的,所以要用free把它回收,malloc和free是一夫一妻。

在这种小程序或许不free也没什么太大的问题,但以后做项目时如果不回收就麻烦大了,所以养成free的习惯。

图解如下:

之后p就会变成

然后free(p)就是把p指向的那个结点,也就是图中的头结点,给干掉,

而pHead也被函数结束后干掉,而L只拿着一个head的地址但却找不到人了;

这里的图是只有一个头结点时的释放,但即使有多个结点,也是一样的做法,你们可以自己画图模拟一下,加深记忆。

(三)向链表末端追加元素

void append(Node * pHead,int val)
{
Node * r=pHead;
Node * pNew = (Node *)malloc(sizeof(Node)); //生成新节点
if(pNew == NULL)
{
printf("新节点分配失败,程序终止! \n");
exit(-);
}
pNew->data=val;
pNew->next=NULL; while(r->next != NULL) //让尾指针循环直到最后一个节点
{
r=r->next;
}
r->next=pNew;
r=pNew;
}

这个代码太长,有点难画图,我尽量吧,图示如下:

然后定义一个指针r,把pHead复制过去

为了方便理解,我把所有新生成的结点都叫做x。

然后定义一个PNew指针指向新生成的结点x;然后赋值,并置为NULL

其实pNew->data就是x.data

然后这一句代码

while(r->next != NULL) //让尾指针循环直到最后一个节点
{
r=r->next;
}

这是为了让r指针指向最后一个结点,

为什么是 r->next != NULL  而不是 r!=NULL; 这里是有区别的,因为我这种追加的方法是属于后插法。

总之就是根据图来写代码

r->next是这个

而r是这个

所以判断的时候,应该判断的是链表中的next;而不是判断r有没有指向谁;

接下来就是最后的连起来了。

r->next=pNew;
r=pNew;

r->next=pNew;//为什么这里它们明明是指向了PNew,但图中却指向x呢?

我这么理解不懂对不对,一个指针指向了一个结点,那么这个指针就相当于这个结点了

然后最后一个

(四)遍历输出链表

void Showlist(Node * pHead)
{
pHead=pHead->next; //跳过头结点输出
while(pHead!=NULL)
{
printf("%d ",pHead->data);
pHead=pHead->next;
}
}

最后一个就不画图了,相信大家也能看懂。

至此,整篇文章应该写完了。啧啧啧啧,去吃饭了。

有错请在下方评论,咱们一起进步。

谢谢~

忘了贴完整代码了,测试你们自己测试一下,我这里测试结果无误

# include<stdio.h>
# include<stdlib.h>
typedef struct n
{
int data; //数据域
struct n * next; //指针域
} Node;
void InitHead(Node * *pHead) //为链表生成头结点 使头指针指向头结点
{
*pHead = (Node *)malloc(sizeof(Node));
if(*pHead == NULL)
{
printf("头结点分配失败,程序终止! \n");
exit(-1);
}
(*pHead)->next=NULL;
}
void Free_list(Node * pHead) //释放链表
{
Node * p;
while(pHead != NULL)
{
p = pHead;
pHead = pHead->next;
free(p);
p = NULL;
}
}
void append(Node * pHead,int val)
{
Node * r=pHead;
Node * pNew = (Node *)malloc(sizeof(Node)); //生成新节点
if(pNew == NULL)
{
printf("新节点分配失败,程序终止! \n");
exit(-1);
}
pNew->data=val;
pNew->next=NULL; while(r->next != NULL) //让尾指针循环直到最后一个节点
{
r=r->next;
} r->next=pNew;
r=pNew; }
void Showlist(Node * pHead)
{
pHead=pHead->next; //跳过头结点输出
while(pHead!=NULL)
{
printf("%d ",pHead->data);
pHead=pHead->next;
}
} int main(void)
{
Node * L = NULL;
InitHead(&L);
append(L,1);
append(L,4);
append(L,7);
append(L,9);
append(L,332);
append(L,6);
append(L,235);
Showlist(L);
Free_list(L);
L=NULL;
return 0;
}

  

C语言写单链表的创建、释放、追加(即总是在最后的位置增加节点)的更多相关文章

  1. c语言之单链表的创建及排序

    今天对之前学习过的链表知识进行简单的总结顺便写点代码:创建一个链表有头插法跟尾插法两种,在下面代码中我们为结点分配的内存实在堆上分配的,因此需要我们手动释放,释放用free()函数 下面代码贴出具体代 ...

  2. C语言实现单链表-03版

    在C语言实现单链表-02版中我们只是简单的更新一下链表的组织方式: 它没有更多的更新功能,因此我们这个版本将要完成如下功能: Problem 1,搜索相关节点: 2,前插节点: 3,后追加节点: 4, ...

  3. C/C++语言实现单链表(带头结点)

    彻底理解链表中为何使用二级指针或者一级指针的引用 数据结构之链表-链表实现及常用操作(C++篇) C语言实现单链表,主要功能为空链表创建,链表初始化(头插法),链表元素读取,按位置插入,(有序链表)按 ...

  4. C语言实现单链表-02版

    我们在C语言实现单链表-01版中实现的链表非常简单: 但是它对于理解单链表是非常有帮助的,至少我就是这样认为的: 简单的不能再简单的东西没那么实用,所以我们接下来要大规模的修改啦: Problem 1 ...

  5. C语言实现单链表,并完成链表常用API函数

    C语言实现单链表,并完成链表常用API函数: 1.链表增.删.改.查. 2.打印链表.反转打印.打印环形链表. 3.链表排序.链表冒泡排序.链表快速排序. 4.求链表节点个数(普通方法.递归方法). ...

  6. C语言实现单链表节点的删除(带头结点)

    我在之前一篇博客<C语言实现单链表节点的删除(不带头结点)>中具体实现了怎样在一个不带头结点的单链表的删除一个节点,在这一篇博客中我改成了带头结点的单链表.代码演示样例上传至 https: ...

  7. java实现单链表的创建、增、删、改、查

    文章目录 单链表的创建.增.删.改.查 1.增加一个节点 2.删除一个节点 3.修改某一个节点 5.遍历单链表 单链表的创建.增.删.改.查 双向链表的增删改查:https://blog.csdn.n ...

  8. 「C语言」单链表/双向链表的建立/遍历/插入/删除

    最近临近期末的C语言课程设计比平时练习作业一下难了不止一个档次,第一次接触到了C语言的框架开发,了解了View(界面层).Service(业务逻辑层).Persistence(持久化层)的分离和耦合, ...

  9. go语言实现单链表

    线性表包含两种存储方法:顺序存储结构和链式存储结构,其中顺序表的缺点是不便插入与删除数据. 单链表:每个结点包含两部分:数据域+指针域,上一个结点的指针指向下一结点,依次相连,形成链表.特别注意的是每 ...

随机推荐

  1. QT信号和槽

    QT信号和槽 ============ 信号和槽是一种高级接口,应用于对象之间的通信,它是 QT 的核心特性.要正确的处理信号和槽,必须借助一个称为 moc(Meta Object Compiler) ...

  2. John

    John Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others) Total Submissi ...

  3. Good Luck in CET-4 Everybody!

    Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Ja ...

  4. Android 开发笔记___shape

    shape_oval <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android= ...

  5. Linux双网卡绑定实现负载均衡

    系统环境:CentOS release 6.9 (Final) Linux centos6 2.6.32-696.10.1.el6.x86_64 Ubuntu系统下使用ifenslave进行网卡配置, ...

  6. SQL Server 行转列,列转行。多行转成一列

    一.多行转成一列(并以","隔开) 表名:A 表数据: 想要的查询结果: 查询语句: SELECT name , value = ( STUFF(( SELECT ',' + va ...

  7. Python函数篇:装饰器

    装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象.它经常用于有切面需求的场景,比如:插入日志.性能测试.事务处理. ...

  8. 二叉搜索树 (BST) 的创建以及遍历

    二叉搜索树(Binary Search Tree) : 属于二叉树,其中每个节点都含有一个可以比较的键(如需要可以在键上关联值), 且每个节点的键都大于其左子树中的任意节点而小于右子树的任意节点的键. ...

  9. Vim自动补全神器YouCompleteMe的配置

    简介:YouCompleteMe号称Vim的自动补全神器,该项目在github的地址:YouCompleteMe:以下在10.0.1 build-1379776平台配置完成 插件安装操作: 1.确保V ...

  10. c语言的类型、运算符与表达式

    title: 2017-10-17c语言的类型.运算符与表达式 tags: c程序设计语言 grammar_cjkRuby: true --- 1.1 数据类型 char 字符型,一个字节 int 整 ...