C链表笔记

目录

前言(这里全部用伪代码)

1、建立一个链表

2、malloc和free

3、建立一个动态链表

4、建立节点

5、插入节点(头插法)   

6、删除一个节点


前言(这里全部用伪代码)

        链表是可以用来优化管理零散存储区域的一种数据存储结构。一个链表是由若干个单位结构体变量组成的,单位结构变量里面包含了数据和以及结构体的指针。在底层开发,一般物理地址是连续固定的,因此可以将固定的数据写入连续的存储空间(例如数组等)。但是如果上升到系统应用层面,很显然这一种存储数据方式是很不合理的。例如以下的存储区域:虽然第一行数据A1\A2\A3\A4\A5是放在一起连续的数据,但是第二行存放的数据,数据之间的存放并不是连续的。若要存放连续的数据,则要开辟另外连续的存储空间,这样反而更加浪费系统资源。

数据A1数据A2数据A3数据A4数据A5
0xff数据B0xff数据C1数据C2

        而链表却能解决这一个问题。他可以把两个空的存储空间组成一个可以存放占用连续两个地址的数据。通过链表,把存放0xff的地址用某种方法连接起来,用于存放占用两个连续地址的数据。

数据A1数据A2数据A3数据A4数据A5
链表结构1数据B链表结构2数据C1数据C2

1、建立一个链表

        下面是一个结构体,里面有一个uint32_t类型的变量data,和一个node类型的结构体指针next。

struct node
{
    uint32_t data;
    struct node* next;
}

在定义好上面的结构体之后,在这里再定义几个结构体变量。然后把第一个结构体中的结构体指针node1.next取结构体node2的地址,把第二个结构体中的结构体指针node2.next取结构体node3的地址。

struct node node1 = {1,NULL};
struct node node2 = {2,NULL};
struct node node3 = {3,NULL};
node1 -> next = &node2;
node2 -> next = &node3;

        这就成成为了一个有node1、node2、node3组成的静态链表。每个链表单位结构体组成如下:

uint32_t datastruct node* next

        然后上一个单位结构体的指针指向下一个单位结构体的地址。

node1->node2->node3

        在就是链表(这种链表是静态的)的雏形,他有头部node1、中间成员node2以及尾部node3。每个单位结构体通常被称为:节点,一个节点通常包含着数据和指针信息。一般地,静态链表的作用有限,在实际应用中,都是多使用动态链表。

2、malloc和free

        在C的库中,要知道两个函数:malloc和free,就可以控制系统的内存分配,这是后面建立动态链表的工具之一。

        如下,是malloc以及free的使用方法:

uint32_t *p = (uint32_t*)malloc(sizeof(uint32_t));
\*定义一个指针p,然后用malloc开辟一个uint32_t类型的区域给指针p,并且把指针强制转换成uint32_t*类型*\
*p = 1;\\给p指针指向的区域写入一个数据:1
free(p);\\手动释放这个区域的空间
p = NULL;\\指针p用完了,指向NULL防止被其他程序误用

        malloc是向内存(栈堆)申请一块可以存放指定类型数据的存储空间。然后free是把指定的空间释放出去。所以上面的操作,最后p指针指向的区域里面的数据本来是1,后面就释放掉了。可以是这样理解,比如地址0x80fe上面本来有一个变量为uint32_t a,然后用free释放了,系统就可以把这个空间分配给其他变量,例如分给了uint32_t b。所以free释放的只是空间,不是连指针也一起消失的。

        关于栈和堆,栈上面是放的是指针,这些指针指向的却是堆上面的数据。当程序运行完之后相应的栈被销毁,但是,原来这个栈上指针指向的堆数据还在的!!!所以一般一个malloc就和一个free对应,不要数量不等,否则容易出问题。

3、建立一个动态链表

         一般地,静态链表需要在程序运行完之后才会释放空间,这样使得系统的效率,存储空间利用率降低。为此,需要使用动态链表。有下面一段代码,建立了一个动态链表表头。

struct node
{
    uint32_t data;
    struct node* next;
}

struct node* creat_list(void)\\建立一个返回类型为struct node*的函数用来建立动态链表
{
    struct node* head = (struct node*)malloc(sizeof(struct node));
    \\建立一个struct node*类型的指针head,指向一个开辟的内存空间,可以存放struct node类型数据
    head.data = 1;
    head.next = NULL;
    return head;
}

        在这里,首先是建立一个nood的结构体,里面有一个uint32_t类型数据data,一个struct node*类型指针next。然后建立一个返回struct node*类型指针的函数creat_list,用于建立一个链表的表头,这个表头(头节点)里面数据是1,往后一个指针next指向NULL。整个指针函数返回的是表头head的指针。

