掌握C語言文件操作:從理論到實戰指南

????????文件操作是C語言編程中不可或缺的一部分,它使得程序能夠持久化存儲數據,并在需要時高效讀寫。本文將從基礎概念到實戰技巧,系統講解C語言文件操作的核心知識點,并結合代碼示例幫助讀者深入理解。

一. 為什么需要文件操作?

? ? ? ? 程序運行時,數據存儲在內存中,一旦程序結束,內存數據就會被釋放。文件操作解決了數據的持久化問題,例如:

? ? ? ? 保存用戶配置:如游戲的存檔和設置。

? ? ? ? 處理大規模數據:如日志文件或數據庫的讀寫。

? ? ? ? 跨進程通信:通過文件共享數據。

? ? ? ? 二. 文件類型

? ? ? ? 先補充文件和文件名的概念:

? ? ? ? 文件:磁盤(硬盤)上的文件是文件。

? ? ? ? 文件名:也稱文件標識,例如C:\code\test.txt。由文件路徑、文件名主干、文件后綴三部分組成,以便用戶識別和引用。?

? ? ? ? 在程序設計中,我們一般講兩種文件:程序文件數據文件(從文件功能的角度分類)。

1. 程序文件

? ? ? ? 程序文件包含三種:

? ? ? ? 源文件(.c):開發者編寫的代碼文件。

? ? ? ? 目標文件(.obj):編譯后的中間文件。

? ? ? ? 可執行文件(.exe):鏈接后直接運行的程序。?

? ? ? ? 2. 數據文件?

? ? ? ? 文件的內容不一定是程序,而是程序運行時讀寫的數據,比如程序運行需要從中讀取數據的文件,或者輸出內容的文件。這就是數據文件,也是本章討論的重點。

? ? ? ? 數據文件包含兩種:

? ? ? ? 文本文件:如.txt、.csv,內容為ASCII字符。

? ? ? ? 二進制文件:如圖片、音頻,內容為二進制數據。

? ? ? ? 不同數據存儲的數據文件類型不同,字符型數據一律以ASCII形式存儲,數值型數據可以用ASCII形式存儲,也可以使用二進制形式存儲。

? ? ? ? 例如,十進制整型10000,二進制為00000000 00000000 00100111 00010000。如果以ASCII碼的形式輸出到磁盤(文本文件),為“1” “0” “0” “0” “0”,磁盤中占用5個字節(每個字符1個字節);如果以二進制形式輸出(二進制文件),為0x10,0x27,0x00,0x00,則在磁盤中只占4個字節

????????測試代碼:

#include <stdio.h>int main()
{int a = 10000;FILE* pf = fopen("test.txt", "wb");//wb - write binaryfwrite(&a, 4, 1, pf);//二進制的形式寫到文件中fclose(pf);pf = NULL;return 0;
}

? ? ? ? 這段代碼創建了一個新文件,并將整數10000以二進制形式寫入文件中。運行后調試控制臺沒有任何結果,但是編譯器已經創建了一個二進制文件(.txt),可以通過添加現有項找到該二進制文件。

?????????我們通過選擇打開方式為二進制編譯器就能打開該二進制文件。

? ? ? ? 文件內容:

? ? ? ? 三. 流和標準流?

? ? ? ? 程序的數據需要輸出到各種外部設備,也需要從外部設備獲取數據,不同的外部設備的輸入輸出操作各不相同。為了方便程序員對各種設備進行方便的操作,我們抽象出了流的概念。?

? ? ? ? 1. 什么是流?

? ? ? ? 在C語言中,流(stream)是一個抽象概念,表示程序與外部設備(如鍵盤、顯示器、文件、網絡等)之間數據傳輸的通道。

? ? ? ? 可以將流想象成一條“數據河流”,數據在這條河中單向流動,因此有輸入流也有輸出流:

? ? ? ? 輸入流:數據從外部設備(如鍵盤、文件)流向程序。

? ? ? ? 輸出流:數據從程序流向外部設備(如顯示器、文件)。

? ? ? ? 2. 流的抽象意義?

? ? ? ? 流的抽象意義有兩點:?

? ? ? ? 1. 統一接口:不同設備的操作方式差異巨大(例如鍵盤輸入和文件讀取),但流通過統一接口(如fgetc、fprintf)屏蔽了底層細節,程序員無需關心設備的具體實現。
? ? ? ? 2. 緩沖機制:流通常與緩沖區(Buffer)結合使用。例如,數據從內存寫入磁盤時,先暫存到緩沖區,緩沖區滿后一次性寫入,提升IO效率(計算機系統在進行輸入/輸出操作時的性能表現)。?

? ? ? ? 3. 標準流?

