C語(yǔ)言中的文件操作詳解
1.為什么使用文件
在學(xué)習(xí)結(jié)構(gòu)體時(shí),寫了一個(gè)簡(jiǎn)易的通訊錄的程序,當(dāng)程序運(yùn)行起來(lái)的時(shí)候,可以在通訊錄中增加和刪除數(shù)據(jù),此時(shí)數(shù)據(jù)是存放在內(nèi)存當(dāng)中的,當(dāng)程序退出的時(shí)候,通訊錄中的數(shù)據(jù)自然就不存在了,等下次通訊錄運(yùn)行的時(shí)候,數(shù)據(jù)又得重新錄入了,這樣的通訊錄使用起來(lái)會(huì)有點(diǎn)難受。
所以應(yīng)該通訊錄應(yīng)該要能夠把數(shù)據(jù)給記錄下來(lái),只有選擇刪除的時(shí)候,數(shù)據(jù)才不復(fù)存在。而這就涉及到了數(shù)據(jù)持久化的問(wèn)題,一般數(shù)據(jù)持久化的方法有,把數(shù)據(jù)存放在磁盤文件、存放到數(shù)據(jù)庫(kù)等方式。
使用文件可以將數(shù)據(jù)直接存放到電腦的硬盤上,做到了數(shù)據(jù)的持久化。
2.什么是文件
磁盤上的文件是文件。
但是在程序設(shè)計(jì)中,文件可以分為兩種:程序文件和數(shù)據(jù)文件(從文件功能的角度來(lái)分類)
2.1程序文件
包括源程序文件(后綴為.c),目標(biāo)文件(windows環(huán)境后綴為.obj),可執(zhí)行程序(windows環(huán)境后綴為.exe)
平時(shí)用來(lái)寫C語(yǔ)言代碼的那個(gè)文件就是源程序文件

可執(zhí)行程序就是代碼運(yùn)行起來(lái)后彈出的那個(gè)黑框框

目標(biāo)文件就是可執(zhí)行程序在形成過(guò)程中生成的文件

2.2數(shù)據(jù)文件
文件的內(nèi)容不一定是程序,而是程序運(yùn)行起來(lái)時(shí)讀寫的數(shù)據(jù),比如程序運(yùn)行需要從中讀取數(shù)據(jù)的文件,或者輸出內(nèi)容的文件。
以下的內(nèi)容基本都是圍繞這個(gè)數(shù)據(jù)文件來(lái)展開(kāi)的,而在之前所處理數(shù)據(jù)的輸入以及輸出都是以終端(終端就是輸入輸出設(shè)備)為對(duì)象的,即從終端的鍵盤上輸入數(shù)據(jù),運(yùn)行結(jié)果輸出(顯示)到顯示器上。
有時(shí)候會(huì)把信息輸出到磁盤上面,當(dāng)需要的時(shí)候再?gòu)拇疟P上讀取數(shù)據(jù)到內(nèi)存中使用,這里處理的就是磁盤上文件。
2.3文件名
一個(gè)文件要有唯一的文件標(biāo)識(shí),以便用戶識(shí)別和引用。
為了方便起見(jiàn),文件標(biāo)識(shí)通常被稱為文件名。
文件名包含三個(gè)部分:文件路徑+文件名主干+文件后綴
例如:C:\code\test.txt
3.文件的打開(kāi)和關(guān)閉
3.1文件指針
緩沖系統(tǒng)中,關(guān)鍵的概念是“文件類型指針”,簡(jiǎn)稱“文件指針”
每個(gè)被使用的文件都在內(nèi)存中開(kāi)辟了一個(gè)文件信息區(qū),用來(lái)存放文件的相關(guān)信息(如文件的名字,文件的狀態(tài)以及文件的位置等)。這些信息是保存在一個(gè)結(jié)構(gòu)體變量中的,而這個(gè)結(jié)構(gòu)體類型是由系統(tǒng)來(lái)聲明的,取名FILE。
例如,在VS2013編譯環(huán)境下提供的stdio.h頭文件中有以下的文件類型聲明:
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)容不完全相同,但是大同小異。
每當(dāng)打開(kāi)一個(gè)文件的時(shí)候,系統(tǒng)會(huì)根據(jù)文件的情況自動(dòng)創(chuàng)建一個(gè)FILE結(jié)構(gòu)的變量,并填充其中的信息,使用者不必關(guān)心其中的細(xì)節(jié)。
一般通過(guò)一個(gè)FILE的指針來(lái)維護(hù)這個(gè)FILE結(jié)構(gòu)的變量,這樣使用起來(lái)更加方便。
創(chuàng)建一個(gè)FILE*的指針變量:
FILE* pf://文件指針變量
定義pf是一個(gè)指向FILE類型數(shù)據(jù)的文件指針變量,可以使pf指向某個(gè)文件的文件信息區(qū)(一個(gè)結(jié)構(gòu)體變量),通過(guò)該文件信息區(qū)中的信息就能夠訪問(wèn)該文件,也就是說(shuō),通過(guò)文件指針變量能夠找到與他關(guān)聯(lián)的文件。
如圖:

