C语言-数据结构-2-单链表程序-增删改查

今天学习内容是关于C语言数据结构的单链表程序,其中涉及了结构体、malloc动态分配地址的操作。

首先我们根据程序需要,写出头文件

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1//使scanf函数生效
#include <stdio.h>//标准输入输出函数
#include <stdlib.h>//malloc动态地址分配函数

//原本是使用malloc.h来作为malloc()函数的头文件,但是后续发现malloc.h是非标准扩展,主要在某些Unix/Linux系统中存在,不是C标准的一部分,因此采用C标准库头文件stdlib.h,其中包含了malloc,calloc,realloc,free等内存管理函数。

为什么推荐使用 stdlib.h

  • 标准兼容性 - 符合C标准,在所有编译器上都能工作

  • 可移植性 - 代码可以在不同平台间移植

  • 未来兼容 - 不会被新编译器淘汰


接下来我们回归正题,在完成头文件的选择后,需要使用#define宏定义两个数,用于后续的使用,这里无需深刻理解,等在程序中遇到的时候再回来看

cpp 复制代码
#define NULL 0
#define LEN sizeof(struct student)

int n;//用于计数,当创建一个新的结点时加一

接下来是定义一个结构体student,该结构体包含了num学号,score分数,struct student*next下一个结构体的存储地址。

cpp 复制代码
struct student
{
    int num;
    float score;
    struct student* next;
};

接下来是创建链表的函数creat(),在本函数中创建了head、p1、p2三个结构体指针,其中head表示头结点,而p1作为缓冲指针,接收键盘输入的新数据,然后通过p2连接到链表中。(第一个结点连接的时候head和p2两个指针所指向的结构体是同一个,因此p2->next等同于head->next,并且在后续,p2->next能够重复使用,用于连接下一个新的结点)

模糊点:struct student*的使用原因:函数要返回什么类型的数据,就用什么类型来定义函数的返回类型

cpp 复制代码
struct student* creat()
{
    struct student* head, * p1, * p2;
    n = 0;
    head = NULL;
    p1 = p2 = (struct student*)malloc(LEN);//注意此处的p1和p2结构体指针指向同一个结构体
    scanf_s("%d%f", &p1->num, &p1->score);
    while (p1->num != 0)
    {
        n++;//标识为第n个结点
        if (n == 1)//当链表初次创建时,n=1,将结构体指针p1的地址赋值给结构体指针head
            head = p1;
        else//当链表已经存在一个结点,p2的指向始终是新增结点p1的前一位
            p2->next = p1;//因为前两行head=p1;并且p1和p2的地址相同,因此此处的p2->next等同于head->next;
        p2 = p1;//此处将p2指针更新成新增的结构体地址,而p1在下一行中要被重新分配
        p1 = (struct student*)malloc(LEN);
        scanf_s("%d%f", &p1->num, &p1->score);//若%p1->num为0,当回车按下时,循环会结束,将p2的next设置为空,刚输入未接入链表的p1会被释放内存空间
    }
    p2->next = NULL;
    free(p1);
    return head;//返回头结点的地址
}

开辟一个新的地址存储结构体变量,并且p1和p2指向相同

p1的用法是代表最新结点,p2的用法是代表上一个结点,因此p1和p2的地址在while中总有相同的时候,只不过他们一相同,p1就会指向新的地址

通俗理解就是,p2是p1的专属摄影师,p1走到哪,p2就会给p1拍一张照片,在p1走到下一个地点时,p2会先在刚拍好的照片中写下下一个地点(p2->next)


那么,当你了解如何创建链表,并且能够不看示例,自己写出创建链表的函数之后,删除结点和添加结点的方法就无师自通了,接下来我就不再继续详细讲解删除结点和添加结点的操作了。

文章的最后有全部代码


接下来是删除结点

cpp 复制代码
struct student* del(struct student* head, int num)
{
    struct student* p1, * p2 = NULL;  // 初始化 p2 为 NULL
    if (head == NULL)
    {
        printf("链表为空!\n");
        return head;
    }