? ? ? ? 但是我們從鍵盤輸入數據,向屏幕上輸出,并沒有打開流。是因為C語言程序啟動時,默認打開三個預定義的流,稱為標準流:

? ? ? ? 1. stdin - 標準輸入流,通常關聯鍵盤輸入,scanf函數就是從標準輸入流中讀取數據。

? ? ? ? 2. stdout - 標準輸出流,通常關聯顯示器輸出,printf函數就是將信息輸出到標準輸出流中。

? ? ? ? 3. stderr - 標準錯誤流,專用于輸出錯誤信息,默認也關聯顯示器。

?????????標準流的特點:?

? ? ? ? 1. 無需手動打開和關閉:程序啟動時自動創建,結束時自動釋放。

? ? ? ? 2. 數據類型為FILE*(稱為文件指針):C語言中,就是通過FILE*的文件指針來維護流的各種操作。?

?????????四. 文件指針

?????????每個被使用的文件,都在內存中開辟了一個相應的文件信息區,用來存放文件的相關信息,如文件的名字,文件狀態以及文件當前的位置等。

? ? ? ? 這些信息被保存在一個名為FILE的結構體變量中。該結構體是由系統聲明的,如VS2013編譯環境下提供的stdio.h頭文件中有以下的文件類型聲明:

struct _iobuf
{char *_ptr;int  _cnt;char *_base;int  _flag;int  _file;int  _charbuf;int  _bufsize;char *_tmpfname;
};typedef struct _iobuf FILE;

? ? ? ? 不同的編譯器的FILE類型包含的內容不完全相同,但是大同小異。每當打開?個文件的時候,系統會根據文件的情況自動創建?個FILE結構的變量,并填充其中的信息,使用者不必關心細節。

?????????般都是通過?個FILE的指針來維護這個FILE結構的變量,這樣使用起來更加方便。下面我們可以創建?個FILE*的指針變量:?

FILE* pf;//文件指針變量

????????定義pf是?個指向FILE類型數據的指針變量。可以使pf指向某個文件的文件信息區(是?個結構體變量)。通過該文件信息區中的信息就能夠訪問該文件。也就是說,通過文件指針變量能夠間接找到與它關聯的文件。

? ? ? ? 示意圖:

? ? ? ? 五. 文件操作核心函數?

? ? ? ? 1. 打開與關閉文件?

? ? ? ? 文件在讀寫之前應該先打開文件,在使用結束后應該關閉文件。ANSI C規定使用fopen函數來打開文件,fclose函數來關閉文件。函數原型如下:

? ? ? ? fopen打開文件,需指定文件路徑(文件名,filename)和模式(mode)。打開文件失敗時,會返回NULL

? ? ? ? 比如:

#include <stdio.h>int main()
{FILE* pf = fopen("test.txt", "r");//用只讀的方式打開文件if (pf == NULL){perror("fopen");return 1;}fclose(pf);pf = NULL;return 0;
}

? ? ? ? 這里并不存在文件名為test.txt的文件,所以pf為NULL。運行結果:

? ? ? ? 文件的打開模式有以下幾種:

模式含義文件不存在時的行為
"r"(只讀)為了讀取數據,打開一個已經存在的文本文件出錯
"w"(只寫)為了寫入數據,打開一個文本文件(寫入會覆蓋原有內容)建立一個新文件
"a"(追加)向文本文件尾添加數據建立一個新文件
"rb"(只讀)為了讀取數據,打開一個已經存在的二進制文件出錯
"wb"(只寫)為了寫入數據,打開一個二進制文件建立一個新文件
"ab"(追加)向二進制文件尾添加數據建立一個新文件
"r+"(讀寫)為了讀和寫,打開一個文本文件出錯
"w+"(讀寫)為了讀和寫,建立一個新的文本文件建立一個新文件
"a+"(讀寫)打開一個文本文件,在文件尾進行讀寫?建立一個新文件
"rb+"(讀寫)為了讀和寫,打開一個二進制文件出錯
"wb+"(讀寫)為了讀和寫,建立一個新的二進制文件建立一個新文件
"ab+"(讀寫)打開一個二進制文件,在文件尾進行讀寫?建立一個新文件

? ? ? ? 比如:

#include <stdio.h>int main()
{int a = 10000;FILE* pf = fopen("test.txt", "w");//用只寫的方式打開文件if (pf == NULL){perror("fopen");return 1;}fclose(pf);pf = NULL;return 0;
}

? ? ? ? 運行后就會發現,程序文件相同目錄下生成了一個新文件,名為test.txt。

? ? ? ? 默認在當前目錄下讀或寫,也可以通過絕對路徑和相對路徑讓代碼按照指定路徑讀/寫文件:

? ? ? ? 絕對路徑:從根目錄開始,完整描述文件或目錄位置的路徑,以“ / ”或“ \ ”?分隔。例如,C:\User\Username\Documents\file.txt。