每個(gè)文件在打開(kāi)的時(shí)候都會(huì)在內(nèi)存中開(kāi)辟一個(gè)文件信息區(qū),這個(gè)文件信息區(qū)就是FILE結(jié)構(gòu)體類型的變量,而此時(shí)文件指針就會(huì)指向這個(gè)變量。
3.2文件的打開(kāi)和關(guān)閉
文件在讀寫之前應(yīng)該先打開(kāi)文件,在使用結(jié)束之后應(yīng)該關(guān)閉文件。
在編寫程序的時(shí)候,在打開(kāi)文件的同時(shí),都會(huì)放回一個(gè)FILE*的指針變量指向該文件,也相當(dāng)于建立了指針和文件的關(guān)系。
ANSI C規(guī)定使用fopen函數(shù)來(lái)打開(kāi)文件,使用fclose函數(shù)來(lái)關(guān)閉文件。
打開(kāi)文件:
FILE* fopen(const char* filename, const char* mode);
關(guān)閉文件:
int fclose (FILE* stream);

用只寫的方式打開(kāi)文件:
代碼如下:
#include <stdio.h>
int main()
{
//打開(kāi)文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)//遇到錯(cuò)誤時(shí)函數(shù)會(huì)返回NULL指針
{
perror("fopen");
return 1;
}
//文件操作
//
//關(guān)閉文件
fclose(pf);
pf = NULL;//把文件指針置空,防止野指針的問(wèn)題
return 0;
}在相應(yīng)的目錄下創(chuàng)建了一個(gè)test.txt文件,如果文件不存在,則進(jìn)行創(chuàng)建,如果文件存在,則對(duì)文件的內(nèi)容進(jìn)行銷毀
用只讀的方式打開(kāi)文件:
代碼如下:
int main()
{
//打開(kāi)文件
FILE* pf = fopen("test.txt", "r");
//文件操作
//
//關(guān)閉文件
fclose(pf);
pf = NULL;//把文件指針置空,防止野指針的問(wèn)題
return 0;
}相應(yīng)的目錄下必須要存在這個(gè)test.txt文件,否則編譯器會(huì)報(bào)錯(cuò)。
注意:在相應(yīng)的目錄下查看文件時(shí)要在查看中打開(kāi)文件擴(kuò)展名

以上fopen函數(shù)中的文件名參數(shù)寫的都是相對(duì)路徑,如果要寫絕對(duì)路徑的話要從對(duì)應(yīng)的根目錄開(kāi)始寫。
4.文件的順序讀寫
先來(lái)認(rèn)識(shí)一些輸入和輸出函數(shù):

