C语言实现链表的基本操作(超详细注释)

第一次学数据结构的时候,c语言基础实在太差,在尝试用c语言来实现的时候一直碰壁,数据结构书里的代码是伪代码,使用的时候各种内存访问冲突,各种锟斤拷烫烫烫,各种变量级别不同。于是我又重新学了一遍c语言,并且又看了一本详细阐述c语言指针有关的书,终于明白了,以前为什么出那么多错。之前一直出错的本质就是没有理解指针。学完了以后,现在已经可以成功使用C语言实现数据结构中的算法了。分享一下我的学习成果,希望和我一样的人也能少走弯路。

我想在函数内完成我想要的操作,之前一直用的是一级指针,可是每次创建完的链表都会内存访问冲突。后来经过深入的学习,我发现了这个错误的本质和c语言中经典的用swap函数交换两个变量的值到主函数中没有交换的错误是一样的。然后我就采用了二级指针,解决了这个问题。

代码中LinkList *Head等价于LNode **Head,
LinkList head等价于LNode *head。
为了便于区分我将二级指针中的头节点的首字母进行了大写。
 

下面是头文件“链表.h”,主要放了链表结构的声明以及相关函数的声明。

//蔚明
//链表.h

/*我想在函数内完成我想要的操作,之前一直用的
是一级指针,可是每次创建完的链表都会内存访问
冲突。后来经过深入的学习,我发现了这个错误的
本质和c语言中经典的用swap函数交换两个变量的值
到主函数中没有交换的错误是一样的。然后我就采用
了二级指针,解决了这个问题,代码中多次用到二级指
针。
代码中LinkList *Head等价于LNode **Head,
LinkList head等价于LNode *head。
为了便于区分我将二级指针中的头节点的首字母进行了大写。*/

#ifndef 链表_H
#define 链表_H

#pragma warning(disable:6031)//在visual studio中使用字符串函数,输入函数时
#pragma warning(disable:4996)//会报错(C4996,C6031),使用这两行代码以后可以正常使用。
//也可以"字符串函数名_s()"的方式使用函数,但需要更多参数。

#include "stdio.h"
#include "stdlib.h"

#define OK 0
#define FAIL -1
#define ENTER printf("\n");

typedef int Elemtype;//可将"int"更换为任意类型变量。

typedef struct LNode//定义一个链表的结构类型。
{
    Elemtype data; //结点的数据域。
    struct LNode* next; //结点的指针域。
}LNode, * LinkList;

int Init_List(LNode** L);//使用这个函数来初始化一个节点。运行成功返回OK。

int Assign_List(LNode** L);//为结点数据域赋值,成功返回OK,此函数可按需改变。

void Get_List(LNode* p, Elemtype* e);//取结点p的数据域。此函数可按需改变

void Print_List(Elemtype e);//打印数据域内容。

int Creat_List(LinkList* Head);//使用这个函数来创造一个链表,运行成功返回OK.

void Read_List(LinkList head);//遍历链表

LNode* Search_List(LinkList head, Elemtype e);//在链表中查找第一个含有元素e的节点,并返回这个结点的地址。

int Insert_List(LinkList* Head, Elemtype e);//在第一个含有元素e的结点后面插入一个新的结点,成功返回OK。

int Delete_List(LinkList* Head, Elemtype e);//删除第一个含有元素e的节点。

void Destroy_List(LinkList* Head);//销毁整个链表。

#endif

在编写这个代码时,注释中有此函数可按需更改。意思是更改这个函数。可以对不同的数据域进行操作。以便于快速的修改整个程序代码。创造不同的链表的目的。

下面是源文件“链表.c”,主要放了链表相关函数的定义。

//蔚明
//链表.c

/*在编写这个代码时,注释中有此函数可按需更改。
意思是更改这个函数可以对不同的数据域进行操作。
以便于快速的修改整个程序代码。创造不同的链表的目的。*/

#include "链表.h"

int Init_List(LNode** L)//使用这个函数来初始化一个节点。运行成功返回OK。
{
    if ((*L = (LNode*)malloc(sizeof(LNode))) == NULL) //为结点分配内存
    {
        puts("内存分配失败!!!");
        exit(1);
    }
    else return OK;
}

int Assign_List(LNode** L)//为结点数据域赋值,成功返回OK,此函数可按需改变
{
    Elemtype data;
    puts("请为结点赋值");
    scanf("%d", &data);
    (*L)->data = data;
    return OK;
}

void Get_List(LNode* p, Elemtype* e)//取结点p的数据域。
{
    *e = p->data;
}

void Print_List(Elemtype e)//打印数据域内容。此函数可按需改变
{
    printf("%d  ", e);
}