? ? ? ? 相對路徑:相對于當前工作目錄或文件位置的路徑。例如,當前路徑是C:\User\Username\Documents,那么file.txt的相對路徑可以是..\Picture\file.jpg,表示文件file.jpg位于當前目錄的上一級目錄Picture中( . 表示當前目錄,.. 表示上一級目錄,以此類推)。

? ? ? ? 注意:代碼中要連用兩個反斜杠,表示一個反斜杠,防止它被解釋為一個轉義序列符。

? ? ? ? 2. 順序讀寫函數?

? ? ? ? 當我們掌握了打開和關閉文件,就要來學習如何讀寫文件。

????????順序讀寫函數有以下幾種:

函數名功能適用于
fgetc字符輸入函數(讀取單個字符)所有輸入流
fputc字符輸出函數(寫入單個字符)所有輸出流
fgets文本行輸入函數(讀取一行文本)所有輸入流
fputs文本行輸出函數(寫入一行文本)所有輸出流
fscanf格式化輸入函數所有輸入流
fprintf格式化輸出函數所有輸出流
fread二進制輸入文件輸入流
fwrite二進制輸出文件輸出流

? ? ? ? 上面說的適用于所有輸出/入流,一般指適用于標準輸出/入流和其他輸出/入流(如文件輸出/入流)?。前六個函數是針對文本數據進行文件的輸出和輸出,最后兩個是針對二進制數據進行文件的輸出和出入。

? ? ? ? 2.1 fputc和fgetc

? ? ? ?fputc函數的原型:

? ? ? ? ?作用:fputc函數將字符character(傳遞參數是字符的ACSII碼值),寫入stream流(指向的對應文件信息區的指針),并前進位置指示器(即光標)。

? ? ? ? 返回值:如果寫入成功,會返回該字符的ACSII碼值;如果寫入失敗,會返回EOF

? ? ? ? 例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//寫文件fputc('a', pf);	fputc('b', pf);fputc('c', pf);fputc('d', pf);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 這段代碼將字符a、b、c、d寫入data.txt中。運行之后就會發現,程序文件相同路徑下生成了一個data.txt文本文件,打開后會有如下內容:

? ? ? ? 當寫入第一個字符a時,光標就會移動到a的后面,隨后寫入字符b,再次移動光標,以此類推。?

? ? ? ?fgetc函數的原型:

? ? ? ??作用:fgetc函數從流中獲取字符,并前進光標。

? ? ? ? 返回值:如果獲取成功,會返回該字符的ACSII碼值(int類型);如果獲取字符失敗,會返回EOF。?

? ? ? ? 例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//讀文件(文件原有字符串“abcd”)int ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);//也可以通過fgetc的返回值讀取文件中所有字符://while ((int ch = fgetc(pf)) != EOF)//{//	printf("%c", ch);//}//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 這段代碼從data.txt中讀取了前四個字符,并依次打印。運行結果:

?????????當讀取第一個字符a時,光標就會移動到a的后面,隨后寫讀取字符b,再次移動光標,以此類推。?

? ? ? ? 再來看一個例子:

#include <stdio.h>int main()
{int ch = fgetc(stdin);putchar(ch);//打印一個字符,相當于printf("%c", )//getchar -- 讀取一個字符,相當于scanf("%c", )return 0;
}

? ? ? ? fgetc函數從標準輸入流stdin中獲取字符,putchar函數再將字符輸出。運行后會發現,控制臺窗口沒有輸出任何數據,光標停在首位。因為此時標準輸入流中沒有數據,我們可以通過鍵盤輸入字符,這個字符就會進入標準輸入流,并被獲取和打印。運行結果:

? ? ? ? 這說明,fgetc函數適用于所有輸入流,同樣的也可以證明fputc函數適用于所有輸出流,代碼如下:

#include <stdio.h>int main()
{int ch = fgetc(stdin);fputc(ch, stdout);return 0;
}

? ? ? ? 我們通過鍵盤輸入一個字符,對應的就會打印這個字符。運行結果:

?????????2.2?fputs和fgets

? ? ? ? fputs函數的原型:

? ? ? ? 作用:fputs函數將指針str指向的字符串寫入流中。

? ? ? ? 返回值:如果寫入成功,會返回一個非負值(non-negative value);如果寫入失敗,會返回EOF。?fputs函數將字符串寫入流中時,遇到“ \0 ”結束寫入。

? ? ? ? 例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//寫文件//寫入一行字符fputs("How are you?\n", pf);fputs("abcdefg\n", pf);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 這里就會發現,data.txt多出了很多內容:

? ? ? ? 注意:

????????1. 寫入字符串含“\n”,寫入文件時就會換行。