這里的輸出流和輸出流是一個(gè)抽象的概念,比如說(shuō)一些外部設(shè)備如鍵盤、顯示器、U盤等等,這些設(shè)備的輸入和輸出方式都是不同的,這個(gè)時(shí)候C語(yǔ)言中的庫(kù)將這些輸入和輸出的方式都封裝成一個(gè)流,只需要知道這個(gè)流就能完成一個(gè)輸入和輸出,而不用去學(xué)習(xí)硬件上的其他知識(shí)。
再打開(kāi)編譯器的時(shí)候,會(huì)默認(rèn)打開(kāi)三個(gè)流,分別是標(biāo)準(zhǔn)輸入流、標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯(cuò)誤流
- 標(biāo)準(zhǔn)輸入流:stdin,指的是鍵盤
- 標(biāo)準(zhǔn)輸出流:stdout,指的是屏幕
- 標(biāo)準(zhǔn)錯(cuò)誤流:stderr,也是屏幕
接下來(lái)具體講講輸入和輸出函數(shù)
字符輸出函數(shù):
int fputc(int c, FILE* stream);
fputc會(huì)返回被輸出字符的ASCII碼值,遇到錯(cuò)誤時(shí)返回EOF。
#include <stdio.h>
int main()
{
//打開(kāi)文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//寫文件
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);
}
//關(guān)閉文件
fclose(pf);
pf = NULL;
return 0;
}將字符a到z寫到文件當(dāng)中,也可以理解為將數(shù)據(jù)輸出到文件里面,因?yàn)檫m用于所有輸出流,所以這里的參數(shù)可以用文件流,文件流也是文件指針。
此時(shí)可以查看test.txt里面的數(shù)據(jù):

