【数据结构实战】:基于C语言单链表实现红旗渠景区年卡信息管理系统

本文将以红旗渠景区年卡 VIP 信息管理 为实际场景,使用 C 语言实现基于带头结点单链表的信息管理系统,完成用户信息的录入、浏览、查找、插入、删除、排序、统计等核心功能,同时解决开发中的内存泄漏、边界判断、数据容错等问题,最终实现一个完整、健壮的小型管理系统。

一、项目背景与开发目标

1.1 项目背景

红旗渠作为国家 5A 级旅游景区,推出了 98 元全年不限次的旅游年卡,为了高效管理年卡 VIP 用户的信息,需要开发一套轻量级的信息管理系统,支持用户信息的全流程操作,本项目采用单链表作为核心数据结构,适配景区小规模用户信息的存储与管理需求。

1.2 开发目标

  1. 掌握线性表链式存储(单链表)的核心实现,包括节点定义、链表初始化、增删改查等操作;
  2. 针对实际业务场景完成数据抽象,定义符合需求的用户信息结构体;
  3. 解决 C 语言开发中的常见问题:内存分配、内存泄漏、野指针、输入容错、边界判断;
  4. 实现完整的系统菜单,支持用户交互式操作,完成从代码编写到系统运行的全流程开发。

二、项目核心设计

2.1 数据抽象:定义用户信息与链表节点结构体

首先针对年卡 VIP 用户的核心信息进行数据抽象,定义存储用户信息的ElemType结构体,包含姓名、性别、身份证号、手机号 四大核心字段;再定义单链表的节点结构体Node,包含数据域 (存储用户信息)和指针域(指向下一个节点)

cpp 复制代码
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

// 定义人员信息结构体:存储年卡用户核心信息
typedef struct {
    char name[10];    // 姓名(10字符满足常规姓名存储)
    char sex[10];     // 性别(预留冗余,存储男/女)
    char idcard[20];  // 身份证号(18位+字符串结束符)
    char tele[12];    // 手机号(11位+字符串结束符)
} ElemType;  

// 定义链表节点结构体:数据域+指针域
typedef struct Node{
    ElemType data;    // 数据域:存储人员信息
    struct Node *next;// 指针域:指向下一个节点
}Node;   

2.2 数据结构选择:带头结点的单链表

本项目选用带头结点的单链表 作为核心存储结构,相比不带头结点的单链表,其核心优势在于:统一空表和非空表的操作逻辑,避免频繁的边界条件判断(如插入 / 删除第一个节点时的特殊处理),降低代码复杂度,提升程序健壮性。

带头结点单链表的结构特点:

  • 头结点不存储实际用户数据,仅用于指向第一个数据节点;
  • 空表时,头结点的指针域直接置NULL
  • 所有数据节点均在头结点之后,操作时从头部开始遍历即可。

三、核心功能实现:从链表初始化到功能开发

本系统的核心功能围绕单链表的操作展开,所有函数均以链表头指针Node *L为核心参数,以下为关键功能的代码实现与核心解析。

3.1 链表初始化:Init

为头结点分配内存,初始化头结点的指针域为NULL,返回初始化后的头指针,是所有链表操作的前置步骤 。增加内存分配失败判断,避免程序崩溃。

cpp 复制代码
// 初始化带头结点的单链表
Node * Init(Node *L){ 
    L= (Node*) malloc(sizeof(Node)); // 为头结点分配内存
    if(L == NULL){ // 内存分配失败容错
        printf("内存分配失败!\n");
        exit(1);
    }
    L->next = NULL; // 头结点指针域置空,链表初始为空
    return L;
}

3.2 链表创建:Creat(头插法录入用户信息)

采用头插法 实现 n 个用户信息的批量录入,头插法的优势是插入逻辑简单、时间复杂度 O (1) ,缺点是录入顺序与存储顺序相反,后续可通过排序功能修正。增加输入人数合法性判断(必须为正整数),解决原代码无容错的问题。