4、建立节点

        上一步是编写了一个返回表头的指针函数,这一步实现的是编写一个建立节点的函数。在上一步的基础上,再编写一个creat_node函数。

struct node
{
    uint32_t data;
    struct node* next;
}

struct node* creat_list(void)\\建立一个返回类型为struct node*的函数用来建立动态链表
{
    struct node* head = (struct node*)malloc(sizeof(struct node));
    \\建立一个struct node*类型的指针head,指向一个开辟的内存空间,可以存放struct node类型数据
    head.data = 1;
    head.next = NULL;
    return head;
}

struct node* creat_node(uint32_t data1)\\建立一个返回类型为struct node*的函数用来建立节点
{
    struct node* node_1 = (struct node*)malloc(sizeof(struct node));
    \\建立一个struct node*类型的指针node_1,指向一个开辟的内存空间,可以存放struct node类型数据
    head.data = data1;
    head.next = NULL;
    return node_1;
}

         在这里建立的节点的函数和创建表头的函数结构差不多,都是一个结构体里面包含了一个数据和一个指针,然后返回该结构体地址。

5、插入节点(头插法)   

        在上一步的基础上,加入add_node函数。

struct node
{
    uint32_t data;
    struct node* next;
}

struct node* creat_list(void)\\建立一个返回类型为struct node*的函数用来建立动态链表
{
    struct node* head = (struct node*)malloc(sizeof(struct node));
    \\建立一个struct node*类型的指针head,指向一个开辟的内存空间,可以存放struct node类型数据
    head.data = 1;
    head.next = NULL;
    return head;
}

struct node* creat_node(uint32_t data1)\\建立一个返回类型为struct node*的函数用来建立节点
{
    struct node* node_new = (struct node*)malloc(sizeof(struct node));
    \\建立一个struct node*类型的指针node_1,指向一个开辟的内存空间,可以存放struct node类型数据
    head.data = data1;
    head.next = NULL;
    return node_new;
}

void add_node(struct node* head,uint32_t data2)\\插入节点函数,写入要插入的表头,数据
{
    struct node* node_new = creat_node(data2);\\给新定义的struct node*类型结构体node_new开辟新的空间,然后存放数据data2
    node_new.next = head.next;\\把结构体node_new的元素next赋值,内容为表头的next里面的值,其指向一个地址NULL
    head.next = node_new;\\把表头的next赋值,内容为结构体node_new的地址
}

        这一个函数,是把表头里面的指针元素next指向下一个插入节点的地址,然后插入节点的地址就会指向NULL,变成了尾部。如果再插入一个节点,表头里面的指针元素next指向最近插入节点的地址,然后再给最近插入节点里面的data赋值,如此类推。

struct node* list = creat_list();\\建立一个表头
add_node(list,2);\\建立一个节点,插入到list的后面
add_node(list,3);\\建立一个节点,插入到list的后面,上一个节点的排序往后移动
add_node(list,4);\\建立一个节点,插入到list的后面,上一个节点的排序往后移动
add_node(list,5);\\建立一个节点,插入到list的后面,上一个节点的排序往后移动
add_node(list,6);\\建立一个节点,插入到list的后面,上一个节点的排序往后移动

        上述程序运行时的顺序如下表示:

list->node1,2
list->node2,3->node1,2
list->node3,4->node2,3->node1,2

......

        首先建立了一个表头,然后建立第二个节点。然后,建立第三个节点,其位置插入到表头和第二个节点之间。于是前面第二个节点位置往后推,变成了第三个节点,表头位置不变。这个就是头插法。

6、删除一个节点

        假若,在上一步建立了一个表头list以及后面的5个节点。现在需要删除某一个节点,可以建立一个删除节点的函数del_node:

void del_node(struct node* head,uint32_t del_data)\\插入节点函数,写入要插入的表头,数据
{
    struct node* node_del = head.nest;\\定义一个struct node*类型指针node_del,取值为head.nest,表头的下一个地址
    struct node* node_delfront = head;\\定义一个struct node*类型指针node_delfront,内容为head的地址
    if(node_del != NULL)\\判断一下node_del是不是只有表头
    {
        while(node_del.data  != del_data)\\判断当前的节点数据是否与要删除的相同
            {
                node_delfront = node_del;
                node_del = node_delfront.next;
                if(node_del == NULL)\\如果要删除的数据,链表里面没有,结束程序
                {
                    return 0;
                }
            }
            node_delfront.next = node_del.next;\\把要删除节点的上一个节点的元素next赋值,为要删除节点的元素next的值
            free(node_del);\\节点删除完毕后记得释放删除节点的空间
    }
}