? ? ? ? 2. 我們會發現,原本的“abcd”已經不見了,這就是“w”只寫模式的特點:寫入會覆蓋原有內容。?

? ? ? ? fgets函數的原型:

? ? ? ? 作用:從流中讀取字符,并將其作為字符串儲存到str中,直到讀取(num-1)個字符或者到達換行符或文件結束符(end-of-file)為止(以先發生的為準),并移動光標至讀取字符的后面?。當換行符使fgets停止讀取時,換行符仍被函數認為是一個有效字符,并包含在復制到str的字符串中。

? ? ? ? 返回值:如果成功,該函數返回str;如果發生讀取失敗,則返回的指針是空指針

? ? ? ? 例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//讀文件,data.txt中有字符串“abcdefghijk”//讀取多個字符char ch[10];fgets(ch, 10, pf);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 調試并打開監視:

? ? ? ? 這里說明,函數參數num為10時,實際上是只讀取了9個有效字符,和一個“\0”。把數組大小和num改為20,再調試并打開監視:?

? ? ? ? fgets讀取完所有字符后就不會再讀取。

? ? ? ? 再舉一個例子:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//讀文件//data.txt中有://hello world//hahahachar ch[20];fgets(ch, 20, pf);printf("%s", ch);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 運行結果:

? ? ? ? 并沒有讀取到hahaha。但是調試并打開監視:

? ? ? ? 這里ch儲存了轉行符,所以只要再讀取一次就能打印hahaha:

? ? ? ? 2.3?fprintf和fscanf

? ? ? ? fprintf函數的原型:

?

? ? ? ? fprintf函數將具有一定格式的數據寫入流中。函數參數后面有省略號,這被稱為可變參數。printf函數的參數中也存在可變參數,例如:

printf("%d",10);
printf("hello");
printf("%d %s",10,:"hello");

? ? ? ? 參數的類型和數量都不同,所以函數參數用可變參數代替。fprintf的使用和printf非常相似,只是fprintf的函數參數比printf多了一個文件指針類型的流,因此使用fprintf函數并不困難,例如:

#include <stdio.h>int main()
{int age = 18;char name[20] = "zhangsan";double grades = 95.5;FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//寫文件fprintf(pf, "%d %s %.2lf", age, name, grades);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 運行結果:

? ? ? ? 能夠按一定格式輸出,也就能按一定格式輸入,fscanf函數就能從文件中獲取數據。

? ? ? ? fscanf函數的原型如下:

? ? ? ? fscanf函數和scanf函數的參數相似(fscanf函數的參數多了“FILE* stream”),使用方法也很相似,例如:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//讀文件//文件原有://18 zhangsan 99.50//scanf("%d %s %lf", &age, name, &grades);--再加上流就是fscanf函數:fscanf(pf, "%d %s %lf", &age, name, &grades);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 運行結果:

?????????2.4?fwrite和fread

? ? ? ? fwrite函數的原型:

????

? ? ? ? fwrite函數將數據以二進制形式寫入流中。該函數有以下參數:

? ? ? ? 1. const void* ptr:?ptr指向被寫的數據。

? ? ? ? 2. size_t size:被寫的數據中一個元素的長度(單位是字節)。

? ? ? ? 3. size_t count:元素的個數。

? ? ? ? 4. FILE* stream:寫入數據到stream流。

? ? ? ? 例如,將一個整型數組數據以二進制形式寫入data.txt文件中,代碼如下:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "wb");//以二進制寫的方式打開文件if (pf == NULL){perror("fopen");return 1;}//寫文件int arr[] = { 1,2,3,4,5,6,7,8,9,10 };fwrite(arr, sizeof(int), 10, pf);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 運行后打開data.txt,會發現寫入了數據,但是并不能看出來是10個數字:

? ? ? ? 用文本文件的方式打開二進制文件,數據就會變為亂碼,但實際上數組中的十個整型數據已經寫入了data.txt。?

? ? ? ? 我們可以用fread函數讀取數據驗證想法,fread函數的原型如下:

? ? ? ? fread函數從流中讀取二進制數據。該函數的四個參數和fwrite函數一樣。例如:

#include <stdio.h>int main()
{FILE* pf = fopen("data.txt", "rb");//以二進制只讀的方式打開文件if (pf == NULL){perror("fopen");return 1;}//寫文件int arr[10] = {0};fread(arr, sizeof(int), 10, pf);int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 運行結果:

? ? ? ? 說明fread函數成功從data.txt中讀取這十個數字,原本文件中確實儲存著這十個數字。?

? ? ? ? 如果讓fread函數讀取文本文件,就不會讀取到正確數據。例如,我們在data.txt中寫入十進制整型1到10,再用相同代碼讀取數據,運行結果:

? ? ? ? 讀取數據明顯不正確。所以要注意文本文件和二進制文件的區別,以及對應的數據之間的區別。

? ? ? ? 3. sscanf和sprintf

? ? ? ? 這里補充sscanfsprintf函數。

????????我們介紹過printf、fprintf、scanf和fscanf函數:

? ? ? ? scanf?-- 針對stdin的格式化的輸入函數

? ? ? ? printf?-- 針對stdout的格式化的輸出函數

? ? ? ? fscanf -- 針對所有輸入流 格式化的輸入函數

? ? ? ? fprintf -- 針對所有輸出流的格式化的輸出函數

? ? ? ? sscanf和sprintf函數能夠實現有格式的數據與字符串之間的轉換

? ? ? ? sscanf -- 從字符串中,按照格式提取格式化的數據

? ? ? ? sprintf -- 將帶有格式的數據,按照格式轉化成字符串

? ? ? ? sprintf函數的原型:

? ? ? ? sprintf函數寫格式化的數據到str指向的字符串中,也就是將格式化的數據轉換成字符串。例如:

#include <stdio.h>int main()
{int age = 18;char name[20] = "zhangsan";double grades = 95.5;char buf[120] = { 0 };//printf("%d %s %.1lf", age, name, grades)sprintf(buf, "%d %s %.1lf", age, name, grades);printf("%s\n", buf);return 0;
}

? ? ? ? 運行結果:

? ? ? ? sprintf函數將age、name和grades這三個不同類型的變量轉換成了字符串并寫入buf指向的字符串中。?

? ? ? ? sscanf函數的原型:

? ? ? ? sscanf函數和sprintf函數的作用相反,sscanf函數從字符串中讀取有格式的數據。例如:

#include <stdio.h>int main()
{int age = 18;char name[20] = "zhangsan";double grades = 95.5;char buf[120] = { 0 };sprintf(buf, "%d %s %.1f", age, name, grades);int age2 = 0;char name2[20] = "";double grades2 = 0;//scanf("%d %s %lf", &age2, &name2, &grades2);sscanf(buf, "%d %s %lf", &age2, &name2, &grades2);printf("%d\n", age2);printf("%s\n", name2);printf("%.1lf\n", grades2);return 0;

????????運行結果:

? ? ? ? sscanf函數從buf指向的字符串中,按%d %s %lf的格式順序讀取數據,并分別存儲在變量中。?

4. 文件的隨機讀寫

? ? ? ? 文件的讀寫,既支持順序讀寫,也支持隨機讀寫。比如文件中有“abcdef”,假設此時光標默認在f的右邊,我們可以通過一些函數讓光標移動到e的左側,以此為起始位置進行讀寫,這就是文件的隨機讀寫。?

? ? ? ? 4.1 fseek?

? ? ? ? fseek函數的原型:

? ? ? ? fseek函數可以根據文件指針的位置和偏移量來重新定位文件指針(即文件內容的光標)。該函數有三個參數:

? ? ? ? 1. FILE* stream:指向文件的流。

? ? ? ? 2. long int offset:相對于起始位置origin的偏移量(單位字節),向右偏移是正數,向左偏移是負數。

? ? ? ? 3. int origin:origin有三種情況:SEEK_SET(文件的起始處),SEEK_CUR(光標的當前位置),SEEK_END(文件的末尾處)。

? ? ? ? 例如,文件中有“abcdefghi”?:

int ch = fgetc(pf);
printf("%c\n",ch);
ch = fgetc(pf);
printf("%c\n",ch);

? ? ? ? 這樣就會打印a和b兩個字符。如果想要讀取a后立即讀取e,就可以用fseek函數重新定位光標(光標應該在e的左側):

fseek(pf,4,SEEK_SET);//從文件起始處,偏移量為4的地方
fseek(pf,3,SEEK_CUR);//從光標的當前位置(即a的右側),偏移量為3的地方
fseek(pf,-5,SEEK_END);//從文件末尾處,偏移量為-5(向左偏移,為負數)的地方

? ? ? ? 這三種寫法都是定位光標在e的左側。完整代碼:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//讀文件int ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 4, SEEK_SET);//fseek(pf, 3, SEEK_CUR);//fseek(pf, -5, SEEK_END);ch = fgetc(pf);printf("%c\n", ch);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 運行結果:

? ? ? ? 4.2 ftell?

? ? ? ? ftell函數的原型:

? ? ? ? ftell函數會返回文件指針相對于起始位置的偏移量(返回類型為long int)。?

? ? ? ? 例如:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//讀文件//文件原有內容:abcdefghiint ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 4, SEEK_SET);ch = fgetc(pf);printf("%c\n", ch);printf("%ld\n", ftell(pf));//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 當第二次fgetc函數運行后,光標指在e和f之間,與起始位置的偏移量為5。?

? ? ? ? 運行結果:

4.3 rewind?

? ? ? ? rewind函數的原型:

? ? ? ? rewind函數可以讓文件指針的位置回到文件的起始位置

? ? ? ? 例如:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//讀文件int ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 4, SEEK_SET);ch = fgetc(pf);printf("%c\n", ch);rewind(pf);//光標再次回到起始位置ch = fgetc(pf);//此時讀取的就是字符aprintf("%c\n", ch);//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 第二次fgetc后,rewind函數讓光標再次指向了初始位置,則第三次fgetc函數讀取到的字符仍然是字符a。

運行結果:

? ? ? ? 以上就是文件的隨機讀寫中最重要的三個函數。?

六. 文件讀取結束的判定

? ? ? ? 在讀取文件時,我們可以利用“文件是否讀取結束”這一信息判斷是否繼續讀取文件。

? ? ? ? 例如:

#include <stdio.h>int main()
{int age;char name[20];double grades;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//讀文件//文件有:"abcdefghi"int ch = 0;while ((ch = fgetc(pf)) != EOF){printf("%c\n", ch);}//關閉文件fclose(pf);pf = NULL;return 0;
}

? ? ? ? 運行結果:

? ? ? ? 這里就是循環判斷fgetc函數的返回值是否為EOF(文件結束標志),如果返回值不是EOF,說明文件還可以正常讀取,代碼就會繼續讀取并打印數據;如果返回值是EOF,說明文件已經讀取結束,跳出循環。

? ? ? ? 判定文件讀取結束,一般都是判斷函數的返回值:

? ? ? ? 1.文本文件(EOF/NULL):

?????????????????fgetc(函數返回數據的ASCII碼值):判斷返回值是否為EOF

? ? ? ? ?????????fgets(函數返回str):判斷返回值是否為NULL

? ? ? ? 2. 二進制文件(返回值是否小于實際要讀的個數):

? ? ? ? ? ? ? ? ?fread:判斷返回值是否小于實際要讀的個數

? ? ? ? 文件讀取結束有兩種情況:

? ? ? ? 1. 正常讀取:遇到文件末尾而結束。

? ? ? ? 2.?異常讀取:發生讀取錯誤而結束。

? ? ? ? 想要知道是哪種情況導致文件讀取結束,就需要用到feof函數和ferror函數:

????????int feof ( FILE * stream ):如果文件讀取時遇到文件末尾,則返回非0的整型。

????????int ferror ( FILE * stream ):如果文件讀取時發生錯誤,則返回非0的整型。

? ? ? ? 例如(文本文件):

int main(void)
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//fgetc 當讀取失敗的時候或者遇到文件結束的時候,都會返回EOFint c; // 注意:int,非char,要求處理EOFwhile ((c = fgetc(pf)) != EOF) //I/O讀取文件循環{putchar(c);}//判斷是什么原因結束的if (ferror(pf))puts("I/O error when reading");else if (feof(pf))puts("End of file reached successfully");fclose(pf);pf = NULL;
}

? ? ? ? 運行結果:

? ? ? ? 根據返回結果,文件讀取結束是因為到達文件末尾。

? ? ? ? 例如(二進制文件):?

#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);
}

