C语言之数据结构初见篇(5):单链表的介绍(1)

目录

一:单链表的定义

二:两种实现方式

三:核心操作及代码要点

1.定义节点的结构


  • 节点:数据域 + 指针域(指向下一个节点)

  • 头指针:指向第一个节点的指针(链表的入口)

  • 空指针:最后一个节点的next指向NULL

二:两种实现方式

1. 不带头节点的单链表

  • head直接指向第一个数据节点

  • 空链表时 head = NULL

  • 插入/删除第一个节点时需要修改head

2. 带头节点的单链表

  • 头节点:数据域不存有效数据,next指向第一个数据节点

  • 空链表时 head->next = NULL

  • 统一了插入/删除的代码逻辑(不用单独处理第一个节点)

三:核心操作及代码要点

接下来我会从节点的结构、链表的头插、尾插、尾删、头删、查找、销毁等各方面给大家详细介绍所有的知识点

这里的代码和我们之前介绍的顺序表的文件结构差不多,要是创建三个文件,一个头文件,一个测试文件,一个主体文件,下面的话大家自己应该可以分辨出具体是哪一个文件,所以我就不写文件具体是哪一个了

1.定义节点的结构

代码如下:

cpp 复制代码
typedef int SLDataType; //由于数据类不止一种,所以这里重新定义一个数据类型

typedef struct SListNode
{
	SLDataType data; //由于数据类不止一种,所以这里重新定义一个数据类型
	struct SListNode* next; //结构体指针
}SLTNode; //为了SListNode 后续的书写,这里改一个简单的名字 SLTNode

void SLTPrint(SLTNode* phead);// 打印节点数据  phead是头节点参数

代码解析:

cpp 复制代码
typedef int SLDataType; //由于数据类不止一种,所以这里重新定义一个数据类型
  • 作用 :给 int 起了一个别名 SLDataType

  • 意图 :以后如果要修改链表存储的数据类型(比如改成 chardouble),只需要改这一行,不用在代码里到处找 int 去替换

  • 好处:提高了代码的可维护性

cpp 复制代码
typedef struct SListNode
{
    SLDataType data; //由于数据类不止一种,所以这里重新定义一个数据类型
    struct SListNode* next; //结构体指针
}SLTNode; //为了SListNode后续的书写,这里改一个简单的名字 SLTNode
  • typedef struct SListNode 告诉编译器要定义一个结构体类型,名字叫 struct SListNode

  • SLDataType data; 数据域,存储实际数据,类型是上面定义的 SLDataType(也就是int)

  • struct SListNode* next; 指针域,存放下一个节点的地址

结构示意图:

┌─────────────────┐

│ SLTNode │

├────────┬────────┤

│ data │ next │───→ 下一个节点

└────────┴────────┘

cpp 复制代码
void SLTPrint(SLTNode* phead); // 打印节点数据   phead是头节点参数
  • 函数名SLTPrint(Singly Linked List Print)

  • 返回值void 不返回任何值

  • 参数SLTNode* phead 接收链表的头指针

    • 如果传进来的 phead == NULL,说明是空链表

    • 如果不为空,则通过遍历打印所有节点的数据

  • 作用 :遍历链表并打印每个节点的 data 值(用于调试和观察链表状态)

打印链表内容的主体代码如下:

cpp 复制代码
//链表的主体
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

解析:

cpp 复制代码
void SLTPrint(SLTNode* phead)

第1行:函数头

  • 参数SLTNode* phead - 接收链表的头指针

  • 返回值void - 只打印,不返回数据

  • 作用:遍历整个链表并打印每个节点的数据

cpp 复制代码
SLTNode* pcur = phead;

第2行:创建遍历指针

  • 定义一个 pcur 指针,初始指向头节点
cpp 复制代码
while (pcur)
{
    printf("%d->", pcur->data);
    pcur = pcur->next;
}

第3-7行:遍历循环

循环条件while (pcur)

  • 等价于 while (pcur != NULL)

  • pcur 为 NULL 时,说明已经遍历到链表末尾,循环结束

循环体

  1. printf("%d->", pcur->data);

    • 打印当前节点的数据

    • %d 对应 SLDataTypeint 类型

    • -> 是链表打印的常见格式,表示指向下一个

  2. pcur = pcur->next;

    • 核心操作:将指针移动到下一个节点

    • 这是链表遍历的关键一步

执行过程示例

