本教程演示如何在 C++ 中設計包含動態成員(字符串、字符串陣列)的結構體,實現完整的**內存申請、數據輸入、文件寫入、鍵值讀取、內存釋放**全生命周期管理。案例模擬「教師-學生」關係:固定 5 位教師,每位教師 3 名學生,數據持久化至文本文件並支持鍵值查詢。
#define TEACHER_COUNT 5
#define STUDENT_PER_TEACHER 3
#define FILE_NAME "f:/teacher_data.txt"
#define MAX_LINE_LEN 1024
#define MAX_KEY_LEN 20
struct AdvTeacher {
char* name; // 教師姓名
char* title; // 職稱
int age; // 年齡
char* addr; // 地址
char** student; // 學生姓名陣列(動態二維)
};
所有字符串採用 char* 動態分配,student 為二級指針指向字符串指針陣列。
// 創建結構體陣列及所有動態成員
int CreateStructArray(AdvTeacher** t, int teacherCnt, int studentCnt) {
if (!t) return -1;
AdvTeacher* arr = (AdvTeacher*)malloc(teacherCnt * sizeof(AdvTeacher));
if (!arr) return -2;
for (int i = 0; i < teacherCnt; ++i) {
arr[i].name = (char*)malloc(256);
arr[i].title = (char*)malloc(256);
arr[i].addr = (char*)malloc(256);
if (!arr[i].name || !arr[i].title || !arr[i].addr) return -3;
arr[i].student = (char**)malloc(studentCnt * sizeof(char*));
if (!arr[i].student) return -4;
for (int j = 0; j < studentCnt; ++j) {
arr[i].student[j] = (char*)malloc(256);
if (!arr[i].student[j]) return -5;
}
}
*t = arr;
return 0;
}
// 遞歸銷毀:逆序釋放,防洩漏
int FreeStructArray(AdvTeacher** t, int teacherCnt, int studentCnt) {
if (!t || !*t) return 0;
AdvTeacher* arr = *t;
for (int i = 0; i < teacherCnt; ++i) {
for (int j = 0; j < studentCnt; ++j)
free(arr[i].student[j]);
free(arr[i].student);
free(arr[i].name);
free(arr[i].title);
free(arr[i].addr);
}
free(arr);
*t = nullptr; // 置空防野指針
return 0;
}
關鍵點:雙重指針參數 AdvTeacher** 實現「輸出參數」語義;嚴格逆序釋放;malloc 失敗即時返回錯誤碼並清理已分配內存(生產代碼建議用 RAII 封裝)。
int StructWriteFile(const char* fileName, const AdvTeacher* t, int teacherCnt, int studentCnt) {
FILE* fp = fopen(fileName, "w+");
if (!fp) return -1;
char buf[1024];
for (int i = 0; i < teacherCnt; ++i) {
fprintf(fp, "name%d = %s\n", i+1, t[i].name);
fprintf(fp, "age%d = %d\n", i+1, t[i].age);
fprintf(fp, "title%d = %s\n", i+1, t[i].title);
fprintf(fp, "addr%d = %s\n", i+1, t[i].addr);
for (int j = 0; j < studentCnt; ++j) {
fprintf(fp, "%dstudentname%d = %s\n", i+1, j+1, t[i].student[j]);
}
}
fclose(fp);
return 0;
}
採用 key = value 行式格式,鍵名編碼層級關係(如 2studentname3 表示第 2 位教師的第 3 名學生),便於後續解析。
int StructFileRead(const char* fileName, const char* key, char* value, int* valueLen) {
FILE* fp = fopen(fileName, "r");
if (!fp) return -2;
char line[MAX_LINE_LEN];
while (fgets(line, sizeof(line), fp)) {
char* eq = strchr(line, '=');
if (!eq) continue; // 非鍵值行
if (!strstr(line, key)) continue; // 鍵不匹配
// 定位 value 起始(跳過 '=' 及空格)
char* p = eq + 1;
while (*p == ' ') ++p;
if (*p == '\n') { fclose(fp); return -3; } // 無值
// 定位 value 結束(空格或換行)
char* end = p;
while (*end && *end != ' ' && *end != '\n') ++end;
*valueLen = end - p;
memcpy(value, p, *valueLen);
value[*valueLen] = '\0';
fclose(fp);
return 0;
}
fclose(fp);
return -4; // 未找到鍵
}
單次掃描文件,strstr 匹配鍵名,手動解析值邊界,避免正則表達式依賴。
void ReadFileMenu() {
char key[256], value[1024];
int valueLen;
while (true) {
system("cls");
puts("=== 讀取文件菜單 ===");
puts("格式示例: name1 (第1位教師姓名)");
puts("格式示例: 1studentname2 (第1位教師第2名學生)");
puts("輸入 quit 退出");
printf("請輸入查詢鍵: ");
scanf("%s", key);
if (strcmp(key, "quit") == 0) break;
if (strlen(key) < 4) { puts("鍵名太短"); system("pause"); continue; }
strcpy(value, "不存在匹配項"); // 預設值
if (StructFileRead(FILE_NAME, key, value, &valueLen) == 0)
printf("\n%s = %s\n\n", key, value);
else
puts("查詢失敗");
system("pause");
}
}
int main() {
AdvTeacher* teachers = nullptr;
if (CreateStructArray(&teachers, TEACHER_COUNT, STUDENT_PER_TEACHER) != 0) return -1;
// 用戶輸入數據
for (int i = 0; i < TEACHER_COUNT; ++i) {
printf("教師 %d 姓名: ", i+1); scanf("%s", teachers[i].name);
printf("教師 %d 年齡: ", i+1); scanf("%d", &teachers[i].age);
printf("教師 %d 職稱: ", i+1); scanf("%s", teachers[i].title);
printf("教師 %d 地址: ", i+1); scanf("%s", teachers[i].addr);
for (int j = 0; j < STUDENT_PER_TEACHER; ++j) {
printf(" 學生 %d 姓名: ", j+1); scanf("%s", teachers[i].student[j]);
}
}
StructWriteFile(FILE_NAME, teachers, TEACHER_COUNT, STUDENT_PER_TEACHER);
ReadFileMenu();
FreeStructArray(&teachers, TEACHER_COUNT, STUDENT_PER_TEACHER);
return 0;
}
| 原始做法 | 現代替代 | 優勢 |
|---|---|---|
char* + malloc/free | std::string | 自動內存管理、避免洩漏/越界 |
char** 學生陣列 | std::vector<std::string> | 動態擴容、範圍檢查、範基 for |
FILE* + fprintf/fscanf | std::ofstream/ifstream + nlohmann::json | 類型安全、結構化序列化、跨平台 |
| 手工鍵值解析 | std::unordered_map<std::string, std::string> | O(1) 查詢、自動處理轉義 |
| 錯誤碼返回 | std::expected (C++23) / 異常 / std::optional | 強制處理錯誤、上下文豐富 |
本教程完整展示了 C 風格手動內存管理與文件持久化的全流程,涵蓋雙重指針參數、分級分配/釋放、文本鍵值格式設計、流式解析等核心技術。雖然教學價值高,但**生產環境強烈建議使用現代 C++ 標準庫組件**,將精力集中在業務邏輯而非內存細節上。理解底層機制,是為了更好地駕馭高層抽象。