? ? ? ? 這里就是判斷fread函數的返回值(ret_code)是否小于實際要讀的個數,如果不小于,代碼接著打印數據,?如果小于,代碼就不會再打印數據,而是用feof函數和ferror函數檢測錯誤情況。

? ? ? ? 運行結果:

? ? ? ? 由此可知,在文件讀取過程中,不能用feof函數和ferror函數的返回值直接來判斷文件是否結束,正確方法應該是判斷各種文件順序寫函數的返回值

七. 文件緩沖區

????????ANSI C 標準采用“緩沖文件系統”?處理數據文件。

????????緩沖文件系統是指系統自動地在內存中為程序中每?個正在使用的文件開辟一塊“文件緩沖區”。從內存向磁盤輸出數據會先送到內存中的緩沖區,裝滿緩沖區后才?起送到磁盤上。如果從磁盤向計算機讀入數據,則從磁盤文件中讀取數據輸入到內存緩沖區(充滿緩沖區),然后再從緩沖區逐個地將數據送到程序數據區(程序變量等)。

????????緩沖區的大小根據C編譯系統決定的。?

? ? ? ? 示意圖:

? ? ? ? 例如:

#include <stdio.h>
#include <windows.h>
//VS2022 WIN11環境測試
int main()
{FILE*pf = fopen("test.txt", "w");fputs("abcdef", pf);//先將代碼放在輸出緩沖區printf("睡眠10秒-已經寫數據了,打開test.txt 文件,發現文件沒有內容\n");Sleep(10000);printf("刷新緩沖區\n");fflush(pf);//刷新緩沖區時,才將輸出緩沖區的數據寫到?件(磁盤)//注:fflush 在?版本的VS上不能使?了printf("再睡眠10秒-此時,再次打開test.txt 文件,文件有內容了\n");Sleep(10000);fclose(pf);//注:fclose在關閉?件的時候,也會刷新緩沖區pf = NULL;return 0;
}