int Creat_List(LinkList* Head) //使用这个函数来创造一个链表,运行成功返回OK.
{
    puts("正在创建一个链表。");

    //创建一个只有头结点的空链表。
    if (Init_List(Head) != OK) exit(1); 
    (*Head)->next = NULL;

    LNode* r = *Head; //尾指针初始化并指向头结点。

    char over_symbol = '#'; //结束创建的标志。

    while (1==1)
    {
        //生成新的结点p。
        LNode* p; 
        if (Init_List(&p) != OK) exit(1); //初始化结点p。
        Assign_List(&p); //为新结点数据域赋值。
        p->next = NULL;

        r->next = p; //把新结点插到尾结点之后。
        r = p; //尾指针指向新结点。

        //如果没有getchar()吃掉回车,会直接跳过下面的scanf()函数。
        puts("结束请输入:over,否则请按<Enter>");
        getchar();//吃掉回车。
        scanf("ove%c", &over_symbol); //当输入"over"时跳出循环。
        getchar();//吃掉回车。

        if (over_symbol == 'r') break;//判断是否满足结束条件。
    }

    puts("创建成功");
    return OK;
}

void Read_List(LinkList head)//遍历链表
{
    LNode* read = head->next; //定义一个读取数据域的指针。
    Elemtype e; //定义一个保存数据域的变量

    //寻链向下进行操作。
    while (read != NULL)
    {
        Get_List(read, &e); //把结点数据域内容读到变量e
        Print_List(e); //打印数据域内容
        read = read->next;
    }

    ENTER;
}

LNode* Search_List(LinkList head, Elemtype e) //在链表中查找第一个含有元素e的节点,并返回这个结点的地址。没找到返回NULL。
{
    LNode* read = head->next; //定义一个读取数据域的指针。

    while (read != NULL) //在到达尾结点之前一直循环。
    {
        if (read->data == e) return read; //如果找到就返回,否则寻链向下。
        else read = read->next;
    }

    return NULL; //到了为结点还是没找到,返回NULL。
}

int Insert_List(LinkList* Head, Elemtype e) //在第一个含有元素e的结点后面插入一个新的结点,成功返回OK.
{
    puts("\n正在插入一个结点");

    LNode* read = (*Head)->next; //定义一个读取数据域的指针。
    LNode* pre_read = *Head; //定义一个指向读取数据域的指针的前驱的指针。
    LNode* temp = NULL;

    //查找需要操作的结点。
    while (read != NULL) //在到达尾结点之前一直循环。
    {
        if (read->data == e) break; //如果找到就返回,否则寻链向下。
        else
        {
            read = read->next;
            pre_read = pre_read->next;
        }
    }
    if (read == NULL) return FAIL; //到了为结点还是没找到,返回。
    temp = read; //使用temp临时保存要 操作的结点

    LNode* p = NULL; //生成新的结点p。
    if (Init_List(&p) != OK) exit(1); //初始化结点p。
    Assign_List(&p); //为新结点数据域赋值。
    p->next = temp; //将新结点与原结点后继相连。

    pre_read->next = p;//把原结点的前驱的后继与新结点相连。

    puts("插入成功");
    return OK;
}

int Delete_List(LinkList* Head, Elemtype e) //删除第一个含有元素e的节点,成功返回OK。
{
    puts("\n正在删除一个结点");

    LNode* read = (*Head)->next; //定义一个读取数据域的指针。
    LNode* pre_read = *Head; //定义一个指向读取数据域的指针的前驱的指针。
    LNode* temp = NULL;

    //查找需要操作的结点。
    while (read != NULL) //在到达尾结点之前一直循环。
    {
        if (read->data == e) break; //如果找到就返回,否则寻链向下。
        else
        {
            read = read->next;
            pre_read = pre_read->next;
        }
    }
    if (read == NULL) return FAIL; //到了为结点还是没找到,返回。
    temp = read->next; //使用temp临时保存要 操作的结点的后继。

    free(read); //释放结点的空间。

    pre_read->next = temp; //把此结点前驱和后继相连。

    puts("删除成功");
    return OK;
}

void Destroy_List(LinkList* Head) //销毁整个链表。
{
    puts("\n正在销毁一个链表");

    LNode* p = *Head; //定义一个访问链表结点的指针。
    LNode* temp = p->next; //使用temp临时保存要操作的结点的后继。

    //寻链向下,依次释放内存。
    do {
        free(p); //释放结点的空间。
        p = temp; //结点指向它的后继。
        temp = p->next;
    } 
    while (temp != NULL);
    
    puts("销毁成功");
    return;
}

下面是源文件“main.c”,主函数在这个文件中,主要是测试链表有关函数的功能是否正常。