cpp 复制代码
// 创建链表:头插法录入n个用户信息
void Creat(Node *L){  
    int n;
    printf("please input the number of people:\n"); 
    scanf("%d",&n);
    if(n <= 0){ // 输入人数合法性判断
        printf("人数输入错误,需输入正整数!\n");
        return;
    }
    Node *s;  // 定义新节点指针
    printf("--- * * * * * * * * * * * * *---\n");
    printf("please input:\n1. name 2. sex 3.idcard 4. tele\n");
    int i;
    for(i=0;i<n;i++){
        s= (Node*) malloc(sizeof(Node)); // 为新节点分配内存
        if(s == NULL){
            printf("内存分配失败!\n");
            exit(1);
        }
        // 按格式录入用户信息:空格分隔
        scanf("%s%s%s%s",s->data.name,s->data.sex,s->data.idcard,s->data.tele); 
        s->next = L->next; // 新节点指向原头结点的后继节点
        L->next = s;       // 头结点指向新节点,完成插入
    }
    printf("录入完成\n");
}

3.3 信息浏览:show(遍历链表)

遍历带头结点的单链表,跳过头结点 从第一个数据节点开始,逐行打印用户信息,增加空表判断 ,避免空链表遍历导致的错误。使用制表符\t对齐输出,提升信息可读性。

cpp 复制代码
// 展示所有用户信息:遍历带头结点单链表
int show(Node*L) 
{ 
    printf("-----------------------\n");
    printf("people information :\n");
    Node *p = L->next; // p指向第一个数据节点(跳过表头)
    if(!p){ // 空表判断
        printf("null\n");
        return 0;
    }
    // 打印表头,制表符对齐
    printf("name\tsex\tidcard\t\t\ttele:\n");
    while(p){ // 遍历至链表尾(p==NULL结束)
        printf("%s\t%s\t%s\t%s\n",p->data.name,p->data.sex,p->data.idcard,p->data.tele);
        p = p->next; // p指针后移
    }
    printf("-----------------------\n");
    return 1;
}

3.4 精准查找:Locate(按身份证号查找)

身份证号 为唯一标识(年卡用户的核心唯一字段),遍历链表逐个比对身份证号,找到匹配节点后打印用户信息,未找到则给出提示。使用strcmp函数实现字符串比对,是实际业务中精准查询的核心实现。

cpp 复制代码
// 声明链表长度函数:用于查找/删除的合法性判断
int Length(Node *L);

// 按身份证号查找用户:核心功能,精准匹配年卡用户
void Locate(Node *L , char *m){ 
    int i = 1;          // 记录节点位置
    Node *p = L->next;  // p指向第一个数据节点
    // 循环条件:p不为空 且 身份证号不匹配(strcmp=0为匹配)
    while(p && strcmp(p->data.idcard, m)){
        p = p->next;
        i++;
    } 
    // 位置超过链表长度,说明无该用户
    if(i > Length(L)){
        printf("无该人员信息!\n");
    } else {
        printf("该人员相关信息如下:\n");
        printf("%s %s %s %s\n",p->data.name,p->data.sex,p->data.idcard,p->data.tele);
    }
} 

3.5 插入与删除:input & Delete(按位置操作)

插入和删除是单链表的核心操作,均按位置实现 ,利用带头结点的优势,从表头开始找到目标位置的前驱节点,再完成插入 / 删除,避免对首节点的特殊处理。

3.5.1 按位置插入:input
cpp 复制代码
// 插入用户信息:按位置插入(带头结点)
int input(Node *L ,int i,ElemType x){ 
    Node *p = L;        // p从表头开始(带头结点关键)
    int j = 0;          // 记录p的当前位置
    // 找到第i-1个节点:循环条件保证位置合法
    while((j+1 < i) && p){
        p = p->next;
        j++;
    }
    // 位置合法性判断:p为空 或 插入位置超出链表长度+1
    if(!p || (j+1) > i){
        printf("插入信息位置错误!\n");
        return 0;
    }
    Node *s = (Node*)malloc(sizeof(Node)); // 为新节点分配内存
    if(s == NULL){
        printf("内存分配失败!\n");
        exit(1);
    }
    s->data = x;        // 赋值新节点数据域
    s->next = p->next;  // 新节点指向原i-1节点的后继
    p->next = s;        // i-1节点指向新节点,完成插入
    printf("插入完成\n");
    return 1;
} 
3.5.2 按位置删除:Delete

删除操作的核心是释放待删除节点的内存 ,并将野指针置空,解决内存泄漏问题,这是 C 语言开发中极易忽略的点。