? ? ? ? 根據代碼指示在不同時間段打開text.txt文件,就會發現文件內容的變化。

? ? ? ? 這里得出結論:因為有緩沖區的存在,C語言在操作文件的時候,需要做刷新緩沖區或者在文件操作結束的時候關閉文件。如果不做,可能導致讀寫文件的問題。

八. 總結

????????文件操作是C語言中實現數據持久化的核心技能。通過本文的學習,我們了解到文件的打開、讀寫、隨機訪問及錯誤處理等關鍵操作。?

? ? ? ? 掌握文件操作,讓你的程序真正“記住”數據!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/76367.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/76367.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/76367.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux 線程:從零構建多線程應用:系統化解析線程API與底層設計邏輯

線程 線程的概述 在之前&#xff0c;我們常把進程定義為 程序執行的實例&#xff0c;實際不然&#xff0c;進程實際上只是維護應用程序的各種資源&#xff0c;并不執行什么。真正執行具體任務的是線程。 那為什么之前直接執行a.out的時候&#xff0c;沒有這種感受呢&#xf…

014_多線程

多線程 多線程創建線程方式一&#xff1a;繼承Thread類方式二&#xff1a;實現Runable接口方式三&#xff1a;實現Callbale接口 Thread的常用方法線程安全線程同步方式一&#xff1a;同步代碼塊同步方法方式三&#xff1a;Lock鎖 線性池創建線程池處理Runnable任務處理Callable…

機場跑道異物檢測數據集VOC+YOLO格式33793張31類別

數據集分辨率都是300x300,都是貼近地面拍攝&#xff0c;具體看圖片 據集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路徑的txt文件&#xff0c;僅僅包含jpg圖片以及對應的VOC格式xml文件和yolo格式txt文件) 圖片數量(jpg文件個數)&#xff1a;33793 標注數量(xml文件…

Spring Cloud 遠程調用

4.OpenFeign的實現原理是什么&#xff1f; 在使用OpenFeign的時候&#xff0c;主要關心兩個注解&#xff0c;EnableFeignClients和FeignClient。整體的流程分為以下幾個部分&#xff1a; 啟用Feign代理&#xff0c;通過在啟動類上添加EnableFeignClients注解&#xff0c;開啟F…

Unity中使用FMETP STREAM傳輸實時畫面

一、客戶端&#xff08;發送端&#xff09; 總體思路&#xff1a;先把畫面編碼Encoder&#xff0c;再發送給服務端 新建場景&#xff0c;創建一個實體&#xff0c;名為FMnet&#xff0c;添加組件FMNetworkManager&#xff0c;將NetworkType設置為客戶端Client&#xff0c;設置…

Baklib三步構建企業內容中臺

需求調研構建內容中臺 企業內容中臺建設的首要環節在于精準識別業務需求與知識管理痛點。通過Baklib 是什么類型的工具的定位分析可知&#xff0c;其作為知識管理中樞&#xff0c;能夠系統梳理客戶服務場景中的高頻咨詢、產品文檔更新需求及跨部門協作流程。在需求調研階段&am…

實現抗隱私泄漏的AI人工智能推理

目錄 什么是私人AI? 什么是可信執行環境? TEE 如何在 AI 推理期間保護數據? 使用 TEE 是否存在風險? 有哪些風險? Atoma 如何應對這些風險 為什么去中心化網絡是解決方案 人工智能推理過程中還有其他保護隱私的方法嗎? 私人人工智能可以實現什么? 隱私驅動的應…

一、TorchRec里邊的輸入輸出類型

TorchRec中的輸入和輸出格式 文章目錄 TorchRec中的輸入和輸出格式前言一、JaggedTensor1.1 核心概念1.2 核心屬性&#xff0c;也就是參數1.3 關鍵操作與方法 二、KeyedJaggedTensor2.1 核心概念2.2 核心屬性&#xff0c;也就是參數 3、KeyedTensor總結 前言 TorchRec具有其特…

JAVA實現在H5頁面中點擊鏈接直接進入微信小程序

在普通的Html5頁面中如何實現點擊URL鏈接直接進入微信小程序&#xff0c;不需要掃描小程序二維碼&#xff1f; 網上介紹的很多方法是在小程序后臺設置Schema&#xff0c;不過我進入我的小程序后臺在開發設置里面 沒有找到設置小程序Schema的地方&#xff0c;我是通過調用API接口…

