You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用双向链表实现学生数据库时scanf/fgets输入卡顿问题求助

双向链表学生数据库中scanf与fgets输入失效问题排查及修复

核心问题分析

代码中输入卡顿的表象背后,存在多个致命问题,最直接的触发点是野指针未初始化,其次是scanf与fgets的输入缓冲区残留冲突,同时还有内存分配、链表逻辑的错误:

1. 野指针导致的未定义行为

main函数中声明了StudentPtr newPtr;但未初始化,直接访问newPtr->name、newPtr->lastname等成员属于非法内存访问,会导致程序崩溃、卡顿或其他不可预测的行为,这是你看到“程序卡顿”的主要原因。

2. 输入缓冲区残留问题

用scanf("%d",&option)读取选项后,输入缓冲区中会留下用户输入的换行符\n,后续调用fgets时会直接读取这个空行,导致fgets看似“失效”,但实际是读取了空内容。

3. 字符串成员未分配内存

struct student中的name、lastname、class都是char*类型,但从未给这些指针分配内存,直接用fgets写入会导致内存越界,触发段错误或破坏其他内存数据。

4. 链表节点的字符串指针共享问题

insert函数中直接将传入的char*指针赋值给新节点的成员,会导致多个链表节点指向同一块内存,后续输入新数据时会覆盖旧节点的内容,甚至出现悬空指针问题。

5. 双向链表逻辑缺失

代码中定义了双向链表的prev指针,但insert函数完全未处理prev的关联,实际是单向链表,不符合双向链表的设计要求。

6. 其他语法错误

  • printf("your option: ",&option);格式字符串错误,多余的&option无意义,应改为printf("your option: ");
  • delete函数无参数,无法实现删除逻辑;insert函数未处理studentID和grad_Year的初始化(原代码中这两个值未读取就传入)

修复后的代码示例

头文件(Student.h)

#ifndef STUDENT_H_
#define STUDENT_H_

#define BUFFERSIZE 128

typedef struct student{
    char name[BUFFERSIZE];
    char lastname[BUFFERSIZE];
    long studentID;
    char class[BUFFERSIZE];
    int grad_Year;

    struct student *next;
    struct student *prev;
}student_t;

typedef struct DLL{
    struct student* head;   // 头节点指针
    struct student* tail;   // 尾节点指针
}DLL_t;

#endif /* STUDENT_H_ */

源文件(Student.c)

#ifndef STUDENT_C_
#define STUDENT_C_

#include<stdio.h>
#include<stdlib.h>
#include <string.h>

#include "Student.h"

// 类型定义
typedef struct student studentNode;
typedef studentNode *StudentPtr;
typedef struct DLL Dll;
typedef Dll *DllPtr;

// 函数声明
void instructions(void);
void insert(DllPtr dll, const char* name, const char* lastname, const char* class, long studentID, int grad_Year);
void deleteStudent(DllPtr dll, long studentID);
void clean_stdin_buffer(void);

// 打印操作菜单
void instructions(void){
     printf( "Enter your choice:\n"
          "   1 to insert an element into the list.\n"
          "   2 to delete an element from the list.\n"
          "   3 to end.\n" );
}

// 向双向链表插入节点(尾部插入,简化逻辑)
void insert(DllPtr dll, const char* name, const char* lastname, const char* class, long studentID, int grad_Year){
    StudentPtr newPtr = (studentNode *)malloc(sizeof(studentNode));
    if(newPtr == NULL){
        printf("Memory allocation failed!\n");
        return;
    }

    // 复制字符串到节点内存
    strncpy(newPtr->name, name, BUFFERSIZE-1);
    newPtr->name[BUFFERSIZE-1] = '\0';
    strncpy(newPtr->lastname, lastname, BUFFERSIZE-1);
    newPtr->lastname[BUFFERSIZE-1] = '\0';
    strncpy(newPtr->class, class, BUFFERSIZE-1);
    newPtr->class[BUFFERSIZE-1] = '\0';

    newPtr->studentID = studentID;
    newPtr->grad_Year = grad_Year;
    newPtr->next = NULL;
    newPtr->prev = NULL;

    // 双向链表插入逻辑
    if(dll->head == NULL){ // 链表为空
        dll->head = newPtr;
        dll->tail = newPtr;
    }else{ // 插入到尾部
        dll->tail->next = newPtr;
        newPtr->prev = dll->tail;
        dll->tail = newPtr;
    }

    printf("Student inserted successfully!\n");
}