cpp 复制代码
// 删除用户信息:按位置删除(带头结点)
int Delete(Node *L,int i){  
    Node *p = L;        // p从表头开始
    int j = 0;          // 记录p的当前位置
    // 找到第i-1个节点:同时保证i节点存在(p->next不为空)
    while((j+1 < i) && p->next){
        p = p->next;
        j++;
    }
    // 位置合法性判断:i节点不存在 或 位置超出范围
    if(!(p->next) || (j+1) > i){
        printf("删除位置错误!\n");
        return 0;
    }
    Node *s = p->next;  // s指向待删除的i节点
    p->next = s->next;  // i-1节点指向i+1节点,跳过待删除节点
    free(s);            // 释放待删除节点内存,解决内存泄漏
    s = NULL;           // 野指针置空,避免内存访问错误
    printf("删除完成\n");
    return 1; 
}

3.6 统计与排序:Length & sort

3.6.1 统计用户总数:Length

遍历链表,统计有效数据节点的数量(不含头结点),为查找、删除的合法性判断提供依据,也是系统统计 VIP 人数的核心功能。

cpp 复制代码
// 计算链表长度:统计有效用户数(不含头结点)
int Length(Node*L){  
    int length = 0;
    Node *s = L->next;  // s指向第一个数据节点
    while(s){           // 遍历所有数据节点
        s = s->next;
        length++; 
    }
    return length; 
}
3.6.2 按身份证号升序排序:sort(冒泡排序)

采用冒泡排序 实现按身份证号的升序排列,通过交换节点的数据域 而非指针域,降低实现难度,适合景区小规模用户数据的排序。增加空表 / 单节点判断,避免无意义的排序操作。

cpp 复制代码
// 冒泡排序:按身份证号升序排列(交换节点数据域,非节点指针)
void sort(Node *L){
    Node *p;
    int len = Length(L);
    ElemType tep;       // 临时结构体,用于数据交换
    if(len <= 1){       // 空表/单节点,无需排序
        printf("无需排序(节点数≤1)\n");
        return;
    }
    for(int i=0; i<len-1; i++){// 排序趟数:n个节点需n-1趟
        p = L->next ;
        for(int j=0; j<len-1-i; j++){// 每趟比较次数:逐趟减少
            if(strcmp(p->data.idcard , p->next->data.idcard )>0){
                // 交换姓名
                strcpy(tep.name , p->data.name);
                strcpy(p->data.name , p->next->data.name);
                strcpy(p->next->data.name , tep.name);
                // 交换性别
                strcpy(tep.sex , p->data.sex);
                strcpy(p->data.sex , p->next->data.sex);
                strcpy(p->next->data.sex , tep.sex);
                // 交换身份证号
                strcpy(tep.idcard , p->data.idcard);
                strcpy(p->data.idcard , p->next->data.idcard);
                strcpy(p->next->data.idcard , tep.idcard);
                // 交换手机号
                strcpy(tep.tele , p->data.tele);
                strcpy(p->data.tele , p->next->data.tele);
                strcpy(p->next->tele , tep.tele);
            }
            p = p->next ;
        }
    }
    printf("排序完成\n");
}

3.7 内存释放:FreeList(避免内存泄漏)

新增链表内存整体释放 函数,在程序退出前调用,从头结点开始 逐个释放所有节点的内存,包括头结点和数据节点,彻底解决 C 语言动态内存分配导致的内存泄漏问题,这是原代码的核心缺失功能,也是工业级开发的必备要求。

cpp 复制代码
// 释放整个链表内存(头结点+所有数据节点)
void FreeList(Node *L){
    Node *p = L;        // p指向当前节点
    Node *q;            // q指向后继节点,用于临时保存
    while(p){
        q = p->next;    // 先保存后继节点,避免释放后丢失
        free(p);        // 释放当前节点
        p = q;          // p指针后移
    }
    L = NULL;           // 头指针置空
    printf("链表内存已全部释放\n");
}

四、系统菜单与主函数实现

设计交互式系统菜单 ,通过while(1)死循环实现菜单的循环展示,用户通过输入序号选择操作,使用switch-case分支实现功能调用,操作后即时展示结果(如插入 / 删除后立即浏览信息),提升用户体验。增加非法输入处理,提示用户输入有效序号。