uniapp解決上架華為應用市場審核要求-監聽權限的申請

支持android平臺全局監聽權限的申請。當申請權限時&#xff0c;會在頁面頂部顯示申請權限的目的。主要解決上架華為應用市場審核要求&#xff1a;APP在調用終端權限時&#xff0c;應同步告知用戶申請該權限的目的。 因為如果不提示&#xff0c;你上架應用市場會被打打回來 Tip…

文件IO5(JPEG圖像原理與應用)

JPEG圖像原理與應用 ? 基本概念 JPEG&#xff08;Joint Photographic Experts Group&#xff09;指的是聯合圖像專家組&#xff0c;是國際標準化組織ISO制訂并于1992年發布的一種面向連續色調靜止圖像的壓縮編碼標準&#xff0c;所以也被稱為JPEG標準。 同樣&#xff0c;JP…

vue3 history路由模式刷新頁面報錯問題解決

在使用history路由模式時刷新網頁提示404錯誤&#xff0c;這是改怎么辦呢。 官方解決辦法 https://router.vuejs.org/zh/guide/essentials/history-mode.html

3D激光輪廓儀知識整理(待完善)

文章目錄 1.原理和應用場景1.1 相機原理1.1.1 測量原理1.1.2 相機激光器1.1.3 沙姆鏡頭1.1.4 相機標定1.1.5 中心線提取 1.2 應用場景1.2.1 測量相關應用1.2.2 缺陷檢測相關應用 2.相機參數介紹及選型介紹2.1 成像原理2.2 原始圖成像2.3 生成輪廓圖2.4 相機規格參數2.4.1 單輪廓…

w285藥店管理系統的設計與實現

&#x1f64a;作者簡介&#xff1a;多年一線開發工作經驗&#xff0c;原創團隊&#xff0c;分享技術代碼幫助學生學習&#xff0c;獨立完成自己的網站項目。 代碼可以查看文章末尾??聯系方式獲取&#xff0c;記得注明來意哦~&#x1f339;贈送計算機畢業設計600個選題excel文…

Google Chrome Canary版官方下載及安裝教程【適用于開發者與進階用戶】

谷歌瀏覽器&#xff08;Google Chrome&#xff09;以其高性能、強擴展性和良好的用戶體驗深受全球用戶喜愛。在其多個版本中&#xff0c;Chrome Canary因具備最前沿的功能測試環境&#xff0c;成為開發者和技術探索者的首選。如果你希望第一時間體驗Google Chrome最新功能&…

RocketMQ深度百科全書式解析

?一、核心架構與設計哲學? ?1. 設計目標? ?海量消息堆積?&#xff1a;單機支持百萬級消息堆積&#xff0c;適合大數據場景&#xff08;如日志采集&#xff09;。?嚴格順序性?&#xff1a;通過隊列分區&#xff08;Queue&#xff09;和消費鎖機制保證局部順序。?事務…

每日一題(小白)暴力娛樂篇19

樣例&#xff1a; 6 1 1 4 5 1 4 輸出&#xff1a; 56 66 52 44 54 64 分析題意可以得知&#xff0c;就是接收一串數字&#xff0c;將數字按照下標每次向右移動一位&#xff08;末尾循環到第一位&#xff09;&#xff0c;每次移動玩計算一下下標和數字的乘積且累加。 ①接收…

如何應對“最后時刻任務堆積”(鼓包現象)

應對“最后時刻任務堆積”&#xff08;鼓包現象&#xff09;的方法包括&#xff1a;合理規劃項目時間表、強化進度跟蹤管理、明確任務優先級、有效的資源配置、提升團隊溝通效率。其中&#xff0c;強化進度跟蹤管理尤為關鍵。根據項目管理協會&#xff08;PMI&#xff09;的調查…

19C-19.3環境-impdp導入到view時卡死

幫客戶導入一個用戶時&#xff0c;發現VIEW部分無法進行下去 Processing object type SCHEMA_EXPORT/TABLE/IDENTITY_COLUMN Processing object type SCHEMA_EXPORT/PACKAGE/PACKAGE_SPEC Processing object type SCHEMA_EXPORT/FUNCTION/FUNCTION Processing object type SCH…

一、簡單的 Django 服務

一、配置虛擬環境 1.1 創建一個文件夾在導航欄輸入cmd打開 1.2 安裝依賴兩個庫 pip install virtualenv virtualenvwrapper-win -i https://pypi.tuna.tsinghua.edu.cn/simple驗證是否安裝成功 virtualenv --version pip show virtualenvwrapper-win 1.3 創建虛擬環境 mkvi…