
1. 為什么使用文件?
2. 什么是文件?
2.1 程序文件
2.2 數據文件
2.3 文件名
2.4 常見文件后綴
計算機中的文件后綴非常多,不同后綴對應不同的文件類型和用途。
一、程序文件(可執行或包含代碼)
?? ???? ?.exe:Windows系統的可執行程序(如軟件、游戲啟動文件)。
?? ???? ?.msi:Windows的安裝包程序(用于安裝軟件)。
?? ???? ?.bat / .cmd:Windows的批處理腳本(可自動執行一系列命令)。
?? ???? ?.sh:Linux/macOS的腳本文件(類似批處理,可執行命令)。
?? ???? ?.c / .cpp / .java / .py / .js:各種編程語言的源文件(包含代碼,需編譯/解釋后執行)。
?? ???? ?.class:Java編譯后的字節碼文件(可被Java虛擬機執行)。
?? ???? ?.app:macOS的應用程序(本質是包含可執行文件的文件夾)。
?? ???? ?.apk:安卓系統的安裝包(包含手機應用的可執行代碼)。
二、數據文件(存儲內容,需對應程序打開)
1. 文檔類
?? ???? ?.txt:純文本文件(記事本可打開)。
?? ???? ?.docx / .doc:Word文檔(文字、格式)。
?? ???? ?.xlsx / .xls:Excel表格(數據、公式)。
?? ???? ?.pptx / .ppt:PowerPoint演示文稿(幻燈片)。
?? ???? ?.pdf:便攜文檔格式(跨平臺的只讀文檔)。
2. 媒體類
?? ???? ?.jpg / .jpeg / .png / .gif:圖片文件(分別對應不同壓縮格式)。
?? ???? ?.mp3 / .wav / .flac:音頻文件(音樂、聲音)。
?? ???? ?.mp4 / .avi / .mov / .mkv:視頻文件(電影、視頻片段)。
3. 壓縮/備份類
?? ???? ?.zip / .rar / .7z:壓縮包(用于打包多個文件,節省空間)。
?? ???? ?.iso:光盤鏡像文件(可模擬光盤內容,用于安裝系統、游戲)。
4. 其他常用數據
?? ???? ?.html / .htm:網頁文件(瀏覽器可打開)。
?? ???? ?.csv:逗號分隔的表格數據(Excel、記事本均可打開)。
?? ???? ?.json:存儲結構化數據的文件(常用于程序間數據交換)。
核心區分小技巧:
?? ???? ?如果你雙擊一個文件,它能“自己啟動并讓電腦做事”(比如打開軟件、運行游戲),大概率是程序文件(如.exe、.app)。
?? ???? ?如果你雙擊一個文件,必須先打開某個軟件才能顯示內容(比如用Word打開.docx,用播放器打開.mp4),那就是數據文件。
3. ?進制?件和?本?件
#include <stdio.h>
int main()
{int a = 10000;FILE* pf = fopen("test.txt", "wb");fwrite(&a, 4, 1, pf);//?進制的形式寫到?件中fclose(pf);pf = NULL;return 0;}
ps:二進制文件本身的內容就不是文字,用文本工具打開自然會是亂碼
ps:就是說二進制的程序文件可以在源文件下用二進制編輯器的打開方式去實現。
3.1? 這邊用二進制編譯器輸出時為啥沒有ox?
ox 并不是二進制或十六進制數據本身的一部分,它只是人類為了區分進制而加上的 '標記'。
4. 文件的打開和關閉
4.1 流和標準流
4.1.1 流
4.1.2 標準流
- stdin - 標準輸?流,在?多數的環境中從鍵盤輸?,scanf函數就是從標準輸?流中讀取數據。
- stdout - 標準輸出流,?多數的環境中輸出?顯?器界?,printf函數就是將信息輸出到標準輸出流中。
- stderr - 標準錯誤流,?多數環境中輸出到顯?器界?。
4.1.3??為什么和 FILE*(文件指針)有關?
在C語言里,所有“流”(包括標準流、你自己打開的文件)都用 FILE* 類型的指針來表示,這個指針就像“管道的閥門”——通過它操作流里的數據。
?? ???? ?標準流對應的 FILE* 指針是系統預先定義好的:stdin(標準輸入)、stdout(標準輸出)、stderr(標準錯誤)。
?? ???? ?你自己用 fopen 打開的文件(比如 fopen("a.txt", "r")),得到的也是 FILE* 指針,本質上和標準流的指針是同一類東西,只是指向的“管道”不同(一個是默認的屏幕/鍵盤,一個是你指定的文件)。
4.2 文件指針
struct _iobuf
{
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};typedef struct _iobuf FILE;
FILE* pf;//?件指針變量
4.2.1 文件信息區
1. FILE結構體(你說的“文件信息區”)是程序和實際文件之間的“中間人”,程序通過它來操作文件內容。
打個比方:
你想往一個筆記本(實際文件)里寫東西,不會直接拿筆戳筆記本——得先翻開筆記本(打開文件),這時FILE結構體就像筆記本的“封面+書簽”:
?? ???? ?封面記錄著筆記本的位置(存在硬盤哪里)、能不能涂改(讀寫權限);
?? ???? ?書簽標記著當前寫到哪一行(文件當前位置)。
2. FILE結構體和“程序打開的某個文件”是一一綁定的,但不是和“程序本身”綁定。
舉個例子:
你寫了一個程序,同時打開了a.txt和b.txt兩個文件。這時程序里會有兩個FILE結構體指針:
?? ???? ?一個指向 a.txt 的FILE結構體(記錄a.txt的讀寫位置、權限等);
?? ???? ?另一個指向 b.txt 的FILE結構體(記錄b.txt的信息)。
這兩個結構體分別和 a.txt、b.txt綁定,程序通過它們分別操作兩個文件。
- 當你關閉a.txt后,對應的FILE結構體就會被釋放,不再和任何文件綁定;
- 而b.txt的結構體還在,直到你關閉它。
4.2.2 為啥說我們不需要特別關注到文件信息區的細節,而是通過它的指針形式找到它?
因為FILE結構體的內部細節對我們寫程序來說,屬于“沒必要知道的底層實現”,就像你用手機拍照時,不用知道攝像頭傳感器的具體工作原理,按快門就行。
1. FILE結構體的內部太復雜,且不統一
它里面可能包含幾十種信息(比如不同操作系統的FILE結構不一樣),但我們編程時只需要“打開/讀寫/關閉”這幾個動作。
就像你用遙控器開空調,不需要知道遙控器內部的電路板怎么設計——你只需要按“開”鍵,空調就工作了。
FILE指針就是那個“遙控器”,通過它調用fopen、fprintf等函數,就能完成操作,不用關心里面具體存了啥。
2. 用指針更安全、更方便
如果直接操作FILE結構體的內部數據(比如手動改它的“當前位置”),很容易改錯(比如改到負數位置),導致程序崩潰。
而C語言提供的函數(如fread、fseek)已經幫我們封裝好了這些操作,通過指針調用它們,相當于讓“專業工具”來處理,不容易出錯。
4.3 文件的打開和關閉
//打開?件FILE * fopen ( const char * filename, const char * mode );//關閉?件int fclose ( FILE * stream );
- 權限問題
- 路徑要對
文件使用方式 | 含義 | 如果指定文件不存在 |
---|---|---|
“r”(只讀) | 為了輸入數據,打開一個已經存在的文本文件 | 出錯 |
“w”(只寫) | 為了輸出數據,打開一個文本文件 | 建立一個新的文件 |
“a”(追加) | 向文本文件尾添加數據 | 建立一個新的文件 |
“rb”(只讀) | 為了輸入數據,打開一個二進制文件 | 出錯 |
“wb”(只寫) | 為了輸出數據,打開一個二進制文件 | 建立一個新的文件 |
“ab”(追加) | 向一個二進制文件尾添加數據 | 建立一個新的文件 |
“r+”(讀寫) | 為了讀和寫,打開一個文本文件 | 出錯 |
“w+”(讀寫) | 為了讀和寫,建議一個新的文件 | 建立一個新的文件 |
“a+”(讀寫) | 打開一個文件,在文件尾進行讀寫 | 建立一個新的文件 |
“rb+”(讀寫) | 為了讀和寫打開一個二進制文件 | 出錯 |
“wb+”(讀寫) | 為了讀和寫,新建一個新的二進制文件 | 建立一個新的文件 |
“ab+”(讀寫) | 打開一個二進制文件,在文件尾進行讀和寫 | 建立一個新的文件 |
/* fopen fclose example */
#include <stdio.h>
int main ()
{FILE * pFile;//打開?件pFile = fopen ("myfile.txt","w");//?件操作if (pFile!=NULL){fputs ("fopen example",pFile);//關閉?件fclose (pFile);}return 0;
}
4.3.1 文件路徑中起始點的解析
假設程序當前運行的目錄是 C:\A\B\C(即當前路徑在 C 文件夾里),來看兩個例子的區別:
例子1:.\\..\\..\\test.txt(1個 . + 2個 ..)
?? ???? ?解析步驟:
?? ?1.?? ?. → 從當前目錄 C:\A\B\C 開始
?? ?2.?? ?第一個 .. → 跳至上一級 C:\A\B
?? ?3.?? ?第二個 .. → 再跳至上一級 C:\A
?? ?4.?? ?最終找 test.txt → 實際路徑是 C:\A\test.txt
例子2:..\\..\\..\\test.txt(3個 ..)
?? ???? ?解析步驟:
?? ?1.?? ?從當前目錄 C:\A\B\C 開始(默認起點,無需 .)
?? ?2.?? ?第一個 .. → 跳至 C:\A\B
?? ?3.?? ?第二個 .. → 跳至 C:\A
?? ?4.?? ?第三個 .. → 再跳至 C:\(A 文件夾的上一級是根目錄 C:\)
?? ?5.?? ?最終找 test.txt → 實際路徑是 C:\test.txt
核心區別:
?? ???? ?例子1的路徑最終定位到 C:\A\test.txt(向上跳2級)
?? ???? ?例子2的路徑最終定位到 C:\test.txt(向上跳3級)
跳轉的層級不同,找到的文件位置自然不一樣~
4.3.2? 文件關閉和free有點相似
你混淆的點其實和「動態內存管理」「資源釋放」的共性與差異有關,這幾個概念確實容易串在一起,咱們分清楚核心邏輯就好了:
一、fclose() + 置空指針 與 free() + 置空指針的「異曲同工」之處
兩者本質都是 “釋放資源后避免誤用”,邏輯完全一致:
?? ???? ?free(p):釋放動態分配的內存(比如malloc申請的),但指針p仍保留原地址(變成野指針)。如果之后再用p讀寫,會訪問無效內存,導致錯誤。
?? ???? ?fclose(fp):釋放文件相關的系統資源(文件描述符、緩沖區等),但指針fp仍保留原地址(變成野指針)。如果之后再用fp操作文件,會訪問已回收的資源,導致錯誤。
所以兩者都需要在釋放后 手動置空指針(p = NULL; 或 fp = NULL;),目的是把“隱藏的錯誤”變成“明確的空指針錯誤”,方便調試。
二、為什么有人會誤認為“不置空會讓程序一直運行”?
可能和 “內存泄漏”的概念混淆了。動態內存管理中:
?? ???? ?如果用malloc申請了內存,卻忘記free,會導致 內存泄漏(系統內存被持續占用,無法回收)。
?? ???? ?對于長期運行的程序(比如服務器),內存泄漏累積到一定程度,可能導致系統內存不足,甚至程序崩潰(但不是“程序一直運行停不下來”,而是“運行中出問題”)。
但注意:
?? ???? ?內存泄漏的核心是“資源沒釋放”,和指針是否置空無關(哪怕free后沒置空,資源也已經釋放了,不會泄漏)。
?? ???? ?程序是否“停不下來”,只看代碼邏輯(比如while(1)死循環),和內存/文件資源是否釋放完全無關。
5. 文件的順序讀寫
5.1 順序讀寫函數介紹
函數名 | 功能 | 適用于 |
---|---|---|
fgetc | 字符輸入函數 | 所有輸入流 |
fputc | 字符輸出函數 | 所有輸出流 |
fgets | 文本行輸入函數 | 所有輸入流 |
fputs | 文本行輸出函數 | 所有輸出流 |
fscanf | 格式化輸入函數 | 所有輸入流 |
fprintf | 格式化輸出函數 | 所有輸出流 |
fread | 二進制輸入 | 文件輸入流 |
fwrite | 二進制輸出 | 文件輸出流 |
5.2 對比?組函數:
scanf / fscanf / sscanfprintf / fprintf /? sprintf
5.2.1 scanf/fscanf/sscanf 的區別
scanf
功能:從標準輸入流(通常是鍵盤)讀取格式化數據。
原型:int scanf(const char *format, ...);
示例:
#include <stdio.h>int main()
{int num;printf("請輸入一個整數: ");scanf("%d", &num);printf("你輸入的整數是: %d\n", num);return 0;
}
說明:程序運行時,會等待用戶通過鍵盤輸入數據,scanf
?按照指定的格式?%d
?讀取整數并存儲到變量?num
?中。
fscanf
功能:從指定的文件流中讀取格式化數據。
原型:int fscanf(FILE *stream, const char *format, ...);
示例:
#include <stdio.h>int main()
{FILE *fp;int num;fp = fopen("test.txt", "r");if (fp != NULL) {fscanf(fp, "%d", &num);printf("從文件中讀取的整數是: %d\n", num);fclose(fp);}return 0;
}
說明:假設?
test.txt
?文件中存儲了一個整數,fscanf
?從打開的文件流?fp
?中,按照?%d
?的格式讀取整數。它適用于從文件中讀取特定格式的數據,而不是從標準輸入讀取。
sscanf
功能:從字符串中讀取格式化數據。
原型:int sscanf(const char *str, const char *format, ...);
示例:
#include <stdio.h>int main()
{char str[] = "123";int num;sscanf(str, "%d", &num);printf("從字符串中讀取的整數是: %d\n", num);return 0;
}
- 說明:
sscanf
?從給定的字符串?str
?中,按照?%d
?的格式解析出整數,并存儲到變量?num
?中。它常用于從已有的字符串中提取特定格式的數據。
5.2.2? printf/fprintf/sprintf 的區別
printf
功能:將格式化的數據輸出到標準輸出流(通常是屏幕)。
原型:int printf(const char *format, ...);
示例:
#include <stdio.h>int main()
{int num = 10;printf("整數的值是: %d\n", num);return 0;
}
說明:
printf
?按照指定的格式?%d
?將變量?num
?的值輸出到屏幕上。fprintf
功能:將格式化的數據輸出到指定的文件流中。
原型:int fprintf(FILE *stream, const char *format, ...);
示例:
#include <stdio.h>int main()
{FILE *fp;int num = 20;fp = fopen("output.txt", "w");if (fp != NULL) {fprintf(fp, "整數的值是: %d\n", num);fclose(fp);}return 0;
}
說明:
fprintf
?將格式化的數據輸出到打開的文件流?fp
?指向的文件中,這里是將包含整數?num
?值的字符串寫入到?output.txt
?文件。sprintf
功能:將格式化的數據輸出到字符串中。
原型:int sprintf(char *str, const char *format, ...);
示例:
#include <stdio.h>int main()
{char result[50];int num = 30;sprintf(result, "整數的值是: %d\n", num);printf("生成的字符串是: %s", result);return 0;
}
說明:sprintf
?按照指定的格式將數據寫入到字符數組?result
?中,而不是直接輸出到屏幕或文件,之后可以對這個字符串進行進一步的處理,比如傳遞給其他函數 。
5.3 我對內存和文件的總結
假設寫了一個簡單的程序:
#include <stdio.h>
int main()
{FILE *f = fopen("test.txt", "r"); // 用r模式打開文件int num_from_file;fscanf(f, "%d", &num_from_file); // 從文件讀數據(比如讀到123)fclose(f); // 關閉文件int num_from_keyboard;printf("請輸入一個數字:");scanf("%d", &num_from_keyboard); // 從鍵盤讀數據(比如輸入456)int result = num_from_file + num_from_keyboard; // 計算123+456printf("結果是:%d", result); // 屏幕顯示589return 0;
}
這里的關鍵:
?? ???? ?test.txt是外部文件,里面的123是“長期存儲”的(關了電腦也在)。
?? ???? ?程序運行時,會把123從文件讀到num_from_file這個變量里——這個變量存在內存里(臨時存儲,程序結束就消失了)。
?? ???? ?你從鍵盤輸入的456,會存在num_from_keyboard這個變量里——也在內存里,和文件沒關系。
總結來了:
- ?r模式的核心:只允許“讀取文件內容”,絕對不能改文件本身(文件里的123永遠是123)。
- ?程序的臨時變量:鍵盤輸入的數字、計算過程中的數據,都存在程序的內存里(像局部變量這種),是程序自己的“臨時工作區”,和被打開的文件沒關系。? ?
- 文件和程序是分開的:用r模式打開文件,只是“借用”文件的內容來用,程序后續的代碼(比如輸入、計算、顯示)都是服務于程序本身,不會碰文件一根毫毛。
簡單說:r模式下,文件是“只讀的素材”,程序是“處理素材的工具”,工具自己怎么折騰(臨時變量、輸入)都不影響素材本身。
6. 文件的隨機讀寫
6.1 fseek
int fseek ( FILE * stream, long int offset, int origin );
例子:
/* fseek example */
#include <stdio.h>
int main ()
{FILE * pFile;pFile = fopen ( "example.txt" , "wb" );fputs ( "This is an apple." , pFile );fseek ( pFile , 9 , SEEK_SET );fputs ( " sam" , pFile );fclose ( pFile );return 0;
}
例子2:
int fseek(FILE *stream, long offset, int origin);
參數說明:
stream
:文件指針(如?FILE *fp
)offset
:偏移量(字節數),可正可負(正數向后移,負數向前移)origin
:起始點(從哪里開始計算偏移),有 3 種取值:SEEK_SET
:從文件開頭開始(值為 0)SEEK_CUR
:從當前位置開始(值為 1)SEEK_END
:從文件末尾開始(值為 2)
// 假設文件已打開(fp)
fseek(fp, 100, SEEK_SET); // 移動到文件第100個字節處(從開頭數)
fseek(fp, 50, SEEK_CUR); // 從當前位置再向后移動50字節
fseek(fp, -30, SEEK_END); // 從文件末尾向前移動30字節(定位到倒數第30字節)
6.2 ftell
long int ftell ( FILE * stream );
/* ftell example : getting size of a file */
#include <stdio.h>
int main ()
{FILE * pFile;long size;pFile = fopen ("myfile.txt","rb");if (pFile==NULL)perror ("Error opening file");else{fseek (pFile, 0, SEEK_END); // non-portablesize=ftell (pFile);fclose (pFile);printf ("Size of myfile.txt: %ld bytes.\n",size);}
return 0;
}
- 記錄當前位置,方便后續回到該位置
- 計算文件大小(先定位到文件末尾,再用?
ftell
?獲取偏移量):
fseek(fp, 0, SEEK_END); // 移動到文件末尾
long file_size = ftell(fp); // 此時的偏移量就是文件總字節數
6.3 rewind
(fp, 0, SEEK_SET)
。
void rewind ( FILE * stream );
/* rewind example */
#include <stdio.h>
int main ()
{int n;FILE * pFile;char buffer [27];pFile = fopen ("myfile.txt","w+");for ( n='A' ; n<='Z' ; n++)fputc ( n, pFile);rewind (pFile);fread (buffer,1,26,pFile);fclose (pFile);buffer[26]='\0';printf(buffer);return 0;
}
區別:rewind
?沒有返回值,而?fseek
?會返回 0(成功)或非 0(失敗),可以判斷操作是否成功。
6.4? 三者總結
fseek
?是 "主動移動" 指針到任意位置ftell
?是 "查詢" 當前指針的位置rewind
?是 "快速復位" 指針到開頭
7. 文件讀取結束的判定
7.1 被錯誤使用的 feof
- fgetc 判斷是否為 EOF .
- fgets 判斷返回值是否為 NULL .
- fread判斷返回值是否?于實際要讀的個數。
#include <stdio.h>
#include <stdlib.h>int main(void)
{int c; // 注意:int,?char,要求處理EOFFILE* fp = fopen("test.txt", "r");if(!fp) {perror("File opening failed");return EXIT_FAILURE;}//fgetc 當讀取失敗的時候或者遇到?件結束的時候,都會返回EOFwhile ((c = fgetc(fp)) != EOF) // 標準C I/O讀取?件循環{putchar(c);}//判斷是什么原因結束的if (ferror(fp))puts("I/O error when reading");else if (feof(fp))puts("End of file reached successfully");fclose(fp);
}
#include <stdio.h>enum { SIZE = 5 };
int main(void)
{double a[SIZE] = {1.,2.,3.,4.,5.};FILE *fp = fopen("test.bin", "wb"); // 必須??進制模式fwrite(a, sizeof *a, SIZE, fp); // 寫 double 的數組fclose(fp);double b[SIZE];fp = fopen("test.bin","rb");size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 讀 double 的數組if(ret_code == SIZE) {puts("Array read successfully, contents: ");for(int n = 0; n < SIZE; ++n)printf("%f ", b[n]);putchar('\n');} else { // error handlingif (feof(fp))printf("Error reading test.bin: unexpected end of file\n");else if (ferror(fp)) {perror("Error reading test.bin");}}fclose(fp);
}
8. 文件緩沖區
8.1 考點一:緩沖區概念
#include <stdio.h>
#include <stdlib.h>int main()
{FILE *fp;fp = fopen("test.txt", "w");if (fp == NULL) {perror("fopen");exit(EXIT_FAILURE);}// 向文件寫入數據,此時數據先寫入緩沖區fputs("Hello, File Buffer!", fp); // 程序結束前,緩沖區數據會自動寫入磁盤fclose(fp); return 0;
}