cpp 复制代码
// 主函数:系统菜单+功能调用
int main(){
    ElemType y;         // 存储插入的用户信息
    char m[20];         // 存储查找的身份证号(20字符适配18位身份证)
    Node *List = Init(List); // 初始化链表,List为头指针
    int i;              // 存储插入/删除位置
    int ch;             // 存储用户菜单选择
    while(1){ // 死循环:直到用户选择0退出
        printf("\n****************************\n");
        printf("红旗渠景区年卡信息管理系统:\n");
        printf("1.录入用户信息\n");
        printf("2.浏览用户信息\n");
        printf("3.查找用户信息\n");
        printf("4.添加用户信息\n");
        printf("5.删除用户信息\n");
        printf("6.排列用户信息\n");
        printf("7.统计用户总人数\n");
        printf("0.退出系统\n");
        printf("****************************\n"); 
        printf("输入要进行操作所对应的序号:\n");
        scanf("%d",&ch);
        // 分支选择:匹配用户操作
        switch(ch){
            case 1:{ 
                Creat(List);
                break;
            }
            case 2:{ 
                show(List);
                break;
            }
            case 3:{ 
                printf("输入要查找人员的身份证号信息:\n");
                scanf("%s",m);
                Locate(List,m);
                break;
            }
            case 4:{
                printf("输入要插入的位置:\n");
                scanf("%d",&i);
                printf("输入要插入人员信息(姓名 性别 身份证 手机号):\n");
                scanf("%s%s%s%s",y.name,y.sex,y.idcard,y.tele);
                input(List,i,y);
                show(List); // 插入后展示,直观看到结果
                break;
            }
            case 5:{
                printf("输入删除位置:\n");
                scanf("%d",&i);
                Delete(List,i);
                show(List); // 删除后展示,直观看到结果
                break;
            }
            case 6:{
                sort(List);
                show(List); // 排序后展示,直观看到结果
                break;
            }
            case 7:{
                int len=Length(List);
                printf("VIP总人数为:%d\n",len); 
                break;
            }
            case 0:{
                FreeList(List); // 退出前释放所有内存
                printf("系统已退出!\n");
                exit(0);       // 正常退出程序
            }
            default:{ // 非法输入处理
                printf("请输入有效序号(0-7)\n");
                break;
            }
        } 
    }
    return 0;
}

完整代码如下:

cpp 复制代码
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

// 定义人员信息结构体:存储年卡用户核心信息
typedef struct {
    char name[10];    // 姓名(10字符足够存储常规姓名)
    char sex[10];     // 性别(男/女,预留冗余)
    char idcard[20];  // 身份证号(18位+结束符,原代码无错误)
    char tele[12];    // 手机号(11位+结束符,原代码无错误)
} ElemType;  

// 定义链表节点结构体:数据域+指针域
typedef struct Node{
    ElemType data;    // 数据域:存储人员信息
    struct Node *next;// 指针域:指向下一个节点
}Node;   

// 初始化带头结点的单链表
// 带头结点:统一空表/非空表操作,避免边界判断
Node * Init(Node *L){ 
    L= (Node*) malloc(sizeof(Node)); // 为头结点分配内存
    if(L == NULL){ // 内存分配失败判断:增加程序健壮性
        printf("内存分配失败!\n");
        exit(1);
    }
    L->next = NULL; // 头结点指针域置空,链表初始为空
    return L;
}

// 创建链表:头插法录入n个用户信息
// 头插法:插入简单,缺点是录入顺序与存储顺序相反(后续可通过排序修正)
void Creat(Node *L){  
    int n;
    printf("please input the number of people:\n"); 
    scanf("%d",&n);
    if(n <= 0){ // 输入人数合法性判断:修复原代码无容错问题
        printf("人数输入错误,需输入正整数!\n");
        return;
    }
    Node *s;  // 定义新节点指针
    printf("--- * * * * * * * * * * * * *---\n");
    printf("please input:\n1. name 2. sex 3.idcard 4. tele\n");
    int i;
    for(i=0;i<n;i++){
        s= (Node*) malloc(sizeof(Node)); // 为新节点分配内存
        if(s == NULL){
            printf("内存分配失败!\n");
            exit(1);
        }
        // 录入用户信息:空格分隔,需严格按格式输入
        scanf("%s%s%s%s",s->data.name,s->data.sex,s->data.idcard,s->data.tele); 
        s->next = L->next; // 新节点指向原头结点的后继节点
        L->next = s;       // 头结点指向新节点,完成插入
    }
    printf("录入完成\n");
}