    p1 = head;

    // 遍历链表查找要删除的节点
    while (p1 != NULL && p1->num != num)
    {
        p2 = p1;      // p2 始终指向 p1 的前一个节点
        p1 = p1->next;
    }

    if (p1 == NULL)//这个就是上一程序中while循环里的p1!=NULL的情况,意思是链表已经查询到最后一个NULL结点了,还没找到需要查找的学号,就会报告错误并结束程序
    {
        printf("未找到学号为 %d 的节点\n", num);
        return head;
    }

    // 找到要删除的节点
    if (p2 == NULL)   // 删除的是头节点
    {
        head = p1->next;
    }
    else              // 删除的是中间或尾节点
    {
        p2->next = p1->next;
    }

    free(p1);
    n--;
    printf("删除成功!\n");
    return head;
}

接下来是插入结点(按学号顺序插入)

cpp 复制代码
struct student* insert(struct student* head, struct student* stu)
{
    struct student* p0, * p1, * p2;

    p0 = stu;                   // 要插入的新节点
    p1 = head;                  // 从头部开始查找
    p2 = NULL;
    if (head == NULL)            // 空链表情况
    {
        head = p0;
        p0->next = NULL;
        n++;
        return head;
    }

    // 查找插入位置(按学号升序)
    while ((p1->num < p0->num) && (p1->next != NULL))
    {
        p2 = p1;                // p2记录前一个节点
        p1 = p1->next;          // p1指向下一个节点
    }

    if (p0->num <= p1->num)      // 找到插入位置
    {
        if (head == p1)          // 插入到链表头部
            head = p0;
        else                    // 插入到p2和p1之间
           p2->next = p0;

        p0->next = p1;          // 新节点指向p1
        n++;                    // 节点数加1
    }
    else                        // 插入到链表尾部
    {
        p1->next = p0;
        p0->next = NULL;
        n++;
    }

    return head;
}

接下来是输出函数

cpp 复制代码
// 输出链表
void print(struct student* head)
{
    struct student* p;
    printf("\n现在共有 %d 条记录:\n", n);
    p = head;
    if (head != NULL)
    {
        do
        {
            printf("学号:%d,成绩:%.1f\n", p->num, p->score);
            p = p->next;
        } while (p != NULL);//在creat()函数结尾有令最后一个结点的next为空p2->next = NULL;
    }
    else
    {
        printf("链表为空!\n");
    }
}

接下来是主函数

cpp 复制代码
int main()
{
    struct student* p = NULL;
    struct student a, * p0 = &a;
    int xh;

    printf("请输入学号和成绩,直到学号输入0为止:\n");
    p = creat();//return head;
    print(p);

    printf("请输入待删除的学号:\n");
    scanf_s("%d", &xh);
    p = del(p, xh);//return head;
    print(p);

    printf("请输入待插入的学号和成绩:\n");
    scanf_s("%d%f", &p0->num, &p0->score);
    p = insert(p, p0);//return head;
    print(p);

    return 0;
}

接下来是完整代码示范

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

#define NULL 0
#define LEN sizeof(struct student)

int n;

struct student
{
    int num;
    float score;
    struct student* next;
};



// 创建链表
struct student* creat()
{
    struct student* head, * p1, * p2;
    n = 0;
    head = NULL;
    p1 = p2 = (struct student*)malloc(LEN);
    scanf_s("%d%f", &p1->num, &p1->score);
    while (p1->num != 0)
    {
        n++;
        if (n == 1)
            head = p1;
        else
            p2->next = p1;
        p2 = p1;
        p1 = (struct student*)malloc(LEN);
        scanf_s("%d%f", &p1->num, &p1->score);
    }
    p2->next = NULL;
    free(p1);
    return head;
}

