C 链表的指针入门

C语言学习-链表

(本文对初学者较适宜)
刚开始接触C语言的时候感觉挺简单的,后来发现指针,很抽象,难理解,C语言的特性,就是注重逻辑过程和算法的一种语言,但是刚开始接触嵌入式开发,发现对于一般性的开发,比较牛逼的算法,用不到,所以研究了好长时间的链表的特性,也算是一般性算法的一个入门

学习主要有两点:
1.有助于指针的理解
2.有利于对内存空间的理解,也就是常说的内存管理

网上有好多的链表的讲解,不过写之后发先,不怎么实用,后来在小甲鱼的带领下才看到了对指针和链表深层次的理解。

首先基础的链表,有两种
1.头插法
2.尾插法

在详细讲之前,我发现有人对链表的节点 有些误解,实际上所谓的节点是指结构体的地址,而不是结构体成员的地址,大家有兴趣的可以搜一下linux内核第一宏定义,这玩意很有意思的,可以根据结构体成员的地址找到结构体的地址。
我给大家展示一下第一宏的风采:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define  container_of(ptr, type, member) ({    \
     const typeof( ((type *)0)->member ) *__mptr = (ptr); \
     (type *)( (char *)__mptr - offsetof(type,member) );})
————————————————
版权声明:本文为CSDN博主「confirmwz」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/armlinuxww/article/details/87920310

很精妙的一个写法,大家有兴趣可以看看;

然后开始进入正题:
链表的原理 :
C 链表的指针入门_第1张图片
如图所示,这张图是从百度随便找到的一张图
不管是链表的头插法还是尾插法,基本的结构体如图所示
主要的区别在于接下来的节点所插入的位置不同
1.头插法 第一个节点插入头节点的后面
2.尾插法 第一个节点插入尾节点的后面

不同的插入点导致遍历的链表的顺序相反的:
具体原因是:
我们在遍历链表的时候会发现最后的结果和我们的插入顺序正好都是相反的
这是为什么呢,这其实和插入是的顺序线直接相关,而且直接相关的是插入的位置,
如果对堆栈熟悉的话,是比较容易理解的,他们都是同一种的线性结构的数据结构

头插法的链表代表的是:后进先出的栈结构 (有本书叫做《数据结构》,大家有兴趣可以仔细看看)
尾插法的链表代表的是:先进先出的堆结构

头插法的顺序图:
C 链表的指针入门_第2张图片
尾插法的顺序图:C 链表的指针入门_第3张图片

这就是为什么两者之间遍历的顺序不同,根据如图所示,若果理解了的话可以其实自己尝试去写一写,只有看图写了出来,才能说你掌握了这个知识点。上述两个就是相关的原理。

代码实现如下所示:
头文件

#include //调用“printf,scanf函数”
#include //调用“malloc” 函数

通用结构体

typedef struct Node{
     
	int data;  //方便起见只用一个数字讲解
	struct Node *next; //指向下一个节点的指针
}NODE;

通用读写函数部分

void read_data(NODE *cur){
     
	printf("please input data:");
	scanf("%d",&cur->data); //取地址符号取得是结构体成员的地址,所以要用取地址符号
}

void echo_data(NODE *head)
{
     
	while(*head != NULL){
     
		printf("count : %d data : %d addr: %p \n",count,head->data,head);
		head = head->next; //要养成从右向左读的习惯,而且“=”是赋值,不是等于,弄清楚
	}
}

头插法函数

void add_data(NODE **head){
     
	NODE *cur,*pre;
	cur = (NODE*)malloc(sizeof(NODE));
	if(cur == NULL){
     
		printf("%s %s cur malloc error",__FILE__,__func__);
		exit (1);
	}
	read_data(cur);
	if(*head == NULL){
     
		*head = cur;
		cur->next = NULL;
	}else{
     
		      pre = *head;
		    *head =  cur;
		cur->next =  pre;
	}
}

尾插法函数

void add_data_tail(NODE **head)
{
     
	NODE *cur;
	static NODE *tail = NULL; //当函数调用之后,tail的数据不会被编译器式释放掉,用全局变量也可以实现同样的效果
	cur = (NODE*)malloc(sizeof(NODE));
	
	if(cur == NULL){
     
		printf("malloc error !!! %s %s ",__FILE__,__func__); //调试程序的时候使用,最好写一个log,文件记录相关的错误,这里省掉,__FILE__,__func__,调试用的全局变量,自己可以查查
		exit (1);
	}
	
	if(*head == NULL){
     
			*head = cur;
			cur->next = NULL; //可以简写
	}else{
     
		tail->next = cur;
		cur->next = NULL; //可以简写
	}
					//cur->next = NULL 可以写在这个位置,逻辑上是一样的
		tail = cur;
}

free函数部分

void FREE(NODE **head)
{
     
	NODE *tmp;
	while(*head != NULL ){
     
	tmp = *head;
	*head = (*head)->next;
	free(tmp);
	}
}

gameover 关键是自己写完,多练,多画图,才能自己理解,主函数部分,很简单的,
记住

#include
#include
int main(void)
{
     
	NODE *head = NULL;
	/* 
	 * 自己组件功能代码就可以了
	 */
}

备注:
变量cur:即将插入的
变量pre:前一个插入的
变量head:头节点

当你想要改变一个普通的变量的值,需要用一级指针
当你想要改变一级指针所指向的值,需要用二级指针
当你想要改二级指针所指向的值,需要用三级指针
...............................依次类推
解引用也可以说是同理(后来想明白和数学问题差不多)

你可能感兴趣的:(C语言学习,Linux,c语言,链表,指针)