// 展示所有用户信息:遍历带头结点单链表
int show(Node*L) 
{ 
    printf("-----------------------\n");
    printf("people information :\n");
    Node *p = L->next; // p指向第一个数据节点(跳过表头)
    if(!p){ // 链表为空判断
        printf("null\n");
        return 0;
    }
    // 打印表头,\t为制表符,对齐输出
    printf("name\tsex\tidcard\t\t\ttele:\n");
    while(p){ // 遍历至链表尾(p==NULL结束)
        printf("%s\t%s\t%s\t%s\n",p->data.name,p->data.sex,p->data.idcard,p->data.tele);
        p = p->next; // p后移
    }
    printf("-----------------------\n");
    return 1;
}

// 声明链表长度函数:用于查找/删除的合法性判断
int Length(Node *L);

// 按身份证号查找用户:核心功能,精准匹配年卡用户
void Locate(Node *L , char *m){ 
    int i = 1;          // 记录节点位置
    Node *p = L->next;  // p指向第一个数据节点
    // 循环条件:p不为空 且 身份证号不匹配(strcmp=0为匹配)
    while(p && strcmp(p->data.idcard, m)){
        p = p->next;
        i++;
    } 
    // 若位置超过链表长度,说明无该用户(修复原代码逻辑无问题,优化判断描述)
    if(i > Length(L)){
        printf("无该人员信息!\n");
    } else {
        printf("该人员相关信息如下:\n");
        printf("%s %s %s %s\n",p->data.name,p->data.sex,p->data.idcard,p->data.tele);
    }
} 

// 插入用户信息:按位置插入(带头结点)
// 参数:L-链表头指针,i-插入位置,x-插入的人员信息
int input(Node *L ,int i,ElemType x){ 
    Node *p = L;        // p从表头开始(带头结点关键)
    int j = 0;          // 记录p的当前位置
    // 找到第i-1个节点:循环条件保证位置合法
    while((j+1 < i) && p){
        p = p->next;
        j++;
    }
    // 位置合法性判断:p为空 或 插入位置超出链表长度+1
    if(!p || (j+1) > i){
        printf("插入信息位置错误!\n");
        return 0;
    }
    // 为新节点分配内存
    Node *s = (Node*)malloc(sizeof(Node)); 
    if(s == NULL){
        printf("内存分配失败!\n");
        exit(1);
    }
    s->data = x;        // 赋值新节点数据域
    s->next = p->next;  // 新节点指向原i-1节点的后继
    p->next = s;        // i-1节点指向新节点,完成插入
    printf("插入完成\n");
    return 1;
} 

// 删除用户信息:按位置删除(带头结点)
int Delete(Node *L,int i){  
    Node *p = L;        // p从表头开始
    int j = 0;          // 记录p的当前位置
    // 找到第i-1个节点:同时保证i节点存在(p->next不为空)
    while((j+1 < i) && p->next){
        p = p->next;
        j++;
    }
    // 位置合法性判断:i节点不存在 或 位置超出范围
    if(!(p->next) || (j+1) > i){
        printf("删除位置错误!\n");
        return 0;
    }
    Node *s = p->next;  // s指向待删除的i节点
    p->next = s->next;  // i-1节点指向i+1节点,跳过待删除节点
    free(s);            // 释放待删除节点内存:修复原代码内存泄漏问题
    s = NULL;           // 野指针置空,避免内存访问错误
    printf("删除完成\n");
    return 1; 
}

// 计算链表长度:统计有效用户数(不含头结点)
int Length(Node*L){  
    int length = 0;
    Node *s = L->next;  // s指向第一个数据节点
    while(s){           // 遍历所有数据节点
        s = s->next;
        length++; 
    }
    return length; 
}

// 冒泡排序:按身份证号升序排列(交换节点数据域,非节点指针)
// 优点:实现简单;缺点:效率一般,适合少量数据(年卡管理足够)
void sort(Node *L){
    Node *p;
    int len = Length(L);
    ElemType tep;       // 临时结构体,用于数据交换
    if(len <= 1){       // 空表/单节点,无需排序:修复原代码无判断问题
        printf("无需排序(节点数≤1)\n");
        return;
    }
    for(int i=0; i<len-1; i++){// 排序趟数:n个节点需n-1趟
        p = L->next ;
        for(int j=0; j<len-1-i; j++){// 每趟比较次数:逐趟减少(冒泡特性)
            if(strcmp(p->data.idcard , p->next->data.idcard )>0){
                // 交换两个节点的data域(姓名)
                strcpy(tep.name , p->data.name);
                strcpy(p->data.name , p->next->data.name);
                strcpy(p->next->data.name , tep.name);
                // 交换性别
                strcpy(tep.sex , p->data.sex);
                strcpy(p->data.sex , p->next->data.sex);
                strcpy(p->next->data.sex , tep.sex);
                // 交换身份证号
                strcpy(tep.idcard , p->data.idcard);
                strcpy(p->data.idcard , p->next->data.idcard);
                strcpy(p->next->data.idcard , tep.idcard);
                // 交换手机号
                strcpy(tep.tele , p->data.tele);
                strcpy(p->data.tele , p->next->data.tele);
                strcpy(p->next->data.tele , tep.tele);
            }
            p = p->next ;
        }
    }
    printf("排序完成\n");
}

