目錄
- 文件操作
- 文件基本概念
- 文件指針
- 文件打開模式
- 文件讀取操作
- 字符讀取
- 字符串讀取
- 格式化讀取
- 二進制讀取
- 文件寫入操作
- 字符寫入
- 字符串寫入
- 格式化寫入
- 二進制寫入
- 文件定位操作
- 文件錯誤處理
- 預處理
- 預處理基本概念
- 常見預處理指令
- 文件包含指令
- 宏定義
- 簡單宏
- 帶參數的宏
- 字符串化操作符(#)
- 標記粘貼操作符(##)
- 條件編譯
- #ifdef 和 #ifndef
- #if 和 #elif
- 條件編譯示例:平臺特定代碼
- 其他預處理指令
- #error 指令
- #line 指令
- #pragma 指令
- 預處理的優缺點
- 優點
- 缺點
- 預處理與編譯的區別
- 實際應用示例
- 文件操作示例:學生成績管理系統
- 預處理示例:調試宏
- 總結
在C語言編程中,文件操作和預處理是兩個重要的組成部分。文件操作允許程序與外部存儲設備交互,而預處理則在編譯前對源代碼進行文本處理。這兩個功能為C程序提供了強大的擴展性和靈活性。
文件操作
文件基本概念
在C語言中,文件是存儲在外部介質(如硬盤、U盤)上的數據集合。C語言將文件視為字節序列,并提供了兩種文件處理模式:
- 文本模式:以字符為單位處理文件,自動處理換行符(Windows:
\r\n
? Unix:\n
) - 二進制模式:以字節為單位處理文件,不進行任何轉換
文件指針
文件操作通過文件指針(FILE*)實現,它指向一個包含文件信息的結構體:
#include <stdio.h>FILE *fp; // 聲明文件指針// 打開文件
fp = fopen("example.txt", "r"); // 以只讀模式打開文本文件if (fp == NULL) {printf("無法打開文件\n");return 1;
}// 文件操作...// 關閉文件
fclose(fp);
文件打開模式
模式 | 描述 |
---|---|
“r” | 只讀模式,文件必須存在 |
“w” | 寫入模式,創建新文件或覆蓋 |
“a” | 追加模式,在文件末尾添加內容 |
“r+” | 讀寫模式,文件必須存在 |
“w+” | 讀寫模式,創建新文件或覆蓋 |
“a+” | 讀寫模式,在文件末尾添加內容 |
文件讀取操作
字符讀取
#include <stdio.h>int main() {FILE *fp = fopen("example.txt", "r");if (fp == NULL) {printf("無法打開文件\n");return 1;}int ch;// 逐個字符讀取,EOF表示文件結束while ((ch = fgetc(fp)) != EOF) {putchar(ch); // 輸出到屏幕}fclose(fp);return 0;
}
字符串讀取
#include <stdio.h>int main() {FILE *fp = fopen("example.txt", "r");if (fp == NULL) {printf("無法打開文件\n");return 1;}char buffer[100];// 讀取一行文本(最多99個字符)while (fgets(buffer, sizeof(buffer), fp) != NULL) {printf("%s", buffer);}fclose(fp);return 0;
}
格式化讀取
#include <stdio.h>int main() {FILE *fp = fopen("data.txt", "r");if (fp == NULL) {printf("無法打開文件\n");return 1;}int num;float f;char str[50];// 格式化讀取while (fscanf(fp, "%d %f %s", &num, &f, str) == 3) {printf("讀取: %d, %.2f, %s\n", num, f, str);}fclose(fp);return 0;
}
二進制讀取
#include <stdio.h>typedef struct {int id;char name[50];float score;
} Student;int main() {FILE *fp = fopen("students.dat", "rb"); // 二進制讀取if (fp == NULL) {printf("無法打開文件\n");return 1;}Student s;// 讀取結構體數據while (fread(&s, sizeof(Student), 1, fp) == 1) {printf("ID: %d, 姓名: %s, 分數: %.2f\n", s.id, s.name, s.score);}fclose(fp);return 0;
}
文件寫入操作
字符寫入
#include <stdio.h>int main() {FILE *fp = fopen("output.txt", "w");if (fp == NULL) {printf("無法創建文件\n");return 1;}char text[] = "Hello, World!";for (int i = 0; text[i] != '\0'; i++) {fputc(text[i], fp); // 寫入單個字符}fclose(fp);return 0;
}
字符串寫入
#include <stdio.h>int main() {FILE *fp = fopen("output.txt", "w");if (fp == NULL) {printf("無法創建文件\n");return 1;}char *text = "這是一行文本\n";fputs(text, fp); // 寫入字符串(不自動添加換行符)fclose(fp);return 0;
}
格式化寫入
#include <stdio.h>int main() {FILE *fp = fopen("data.txt", "w");if (fp == NULL) {printf("無法創建文件\n");return 1;}int num = 42;float f = 3.14;char *str = "Hello";// 格式化寫入fprintf(fp, "%d %.2f %s\n", num, f, str);fclose(fp);return 0;
}
二進制寫入
#include <stdio.h>typedef struct {int id;char name[50];float score;
} Student;int main() {FILE *fp = fopen("students.dat", "wb"); // 二進制寫入if (fp == NULL) {printf("無法創建文件\n");return 1;}Student s = {1, "張三", 85.5};// 寫入結構體數據fwrite(&s, sizeof(Student), 1, fp);fclose(fp);return 0;
}
文件定位操作
#include <stdio.h>int main() {FILE *fp = fopen("example.txt", "r");if (fp == NULL) {printf("無法打開文件\n");return 1;}// 獲取文件位置long pos = ftell(fp); // 初始位置為0// 移動文件指針fseek(fp, 10, SEEK_SET); // 從文件開頭移動10個字節fseek(fp, 5, SEEK_CUR); // 從當前位置移動5個字節fseek(fp, -20, SEEK_END); // 從文件末尾向前移動20個字節// 重置文件指針到開頭rewind(fp);fclose(fp);return 0;
}
文件錯誤處理
#include <stdio.h>
#include <errno.h>
#include <string.h>int main() {FILE *fp = fopen("nonexistent.txt", "r");if (fp == NULL) {// 打印錯誤信息printf("錯誤: %s\n", strerror(errno));return 1;}// 檢查文件操作錯誤if (ferror(fp)) {printf("文件操作錯誤\n");clearerr(fp); // 清除錯誤標志}fclose(fp);return 0;
}
預處理
預處理基本概念
預處理是C編譯過程的第一步,由預處理器(Preprocessor)執行。預處理指令以#
開頭,它們在編譯前被處理,用于修改源代碼文本。
常見預處理指令
- 文件包含:
#include
- 宏定義:
#define
、#undef
- 條件編譯:
#if
、#ifdef
、#ifndef
、#elif
、#else
、#endif
- 錯誤處理:
#error
- 行號和文件名:
#line
- 編譯控制:
#pragma
文件包含指令
// 標準庫頭文件
#include <stdio.h> // 從標準庫目錄查找
#include <string.h>// 自定義頭文件
#include "myheader.h" // 從當前目錄或指定目錄查找
宏定義
簡單宏
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))int main() {float radius = 5.0;float area = PI * radius * radius;int x = 10, y = 20;int max_val = MAX(x, y);return 0;
}
帶參數的宏
#define SQUARE(x) ((x) * (x))
#define PRINT_INT(x) printf("Value: %d\n", x)int main() {int a = 5;int result = SQUARE(a + 1); // 展開為 ((a + 1) * (a + 1))PRINT_INT(result); // 展開為 printf("Value: %d\n", result)return 0;
}
字符串化操作符(#)
#define STR(x) #xint main() {printf(STR(Hello World!)); // 展開為 printf("Hello World!");printf(STR(1 + 2)); // 展開為 printf("1 + 2");return 0;
}
標記粘貼操作符(##)
#define CONCAT(a, b) a##bint main() {int xy = 100;printf("%d\n", CONCAT(x, y)); // 展開為 printf("%d\n", xy);return 0;
}
條件編譯
#ifdef 和 #ifndef
#ifdef DEBUGprintf("調試信息: 變量x = %d\n", x);
#endif#ifndef MAX_SIZE#define MAX_SIZE 100
#endif
#if 和 #elif
#define VERSION 2#if VERSION == 1printf("使用版本1\n");
#elif VERSION == 2printf("使用版本2\n");
#elseprintf("未知版本\n");
#endif
條件編譯示例:平臺特定代碼
#ifdef _WIN32// Windows平臺代碼#include <windows.h>#define LINE_END "\r\n"
#elif __linux__// Linux平臺代碼#include <unistd.h>#define LINE_END "\n"
#else#error "不支持的平臺"
#endif
其他預處理指令
#error 指令
#if !defined(__STDC__)#error "需要標準C編譯器"
#endif
#line 指令
#line 100 "custom_file.c"
// 從這里開始,行號從100開始,文件名顯示為custom_file.c
#pragma 指令
#pragma once // 保證頭文件只被包含一次#pragma GCC diagnostic ignored "-Wunused-variable" // 忽略未使用變量警告
預處理的優缺點
優點
- 代碼復用:通過宏和頭文件實現代碼重用
- 條件編譯:支持跨平臺開發和調試版本
- 代碼生成:在編譯前自動生成代碼
- 性能優化:宏替換可以減少函數調用開銷
缺點
- 可讀性降低:過度使用宏會使代碼難以理解
- 調試困難:錯誤可能出現在預處理后的代碼中
- 潛在副作用:宏參數可能被多次求值
- 命名沖突:宏定義可能與其他標識符沖突
預處理與編譯的區別
特性 | 預處理階段 | 編譯階段 |
---|---|---|
執行時間 | 編譯前 | 預處理后 |
處理內容 | 文本替換、文件包含、條件編譯 | 詞法分析、語法分析、代碼生成 |
輸出結果 | 修改后的源代碼 | 目標代碼(匯編或機器碼) |
工具 | 預處理器(cpp) | 編譯器(cc、gcc) |
指令形式 | 以#開頭的預處理指令 | C語言語句 |
實際應用示例
文件操作示例:學生成績管理系統
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX_NAME_LEN 50
#define MAX_STUDENTS 100typedef struct {int id;char name[MAX_NAME_LEN];float score;
} Student;// 保存學生信息到文件
void saveStudents(Student students[], int count) {FILE *fp = fopen("students.dat", "wb");if (fp == NULL) {printf("無法創建文件\n");return;}fwrite(&count, sizeof(int), 1, fp); // 寫入學生數量fwrite(students, sizeof(Student), count, fp); // 寫入學生數據fclose(fp);printf("已保存 %d 名學生信息\n", count);
}// 從文件加載學生信息
int loadStudents(Student students[]) {FILE *fp = fopen("students.dat", "rb");if (fp == NULL) {printf("無法打開文件或文件不存在\n");return 0;}int count;fread(&count, sizeof(int), 1, fp); // 讀取學生數量fread(students, sizeof(Student), count, fp); // 讀取學生數據fclose(fp);printf("已加載 %d 名學生信息\n", count);return count;
}int main() {Student students[MAX_STUDENTS];int count = 0;// 添加學生信息students[count].id = 1;strcpy(students[count].name, "張三");students[count].score = 85.5;count++;students[count].id = 2;strcpy(students[count].name, "李四");students[count].score = 92.0;count++;// 保存到文件saveStudents(students, count);// 清空數組memset(students, 0, sizeof(students));count = 0;// 從文件加載count = loadStudents(students);// 顯示學生信息for (int i = 0; i < count; i++) {printf("ID: %d, 姓名: %s, 分數: %.1f\n", students[i].id, students[i].name, students[i].score);}return 0;
}
預處理示例:調試宏
#ifdef DEBUG#define DEBUG_PRINT(fmt, ...) printf("DEBUG: " fmt, __VA_ARGS__)#define DEBUG_LINE() printf("DEBUG: Line %d in %s\n", __LINE__, __FILE__)
#else#define DEBUG_PRINT(fmt, ...) do {} while(0)#define DEBUG_LINE() do {} while(0)
#endif// 平臺檢測
#ifdef _WIN32#define PLATFORM "Windows"#include <windows.h>
#elif __linux__#define PLATFORM "Linux"#include <unistd.h>
#elif __APPLE__#define PLATFORM "macOS"#include <unistd.h>
#else#define PLATFORM "Unknown"
#endifint main() {DEBUG_LINE();DEBUG_PRINT("程序開始運行,平臺: %s\n", PLATFORM);int x = 42;DEBUG_PRINT("變量x的值: %d\n", x);// 正常代碼...return 0;
}
總結
文件操作和預處理是C語言中兩個重要的功能,它們分別在程序運行時和編譯前發揮作用:
- 文件操作允許程序與外部存儲設備交互,支持文本和二進制兩種模式
- 預處理在編譯前對源代碼進行文本處理,提供宏定義、文件包含和條件編譯等功能
- 文件操作通過文件指針和標準庫函數實現,需要注意文件打開模式和錯誤處理
- 預處理指令以#開頭,它們不是C語言語句,而是由預處理器單獨處理
掌握文件操作和預處理技術,能夠使C程序更加靈活、可移植,并支持復雜的數據處理和代碼組織。