目錄
文章目錄
前言
什么是文件
文件名
文件的打開與關閉
文件的打開與關閉
文件的順序讀寫
字符讀寫
文本行的讀寫
格式化輸入輸出
二進制文件輸入輸出
文件的隨機讀寫
?文件操作知識拓展
?文本文件和二進制文件
文件結束判定
?文件緩沖區(qū)
總結
前言
文件操作可能看起來很簡單,但實際上它涉及到許多細節(jié)和技巧。在這篇博客中,我們將從基礎開始,逐步深入,為您解析C語言中的文件操作
什么是文件
磁盤上的文件都是文件。
在程序設計中,我們一般談的文件有兩種:程序文件、數(shù)據(jù)文件
程序文件
包括源程序文件(后綴為.c),目標文件(windows環(huán)境后綴為.obj),可執(zhí)行程序(windows環(huán)境后綴為.exe)。?
數(shù)據(jù)文件
文件的內(nèi)容不一定是程序,而是程序運行時讀寫的數(shù)據(jù),比如程序運行需要從中讀取數(shù)據(jù)的文件,或者輸出內(nèi)容的文件。?
本期我們主要探討數(shù)據(jù)文件
在以前處理數(shù)據(jù)的輸入輸出都是以終端為對象的,即從終端的鍵盤輸入數(shù)據(jù),運行結果顯示到顯示器上。
其實有時候我們會把信息輸出到磁盤上,當需要的時候再從磁盤上把數(shù)據(jù)讀取到內(nèi)存中使用,這里處理的就是磁盤上文件(從文件中讀數(shù)據(jù),將數(shù)據(jù)輸出讀入文件)。
文件名
在對文件進行操作時,我們必須要知道文件名,接下來我們先對文件名進行介紹。
?一個文件要有一個唯一的文件標識,以便用戶識別和引用。
文件名包含3部分:文件路徑+文件名主干+文件后綴
例如:?c:\code\test.txt
?為了方便起見,文件標識常被稱為文件名。
文件的打開與關閉
緩沖文件系統(tǒng)中,關鍵的概念是“文件類型指針”,簡稱“文件指針”
每個被使用的文件都在內(nèi)存中開辟了一個相應的文件信息區(qū),用來存放文件的相關信息(如文件的名字,文件狀態(tài)及文件當前的位置等)。這些信息是保存在一個結構體變量中的。該結構體類型是有系統(tǒng)聲明的,取名FILE.
怎么理解呢?例如:
struct _iobuf {
????char *_ptr;
????int ?_cnt;
????char *_base;
????int ?_flag;
????int ?_file;
????int ?_charbuf;
????int ?_bufsiz;
????char *_tmpfname;
???};
typedef struct _iobuf FILE;
?不同的C編譯器的FILE類型包含的內(nèi)容不完全相同,但是大同小異。
每當打開一個文件的時候,系統(tǒng)會根據(jù)文件的情況自動創(chuàng)建一個FILE結構的變量,并填充其中的信息,使用者不必關心細節(jié)
?一般都是通過一個FILE的指針來維護這個FILE結構的變量,這樣使用起來更加方便。
?我們先來創(chuàng)建一個FILE* 的指針變量。
FILE* pf;//文件指針變量
定義pf是一個指向FILE類型數(shù)據(jù)的指針變量??梢允筽f指向某個文件的文件信息區(qū)(是一個結構體變量)。通過該文件信息區(qū)中的信息就能夠訪問該文件。也就是說,通過文件指針變量能夠找到與它關聯(lián)的文件。
?或許大家有點決定抽象,那么接下來我們切身感受一下。
文件的打開與關閉
文件在讀寫之前應該先打開文件,在使用結束之后應該關閉文件。
在編寫程序的時候,在打開文件的同時,都會返回一個FILE*的指針變量指向該文件,也相當于建立了指針和文件的關系。
ANSIC 規(guī)定使用fopen函數(shù)來打開文件,fclose來關閉文件。
例如:
FILE * pf=fopen ( const char * filename, const char * mode );
int fclose ( FILE * stream );
?filename是要打開的文件名,? mode就是我們打開文件的方式。stream是指向文件的指針。
?打開方式如下:
文件使用方式 | 含義 | 如果指定文件不存在 |
“r”(只讀) | 為了輸入數(shù)據(jù),打開一個已經(jīng)存在的文本文件 | 出錯 |
“w”(只寫) | 為了輸出數(shù)據(jù),打開一個文本文件 | 建立一個新的文件 |
“a”(追加) | 向文本文件尾添加數(shù)據(jù) | 出錯 |
“rb”(只讀) | 為了輸入數(shù)據(jù),打開一個二進制文件 | 出錯 |
“wb”(只寫) | 為了輸出數(shù)據(jù),打開一個二進制文件 | 建立一個新的文件 |
“ab”(追加) | 向一個二進制文件尾添加數(shù)據(jù) | 出錯 |
“r+”(讀寫) | 為了讀和寫,打開一個文本文件 | 出錯 |
“w+”(讀寫) | 為了讀和寫,建議一個新的文件 | 建立一個新的文件 |
“a+”(讀寫) | 打開一個文件,在文件尾進行讀寫 | 建立一個新的文件 |
“rb+”(讀寫) | 為了讀和寫打開一個二進制文件 | 出錯 |
“wb+”(讀寫) | 為了讀和寫,新建一個新的二進制文件 | 建立一個新的文件 |
“ab+”(讀寫) | 打開一個二進制文件,在文件尾進行讀和寫? | 建立一個新的文件 |
怎么用呢?
我們先來一個簡單的,打開文件和關閉文件。
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");//輸出錯誤原因,雙引號里為要檢查的函數(shù)
return 1;
}
return 0;
}
?注意這里打開的方式是“r”,只讀已經(jīng)存在的文件,在程序當前文件中沒有一個叫data類型為txt的文件,程序就會報錯例如:
?如果存在程序就不會有任何輸出。
把“r”改成“w”,以寫的形式打開,這時我們再次運行就會看到一個data.txt的文件。
我們可以右擊代碼標簽頁,轉到所在文件夾:
?就可以看到。如果看多到txt后綴可以選擇打開文件擴展名:
點擊查看,選擇顯示,勾選文件擴展名選項。
?除此之外,我們還可以將文件放在其他路徑下。
FILE* pf = fopen("data.txt", "w");
這樣寫叫相對路徑。如果你想要創(chuàng)建在其他路徑,這就要給出絕對路徑,例如我們想把文件放在桌面上,這樣也是可以的,只需知道路徑就可以創(chuàng)建到桌面上,在需要創(chuàng)建的文件名前加上位置信息。?
例如:
FILE* pf = fopen("C:\\Users\\86150\\Desktop\\data.txt", "w");
?為了防止出現(xiàn)轉義字符,可以將每個\后加一個\,只要知道想要存放的具體位置信息,就可以將文件創(chuàng)建到指定位置。
在相對路徑的方法中,我們還可以將文件調(diào)整到上級目錄的其他文件路徑下,例如:
?點(.)是當前目錄,(..)是上級目錄,假設我們需要將文件放在程序上級目錄的Debug文件下,我們就可以這樣寫:
FILE* pf = fopen("..\\Debug\\data.txt", "w");
?關閉文件就簡單了,例如:
上述的方法我們打開文件,關閉文件就只需這樣寫:
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
關閉文件之后,及時將pf置為NULL。
文件的順序讀寫
功能 | 函數(shù)名 | 適用于 |
字符輸入函數(shù) | fgetc | 所有輸入流 |
字符輸出函數(shù) | fputc | 所有輸出流 |
文本行輸入函數(shù) | fgets | 所有輸入流 |
文本行輸出函數(shù) | fputs | 所有輸出流 |
格式化輸入函數(shù) | fscanf | 所有輸入流 |
格式化輸出函數(shù) | fprintf | 所有輸出流 |
二進制輸入 | fread | 文件 |
二進制輸出 | fwrite | 文件 |
?說到流,這里我們進行簡單科普:
讀寫文件時我們需要:
- 打開文件
- 讀寫文件
- 關閉文件
?例如:我們在使用scanf,和printf時,并沒有什么打開鍵盤,打開屏幕等一系列操作,默認就直接進行操作。
這是因為C程序只要運行起來,就默認打開三個流:
- 標準輸入流? stdin
- 標準輸出流? stdout
- 標準錯誤流? stderr
所以在使用scanf,printf時就可以默認使用。?它們的類型都是FILE*類型。
什么是流?
流是指數(shù)據(jù)在計算機中的傳輸方式,它是數(shù)據(jù)的有序序列,可以是字節(jié)、字符、圖像、音頻或視頻等形式輸入流用于從外部讀取數(shù)據(jù)到計算機內(nèi)存中,而輸出流則用于將計算機內(nèi)存中的數(shù)據(jù)寫入到外部設備或文件中。流的操作可以是順序的,也可以是隨機的。
我們繼續(xù)回到文件讀寫函數(shù)。
fgetc是字符輸入函數(shù),一次讀入一個字符到程序當中,fputc是字符輸出函數(shù),一次輸出一個字符,它們都是適用于所有流的,可以是從鍵盤輸入,也可以是從文件里讀取輸入。至于輸出,可以輸出到屏幕上,也可以輸出到文件里。
字符讀寫
我們可以來嘗試一下寫文件:
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int i = 0;
for (i = 0; i < 26; i++)
{
fputc('a' + i, pf);
}
fclose(pf);
pf = NULL;
return 0;
}
?寫26個字母寫到這個data.txt文件中。
我們運行一下程序,在當前程序路徑下找到data.txt文件。打開來看,它確實會按照順序寫入26個字母。?
?如果我們想要輸出到屏幕上,就只需把pf換成stdout就行了。fputc('a' + i, stdout);
?接下來我們嘗試一下讀文件:
我們將剛剛寫入的文件數(shù)據(jù)保存,然后對文件進行讀取數(shù)據(jù):
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch;
ch=fgetc(pf);
printf("%c\n", ch);
ch=fgetc(pf);int
printf("%c", ch);
fclose(pf);
pf = NULL;
return 0;
}
這里fgetc的返回值是int類型,如果遇到文件末尾或者讀取失敗會返回EOF。
此外fgetc函數(shù)還可以從鍵盤上讀取,只需改錯stdin即可,ch=fgetc(stdin);
讀取兩次,讀取兩個字符輸出到屏幕上,正常情況下運行應該是輸出ab兩個字符。文件在打開時,文件指針默認指向起始位置,當讀完一個字符后,文件指針就會默認指向下一個位置。
或許大家會想一次讀寫一個字符太麻煩了。接下來就是文本行的讀寫。
文本行的讀寫
fgets和fputs
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputs("abcdef", pf);
fclose(pf);
pf = NULL;
return 0;
}
?這次我們重新讀入,一次讀入abcdef這6個字符。因為是同對一個文件進行操作,所有原先寫入的數(shù)據(jù)會被覆蓋。我們再次打開data.txt??
?就可以觀察到,文件中只有新寫入abcdef。
再來試一試讀:
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char ch[20] = { 0 };
fgets(ch, 20, pf);//讀num-1個
printf("%s\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
?fgets的參數(shù)依次是,讀取后存放的位置,讀取的字符個數(shù)(n-1)個,讀取數(shù)據(jù)的位置。
?我們可以先在讀數(shù)據(jù)的文件中輸入長度超過20的字符進行驗證。
格式化輸入輸出
接下來就是fprintf 和fscanf?
前邊介紹的方法都是字符的讀寫,而fprintf 和fscanf是對數(shù)據(jù)進行格式讀寫,我們直接上代碼:
struct S {
int a;
float s;
};
int main()
{
FILE* pf = fopen("data.txt", "w");//注意,讀寫時操作不同,注意修改
if (pf == NULL)
{
perror("fopen");
return 1;
}
struct S s = { 100,3.14f };
fprintf(pf,"%d %f",s.a,s.s);
fclose(pf);
pf = NULL;
return 0;
}
?它可以將不同格式的數(shù)據(jù)輸出到指定位置。和printf很類似,就是前邊加上輸出的位置。
?運行成功后,打開文件就可以看到100,3.140000出現(xiàn)在文件中。
?既然有格式化輸出,那就必然有從文件中輸入。
直接上代碼:
struct S {
int a;
float s;
};
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
struct S s = { 0 };
fscanf(pf,"%d %f",&(s.a),&(s.s));
printf("%d %f", s.a, s.s);
fclose(pf);
pf = NULL;
return 0;
}
?我們可以保存之前的數(shù)據(jù),再次運行,屏幕上就會輸出文件中的數(shù)據(jù)。fscanf和我們經(jīng)常適用的scanf也很相似,只是多了一個讀取數(shù)據(jù)的位置。
除此之外還有sscanf和sprintf
我們可以來對比一下這幾個函數(shù):
- scanf? 從標準輸入流讀取格式化的數(shù)據(jù)。
- printf? 向標準輸出流寫格式化的數(shù)據(jù)。
- fscanf? 適用于所有輸入流的格式化輸入函數(shù)
- fprintf? 適用于所有輸出流的格式化輸出函數(shù)
sprintf? 將格式化的數(shù)據(jù)寫入到字符串中(將格式化的數(shù)據(jù)轉化為字符串),廢話不多講我們直接上代碼:
struct S {
int a;
float s;
char str[20];
};
int main()
{
char ch[30] = { 0 };
struct S s = { 100,3.14f,"hello world" };
sprintf(ch, "%d %f %s",s.a,s.s,s.str);
return 0;
}
通過調(diào)試,觀察ch中的數(shù)據(jù):
?可以觀察到它確實將數(shù)據(jù)轉化成了字符串,并存放到了數(shù)組當中。
和printf也很類似,只需在前邊添加要存放的數(shù)組。
sprintf可以將任何類型的數(shù)據(jù)轉換成字符串,那就會有還原的函數(shù),那么這就是sscanf函數(shù)的作用。
struct S {
int a;
float s;
char str[20];
};
int main()
{
char ch[30] = { 0 };
struct S s = { 100,3.14f,"hello world" };
struct S t = { 0 };
sprintf(ch, "%d %f %s",s.a,s.s,s.str);
sscanf(ch, "%d %f %s", &(t.a),&(t.s), &(t.str));
printf("%d %f %s\n", t.a,t.s,t.str);
return 0;
}
sprintf將數(shù)據(jù)轉換成字符串,sscanf將數(shù)據(jù)還原。通過結構體訪問成員進而輸出。
sscanf其實也是和scanf很像,它僅僅是在前邊添加了一個轉換的數(shù)組地址。
這里我們繼續(xù)對文件操作函數(shù)進行介紹。
二進制文件輸入輸出
fread和fwrite
寫文件:
struct S {
int a;
float s;
char str[20];
};
int main()
{
struct S s = { 100,12.56f,"hahaha" };
FILE* pf = fopen("data.txt", "wb");//注意這里不再是w而實wb以二進制形式寫入。
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(&s,sizeof(struct S),1,pf);寫一個結構體數(shù)據(jù)到文件中
fclose(pf);
pf = NULL;
return 0;
}
?創(chuàng)建一個結構體s并進行初始化。fwrite怎么用呢?它的第一個參數(shù)是數(shù)據(jù)的來源,第二個參數(shù)是數(shù)據(jù)的大小,第三個參數(shù)是寫入的個數(shù),第四個參數(shù)是寫入的位置。
運行成功后,此時打開文件data.txt觀察數(shù)據(jù):
?結果是亂碼,這就是二進制文件,但這并不是誰都讀不懂,計算機也可以將數(shù)據(jù)轉換成正常數(shù)據(jù)。這就是接下來要介紹的fread函數(shù)的功能
我們看代碼:
struct S {
int a;
float s;
char str[20];
};
int main()
{
struct S s = { 0 };
FILE* pf = fopen("data.txt", "rb");//注意這里打開文件的方式需要修改rb
if (pf == NULL)
{
perror("fopen");
return 1;
}
fread(&s, sizeof(struct S), 1, pf);
printf("%d %f %s", s.a, s.s, s.str);
fclose(pf);
pf = NULL;
return 0;
}
?它和fwrite函數(shù)傳遞的參數(shù)一致,但函數(shù)作用恰好相反。運行程序后就可以將原先寫入的二進制文件轉變?yōu)檎?shù)據(jù)。
文件的隨機讀寫
前邊我們都是在寫文件的順序讀寫,其實文件還存在隨機讀寫。關于隨機讀寫的函數(shù)有3種
fseek、ftell和rewind
fseek
根據(jù)文件指針的位置和偏移量來定位文件指針。
?ftell
返回文件指針相對于起始位置的偏移量
rewind
讓文件指針的位置回到文件的起始位置
?我們直接看應用:
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 9, SEEK_SET);//文件指針偏移 SEEK_SET初始位置開始偏移
int ch=fgetc(pf); //SEEK_CUR 當前位置開始偏移
printf("%c\n", ch); //SEEK_END 末尾位置開始偏移
int p=ftell(pf);//計算文件內(nèi)容偏移量
printf("%d\n", p);
rewind(pf);//回到起始位置
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
?fseek第一個參數(shù)是文件指針,定位要讀取的文件,第二個參數(shù)是偏移量,第三個參數(shù)是文件起始位置。
上述程序運行前可以在文件中初始化為26個字母。運行就可以看到一下結果:
?文件操作知識拓展
?文本文件和二進制文件
根據(jù)數(shù)據(jù)的組織形式,數(shù)據(jù)文件被稱為文本文件或者二進制文件
?數(shù)據(jù)在內(nèi)存中以二進制的形式存儲,如果不加轉換的輸出到外存,就是二進制文件
?如果要求在外存上以ASCII碼的形式存儲,則需要在存儲前轉換。以ASCII字符的形式存儲的文件就是文本文件。
?直白點說就是,打開一個文件,我們能看懂的就屬于文本文件,看不懂的字符就是二進制文件。
一個數(shù)據(jù)在內(nèi)存中是怎么存儲的呢?
?字符一律以ASCII形式存儲,數(shù)值型數(shù)據(jù)既可以用ASCII形式存儲,也可以使用二進制形式存儲。
?例如:整數(shù)10000,如果以ASCII碼的形式輸出到磁盤,則磁盤中占用5個字節(jié)(每個字符一個字節(jié)),而二進制形式輸出,則在磁盤上只占4個字節(jié)。
文件結束判定
被錯誤使用的 feof
切記:在文件讀取過程中,不能用feof函數(shù)的返回值直接用來判斷文件的是否結束。?
當文件讀取結束的時候,判斷是讀取失敗結束,還是遇到文件尾結束
?文本文件讀取是否結束,判斷返回值是否為EOF (fgetc),或者NULL(fgets)
- fgetc判斷是否為EOF.
- fgets判斷返回值是否為NULL.
二進制文件的讀取結束判斷,判斷返回值是否小于實際要讀的個數(shù)?
- fread判斷返回值是否小于實際要讀的個數(shù)
這里就不再舉例介紹了,這些細節(jié)上的問題一定要注意。
?文件緩沖區(qū)
ANSIC 標準采用“緩沖文件系統(tǒng)”處理的數(shù)據(jù)文件的,所謂緩沖文件系統(tǒng)是指系統(tǒng)自動地在內(nèi)存中為程序中每一個正在使用的文件開辟一塊“文件緩沖區(qū)”。從內(nèi)存向磁盤輸出數(shù)據(jù)會先送到內(nèi)存中的緩沖區(qū),裝滿緩沖區(qū)后才一起送到磁盤上。如果從磁盤向計算機讀入數(shù)據(jù),則從磁盤文件中讀取數(shù)據(jù)輸入到內(nèi)存緩沖區(qū)(充滿緩沖區(qū)),然后再從緩沖區(qū)逐個地將數(shù)據(jù)送到程序數(shù)據(jù)區(qū)(程序變量等)。緩沖區(qū)的大小根據(jù)C編譯系統(tǒng)決定
?總結文章來源:http://www.zghlxwxcb.cn/news/detail-723080.html
本期內(nèi)容到此結束,在本篇博客中,向大家介紹了C語言中的文件操作技巧,幫助您更好地管理和處理文件。無論您是初學者還是有經(jīng)驗的程序員,這些技巧都能夠為您帶來便利和效率。希望您能夠在日后的工作和學習中不斷運用和完善這些技能!最后,感謝閱讀!文章來源地址http://www.zghlxwxcb.cn/news/detail-723080.html
到了這里,關于如何使用C語言進行讀寫文件的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!