// 新增:释放整个链表内存(头结点+所有数据节点)
// 程序退出前调用,避免内存泄漏:原代码核心缺失功能
void FreeList(Node *L){
    Node *p = L;        // p指向当前节点
    Node *q;            // q指向后继节点,用于临时保存
    while(p){
        q = p->next;    // 先保存后继节点,避免释放后丢失
        free(p);        // 释放当前节点
        p = q;          // p后移
    }
    L = NULL;           // 头指针置空
    printf("链表内存已全部释放\n");
}

// 主函数:系统菜单+功能调用
int main(){
    ElemType y;         // 存储插入的用户信息
    char m[20];         // 存储查找的身份证号:修复原代码仅10字符,无法存18位身份证
    Node *List = Init(List); // 初始化链表,List为头指针
    int i;              // 存储插入/删除位置
    int ch;             // 存储用户菜单选择
    while(1){ // 死循环:直到用户选择0退出
        printf("\n****************************\n");
        printf("红旗渠景区年卡信息管理系统:\n");
        printf("1.录入用户信息\n");
        printf("2.浏览用户信息\n");
        printf("3.查找用户信息\n");
        printf("4.添加用户信息\n");
        printf("5.删除用户信息\n");
        printf("6.排列用户信息\n");
        printf("7.统计用户总人数\n");
        printf("0.退出系统\n");
        printf("****************************\n"); 
        printf("输入要进行操作所对应的序号:\n");
        scanf("%d",&ch);
        // 分支选择:匹配用户操作
        switch(ch){
            case 1:{ 
                Creat(List);
                break;
            }
            case 2:{ 
                show(List);
                break;
            }
            case 3:{ 
                printf("输入要查找人员的身份证号信息:\n");
                scanf("%s",m);
                Locate(List,m);
                break;
            }
            case 4:{
                printf("输入要插入的位置:\n");
                scanf("%d",&i);
                printf("输入要插入人员信息(姓名 性别 身份证 手机号):\n");
                scanf("%s%s%s%s",y.name,y.sex,y.idcard,y.tele);
                input(List,i,y);
                show(List); // 插入后展示,直观看到结果
                break;
            }
            case 5:{
                printf("输入删除位置:\n");
                scanf("%d",&i);
                Delete(List,i);
                show(List); // 删除后展示,直观看到结果
                break;
            }
            case 6:{
                sort(List);
                show(List); // 排序后展示,直观看到结果
                break;
            }
            case 7:{
                int len=Length(List);
                printf("VIP总人数为:%d\n",len); 
                break;
            }
            case 0:{
                FreeList(List); // 退出前释放所有内存:新增核心操作
                printf("系统已退出!\n");
                exit(0);       // 正常退出程序
            }
            default:{ // 非法输入处理:修复原代码无提示问题
                printf("请输入有效序号(0-7)\n");
                break;
            }
        } 
    }
    return 0;
}
//Node * Init(Node *L)
//void Creat(Node *L)
//int show(Node*L)
//int Length(Node *L)
//void Locate(Node *L , char *m)
//int input(Node *L ,int i,ElemType x)
//int Delete(Node *L,int i)
//void sort(Node *L)
//void sort_advanced(Node *L)(进阶可选函数)
//void FreeList(Node *L)
//int main()
//
//函数名	Node *L 的具体作用
//Init	为头结点分配内存,初始化 L->next = NULL,返回初始化后的头指针
//Creat	从 L 指向的头结点开始,用头插法插入新节点(s->next = L->next; L->next = s)
//show	从 L->next 开始遍历所有数据节点(跳过头结点),打印用户信息
//Length	从 L->next 开始遍历,统计有效数据节点的数量
//Locate	从 L->next 开始逐个节点比对身份证号,找到匹配的用户
//input	从 L 开始找到插入位置的前驱节点(第 i-1 个节点),完成新节点插入
//Delete	从 L 开始找到删除位置的前驱节点,跳过待删节点并释放内存
//sort	从 L->next 开始遍历所有节点,按身份证号比较并交换数据(或指针)
//FreeList	从 L 指向的头结点开始,逐个释放所有节点的内存(包括头结点)