上述程序运行的过程如下:

list->node2,3(将要删除)->node1,2
list->node1,2

        过程就是从表头开始历遍整条链表,直到找到要删除的节点。然后把要删除节点的上一个节点里面的next指向的地址变成要删除节点的下一个节点地址,最后把要删除节点的空间释放,不然空间就会被一直占用,不利于空间的利用。下面是伪代码。

struct node* list = creat_list();\\建立一个表头
add_node(list,2);\\建立一个节点,插入到list的后面
add_node(list,3);\\建立一个节点,插入到list的后面,上一个节点的排序往后移动
add_node(list,4);\\建立一个节点,插入到list的后面,上一个节点的排序往后移动
add_node(list,5);\\建立一个节点,插入到list的后面,上一个节点的排序往后移动
add_node(list,6);\\建立一个节点,插入到list的后面,上一个节点的排序往后移动
\*当前结果为: 1 2 3 4 5 6*\
del_node(list,4);\\删除数据为4的节点
\*最后结果为: 1 2 3 5 6*\

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用链表来实现笔记管理,可以实现更加灵活和高效的增删改查操作。下面是一个简单的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> // 笔记结构体 struct Note { char title[50]; // 标题 char content[200]; // 内容 struct Note *next; // 下一个节点指针 }; // 创建新笔记 struct Note *createNote() { struct Note *note = (struct Note*)malloc(sizeof(struct Note)); printf("请输入标题:"); scanf("%s", note->title); printf("请输入内容:"); scanf("%s", note->content); note->next = NULL; return note; } // 添加笔记 void addNote(struct Note **head) { struct Note *newNote = createNote(); if (*head == NULL) { *head = newNote; } else { struct Note *p = *head; while (p->next != NULL) { p = p->next; } p->next = newNote; } printf("笔记添加成功!\n"); } // 编辑笔记 void editNote(struct Note *head) { if (head == NULL) { printf("当前没有笔记!\n"); return; } char title[50]; printf("请输入要编辑的笔记的标题:"); scanf("%s", title); struct Note *p = head; while (p != NULL) { if (strcmp(p->title, title) == 0) { printf("请输入新内容:"); scanf("%s", p->content); printf("笔记编辑成功!\n"); return; } p = p->next; } printf("未找到该笔记!\n"); } // 删除笔记 void deleteNote(struct Note **head) { if (*head == NULL) { printf("当前没有笔记!\n"); return; } char title[50]; printf("请输入要删除的笔记的标题:"); scanf("%s", title); if (strcmp((*head)->title, title) == 0) { struct Note *temp = *head; *head = (*head)->next; free(temp); printf("笔记删除成功!\n"); return; } struct Note *p = *head; while (p->next != NULL) { if (strcmp(p->next->title, title) == 0) { struct Note *temp = p->next; p->next = temp->next; free(temp); printf("笔记删除成功!\n"); return; } p = p->next; } printf("未找到该笔记!\n"); } // 查看笔记 void viewNote(struct Note *head) { if (head == NULL) { printf("当前没有笔记!\n"); return; } printf("所有笔记如下:\n"); struct Note *p = head; while (p != NULL) { printf("标题:%s\n", p->title); printf("内容:%s\n", p->content); p = p->next; } } int main() { struct Note *head = NULL; // 头结点指针初始化为空 int choice; while (1) { printf("请选择操作:\n"); printf("1. 添加笔记\n"); printf("2. 编辑笔记\n"); printf("3. 删除笔记\n"); printf("4. 查看笔记\n"); printf("5. 退出\n"); printf("请输入序号:"); scanf("%d", &choice); switch (choice) { case 1: addNote(&head); // 传入头结点指针的地址 break; case 2: editNote(head); break; case 3: deleteNote(&head); // 传入头结点指针的地址 break; case 4: viewNote(head); break; case 5: printf("退出笔记管理系统!\n"); return 0; default: printf("无效的选择!\n"); break; } } } ``` 上述代码中,我们使用了链表来存储笔记,每个笔记结构体中包含了标题、内容和指向下一个笔记结构体的指针。在添加、编辑、查看和删除笔记时,我们都使用指针来遍历链表,并实现相应的操作。 需要注意的是,在添加和删除笔记时,需要传入头结点指针的地址,因为头结点指针可能会发生变化,我们需要在函数中修改头结点指针的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值