//蔚明
//main.c

#include"链表.h"

void main()
{
    LinkList head = NULL;
    Elemtype e = 0;
    if (Creat_List(&head) != OK)//创建一个头结点为head的链表。
    {
        puts("创建链表失败!!!");
        exit(1);
    }
    Read_List(head);//读取链表的数据域并打印。

    puts("请输入那个结点前需要插入");
    scanf("%d", &e);
    if(Insert_List(&head, e) != OK)//在链表中指定的结点前插入元素e
    {
    puts("插入失败!!!");
    exit(1);
    }
    puts("插入后的链表");
    Read_List(head);//读取链表的数据域并打印。

    puts("请输入那个结点需要删除");
    scanf("%d", &e);
    if (Delete_List(&head, e) != OK)
    {
        puts("删除失败!!!");
        exit(1);
    }
    puts("删除后的链表");
    Read_List(head);//读取链表的数据域并打印。

    Destroy_List(&head);//释放为这个链表申请的内存。

    return;
}

运行结果如下:

我把Elemtype修改成了一个有名字,年龄的结构体变量,然后略微修改了对变量赋值的函数以及查找该变量的条件判断语句,部分修改如下

//Elemtype由int变为如下
typedef struct person
{
    char name[10];
    int age;
}Elemtype;


//变量的赋值和呈现进行修改
int Assign_List(LNode** L)//为结点数据域赋值,成功返回OK,此函数可按需改变
{
    Elemtype data={"",0};
    puts("请输入名字");
    scanf("%s", data.name);
    getchar();
    puts("请输入年龄");
    scanf("%d", &data.age);
    (*L)->data = data;
    return OK;
}

void Print_List(Elemtype e)//打印数据域内容。此函数可按需改变
{
    printf("姓名:%s,年龄:%d\n", e.name,e.age);
}

//略微修改插入和删除函数中的判断语句

if (read->data == e) break; //改为if (strcmp(read->data.name, e.name)==0) return read;

//略微修改主函数中的赋值语句

scanf("%d",&e);//改为 scanf("%s", e.name);e.age = 0;

得到的运行结果如下

我学C语言大概半年,编写代码的手段可能比较稚嫩,如果写的比较垃圾,欢迎前辈指正。

创作不易,求点赞。

  • 52
    点赞
  • 242
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
/* * 单链表基本操作,包括: * 1. 初始化链表 * 2. 遍历链表 * 3. 插入节点 * 4. 删除节点 */ #include <stdio.h> #include <stdlib.h> // 定义链表节点结构体 typedef struct node { int data; // 数据域 struct node *next; // 指针域,指向下一个节点 } Node; // 初始化链表,返回头节点 Node *initList() { Node *head = (Node*)malloc(sizeof(Node)); // 创建头节点 head->next = NULL; // 头节点的指针域为空 return head; } // 遍历链表 void traverseList(Node *head) { Node *p = head->next; // p指向第一个节点 while (p != NULL) { printf("%d ", p->data); // 输出节点的数据域 p = p->next; // p指向下一个节点 } printf("\n"); } // 插入节点,在第i个位置插入节点,数据域为data void insertNode(Node *head, int i, int data) { Node *p = head; int j = 0; while (p != NULL && j < i-1) { // 找到第i-1个节点 p = p->next; j++; } if (p == NULL || j > i-1) { // 如果i大于链表长度或小于等于0,插入失败 printf("Insert failed.\n"); return; } Node *newNode = (Node*)malloc(sizeof(Node)); // 创建新节点 newNode->data = data; // 设置新节点的数据域 newNode->next = p->next; // 新节点的指针域指向第i个节点 p->next = newNode; // 第i-1个节点的指针域指向新节点 } // 删除节点,删除第i个节点 void deleteNode(Node *head, int i) { Node *p = head; int j = 0; while (p != NULL && j < i-1) { // 找到第i-1个节点 p = p->next; j++; } if (p == NULL || p->next == NULL || j > i-1) { // 如果i大于链表长度或小于等于0,删除失败 printf("Delete failed.\n"); return; } Node *q = p->next; // q指向第i个节点 p->next = q->next; // 第i-1个节点的指针域指向第i+1个节点 free(q); // 释放第i个节点的空间 } // 主函数 int main() { Node *head = initList(); // 初始化链表 insertNode(head, 1, 1); // 在第1个位置插入节点 insertNode(head, 2, 2); // 在第2个位置插入节点 insertNode(head, 3, 3); // 在第3个位置插入节点 traverseList(head); // 遍历链表 deleteNode(head, 2); // 删除第2个节点 traverseList(head); // 遍历链表 return 0; }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值