C语言向学生活动文件末尾追加活动代码失败问题求助
问题:C语言无法向学生活动文件指定行追加内容
我的学生活动文件格式如下:
S1,CM004;CM020;E001 S2,CM010;CM009;CM004;CM006;SM001;E001;SM002;PC007 S3,CM020;CM17;T001
我想给指定学生(比如S1)的活动行末尾追加活动代码(比如;E002或;CM003),但现有C语言代码实现失败,无法追加到行尾。我调整过fseek中SEEK_CUR的偏移量,问题仍未解决,相关代码如下:
typedef struct { char studentID[20]; char activities[50][10]; int activityCount; } StudentDetails; void addActivity(const char *studentID, const char *codeToAdd) { FILE *studentFile = fopen("StudentAccounts.txt", "r+"); if (studentFile == NULL) { perror("Error opening student file"); return; } StudentDetails studentDetails; int found = 0; // Read student's chosen activities while (fscanf(studentFile, "%19[^,],", studentDetails.studentID) == 1) { // Check if the student ID matches if (strcmp(studentDetails.studentID, studentID) == 0) { found = 1; studentDetails.activityCount = 0; // Read and store existing activities while (fscanf(studentFile, "%9[^;];", studentDetails.activities[studentDetails.activityCount]) == 1) { trimWhitespace(studentDetails.activities[studentDetails.activityCount]); studentDetails.activityCount++; if (studentDetails.activityCount >= 50) { printf("Warning: Maximum number of activities reached.\n"); break; } } // Add the new activity if not already present int activityFound = 0; for (int i = 0; i < studentDetails.activityCount; i++) { if (strcmp(studentDetails.activities[i], codeToAdd) == 0) { activityFound = 1; break; } } if (!activityFound) { // Append the new activity code strncpy(studentDetails.activities[studentDetails.activityCount], codeToAdd, sizeof(studentDetails.activities[0])); studentDetails.activityCount++; // Move file pointer back to the beginning of the line fseek(studentFile, -strlen(studentDetails.studentID) - 1, SEEK_CUR); // Write updated activities to the file for (int i = 0; i < studentDetails.activityCount; i++) { fprintf(studentFile, "%s;", studentDetails.activities[i]); } // Break out of the loop since we found and updated the student break; } } // Skip the rest of the line while (fgetc(studentFile) != '\n') { // Check for end of file if (feof(studentFile)) { break; } } } fclose(studentFile); if (!found) { printf("Student ID %s not found.\n", studentID); } else { printf("Activity code %s added to student %s.\n", codeToAdd, studentID); } } int studentRole(const char ID[20]) { int choice; char codeToChange[20]; char codeToAdd[20]; char codeToDelete[20]; int coreCredits = 0; int coreModulesSelected = 0; // Count of selected core modules do { printf("\nMenu:\n"); printf("1. View/Track activities and credits\n"); printf("2. Select an Core Module\n"); printf("3. Delete an Core Module\n"); printf("4. Select Activity\n"); printf("5. Delete an Activity\n"); printf("6. Quit\n"); printf("Enter your choice: "); if (scanf("%d", &choice) != 1) { printf("Invalid input. Please enter a number.\n"); while (getchar() != '\n'); // Clear input buffer continue; } switch (choice) { case 1: ViewActivitiesWithPoints(ID); // Call the function to display credits and points break; case 2: ShowActivities("CM"); printf("Enter the code of the activity to add: "); scanf("%s", codeToAdd); while (getchar() != '\n'); // Clear input buffer // Add the activity to the list of chosen activities and also write it to Activity.txt addActivity(ID,codeToAdd); break; case 3: printf("Enter the code of the activity to delete from your Activity.txt file: "); scanf("%s", codeToDelete); deleteActivity(ID, codeToDelete,"CM"); break; case 4: ShowActivities("E"); ShowActivities("SM"); ShowActivities("W"); ShowActivities("CS"); ShowActivities("PS"); ShowActivities("PC"); ShowActivities("CMP"); ShowActivities("T"); break; case 5: printf("Enter the code of the core module to delete: "); scanf("%s", codeToDelete); //deleteData(activities, &count, "coreModules.txt", codeToDelete,"CM"); break; case 6: printf("Goodbye!\n"); break; default: printf("Invalid choice. Please try again.\n"); } } while (choice != 6); return 0; }
问题排查
- 文件指针定位完全错误:你用
fseek(studentFile, -strlen(studentDetails.studentID) - 1, SEEK_CUR)试图回到目标行开头,但读取完活动后,文件指针已经到了行尾(或换行符),这个偏移量计算根本无法定位到正确位置。 - 活动读取逻辑遗漏最后一项:
fscanf("%9[^;];")要求活动后必须跟分号,但文件中最后一个活动后面是换行符,导致最后一项活动无法被读取,写入后会丢失原有最后一项内容。 - 直接覆盖破坏文件结构:就算指针位置正确,直接写入新内容会覆盖后续行的开头(因为新内容长度比原行长),导致整个文件格式混乱。
修复方案
处理文本文件行修改最稳妥的方式是读取整个文件内容,修改目标行后重新写入整个文件,彻底规避直接定位写入的各种问题。修改后的addActivity函数如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char studentID[20]; char activities[50][10]; int activityCount; } StudentDetails; // 实现trimWhitespace函数(去除字符串首尾空白) void trimWhitespace(char *str) { char *start = str; while (*start == ' ' || *start == '\t') start++; memmove(str, start, strlen(start)+1); char *end = str + strlen(str) - 1; while (end > str && (*end == ' ' || *end == '\t')) end--; *(end + 1) = '\0'; } void addActivity(const char *studentID, const char *codeToAdd) { FILE *studentFile = fopen("StudentAccounts.txt", "r"); if (studentFile == NULL) { perror("Error opening student file for reading"); return; } // 创建临时文件存储修改后的内容 FILE *tempFile = fopen("temp.txt", "w"); if (tempFile == NULL) { perror("Error creating temp file"); fclose(studentFile); return; } char line[256]; int found = 0; while (fgets(line, sizeof(line), studentFile) != NULL) { // 去除行尾换行符 line[strcspn(line, "\n")] = '\0'; char currentID[20]; if (sscanf(line, "%19[^,],", currentID) != 1) { // 格式错误,直接写入原行 fprintf(tempFile, "%s\n", line); continue; } if (strcmp(currentID, studentID) == 0) { found = 1; StudentDetails studentDetails; strcpy(studentDetails.studentID, currentID); studentDetails.activityCount = 0; // 分割活动代码,正确读取所有项 char *activityStr = strchr(line, ',') + 1; char *token = strtok(activityStr, ";"); while (token != NULL && studentDetails.activityCount < 50) { trimWhitespace(token); strncpy(studentDetails.activities[studentDetails.activityCount], token, sizeof(studentDetails.activities[0])-1); studentDetails.activities[studentDetails.activityCount][sizeof(studentDetails.activities[0])-1] = '\0'; studentDetails.activityCount++; token = strtok(NULL, ";"); } // 检查活动是否已存在 int activityFound = 0; for (int i = 0; i < studentDetails.activityCount; i++) { if (strcmp(studentDetails.activities[i], codeToAdd) == 0) { activityFound = 1; break; } } if (!activityFound) { if (studentDetails.activityCount >= 50) { printf("Warning: Maximum number of activities reached.\n"); } else { strncpy(studentDetails.activities[studentDetails.activityCount], codeToAdd, sizeof(studentDetails.activities[0])-1); studentDetails.activities[studentDetails.activityCount][sizeof(studentDetails.activities[0])-1] = '\0'; studentDetails.activityCount++; } } // 写入修改后的行 fprintf(tempFile, "%s,", studentDetails.studentID); for (int i = 0; i < studentDetails.activityCount; i++) { if (i > 0) fprintf(tempFile, ";"); fprintf(tempFile, "%s", studentDetails.activities[i]); } fprintf(tempFile, "\n"); } else { // 写入未修改的原行 fprintf(tempFile, "%s\n", line); } } fclose(studentFile); fclose(tempFile); // 替换原文件 if (remove("StudentAccounts.txt") != 0) { perror("Error deleting original file"); remove("temp.txt"); return; } if (rename("temp.txt", "StudentAccounts.txt") != 0) { perror("Error renaming temp file"); remove("temp.txt"); return; } if (!found) { printf("Student ID %s not found.\n", studentID); } else { printf("Activity code %s added to student %s.\n", codeToAdd, studentID); } } // 原studentRole函数可保留不变,此处省略重复代码
修复说明
- 临时文件机制:读取原文件每一行,修改目标行后写入临时文件,最后替换原文件,彻底避免指针定位错误。
- 正确分割活动:用
strtok分割活动字符串,能读取包括最后一项在内的所有活动,不会遗漏。 - 安全写入:严格按照
ID,活动1;活动2;...的格式写入,确保文件结构不被破坏。 - 边界保护:增加字符串截断处理(防止缓冲区溢出),以及活动数量上限判断。
内容的提问来源于stack exchange,提问作者Yehia Alhaddad
相关产品推荐
相关产品推荐