// 根据学号删除节点(示例实现)
void deleteStudent(DllPtr dll, long studentID){
    if(dll->head == NULL){
        printf("List is empty!\n");
        return;
    }

    StudentPtr currentPtr = dll->head;
    while(currentPtr != NULL && currentPtr->studentID != studentID){
        currentPtr = currentPtr->next;
    }

    if(currentPtr == NULL){
        printf("Student ID %ld not found!\n", studentID);
        return;
    }

    // 处理前驱和后继节点
    if(currentPtr->prev != NULL){
        currentPtr->prev->next = currentPtr->next;
    }else{
        dll->head = currentPtr->next; // 删除的是头节点
    }

    if(currentPtr->next != NULL){
        currentPtr->next->prev = currentPtr->prev;
    }else{
        dll->tail = currentPtr->prev; // 删除的是尾节点
    }

    free(currentPtr);
    printf("Student ID %ld deleted successfully!\n", studentID);
}

// 清空stdin输入缓冲区
void clean_stdin_buffer(void){
    int c;
    while((c = getchar()) != '\n' && c != EOF);
}

int main(int argc, char *argv[]){
    int option;
    Dll dll = {NULL, NULL}; // 初始化双向链表
    studentNode inputStudent; // 用栈上的结构体临时存储输入

    instructions();
    printf("your option: ");
    while(scanf("%d", &option) != 1){ // 处理非法输入
        clean_stdin_buffer();
        printf("Invalid input, please enter a number: ");
    }
    clean_stdin_buffer(); // 清空scanf留下的换行符

    while(option != 3){
        switch(option){
            case 1:
                printf("Enter first name: ");
                fgets(inputStudent.name, BUFFERSIZE, stdin);
                inputStudent.name[strcspn(inputStudent.name, "\n")] = '\0'; // 去掉换行符

                printf("Enter last name: ");
                fgets(inputStudent.lastname, BUFFERSIZE, stdin);
                inputStudent.lastname[strcspn(inputStudent.lastname, "\n")] = '\0';

                printf("Enter class: ");
                fgets(inputStudent.class, BUFFERSIZE, stdin);
                inputStudent.class[strcspn(inputStudent.class, "\n")] = '\0';

                printf("Enter Student ID: ");
                while(scanf("%ld", &inputStudent.studentID) != 1){
                    clean_stdin_buffer();
                    printf("Invalid input, please enter a number: ");
                }
                clean_stdin_buffer();

                printf("Enter Graduation Year: ");
                while(scanf("%d", &inputStudent.grad_Year) != 1){
                    clean_stdin_buffer();
                    printf("Invalid input, please enter a number: ");
                }
                clean_stdin_buffer();

                insert(&dll, inputStudent.name, inputStudent.lastname, inputStudent.class,
                       inputStudent.studentID, inputStudent.grad_Year);
                break;

            case 2:
                if(dll.head == NULL){
                    printf("List is empty, no student to delete!\n");
                    break;
                }
                long delID;
                printf("Enter Student ID to delete: ");
                while(scanf("%ld", &delID) != 1){
                    clean_stdin_buffer();
                    printf("Invalid input, please enter a number: ");
                }
                clean_stdin_buffer();
                deleteStudent(&dll, delID);
                break;

            default:
                printf("Invalid choice.\n\n");
                instructions();
                break;
        }

        // 重新获取选项
        printf("\nyour option: ");
        while(scanf("%d", &option) != 1){
            clean_stdin_buffer();
            printf("Invalid input, please enter a number: ");
        }
        clean_stdin_buffer();
    }

    printf("Program ended.\n");
    return 0;
}
#endif /* STUDENT_C_ */

关键修复说明

  1. 替换char*为固定大小数组:避免手动分配字符串内存,简化内存管理,同时防止内存越界。
  2. 初始化所有指针:链表结构体、临时输入结构体都正确初始化,杜绝野指针。
  3. 严格处理输入缓冲区:每次用scanf后都调用clean_stdin_buffer清空残留的换行符,确保fgets能正确读取用户输入。
  4. 字符串复制而非指针赋值:insert函数中用strncpy复制字符串到节点内存,避免多个节点共享同一块内存。
  5. 完善双向链表逻辑:实现了prev指针的关联,支持头部、尾部、中间节点的删除操作。
  6. 增加输入合法性检查:处理用户输入非数字的情况,避免程序崩溃。

内容的提问来源于stack exchange,提问作者MikazukiConwizz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 07:25:19