假设链表为:5 -> 12 -> 8 -> NULL

循环次数 pcur指向 打印 执行后pcur指向
初始 节点5 - 节点5
第1次 节点5 5-> 节点12
第2次 节点12 12-> 节点8
第3次 节点8 8-> NULL
第4次 NULL 循环结束 -

最终输出:5->12->8->NULL

节点的手动创建

代码如下:

cpp 复制代码
void SListTest01()
{
	//由于链表是由一个一个的节点组成,所以这里我们先创建几个节点
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode)); //申请空间,强制转换类型
	node1->data = 1;

	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;

	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;

	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

	//此时我们需要将四个节点链接起来
	node1->next = node2;  //结构体的解引用,相当于 int a =b  b=c c=d  d=NULL
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;//这里我们只创建了四个节点,所以最后面是NULL指针

	//调用链表的打印
	SLTNode* plist = node1; //相当于 node1 = plist 这里我们用新的变量去打印方便后面的修改
	SLTPrint(plist); //将node1参数传过去,即plist参数传过去
}

解析:

这个函数演示了最原始、最直观的链表创建方式:

  1. 手动创建4个节点

  2. 给每个节点的data赋值

  3. 手动链接节点(指定next指针)

  4. 调用打印函数验证结果

第4-5行:创建节点1

cpp 复制代码
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode)); //申请空间,强制转换类型
node1->data = 1;
  • malloc(sizeof(SLTNode)):在堆上申请一块足够存放一个节点大小的内存

  • (SLTNode*):将malloc返回的void*强制转换为SLTNode*类型

  • node1->data = 1;:给数据域赋值为1

  • 此时node1的next是随机值(未初始化),不能直接使用

第7-15行:重复上述过程创建node2、node3、node4

  • 分别赋值2、3、4

  • 每个节点的next都还是随机值

第18-21行:链接节点

cpp 复制代码
node1->next = node2; //结构体的解引用,相当于 int a =b b=c c=d d=NULL
node2->next = node3;
node3->next = node4;
node4->next = NULL; //这里我们只创建了四个节点,所以最后面是NULL指针
  • 核心操作:将每个节点的next指针指向下一个节点

  • node1->next = node2:节点1指向节点2

  • node4->next = NULL:最后一个节点的next必须置NULL,否则遍历时会越界

  • 注释中的比喻:int a = b; b = c; c = d; d = NULL 其实不太准确,指针是"指向"关系,不是赋值

链接后的内存结构

node1 node2 node3 node4

┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐

│ 1 │ ──→ │ 2 │ ──→ │ 3 │ ──→ │ 4 │NULL│

└───┴───┘ └───┴───┘ └───┴───┘ └───┴───┘

第24-25行:调用打印函数

cpp 复制代码
//调用链表的打印
SLTNode* plist = node1; //相当于 node1 = plist 这里我们用新的变量去打印方便后面的修改
SLTPrint(plist); //将node1参数传过去,即plist参数传过去
  • SLTNode* plist = node1;:定义plist指向链表的第一个节点node1

  • 注释说"用新的变量去打印方便后面的修改":这是一种好习惯------保留原始的node1指针,用plist去操作

  • SLTPrint(plist);:调用打印函数,预期输出 1->2->3->4->

综上所有的代码,可以得出一下输出结果:

以上的代码都是最原始的节点创建,是非常简单的,我会在后面一节里面会讲解一个更加方便的灵活的节点创建的代码!!!

相关推荐
Bert.Cai2 小时前
Python flush函数作用
开发语言·python
比昨天多敲两行2 小时前
C++ Lsit
开发语言·c++·算法
野犬寒鸦2 小时前
从零起步学习计算机操作系统:I/O篇
服务器·开发语言·网络·后端·面试
易雪寒2 小时前
Java List 根据List中对象的属性值是否相同作为同一组,分割成多个连续的子List
java·数据结构·list·分组切割
姓刘的哦2 小时前
Qt实现蚂蚁线
开发语言·qt
布局呆星2 小时前
Python 文件操作教程
开发语言·python
Elnaij2 小时前
从C++开始的编程生活(23)——哈希表
开发语言·c++
英英_2 小时前
优化 MATLAB MapReduce 程序性能:从基础调优到进阶提速
开发语言·matlab·mapreduce
LSL666_2 小时前
BaseMapper——新增和删除
java·开发语言·mybatis·mybatisplus