字符輸入函數(shù):
int fgetc(FILE* stream);
fgetc會(huì)返回讀到的字符的ASCII碼值,當(dāng)讀到文件末尾或者遇到錯(cuò)誤時(shí)會(huì)返回EOF。
int main()
{
//打開(kāi)文件
FILE* pf = fopen("test.txt", "r");
if (pf == EOF)
{
perror("fopen");
return 1;
}
//讀文件 - 輸入操作
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
//關(guān)閉文件
fclose(pf);
pf = NULL;
return 0;
}
從剛剛寫的test.txt中讀取信息,也就是從test.txt上的數(shù)據(jù)輸入到了ch中,并在屏幕上輸出。
可以將鍵盤和顯示器作為輸入和輸出的參數(shù):
int main()
{
int ch = fgetc(stdin);
fputc(ch, stdout);
return 0;
}文本行輸出函數(shù):
int fputs(const char* string, FILE* stream);
成功則返回非負(fù)指,遇到錯(cuò)誤則返回EOF
int main()
{
//打開(kāi)文件
FILE* pf = fopen("test1.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//寫文件
fputs("abcdefg\n", pf);
fputs("hijklmn\n", pf);
//關(guān)閉文件
fclose(pf);
pf = NULL;
return 0;
}將字符串輸出到文件中
此時(shí)打開(kāi)test1.txt查看數(shù)據(jù):

如果輸出流寫stdout,可以輸出到屏幕上來(lái)

文本行輸入函數(shù):
char* fgets(char* string, int n, FILE* stream);
將輸入的數(shù)據(jù)存儲(chǔ)到string,會(huì)最多讀取n-1個(gè)字符,最后1個(gè)字符為'\0',返回值為讀取到的字符串,遇到錯(cuò)誤時(shí)或EOF時(shí)返回NULL指針。
int main()
{
//打開(kāi)文件
FILE* pf = fopen("test1.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//寫文件
char arr[256] = { 0 };
while (fgets(arr, 256, pf) != NULL)
{
fputs(arr, stdout);
}
//關(guān)閉文件
fclose(pf);
pf = NULL;
return 0;
}將剛才test1.txt里面的內(nèi)容作為輸入的數(shù)據(jù),然后在屏幕上將數(shù)據(jù)輸出。
格式化輸出函數(shù):
int fprintf( FILE *stream, const char *format [, argument ]...);
和printf很像,只是printf的默認(rèn)輸出流是標(biāo)準(zhǔn)輸出流,而這里的參數(shù)stream適用于所有的輸出流
struct S
{
char name[20];
int age;
double score;
};
int main()
{
struct S s = { "zhangsan", 20, 75.5 };
//打開(kāi)文件
FILE* pf = fopen("test2.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//寫文件
fprintf(pf, "%s %d %f", s.name, s.age, s.score);
//關(guān)閉文件
fclose(pf);
pf = NULL;
return 0;
}此時(shí)已經(jīng)把數(shù)據(jù)寫入到了test2.txt里面,打開(kāi)文件來(lái)查看一下:

同樣的,stream位置的參數(shù)可以是標(biāo)準(zhǔn)輸出流,這樣數(shù)據(jù)就會(huì)輸出到屏幕上面來(lái)了
struct S
{
char name[20];
int age;
double score;
};
int main()
{
struct S s = { "zhangsan", 20, 75.5 };
fprintf(stdout, "%s %d %f", s.name, s.age, s.score);
return 0;
}格式化輸入函數(shù):
int fscanf( FILE *stream, const char *format [, argument ]... );
fscanf和scanf和很像,只是scanf默認(rèn)stream位置的參數(shù)是標(biāo)準(zhǔn)輸入流,也就是鍵盤。
int main()
{
struct S s = {0};
//打開(kāi)文件
FILE* pf = fopen("test2.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//讀文件
fscanf(pf, "%s %d% lf", s.name, &(s.age), &(s.score));
fprintf(stdout, "%s %d %lf", s.name, s.age, s.score);
//關(guān)閉文件
fclose(pf);
pf = NULL;
return 0;
}將數(shù)據(jù)從文件上讀取,然后在屏幕上輸出。
再來(lái)認(rèn)識(shí)兩個(gè)與printf和scanf很像的函數(shù):
sprintf:將格式化的數(shù)據(jù)轉(zhuǎn)換為字符串
int sprintf( char *buffer, const char *format [, argument] ... );
sscanf:將字符串轉(zhuǎn)換為格式化的數(shù)據(jù)
int sscanf( const char *buffer, const char *format [, argument ] ... );
sprintf的使用:
struct S
{
char name[20];
int age;
double score;
};
int main()
{
char buf[256] = { 0 };
struct S s = { "wangwu", 30, 85.5 };
sprintf(buf, "%s %d %lf", s.name, s.age, s.score);
printf("%s\n", buf);
return 0;
}將結(jié)構(gòu)體的數(shù)據(jù)轉(zhuǎn)換為字符串存入buf中,然后將buf在屏幕上輸出出來(lái)。

sscanf的使用:
struct S
{
char name[20];
int age;
double score;
};
int main()
{
char buf[256] = { 0 };
struct S s = { "wangwu", 30, 85.5 };
sprintf(buf, "%s %d %lf", s.name, s.age, s.score);
//將字符串轉(zhuǎn)化為格式化的數(shù)據(jù)
struct S tmp = { 0 };
sscanf(buf, "%s %d %lf", tmp.name, &(tmp.age), &(tmp.score));
printf("%s %d %lf", tmp.name, tmp.age, tmp.score);
return 0;
}
也可以認(rèn)為是從字符串中提取數(shù)據(jù)存入到結(jié)構(gòu)體tmp中來(lái)。
這里列在一起方便對(duì)比一下:


二進(jìn)制輸出函數(shù):
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
- buffer:指向被讀取的數(shù)據(jù)的指針
- size:被讀取的項(xiàng)目的字節(jié)大小
- count:被讀取的項(xiàng)目數(shù)的最大值
- stream:文件流
fwrite的使用:
struct S
{
char name[20];
int age;
double score;
};
int main()
{
struct S s = { "lisi", 15, 95.5 };
//打開(kāi)文件
FILE* pf = fopen("test3.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//寫文件
fwrite(&s, sizeof(struct S), 1, pf);
//關(guān)閉文件
fclose(pf);
pf = NULL;
return 0;
}因?yàn)槭且远M(jìn)制的方式寫的,所以文件中存放的數(shù)據(jù)是這樣子的:

二進(jìn)制輸入函數(shù):
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
跟fwrite相反,將文件中的數(shù)據(jù)作為輸入數(shù)據(jù)存儲(chǔ)到buffer指向的空間中。
fread的使用:
struct S
{
char name[20];
int age;
double score;
};
int main()
{
struct S s = { 0 };
//打開(kāi)文件
FILE* pf = fopen("test3.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//讀文件
fread(&s, sizeof(struct S), 1, pf);
printf("%s %d %lf\n", s.name, s.age, s.score);
//關(guān)閉文件
fclose(pf);
pf = NULL;
return 0;
}5.文件的隨機(jī)讀寫
5.1fseek
根據(jù)文件指針的位置和偏移量來(lái)定位文件指針
int fseek( FILE *stream, long offset, int origin );
- stream:文件流
- offset:相對(duì)于此時(shí)文件指針的位置的偏移量,單位是字節(jié)
- origin:文件指針此時(shí)的位置
origin可以分為3個(gè)值:
SEEK_CUR - 文件指針當(dāng)前的位置
SEEK_SET - 文件開(kāi)始的位置
SEEK_END - 文件末尾的位置
先在對(duì)應(yīng)的目錄下創(chuàng)建一個(gè)test.txt文件,往里面寫入a到f的字符
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//把a(bǔ)到f的字符寫入文件
char ch = 0;
for (ch = 'a'; ch <= 'f'; ch++)
{
fputc(ch, pf);
}
fclose(pf);
pf = NULL;
return 0;
}fseek的使用:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//讓文件指針從此時(shí)的位置向后移動(dòng)5個(gè)字節(jié)
fseek(pf, 5, SEEK_CUR);
int ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}找到f的位置并將其讀取,輸出在屏幕上,結(jié)果是f

offset輸入負(fù)值也可以讓指針往前走
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//讓文件指針從文件尾的位置向前移動(dòng)3個(gè)字節(jié)
fseek(pf, -3, SEEK_END);
int ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
最終屏幕上輸出的結(jié)果是d
注意:文件末尾是f之后的那個(gè)位置
5.2ftell
放回文件指針相對(duì)于起始位置的偏移量
long int ftell ( FILE * stream );
ftell的使用:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 0, SEEK_END);
long size = ftell(pf);
printf("%ld\n", size);
fclose(pf);
pf = NULL;
return 0;
}對(duì)于test.txt這個(gè)文件,文件指針末尾的位置相對(duì)于起始位置是6.
使用比較簡(jiǎn)單,了解一下即可
5.3rewind
讓文件指針回到文件的起始位置
void rewind( FILE *stream );
rewind的使用:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 0, SEEK_END);
//從文件尾回到文件起始位置
rewind(pf);
long size = ftell(pf);
printf("%ld\n", size);
fclose(pf);
pf = NULL;
return 0;
}文件指針先是走到文件末尾,最后回到起始位置,所以最終屏幕輸出結(jié)果是0
6.文本文件和二進(jìn)制文件
根據(jù)數(shù)據(jù)的儲(chǔ)存形式,數(shù)據(jù)文件被稱為文本文件和二進(jìn)制文件。
數(shù)據(jù)在內(nèi)存中以二進(jìn)制的形式存儲(chǔ),如果不加轉(zhuǎn)換的輸出到外存,就是二進(jìn)制文件。外存可以理解為硬盤。
如果要求在外存上以ASCII碼的形式存儲(chǔ),則需要在存儲(chǔ)前轉(zhuǎn)換。以ASCII字符的形式存儲(chǔ)的文件就是文本文件。前面fputc就是以ASCII字符的形式將數(shù)據(jù)存儲(chǔ)在文件中。
下面來(lái)看一個(gè)數(shù)據(jù)在內(nèi)存中是怎么存儲(chǔ)的
字符一律以ASCII形式進(jìn)行存儲(chǔ),數(shù)值型數(shù)據(jù)即可以用ASCII形式存儲(chǔ),也可以使用二進(jìn)制形式來(lái)存儲(chǔ)。
假設(shè)有一個(gè)整數(shù)為10000,如果以ASCII碼的形式輸出到磁盤,則在磁盤中占用5個(gè)字節(jié)(每個(gè)字符占用一個(gè)字節(jié)),而如果以二進(jìn)制的形式輸出到磁盤,則在磁盤上占用4個(gè)字節(jié)。
直接在記事本中輸入10000,就是以ASCII碼的形式進(jìn)行存儲(chǔ)。