// 删除节点 - 修复版本
struct student* del(struct student* head, int num)
{
    struct student* p1, * p2 = NULL;  // 初始化 p2 为 NULL
    if (head == NULL)
    {
        printf("链表为空!\n");
        return head;
    }

    p1 = head;

    // 遍历链表查找要删除的节点
    while (p1 != NULL && p1->num != num)
    {
        p2 = p1;      // p2 始终指向 p1 的前一个节点
        p1 = p1->next;
    }

    if (p1 == NULL)
    {
        printf("未找到学号为 %d 的节点\n", num);
        return head;
    }

    // 找到要删除的节点
    if (p2 == NULL)   // 删除的是头节点
    {
        head = p1->next;
    }
    else              // 删除的是中间或尾节点
    {
        p2->next = p1->next;
    }

    free(p1);
    n--;
    printf("删除成功!\n");
    return head;
}

// 插入节点(按学号顺序插入)
struct student* insert(struct student* head, struct student* stu)
{
    struct student* p0, * p1, * p2;

    p0 = stu;                   // 要插入的新节点
    p1 = head;                  // 从头部开始查找
    p2 = NULL;
    if (head == NULL)            // 空链表情况
    {
        head = p0;
        p0->next = NULL;
        n++;
        return head;
    }

    // 查找插入位置(按学号升序)
    while ((p1->num < p0->num) && (p1->next != NULL))
    {
        p2 = p1;                // p2记录前一个节点
        p1 = p1->next;          // p1指向下一个节点
    }

    if (p0->num <= p1->num)      // 找到插入位置
    {
        if (head == p1)          // 插入到链表头部
            head = p0;
        else                    // 插入到p2和p1之间
           p2->next = p0;

        p0->next = p1;          // 新节点指向p1
        n++;                    // 节点数加1
    }
    else                        // 插入到链表尾部
    {
        p1->next = p0;
        p0->next = NULL;
        n++;
    }

    return head;
}
// 输出链表
void print(struct student* head)
{
    struct student* p;
    printf("\n现在共有 %d 条记录:\n", n);
    p = head;
    if (head != NULL)
    {
        do
        {
            printf("学号:%d,成绩:%.1f\n", p->num, p->score);
            p = p->next;
        } while (p != NULL);//在creat()函数结尾有令最后一个结点的next为空p2->next = NULL;
    }
    else
    {
        printf("链表为空!\n");
    }
}

// 主函数
int main()
{
    struct student* p = NULL;
    struct student a, * p0 = &a;
    int xh;

    printf("请输入学号和成绩,直到学号输入0为止:\n");
    p = creat();//return head;
    print(p);

    printf("请输入待删除的学号:\n");
    scanf_s("%d", &xh);
    p = del(p, xh);//return head;
    print(p);

    printf("请输入待插入的学号和成绩:\n");
    scanf_s("%d%f", &p0->num, &p0->score);
    p = insert(p, p0);//return head;
    print(p);

    return 0;
}
相关推荐
超级无敌大学霸1 小时前
二分查找和辗转相除法
c语言·算法
CryptoRzz2 小时前
印度股票数据 PHP 对接文档 覆盖 BSE(孟买证券交易所)和 NSE(印度国家证券交易所)的实时数据
android·服务器·开发语言·区块链·php
lkbhua莱克瓦242 小时前
集合进阶6——TreeMap底层原理
java·开发语言·笔记·学习方法·hashmap
普通网友2 小时前
内存对齐与缓存友好设计
开发语言·c++·算法
lsx2024062 小时前
DOM 节点信息
开发语言
普通网友2 小时前
C++编译期数据结构
开发语言·c++·算法
Gorgous—l2 小时前
数据结构算法学习:LeetCode热题100-图论篇(岛屿数量、腐烂的橘子、课程表、实现 Trie (前缀树))
数据结构·学习·算法
whatever who cares2 小时前
Java/Android中BigDecimal的相关操作
android·java·开发语言
普通网友3 小时前
嵌入式C++安全编码
开发语言·c++·算法