本人已经大二,大一第一学期就学习了C语言,链表也是那时候就学了。对于学过链表的人来说,觉得很简单;但对于没学过的人来说,觉得概念很难理解。想起以前学习的过程,自学时在网上搜到的讲解,要么只讲代码,要么讲的思路很难理。为了赚积分升二级拿模板,从初学者的角度出发慢慢讲解,请慢慢看。如有大神路过看见有误,还请大神指点一二。
1.重温结构体
我想既然你接触了指针,那就一定学过结构体了。下面定义了一个结构体,typedef代表重命名;struct代表结构体,是一种固定的语法;大括号中的各种类型代表在这个结构体中有那些类型的数据,这个随便定义,定义什么类型、定义多少个都行;XUE代表该结构体重命名的新名字,如果开头没有typedef关键字就不能写。
typedef struct xuesheng //学生基本信息结构体
{
char xuehao[3]; //学号
char xingming[10]; //姓名
int age; //年龄
struct xuesheng *next; //指针域
}XUE;
那么这样定义的结果是什么?在思维上理解为,在内存中开辟了一个空间,空间的大小根据定义数据类型的大小而开辟,比如char代表一个字节,但char xuehao[3]就代表3个字节,思维上理解为下图所示,开辟之后就把该整块空间命名为XUE。
可能你会疑惑,struct xuesheng *next是什么意思,有什么作用?struct xuesheng代表一种类型,和char、int一个性质,*next代表指针,连起来就是,struct xuesheng类型的指针,名字为next。具体作用后面第4步再讲,嘻嘻
此时你可能又疑惑了,struct xuesheng类型占多大的空间,不然怎么在内存中开辟固定大小的空间?首先,你得知道指针本质是一个内存地址,里面记录的是某处内存的地址。然后指针也有类型,如int、double、char等。既然是地址,所以她的大小在win32中是4个字节,在win64中就是8个字节。
此时你可能又感到疑惑,指针不是有类型吗?char型不是只占一个字节吗?因为指针的类型并不决定指针的空间大小,只是决定该指针能指向什么类型的变量。C语言是强类型的语言,指针也是一种变量,如果变量没有类型,呵呵。。。
2.malloc函数的详解
假设我想存储班级的信息,学号、姓名、年龄都有了,可是使用静态的方式定义一个结构体的话,就要为每个人定义一个结构体变量,如果班级有一百多号人,那就要定义一百个变量?NO,C语言有动态管理内存的函数malloc,该函数能在内存堆中开辟指定大小的空间。比如,我想开辟上面定义的结构体大小的空间就这么写
(XUE*)malloc(sizeof(XUE))
第一个小括号代表开辟了该块空间之后返回什么类型,(XUE*)代表返回XUE结构体指针类型(之前已经将结构体重命名为XUE了),如果你不想写重命名后的名字,可以这样写(struct xuesheng*),记住一定是返回指针类型,也不能写其它的指针类型,如int *、char *等都是不行的,因为前面已经说过的类型决定该指针能指向什么类型的变量,你想想,你开辟了该结构体类型而返回整型,那样能行吗。
第二个小括号代表开辟空间的大小。sizeof()也是一个函数,该函数用于统计某变量的空间大小,sizeof(XUE)的作用就是统计XUE结构体的大小。
3.创建链表的思维
都知道数组是线性表,在内存中的思维上可以这样理解
这样寻址很方便,只要指出下标就能找到某个位置,可是缺点也很明显,就是它的大小一开始就要定义好,之后就不能改变了,不够灵活,而且开辟时一定要找一个比它大的空闲空间。
假设一个内存100M大小,运行10个10M大小的app,内存用完,此时5号程序和7号程序都关闭释放了原来占用的内存,此时空闲20M内存,并且两块空间分隔开的,当我想申请一个20M的空间时,如果像数组一样申请是不能够申请的,因为数组要连续的空间,不能分隔开。
所以链表就诞生了,链表能在5号处申请该块空间,再在7号处申请空间,然后把他们连起来,OK,完事
4.代码实现及讲解
先贴上代码,如果看注释看不懂,可以看后面的讲解
#include<stdio.h>
typedef struct xuesheng //学生基本信息结构体
{
char xuehao[3]; //学号
char xingming[10]; //姓名
int age; //年龄
struct xuesheng *next; //指针域
}XUE;
int main()
{
XUE *head,*yidong,*end;//定义结构体指针
head=(XUE*)malloc(sizeof(XUE));//使用head指针接收开辟空间后返回的地址
yidong=head;//将head指针存储的地址赋给yidong指针,即他们都指向开辟空间的地址
for(i=1;i<=3;i++)//开辟三个节点
{
end=(XUE*)malloc(sizeof(XUE));//继续用end指针接收新开辟空间后返回的地址
printf("请输入学号:");
scanf("%s",end->xuehao);//使用该节点存储数据
printf("请输入姓名:");
scanf("%s",end->xingming);//使用该节点存储数据
printf("请输入年龄:");
scanf("%d",end->age);//使用该节点存储数据
yidong->next=end;//将yidong指向空间中的next变量指向end指针指向的空间
yidong=end;//yidong指针重新指向end指针指向的空间
}
return 0;
}
定义结构体指针就是简单的定义语句,定义了三个指针变量。看名字你应该有一点点想法了,哈哈
head指向开辟空间,如下图。注意是指向了整个空间,而不是指向了某个变量。这一整个空间又称为节点
yidogn=head之后就如下图,yidong也指向了该节点
开始循环,循环3次,说明我想创建三个节点。
之后end指向开辟的空间,内存的使用如下图
接着我使用scanf语句向里面输入数据,如下图
yidong->next=end的效果如下图,next变量指向了end指向的节点。注意,是指针变量,前面挖了一个坑说后面再说,next是struct xuesheng *类型,而节点就是这个类型,所以能够指向别的节点。
接着yidong=end的效果如下图。为什么能这么容易的寻找到别的节点并赋值?这就是指针的强大,指针指向了该块地址之后就能轻易的寻找和修改该块地址的东西
此时第一次循环结束,第二次循环结束后如下图
第三次循环结束后如下图
注意:
- 我上面的next中没有写东西并不是代表它没有存储数据,它里面存储的是指向某节点的地址。但是最后一个节点next没有存储东西,因为它后面没有东西可指了。
- end每次循环都指向开辟的新节点,而上一个节点由yidong提供,当yidong提供完地址之后,即使yidong->next指向end之后,yidong就指向end的节点。
end
因为有个空的节点,所以上面建的链表又称为带头节点链表
因为指针的方向只有一个,所以又称为单向链表
链表能充分利用内存的空间,灵活开辟空间和使用,但也有一个缺点,就是查找某个节点的时候要从头开始(双向链表除外)
如果觉得对你有用,给个👍