使用双向链表实现学生数据库时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_ */
关键修复说明
- 替换char*为固定大小数组:避免手动分配字符串内存,简化内存管理,同时防止内存越界。
- 初始化所有指针:链表结构体、临时输入结构体都正确初始化,杜绝野指针。
- 严格处理输入缓冲区:每次用
scanf后都调用clean_stdin_buffer清空残留的换行符,确保fgets能正确读取用户输入。 - 字符串复制而非指针赋值:
insert函数中用strncpy复制字符串到节点内存,避免多个节点共享同一块内存。 - 完善双向链表逻辑:实现了
prev指针的关联,支持头部、尾部、中间节点的删除操作。 - 增加输入合法性检查:处理用户输入非数字的情况,避免程序崩溃。
内容的提问来源于stack exchange,提问作者MikazukiConwizz
相关产品推荐
相关产品推荐