运行结果如下

步骤 1:程序启动 & 菜单展示

步骤 2:录入用户信息

步骤 3:浏览用户信息(头插法录入,顺序与输入相反)

步骤 4:按身份证号查找用户

步骤 5:插入用户信息(插入到第 2 位)

步骤 6:删除用户信息(删除第 4 位)

步骤 7:按身份证号升序排序

步骤 8:统计用户总人数

步骤 9:退出系统(释放内存)

五、系统运行与核心优化点

5.1 系统运行流程

  1. 编译运行代码,初始化链表,展示系统菜单;
  2. 输入1录入 n 个用户信息,按姓名 性别 身份证号 手机号的格式输入;
  3. 输入2浏览所有用户信息,空表时提示null
  4. 输入3按身份证号精准查找用户;
  5. 输入4/5完成用户信息的插入 / 删除,操作后即时展示结果;
  6. 输入6按身份证号升序排序用户信息;
  7. 输入7统计 VIP 用户总数;
  8. 输入0退出系统,自动释放所有链表内存,无内存泄漏。

5.2 项目核心优化点

本项目在基础单链表实现的基础上,针对 C 语言开发的常见问题和实际业务需求进行了多项优化,也是新手开发需要重点关注的点:

  1. 输入容错:增加人数、位置、菜单序号的合法性判断,避免非法输入导致程序异常;
  2. 内存管理 :新增FreeList函数,释放所有节点内存,解决内存泄漏;删除节点后置空野指针,避免内存访问错误;
  3. 边界判断:统一带头结点的操作逻辑,避免空表、首节点 / 尾节点操作的特殊处理;
  4. 数据适配:身份证号数组设为 20 字符、手机号设为 12 字符,适配实际数据长度,避免字符串溢出;
  5. 用户体验:插入 / 删除 / 排序后即时展示结果,制表符对齐输出信息,交互式菜单操作便捷。

六、项目拓展与思考

6.1 数据结构思考

单链表与顺序表均为线性表的实现方式,在景区年卡信息管理场景中,为何选择单链表?

  • 单链表优势:插入 / 删除操作无需移动元素,时间复杂度 O (1);内存动态分配,无需提前确定存储空间,适配景区用户数量动态变化的需求;
  • 顺序表优势:随机访问效率高,时间复杂度 O (1);实现逻辑更简单,适合数据量固定、查询频繁的场景;
  • 场景适配 :景区年卡用户的信息管理以插入 / 删除 / 精准查询为主,数据量规模较小,单链表的动态性和操作效率更适配该场景。

七、总结

本文以红旗渠景区年卡信息管理为实际场景,完成了基于C 语言带头结点单链表的信息管理系统的全流程开发,从数据抽象、结构体定义,到链表的初始化、增删改查、排序、统计,再到系统菜单的设计和内存管理的优化,覆盖了单链表的核心知识点和 C 语言开发的常见问题。

通过本次实战,不仅能吃透线性表的链式存储原理,更能学会将数据结构与实际业务场景结合,培养数据抽象能力和工程化开发思维。新手在学习过程中,建议逐行敲写代码,重点理解带头结点单链表的操作逻辑、内存管理和边界判断,在此基础上进行功能拓展,进一步提升编程能力。

相关推荐
Chase_______1 小时前
【快速入手 Python 基础 | 第1章】:数据存储与运算
开发语言·python
骇客野人1 小时前
Java springboot里注解大全和使用指南
java·开发语言·spring boot
add45a1 小时前
C++与自动驾驶系统
开发语言·c++·算法
坚持学习前端日记1 小时前
python对接comfyui的过程
开发语言·网络·python
TsukasaNZ2 小时前
C++中的命令模式
开发语言·c++·算法
笨笨马甲2 小时前
Qt 快速实现YY语音房间
开发语言·qt
我是苏苏2 小时前
消息中间件RabbitMQ04:路由模式+死信队列的应用实践模板
java·开发语言
花无缺0002 小时前
Java开发踩坑:一次线上性能优化案例
java·开发语言·人工智能·面试