可以將這個(gè)文件在編譯器中打開(kāi)

然后選擇二進(jìn)制的打開(kāi)方式:

然后可以看到顯示的結(jié)果是將二進(jìn)制的形式轉(zhuǎn)換為十六進(jìn)制的形式

剛好對(duì)應(yīng)著10000的ASCII碼的存儲(chǔ)形式
接下來(lái)看看以二進(jìn)制的形式存儲(chǔ):

int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int a = 10000;
fwrite(&a, sizeof(int), 1, pf);//二進(jìn)制的形式寫
fclose(pf);
pf = NULL;
return 0;
}再以二進(jìn)制的形式打開(kāi)test.txt

由于該編譯器是小端字節(jié)序存儲(chǔ),所以可以看到16進(jìn)制顯示的數(shù)字是反過(guò)來(lái)的。
7.文件讀取結(jié)束的判定
7.1被錯(cuò)誤使用的feof
文件在讀取的過(guò)程中,不能用feof函數(shù)的返回值來(lái)判斷文件讀取是否結(jié)束,而是應(yīng)當(dāng)用于在文件讀取結(jié)束的時(shí)候,判斷是因?yàn)樽x取失敗造成的文件讀取結(jié)束,還是因?yàn)橛龅轿募苍斐傻奈募x取結(jié)束。
文件的讀取結(jié)束判斷:
文本文件的讀取結(jié)束判斷:
fgetc函數(shù)判斷返回值是否為EOF
fgets函數(shù)判斷返回值是否為NULL
二進(jìn)制文件的讀取結(jié)束判斷:
fread判斷返回值(實(shí)際讀到的個(gè)數(shù))是否小于預(yù)計(jì)要讀的個(gè)數(shù)
feof和ferror函數(shù)
feof
int feof( FILE *stream );
文件是由于讀取到文件尾結(jié)束時(shí)返回一個(gè)非0值,反之返回0
ferror
int ferror( FILE *stream );
文件是由于讀取錯(cuò)誤而讀取結(jié)束時(shí)返回一個(gè)非0值,反之返回0
文本文件的讀取結(jié)束判斷代碼:
int main()
{
int c; // 注意:int,非char,要求處理EOF
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen");
return 1;
}
//fgetc 當(dāng)讀取失敗的時(shí)候或者遇到文件結(jié)束的時(shí)候,都會(huì)返回EOF
while ((c = fgetc(fp)) != EOF) // 標(biāo)準(zhǔn)C I/O讀取文件循環(huán)
{
putchar(c);
}
//判斷是什么原因結(jié)束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
fp = NULL;
return 0;
}二進(jìn)制文件的讀取結(jié)束判斷代碼:
enum
{
SIZE = 5
};
int main(void)
{
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* fp = fopen("test.txt", "wb");
if (fp == NULL)
{
perror("fopen");
return 1;
}
fwrite(a, sizeof * a, SIZE, fp); // 寫 double 的數(shù)組
fclose(fp);
fp = NULL;
double b[SIZE];
fp = fopen("test.txt", "rb");
size_t ret_code = fread(b, sizeof * b, SIZE + 1, fp); // 讀 double 的數(shù)組
if (ret_code == SIZE)
{
puts("Array read successfully, contents: ");
for (int n = 0; n < SIZE; ++n)
printf("%f ", b[n]);
putchar('\n');
}
if (feof(fp))//判斷是否因?yàn)橛龅轿募苍斐傻奈募x取結(jié)束
printf("Error reading test.txt: unexpected end of file\n");
else if (ferror(fp)) //判斷是否因?yàn)樽x取失敗造成的文件讀取結(jié)束
{
perror("Error reading test.txt");
}
fclose(fp);
fp = NULL;
}8.文件緩沖區(qū)
ANSIC標(biāo)準(zhǔn)采用“緩沖文件系統(tǒng)”處理的數(shù)據(jù)文件的,所謂緩沖文件系統(tǒng)是指系統(tǒng)自動(dòng)地在內(nèi)存中為程序中每一個(gè)正在使用的文件開(kāi)辟一塊“文件緩沖區(qū)”。從內(nèi)存向磁盤輸出數(shù)據(jù)會(huì)先送到內(nèi)存中的緩沖區(qū),裝滿緩沖區(qū)后才一起送到磁盤上。如果從磁盤向計(jì)算機(jī)讀入數(shù)據(jù),則從磁盤文件中讀取數(shù)據(jù)輸入到內(nèi)存緩沖區(qū)(充滿緩沖區(qū)),然后再?gòu)木彌_區(qū)逐個(gè)地將數(shù)據(jù)送到程序數(shù)據(jù)區(qū)(程序變量等)。緩沖區(qū)的大小根據(jù)C編譯系統(tǒng)決定的。
圖解:

int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputs("abcdef", pf);//先將代碼放在輸出緩沖區(qū)
printf("睡眠10秒-已經(jīng)寫數(shù)據(jù)了,打開(kāi)test.txt文件,發(fā)現(xiàn)文件沒(méi)有內(nèi)容\n");
Sleep(10000);
printf("刷新緩沖區(qū)\n");
fflush(pf);//刷新緩沖區(qū)時(shí),才將輸出緩沖區(qū)的數(shù)據(jù)寫到文件(磁盤)
printf("再睡眠10秒-此時(shí),再次打開(kāi)test.txt文件,文件有內(nèi)容了\n");
Sleep(10000);//此時(shí)睡眠10秒,是為了說(shuō)明數(shù)據(jù)是由于fflush的刷新才輸出到文件中的
fclose(pf);
//注:fclose在關(guān)閉文件的時(shí)候,也會(huì)刷新緩沖區(qū)
pf = NULL;
return 0;
}fflush函數(shù)可以讓數(shù)據(jù)不充滿緩沖區(qū)時(shí)就直接輸入或者輸出
結(jié)論
因?yàn)橛芯彌_區(qū)的存在,C語(yǔ)言在操作文件的時(shí)候,需要做刷新緩沖區(qū)或者在文件操作結(jié)束的時(shí)候關(guān)閉文件,如果不做,可能會(huì)導(dǎo)致讀寫文件時(shí)數(shù)據(jù)不完全讀寫的問(wèn)題。
到此這篇關(guān)于C語(yǔ)言中的文件操作詳解的文章就介紹到這了,更多相關(guān)C語(yǔ)言文件操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++第三方日志庫(kù)log4cplus的安裝與使用配置教程
log4cplus是C++編寫的開(kāi)源的日志系統(tǒng),log4cplus具有線程安全、靈活、以及多粒度控制的特點(diǎn),本文給大家介紹C++第三方日志庫(kù)log4cplus的安裝與使用教程,感興趣的朋友一起看看吧2022-02-02
C++中對(duì)象的動(dòng)態(tài)建立與釋放詳解及其作用介紹
這篇文章主要介紹了C++中對(duì)象的動(dòng)態(tài)建立與釋放詳解及其作用介紹,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
C++實(shí)現(xiàn)十六進(jìn)制字符串轉(zhuǎn)換成int整形值的示例
今天小編就為大家分享一篇關(guān)于C++實(shí)現(xiàn)十六進(jìn)制字符串轉(zhuǎn)換成int整形值的示例,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12
Qt實(shí)現(xiàn)手動(dòng)切換多種布局的完美方案
通過(guò)點(diǎn)擊程序界面上不同的布局按鈕,使主工作區(qū)呈現(xiàn)出不同的頁(yè)面布局,多個(gè)布局之間可以通過(guò)點(diǎn)擊不同布局按鈕切換,支持的最多的窗口為9個(gè),不同布局下窗口數(shù)隨之變化,這篇文章主要介紹了Qt實(shí)現(xiàn)手動(dòng)切換多種布局的完美方案,需要的朋友可以參考下2024-07-07
C++詳細(xì)講解模擬實(shí)現(xiàn)位圖和布隆過(guò)濾器的方法
位圖(bitset)是一種常用的數(shù)據(jù)結(jié)構(gòu),常用在給一個(gè)很大范圍的數(shù),判斷其中的一個(gè)數(shù)是不是在其中。在索引、數(shù)據(jù)壓縮方面有很大的應(yīng)用。布隆過(guò)濾器是由布隆提出的,它實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過(guò)濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中2022-06-06
C語(yǔ)言代碼實(shí)現(xiàn)簡(jiǎn)單掃雷游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言代碼實(shí)現(xiàn)簡(jiǎn)單掃雷游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-02-02

