幾個基本概念
什么是IO:
????I/O輸入/輸出(Input/Output),分為IO設(shè)備和IO接口兩個部分。
在C語言中的I/O,個人理解就是把數(shù)據(jù)傳輸?shù)匠绦蚓褪荌,從程序中把數(shù)據(jù)傳輸出去就是O。
很多時候,數(shù)據(jù)來源就是文件,所有很多時候我們把對文件的操作,也叫做I/O操作了。
????關(guān)于這樣的I/O操作,在Linux操作系統(tǒng)上分兩種,其中一個是標(biāo)準(zhǔn)IO,另一個是系統(tǒng)調(diào)用IO。
系統(tǒng)調(diào)用IO與標(biāo)準(zhǔn)IO的區(qū)別:
????????系統(tǒng)調(diào)用IO:由操作系統(tǒng)直接提供的操作文件的接口函數(shù),和操作系統(tǒng)綁定,沒有緩存,更接近硬件,可以操作普通文件與驅(qū)動文件。
????????標(biāo)準(zhǔn)IO:由標(biāo)準(zhǔn)C庫(第三方庫)提供的操作文件的接口函數(shù)(對操作系統(tǒng)提供的系統(tǒng)IO進(jìn)行二次封裝),有緩存,只可以操作普通文件。
C語言中的文件
????????我們對文件的概念已經(jīng)非常熟悉了,比如常見的 word 文檔、txt 文件、c源文件等。
文件是數(shù)據(jù)源的一種,最主要的作用是保存數(shù)據(jù)。
在C語言程序中,文件有著更廣泛的定義:文件通常是在磁盤或硬盤上的已命名的儲存區(qū)。
要看到是通常,那么就說明會有不尋常的。對于C程序來說,設(shè)備也可以看成是文件!
例如顯示器(標(biāo)準(zhǔn)輸出文件 stdout,標(biāo)準(zhǔn)錯誤輸出stderr)和鍵盤(標(biāo)準(zhǔn)輸入文件 stdin)。
我們不去探討硬件設(shè)備是如何被映射成文件的,大家只需要記住,在C語言中硬件設(shè)備可以看成文件!
另外,在C程序看來,文件只有兩種形式:
(1)文本文件
(2)二進(jìn)制文件
????????根據(jù)我們以往的經(jīng)驗,文本文件通常用來保存肉眼能認(rèn)識的字符,比如.txt文件、.c文件、.dat文件等,用文本編輯器打開這些文件,我們能夠順利看懂文件的內(nèi)容。
????????二進(jìn)制文件通常用來保存視頻、圖片、程序等不可閱讀的內(nèi)容,用文本編輯器打開這些文件,會看到一堆亂碼,根本看不懂。
????????但是從物理上講,二進(jìn)制文件和字符文件并沒有什么區(qū)別,它們都是以二進(jìn)制的形式保存在磁盤上的數(shù)據(jù)。
????????我們之所以能看懂文本文件的內(nèi)容,是因為文本文件中采用的是 ASCII、UTF-8、GBK 等字符編碼,文本編輯器可以識別出這些編碼格式,并將編碼值轉(zhuǎn)換成字符展示出來。
????????而二進(jìn)制文件使用的是 mp4、gif、exe 等特殊編碼格式,文本編輯器并不認(rèn)識這些編碼格式,只能按照字符編碼格式胡亂解析,所以就成了一堆亂七八糟的字符,有的甚至都沒見過。
????????總起來說,不同類型的文件有不同的編碼格式,必須使用對應(yīng)的程序(軟件)才能正確解析,否則就是一堆亂碼,無法使用。
文件流
????????文件是數(shù)據(jù)源的一種,除了文件,還有數(shù)據(jù)庫、網(wǎng)絡(luò)、鍵盤等;
數(shù)據(jù)傳遞到內(nèi)存也就是保存到C語言的變量(例如整數(shù)、字符串、數(shù)組、緩沖區(qū)等)。
????????我們把數(shù)據(jù)在數(shù)據(jù)源和程序(內(nèi)存)之間傳遞的過程叫做數(shù)據(jù)流(Data Stream)。相應(yīng)的,數(shù)據(jù)從數(shù)據(jù)源到程序(內(nèi)存)的過程叫做輸入流(Input Stream),從程序(內(nèi)存)到數(shù)據(jù)源的過程叫做輸出流(Output Stream)。
????????輸入輸出(Input output,IO)是指程序(內(nèi)存)與外部設(shè)備(鍵盤、顯示器、磁盤、其他計算機(jī)等)進(jìn)行交互的操作。幾乎所有的程序都有輸入與輸出操作,如從鍵盤上讀取數(shù)據(jù),從本地或網(wǎng)絡(luò)上的文件讀取數(shù)據(jù)或?qū)懭霐?shù)據(jù)等。通過輸入和輸出操作可以從外界接收信息,或者是把信息傳遞給外界。
????????一般情況下,我們可以把打開文件說成打開了一個流。
C語言fopen和fclose函數(shù)
????????C語言中操作文件的正確流程為:打開文件 --> 讀寫文件 --> 關(guān)閉文件。
所謂打開文件,就是獲取文件的有關(guān)信息,例如文件名、文件狀態(tài)、當(dāng)前讀寫位置等,
這些信息會被保存到一個 FILE 類型的結(jié)構(gòu)體變量中。
關(guān)閉文件,就是斷開與文件之間的聯(lián)系,釋放結(jié)構(gòu)體變量,同時禁止再對該文件進(jìn)行操作。
使用 <stdio.h> 頭文件中的 fopen() 函數(shù)即可打開文件,它的用法為:
FILE *fopen(char *filename, char *mode);
filename為文件名(包括文件路徑)
mode為打開模式,它們都是字符串。

fopen() 函數(shù)的返回值類型是(FILE *)
FILE 是 <stdio.h> 頭文件中的一個結(jié)構(gòu)體,它專門用來保存文件信息。
我們不用關(guān)心 FILE 的具體結(jié)構(gòu),只需要知道它的用法就行。
例如:fopen.c
#include <stdio.h>
int main(){
FILE * fp=fopen("./demo.txt","w");
return 0;
}
//編譯執(zhí)行可執(zhí)行文件后,若當(dāng)前文件夾中沒有demo.txt,則創(chuàng)建之,
gcc -g fopen.c -o fopen.exe
./fopen
表示以“只讀”方式打開當(dāng)前目錄下的 demo.txt 文件,并使 fp 指向該文件,
這樣就可以通過 fp 來操作 demo.txt 了。fp 通常被稱為文件指針。若在文件中加了內(nèi)容,我再次執(zhí)行可執(zhí)行文件./fopen,會清空demo.txt了。
再來看一個例子:
FILE *fp = fopen("D:\\demo.txt","rb+");
表示以二進(jìn)制方式打開 D 盤下的 demo.txt 文件,允許讀和寫。
--------文本方式和二進(jìn)制方式并沒有本質(zhì)上的區(qū)別,只是對于換行符的處理不同。
C語言程序?qū)n作為換行符,類 UNIX/Linux 系統(tǒng)在處理文本文件時也將\n作為換行符,所以程序中的數(shù)據(jù)會原封不動地寫入文本文件中,反之亦然。

但是 Windows 系統(tǒng)卻不同,它將\r\n作為文本文件的換行符。
--------在 Windows 系統(tǒng)中,如果以文本方式打開文件,當(dāng)讀取文件時,程序會將文件中所有的\r\n轉(zhuǎn)換成一個字符\n。也就是說,如果文本文件中有連續(xù)的兩個字符是\r\n,則程序會丟棄前面的\r,只讀入\n。
當(dāng)寫入文件時,程序會將\n轉(zhuǎn)換成\r\n寫入。也就是說,如果要寫入的內(nèi)容中有字符\n,則在寫入該字符前,程序會自動先寫入一個\r。
?????????總起來說,對于 Windows 平臺,為了保險起見,我們最好用"t"來打開文本文件,用"b"來打開二進(jìn)制文件。對于 Linux 平臺,使用"t"還是"b"都無所謂,既然默認(rèn)是"t",那我們什么都不寫就行了。
判斷文件是否打開成功
??????打開文件出錯時,fopen() 將返回一個空指針,也就是 NULL,我們可以利用這一點來判斷文件是否打開成功,請看下面的代碼:
FILE *fp;
if( (fp=fopen("D:\\demo.txt","rb") == NULL ){
printf("Fail to open file!\n");
exit(0); //退出程序(結(jié)束程序)
}
??????以上代碼是文件操作的規(guī)范寫法,在打開文件時一定要判斷文件是否打開成功,因為一旦打開失敗,后續(xù)操作就都沒法進(jìn)行了,往往以“結(jié)束程序”告終。
fclose函數(shù)
??????當(dāng)進(jìn)行打開文件操作時, 操作系統(tǒng)會建立文件登記表中記錄該文件處于打開狀態(tài),除了申請進(jìn)行打開文件操作的進(jìn)程,其他程序不能對文件進(jìn)行訪問和寫入。
??????文件一旦使用完畢,應(yīng)該用 fclose() 函數(shù)把文件關(guān)閉,以釋放相關(guān)資源,避免數(shù)據(jù)丟失。fclose() 的用法為:
int fclose(FILE *fp);
fp 為文件指針。例如:
int fclose(fp);
文件正常關(guān)閉時,fclose() 的返回值為0,如果返回非零值則表示有錯誤發(fā)生。
實例演示
??????最后,我們通過一段完整的代碼來演示 fopen 函數(shù)的用法,這個例子會一行一行地讀取文本文件的所有內(nèi)容:
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp =NULL;
//判斷一個,有沒有成功打開文件,如果打開出錯,直接退出程序
if((fp = fopen("./demo.txt","rt")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//打開文件后,就對文件進(jìn)行讀寫操作。。。。
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("關(guān)閉文件成功?。?!");
}else{
puts("文件關(guān)閉失敗");
exit(0);
}
return 0;
}
//編譯與執(zhí)行
gcc -g .\fopen_close.c -o fopenclose.exe
./fopenclose
關(guān)閉文件成功?。?!
C語言fgetc和fputc函數(shù)用法
??????在C語言中,讀寫文件比較靈活,既可以每次讀寫一個字符,也可以讀寫一個字符串,甚至是任意字節(jié)的數(shù)據(jù)(數(shù)據(jù)塊)。每次可以從文件中讀取一個字符,或者向文件中寫入一個字符。
主要使用兩個函數(shù),分別是 fgetc() 和 fputc()。
字符讀取函數(shù) fgetc
??????fgetc 是 file get char 的縮寫,意思是從指定的文件中讀取一個字符。fgetc() 的用法為:
int fgetc (FILE *fp);
?????????fp 為文件指針。
??????fgetc() 讀取成功時返回讀取到的字符,讀取到文件末尾或讀取失敗時返回EOF。
EOF 是 end of file 的縮寫,表示文件末尾,是在 stdio.h 中定義的宏,它的值是一個負(fù)數(shù),往往是 -1。
??????fgetc() 的返回值類型之所以為 int,就是為了容納這個負(fù)數(shù)(char不能是負(fù)數(shù))。
注意:不能武斷地認(rèn)為EOF 就是 -1,也可以是其他負(fù)數(shù),這要看編譯器的實現(xiàn)。
fgetc() 的用法舉例:
char ch;
FILE *fp = fopen("./demo.txt", "r+");
ch = fgetc(fp);
???表示從當(dāng)前目錄中的demo.txt文件中讀取一個字符,并保存到變量 ch 中。在文件內(nèi)部有一個位置指針,用來指向當(dāng)前讀寫到的位置,也就是讀寫到第幾個字節(jié)。
??????在文件打開時,該指針總是指向文件的第一個字節(jié)。使用 fgetc() 函數(shù)后,該指針會向后移動一個字節(jié),所以可以連續(xù)多次使用 fgetc() 讀取多個字符。
注意:這個文件內(nèi)部的位置指針與C語言中的指針不是一回事。
???位置指針僅僅是一個標(biāo)志,表示文件讀寫到的位置,也就是讀寫到第幾個字節(jié),它不表示地址。
???文件每讀寫一次,位置指針就會移動一次,它不需要你在程序中定義和賦值,而是由系統(tǒng)自動設(shè)置,對用戶是隱藏的。
【示例】在屏幕上顯示 當(dāng)前目錄中的demo.txt 文件的內(nèi)容。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp =NULL;
char ch;
//判斷一個,有沒有成功打開文件,如果打開出錯,直接退出程序
if((fp = fopen("./demo.txt","rt")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//打開文件后,就對文件進(jìn)行讀寫操作。。。。
while((ch=fgetc(fp))!=EOF){
printf("%c",ch);
}
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功?。?!");
}else{
puts("文件關(guān)閉失敗");
exit(0);
}
return 0;
}
//編譯執(zhí)行
>gcc -g .\fopen_close.c -o fopenclose.exe
>./fopenclose
xiongshaowen
關(guān)閉文件成功!?。?
對 EOF 的說明
??????EOF 本來表示文件末尾,意味著讀取結(jié)束,但是很多函數(shù)在讀取出錯時也返回 EOF,那么當(dāng)返回 EOF 時,到底是文件讀取完畢了還是讀取出錯了?
我們可以借助 stdio.h 中的兩個函數(shù)來判斷,分別是 feof() 和 ferror()。
??????feof() 函數(shù)用來判斷文件內(nèi)部指針是否指向了文件末尾,它的原型是:
int feof ( FILE * fp );
當(dāng)指向文件末尾時返回非零值,否則返回零值。
??????ferror() 函數(shù)用來判斷文件操作是否出錯,它的原型是:
int ferror ( FILE *fp );
出錯時返回非零值,否則返回零值。
??????需要說明的是,文件出錯是非常少見的情況,上面的示例基本能夠保證將文件內(nèi)的數(shù)據(jù)讀取完畢。
如果追求完美,也可以加上判斷并給出提示:
while((ch=fgetc(fp))!=EOF){
printf("%c",ch);
}
if((ferror(fp))==0){
printf("\n正常讀完文件!");
}else{
printf("\n文件讀取出了錯誤 !");
}
字符寫入函數(shù) fputc
??????fputc 是 file output char 的所以,意思是向指定的文件中寫入一個字符。fputc() 的用法為:
int fputc ( int ch, FILE *fp );
??????ch 為要寫入的字符,fp 為文件指針。
??????fputc() 寫入成功時返回寫入的字符,失敗時返回 EOF,返回值類型為 int 也是為了容納這個負(fù)數(shù)。
例如:
fputc('a', fp);
或者:
char ch = 'a';
fputc(ch, fp);
表示把字符 'a' 寫入fp所指向的文件中。
注意:寫和讀一樣,每寫入一個字符,文件內(nèi)部位置指針向后移動一個字節(jié)。
【示例】從鍵盤輸入一行字符,寫入文件。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp =NULL;
char ch;
//判斷一個,有沒有成功打開文件,如果打開出錯,直接退出程序
if((fp = fopen("./demo.txt","r+")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//打開文件后,就對文件進(jìn)行寫操作。。。。
printf("\n請輸入要寫入文件的字符!以換行結(jié)束: ");
while((ch = getchar())!='\n'){
fputc(ch,fp);
}
printf("\n寫入成功!");
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功!?。?);
}else{
puts("文件關(guān)閉失敗");
exit(0);
}
return 0;
}
C語言fgets和fputs函數(shù)
fgetc() 和 fputc() 函數(shù)每次只能讀寫一個字符,速度較慢;
??????實際開發(fā)中往往是每次讀寫一個字符串或者一個數(shù)據(jù)塊,這樣能明顯提高效率。
讀字符串函數(shù) fgets
??????fgets() 函數(shù)用來從指定的文件中讀取一個字符串,并保存到字符數(shù)組中,它的用法為:
char *fgets ( char *str, int n, FILE *fp );
str 為字符數(shù)組,n 為要讀取的字符數(shù)目,fp 為文件指針。
返回值:讀取成功時返回字符數(shù)組首地址,也即 str;
??? 讀取失敗時返回 NULL;
??? 如果開始讀取時文件內(nèi)部指針已經(jīng)指向了文件末尾,那么將讀取不到任何字符,也返回 NULL。
注意,讀取到的字符串會在末尾自動添加 '\0',n 個字符也包括 '\0'。
???也就是說,實際只讀取到了 n-1 個字符,如果希望讀取 100 個字符,n 的值應(yīng)該為 101。
例如:
#define N 101
char str[N];
FILE *fp = fopen("D:\\demo.txt", "r");
fgets(str, N, fp);
需要重點說明的是,在讀取到 n-1 個字符之前如果出現(xiàn)了換行,或者讀到了文件末尾,則讀取結(jié)束。
???這就意味著,不管 n 的值多大,fgets() 最多只能讀取一行數(shù)據(jù),不能跨行。
在C語言中,沒有按行讀取文件的函數(shù),我們借助 fgets(),將 n 的值設(shè)置很大,每次就可以讀取到一行行的數(shù)據(jù)。
【示例】一行一行地讀取文件。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp = NULL;
char ch;
char str[101] = {0}; //假設(shè)讀100個字符,我們要額外留下一個位置裝'\0'
//判斷一個,有沒有成功打開文件,如果打開出錯,直接退出程序
if((fp = fopen("./demo.txt","r+")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//讀當(dāng)前文件demo.txt,已經(jīng)有幾行內(nèi)容了
while(fgets(str,101,fp) != NULL){
printf("%s",str);
}
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功?。?!");
}else{
puts("\n文件關(guān)閉失敗");
exit(0);
}
}
//編譯執(zhí)行
>gcc -g frwstring.c -o frwstring
> ./frwstring
熊少文
xuhuifeng
xionglingzhou
sbsbsb
aaaaaa
bbbbbb
cccccc
寫字符串函數(shù) fputs
??????fputs() 函數(shù)用來向指定的文件寫入一個字符串,它的用法為:
int fputs( char *str, FILE *fp );
??????str 為要寫入的字符串,fp 為文件指針。寫入成功返回非負(fù)數(shù),失敗返回 EOF。例如:
char *str = "hello world";
FILE *fp = fopen("./demo.txt", "at+");
fputs(str, fp);
表示把把字符串 str 寫入到 D:\demo.txt 文件中。
【示例】向上例中建立的 d:\demo.txt 文件中追加一個字符串。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp = NULL;
char ch;
char str[101] = {0}; //假設(shè)讀100個字符,我們要額外留下一個位置裝'\0'
//判斷一個,有沒有成功打開文件,如果打開出錯,直接退出程序
if((fp = fopen("./demo.txt","r+")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//寫入字符串到當(dāng)前demo.txt中
printf("\n請輸入字符串!");
gets(str); //gets函數(shù),請注意不要越界101
fputs(str,fp);
//讀當(dāng)前文件demo.txt,已經(jīng)有幾行內(nèi)容了,fp指針要賦初始值,即我們要重新打開文件,初始到最開始的位置
printf("讀取的內(nèi)容如下:\n");
//判斷一個
if((fp = fopen("./demo.txt","r")) == NULL){
puts("fail to open file!!!");
exit(0);
}
while(fgets(str,101,fp) != NULL){
printf("\n%s",str);
}
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功!??!");
}else{
puts("\n文件關(guān)閉失敗");
exit(0);
}
}
注意,上面的代碼,讀出的是上一次寫進(jìn)文件后的內(nèi)容不是本次的內(nèi)容,這種問題以后再講,這里只是講一下寫字符串的功能
C語言fread和fwrite的用法
??????fgets() 有局限性,每次最多只能從文件中讀取一行內(nèi)容,因為 fgets() 遇到換行符就結(jié)束讀取,上面代碼我們用了while循環(huán)讀了多行。
??????如果希望讀取多行內(nèi)容,需要使用 fread() 函數(shù);fputs一次只能寫入一行,想一次寫入多行數(shù)據(jù),相應(yīng)地寫入函數(shù)為 fwrite()。
??????對于 Windows 系統(tǒng),使用 fread() 和 fwrite() 時應(yīng)該以二進(jìn)制的形式打開文件!因為換行時有兩個字符‘'\r\n'
??????fread() 函數(shù)用來從指定文件中讀取塊數(shù)據(jù)塊。所謂塊數(shù)據(jù),也就是若干個字節(jié)的數(shù)據(jù),可以是一個字符,可以是一個字符串,可以是多行數(shù)據(jù),并沒有什么限制。
fread() 的原型為:
size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
fwrite() 函數(shù)用來向文件中寫入塊數(shù)據(jù),它的原型為:
size_t fwrite ( void * ptr, size_t size, size_t count, FILE *fp );
對參數(shù)的說明:
(1)ptr 為內(nèi)存區(qū)塊的指針,它可以是數(shù)組、變量、結(jié)構(gòu)體等。
fread() 中的 ptr 用來存放讀取到的數(shù)據(jù),fwrite() 中的 ptr 用來存放要寫入的數(shù)據(jù)。
(2)size:表示每個數(shù)據(jù)塊的字節(jié)數(shù)。
(3)count:表示要讀寫的數(shù)據(jù)塊的塊數(shù)。
(4)fp:表示文件指針。
(5)理論上,每次讀寫 size*count個字節(jié)的數(shù)據(jù)。
(6)size_t 是在 stdio.h 和 stdlib.h 頭文件中使用 typedef 定義的數(shù)據(jù)類型,無符號整數(shù),常用來表示數(shù)量。
返回值:返回成功讀寫的塊數(shù),也即 count。如果返回值小于 count:
(1)對于 fwrite() 來說,肯定發(fā)生了寫入錯誤,可以用 ferror() 函數(shù)檢測。
(2)對于 fread() 來說,可能讀到了文件末尾,可能發(fā)生了錯誤,可以用 ferror() 或 feof() 檢測。
【示例】從鍵盤輸入一個數(shù)組,將數(shù)組寫入文件再讀取出來。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp = NULL;
char ch;
//判斷一個,有沒有成功打開文件,如果打開出錯,直接退出程序 ,'wb+'二進(jìn)制讀寫文件,重寫時先清清除原內(nèi)容
if((fp = fopen("./demo.txt","wb+")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//練習(xí)用fread和fwrite讀寫文件的數(shù)據(jù)
int a[5]={0},b[5]={0};
int i=0;
int size = sizeof(int); //數(shù)據(jù)塊數(shù)
for(i=0;i<5;i++){
scanf("%d",&a[i]);
}
fwrite(a,size,5,fp);
//讀取數(shù)據(jù)到b數(shù)組中
rewind(fp); //將文件內(nèi)部的內(nèi)容指針重新指向文件的頭位置處
fread(b,size,5,fp);
printf("\n從文件中讀到數(shù)組b的數(shù)據(jù)為: \n");
for(i=0;i<5;i++){
printf("%d",b[i]);
}
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功?。?!");
}else{
puts("\n文件關(guān)閉失敗");
exit(0);
}
}
數(shù)據(jù)寫入完畢后,位置指針在文件的末尾,要想讀取數(shù)據(jù),必須將文件指針移動到文件開頭rewind(fp);
【示例】從鍵盤輸入兩個學(xué)生數(shù)據(jù),寫入一個文件中,再讀出這兩個學(xué)生的數(shù)據(jù)顯示在屏幕上。
#include<stdio.h>
#define N 2
struct stu{
char name[10]; //姓名
int num; //學(xué)號
int age; //年齡
float score; //成績
}boya[N], boyb[N], *pa, *pb;
int main(){
FILE *fp;
int i;
pa = boya;
pb = boyb;
if( (fp=fopen("./demo.txt", "wb+")) == NULL ){ //w+b,表示打開文件時,清除原文件的內(nèi)容,若沒有文件,則創(chuàng)建demo.txt文件
puts("Fail to open file!");
exit(0);
}
//從鍵盤輸入數(shù)據(jù)
printf("Input data:\n");
for(i=0; i<N; i++,pa++){
scanf("%s %d %d %f",pa->name, &pa->num,&pa->age, &pa->score);
}
//將數(shù)組 boya 的數(shù)據(jù)寫入文件
fwrite(boya, sizeof(struct stu), N, fp);
//將文件指針重置到文件開頭
rewind(fp);
//從文件讀取數(shù)據(jù)并保存到數(shù)據(jù) boyb
fread(boyb, sizeof(struct stu), N, fp);
//輸出數(shù)組 boyb 中的數(shù)據(jù)
for(i=0; i<N; i++,pb++){
printf("%s %d %d %f\n", pb->name, pb->num, pb->age, pb->score);
}
fclose(fp);
return 0;
}
//編譯與執(zhí)行
>gcc -g freadwrite.c -o freadwritestu.exe
>./freadwritestu
Input data:
xiong 1 50 59.6
xu 2 37 98.9
xiong 1 50 59.599998
xu 2 37 98.900002
C語言fscanf和fprintf函數(shù)
????????fscanf() 和 fprintf() 函數(shù)與前面使用的 scanf() 和 printf() 功能相似,都是格式化讀寫函數(shù),兩者的區(qū)別在于 fscanf() 和 fprintf() 的讀寫對象不是鍵盤和顯示器,而是磁盤文件。
這兩個函數(shù)的原型為:
int fscanf ( FILE *fp, char * format, ... );
int fprintf ( FILE *fp, char * format, ... );
fp 為文件指針,format 為格式控制字符串,... 表示參數(shù)列表。
與 scanf() 和 printf() 相比,它們僅僅多了一個 fp 參數(shù)。
例如:
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE *fp;
int i;
if( (fp=fopen("./demo.txt", "r+")) == NULL ){ //r+,表示打開文件后可讀寫,但不會清空原文件內(nèi)容
puts("Fail to open file!");
exit(0);
}
//讀文件中的數(shù)據(jù)到數(shù)組v中,再打印v數(shù)組內(nèi)容
char v[100] = {0};
fscanf(fp,"%s",v);
printf("%s \n",v);
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功!??!");
}else{
puts("文件關(guān)閉失敗");
exit(0);
}
return 0;
}

寫數(shù)據(jù)到文件中fprintf函數(shù)測試
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE *fp;
int i;
if( (fp=fopen("./demo.txt", "r+")) == NULL ){ //r+,表示打開文件后可讀寫
puts("Fail to open file!");
exit(0);
}
char v[100] = {0};
//讀文件到數(shù)組中
//fscanf(fp,"%s",v);
//printf("%s \n",v);
//寫數(shù)據(jù)到文件中, 這里代碼功能,只是測試用,讀和寫我們要注釋一個,不然有異常結(jié)果
scanf("%100[^\n]",v);
fprintf(fp,"%s",v);
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功?。。?);
}else{
puts("文件關(guān)閉失敗");
exit(0);
}
return 0;
}

【示例】用 fscanf 和 fprintf 函數(shù)來完成對學(xué)生信息的讀寫。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//定義學(xué)生信息的結(jié)構(gòu)體,兩個結(jié)構(gòu)體數(shù)組變量和指針
struct stu
{
char name[10];
int num;
int age;
float score;
} boya[2],boyb[2],*pa,*pb;
int main(){
FILE *fp;
int i;
if( (fp=fopen("./demo.txt", "r+")) == NULL ){ //r+,表示打開文件后可讀寫,沒有文件不創(chuàng)建,不清除原內(nèi)容
puts("Fail to open file!");
exit(0);
}
//從健盤中獲取數(shù)據(jù),保存到boya,后面再寫入到文件中,
pa = boya; //數(shù)組名代表當(dāng)前數(shù)組的地址(頭地址)
pb = boyb;
printf("請輸入學(xué)生信息: ");
for(i=0;i<2;i++,pa++){
scanf("%s %d %d %f",pa->name,&pa->num,&pa->age,&pa->score); //字符數(shù)組名就是地址了,不用&取地址符
}
//寫入文件,重新把pa指向首址,因為上面pa已指向了尾地址了
pa = boya;
for(i=0;i<2;i++,pa++){
fprintf(fp,"%s %d %d %f\n",pa->name,pa->num,pa->age,pa->score);
}
//讀文件到boyb中
rewind(fp); //文件內(nèi)容指針重指到頭位置
for(i=0;i<2;i++,pb++){
fscanf(fp,"%s %d %d %f",pb->name,&pb->num,&pb->age,&pb->score);
}
pb = boyb;
for(i=0;i<2;i++,pb++){
printf("%s %d %d %f\n",pb->name,pb->num,pb->age,pb->score);
}
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功?。?!");
}else{
puts("文件關(guān)閉失敗");
exit(0);
}
return 0;
}
//編譯執(zhí)行
>gcc -g fscanffprintfstu.c -o fscanfprintfstu.exe
>./fscanfprintfstu
輸入學(xué)生信息:
xiongwen 20240430 41 92.5
xuhuifeng 20240429 37 98.5
//顯示的是boyb中的數(shù)據(jù)
xiongwen 20240430 41 92.500000
xuhuifeng 20240429 37 98.500000
關(guān)閉文件成功!?。?
??????打開 當(dāng)前./demo.txt,發(fā)現(xiàn)文件的內(nèi)容是可以閱讀的,格式非常清晰。
??????用 fprintf() 和 fscanf() 函數(shù)讀寫配置文件、日志文件會非常方便,不但程序能夠識別,用戶也可以看懂,可以手動修改,因為它是生產(chǎn)了文本文件,不像其它一樣要用專門軟件格式打開之。
???如果將 fp 設(shè)置為 stdin,那么 fscanf() 函數(shù)將會從鍵盤讀取數(shù)據(jù),與 scanf 的作用相同;
???設(shè)置為 stdout,那么 fprintf() 函數(shù)將會向顯示器輸出內(nèi)容,與 printf 的作用相同。
例如:
#include<stdio.h>
int main(){
int a, b, sum;
fprintf(stdout, "Input two numbers: ");
fscanf(stdin, "%d %d", &a, &b);
sum = a + b;
fprintf(stdout, "sum=%d\n", sum);
return 0;
}
rewind和fseek函數(shù)-移動文件指針
??????前面介紹的文件讀寫函數(shù)都是順序讀寫,即讀寫文件只能從頭開始,依次讀寫各個數(shù)據(jù)。但在實際開發(fā)中經(jīng)常需要讀寫文件的中間部分,要解決這問題,就得先移動文件內(nèi)部的位置指針,再進(jìn)行讀寫。
???這種讀寫方式稱為隨機(jī)讀寫,也就是說從文件的任意位置開始讀寫。
移動文件內(nèi)部位置指針的函數(shù)主要有兩個,即 rewind() 和 fseek()。
??????rewind() 用來將位置指針移動到文件開頭,前面已經(jīng)多次使用過,它的原型為:
void rewind ( FILE *fp );
??????fseek() 用來將位置指針移動到任意位置,它的原型為:
int fseek ( FILE *fp, long offset, int origin );
參數(shù)說明:
fp 為文件指針,也就是被移動的文件。
offset 為偏移量,也就是要移動的字節(jié)數(shù)。
之所以為 long 類型,是希望移動的范圍更大,能處理的文件更大。
offset 為正時,向后移動;offset 為負(fù)時,向前移動。-
origin 為起始位置,也就是從何處開始計算偏移量。
C語言規(guī)定的起始位置有三種,分別為文件開頭、當(dāng)前位置和文件末尾,每個位置都用對應(yīng)的常量來表示:
image.png
例如,把位置指針移動到離文件開頭100個字節(jié)處:
fseek(fp, 100, 0);
-----值得說明的是,fseek() 一般用于二進(jìn)制文件(打開文件模式為b),在文本文件中由于要進(jìn)行轉(zhuǎn)換,計算的位置有時會出錯。
文件的隨機(jī)讀寫
??????在移動位置指針之后,就可以用前面介紹的任何一種讀寫函數(shù)進(jìn)行讀寫了。
由于是二進(jìn)制文件,因此常用 fread() 和 fwrite() 讀寫。
【示例】從鍵盤輸入三組學(xué)生信息,保存到文件中,然后讀取第二個學(xué)生的信息。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//定義學(xué)生信息的結(jié)構(gòu)體,兩個結(jié)構(gòu)體數(shù)組變量和指針
struct stu
{
char name[10];
int num;
int age;
float score;
} boys[3],boy,*pboys;
int main(){
FILE *fp;
int i;
if( (fp=fopen("./demo.txt", "w+b")) == NULL ){ //w+b,表示打開文件后可讀寫,沒有文件創(chuàng)建,清除原內(nèi)容
puts("Fail to open file!");
exit(0);
}
//從健盤中獲取數(shù)據(jù),保存到boys,后面再寫入到文件中,
pboys = boys; //數(shù)組名代表當(dāng)前數(shù)組的地址(頭地址)
printf("請輸入學(xué)生信息: ");
for(i=0;i<3;i++,pboys++){
scanf("%s %d %d %f",pboys->name,&pboys->num,&pboys->age,&pboys->score); //字符數(shù)組名就是地址了,不用&取地址符
}
//寫入文件,寫本個數(shù)據(jù)塊,一個塊大小為結(jié)構(gòu)體所占用的數(shù)據(jù)空間
fwrite(boys,sizeof(struct stu),3,fp);
//讀文件到boy中,寫入三個學(xué)生信息,我們只讀第二學(xué)生信息
fseek(fp,sizeof(struct stu),SEEK_SET);
fread(&boy,sizeof(struct stu),1,fp);
printf("\n第二個學(xué)生為:\n");
printf("%s %d %d %f",boy.name,boy.num,boy.age,boy.score);
//用完了文件后,一定要關(guān)閉
if(fclose(fp) == 0){
puts("\n關(guān)閉文件成功?。?!");
}else{
puts("文件關(guān)閉失敗");
exit(0);
}
return 0;
}
//編譯執(zhí)行結(jié)果
> gcc -g freadwritestufseek.c -o fseek.exe
> ./fseek
請輸入學(xué)生信息:
xiongshaowen 1 41 96.5
xuhuifeng 2 37 98.5
huangren 3 36 90.1
第二個學(xué)生為:
xuhuifeng 2 37 98.500000
關(guān)閉文件成功?。?!
C語言實現(xiàn)文件復(fù)制功能
??????文件的復(fù)制是常用的功能,要求寫一段代碼,讓用戶輸入要復(fù)制的文件以及新建的文件,然后對文件進(jìn)行復(fù)制。能夠復(fù)制的文件包括文本文件和二進(jìn)制文件,你可以復(fù)制1G的電影,也可以復(fù)制1Byte的txt文檔。
??????實現(xiàn)文件復(fù)制的主要思路是:開辟一個緩沖區(qū),不斷從原文件中讀取內(nèi)容到緩沖區(qū),每讀取完一次就將緩沖區(qū)中的內(nèi)容寫入到新建的文件,直到把原文件的內(nèi)容讀取完。
這里有兩個關(guān)鍵的問題需要解決:
開辟多大的緩沖區(qū)合適?緩沖區(qū)過小會造成讀寫次數(shù)的增加,過大也不能明顯提高效率。目前大部分磁盤的扇區(qū)都是4K對齊的,如果讀寫的數(shù)據(jù)不是4K的整數(shù)倍,就會跨扇區(qū)讀取,降低效率,所以我們開辟4K的緩沖區(qū)。
緩沖區(qū)中的數(shù)據(jù)是沒有結(jié)束標(biāo)志的,如果緩沖區(qū)填充不滿,如何確定寫入的字節(jié)數(shù)?最好的辦法就是每次讀取都能返回讀取到的字節(jié)數(shù)。
fread() 的原型為:
size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
???它返回成功讀寫的塊數(shù),該值小于等于 count。
???如果我們讓參數(shù) size 等于1,那么返回的就是讀取的字節(jié)數(shù)。
代碼實現(xiàn):
#include <stdio.h>
#include <stdlib.h>
//實現(xiàn)復(fù)制文件函數(shù)
int copyFile(char * fileRead,char * fileWrite){
FILE * fpRead; //指向要復(fù)制的文件
FILE * fpWrite; //指向復(fù)制后的文件
//設(shè)置緩沖區(qū)
int bufferLen = 4*1024; //4k
char * buffer = (char *)malloc(bufferLen); //動態(tài)分配緩沖區(qū)
if((fpRead = fopen(fileRead,"rb")) == NULL || (fpWrite = fopen(fileWrite,"wb")) == NULL){
printf("打開文件失??!\n");
exit(1); //打開文件失敗,異常退出,exit(0),沒問題時退出
}
//不斷讀取源文件的數(shù)據(jù),放到目標(biāo)文件中,下面代碼是很多語言通用形式
int readCount = 0;
while((readCount = fread(buffer,1,bufferLen,fpRead)) >0){
fwrite(buffer,readCount,1,fpWrite);
}
free(buffer); //功能寮現(xiàn)完成后,釋放緩沖區(qū)
fclose(fpRead);
fclose(fpWrite);
return 1;
}
int main(){
char fileRead[100]; //源文件名字
char fileWrite[100]; //目標(biāo)文件名字
//用戶輸入要復(fù)制的文件
printf("請輸入要復(fù)制的文件的名字: \n");
scanf("%s",fileRead);
printf("請輸入將文件復(fù)制的文件名字: \n");
scanf("%s",fileWrite);
//調(diào)用函數(shù)復(fù)制文件
if(copyFile(fileRead,fileWrite)){
printf("復(fù)制成功! \n");
}else{
printf("復(fù)制失??! \n");
}
return 0;
}
//編譯執(zhí)行效果
>gcc -g freadwritefilecopy.c -o copyfile.exe
>./copyfile
請輸入要復(fù)制的文件的名字,不輸入絕對路徑,為當(dāng)前目錄中:
c:\\a.txt
請輸入將文件復(fù)制的文件名字:
c:\\b.txt
復(fù)制成功!
C語言獲取文件大小
??????實際開發(fā)中,有時候需要先獲取文件大小再進(jìn)行下一步操作。
C語言沒有提供獲取文件大小的函數(shù),要想實現(xiàn)該功能,必須自己編寫函數(shù)。
ftell()函數(shù)
???ftell() 函數(shù)用來獲取文件內(nèi)部指針(位置指針)距離文件開頭的字節(jié)數(shù),它的原型為:
long int ftell ( FILE * fp );
注意:fp 要以二進(jìn)制方式打開,如果以文本方式打開,函數(shù)的返回值可能沒有意義。
??????先使用 fseek() 將文件內(nèi)部指針定位到文件末尾,再使用 ftell() 返回內(nèi)部指針距離文件開頭的字節(jié)數(shù),這個返回值就等于文件的大小。
long fsize(FILE *fp){
fseek(fp, 0, SEEK_END);
return ftell(fp);
}
??????這段代碼并不健壯,它移動了文件內(nèi)部指針,可能會導(dǎo)致接下來的文件操作錯誤。
??????所以,獲取到文件大小后還需要恢復(fù)文件內(nèi)部指針,不然其它地方會出問題,請看下面的代碼:
long fsize(FILE *fp){
long n;
fpos_t fpos; //當(dāng)前位置
fgetpos(fp, &fpos); //獲取當(dāng)前位置
fseek(fp, 0, SEEK_END);
n = ftell(fp);
fsetpos(fp,&fpos); //恢復(fù)之前的位置
return n;
}
??????fpos_t 是在 stdio.h 中定義的結(jié)構(gòu)體,用來保存文件的內(nèi)部指針。
??????fgetpos() 用來獲取文件內(nèi)部指針,fsetpos() 用來設(shè)置文件內(nèi)部指針。
完整的示例:
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
long fsize(FILE *fp);
int main(){
long size = 0;
FILE *fp = NULL;
char filename[30] = "c:\\2.png";
if( (fp = fopen(filename, "rb")) == NULL ){ //以二進(jìn)制方式打開文件
printf("Failed to open %s...", filename);
getch();
exit(EXIT_SUCCESS);
}
printf("%ld\n", fsize(fp));
return 0;
}
long fsize(FILE *fp){
long n;
fpos_t fpos; //當(dāng)前位置
fgetpos(fp, &fpos); //獲取當(dāng)前位置,并保存在fpos中
fseek(fp, 0, SEEK_END);
n = ftell(fp);
fsetpos(fp,&fpos); //恢復(fù)之前的位置
return n;
}

C語言插入、刪除、更改文件內(nèi)容
??????C語言沒有提供插入、刪除、修改文件內(nèi)容的函數(shù),要想實現(xiàn)這些功能,只能自己編寫函數(shù)。
???我們平時所見的文件,例如 txt、doc、mp4 等,文件內(nèi)容是按照從頭到尾的順序依次存儲在磁盤上的,就像排起一條長長的隊伍,稱為順序文件。
??????除了順序文件,還有索引文件、散列文件等,一般用于特殊領(lǐng)域,例如數(shù)據(jù)庫、高效文件系統(tǒng)等。
??????以插入數(shù)據(jù)為例,假設(shè)原來文件的大小為 1000 字節(jié),現(xiàn)在要求在500字節(jié)處插入用戶輸入的字符串,那么可以這樣來實現(xiàn):
1) 創(chuàng)建一個臨時文件,將后面500字節(jié)的內(nèi)容復(fù)制到臨時文件;
2) 將原來文件的內(nèi)部指針調(diào)整到500字節(jié)處,寫入字符串;
3) 再將臨時文件中的內(nèi)容寫入到原來的文件。
??????刪除數(shù)據(jù)時,也是類似的思路。假設(shè)原來文件大小為1000字節(jié),名稱為 demo.mp4,現(xiàn)在要求在500字節(jié)處往后刪除100字節(jié)的數(shù)據(jù),那么可以這樣來實現(xiàn):
1) 創(chuàng)建一個臨時文件,先將前500字節(jié)的數(shù)據(jù)復(fù)制到臨時文件,再將600字節(jié)之后的所有內(nèi)容復(fù)制到臨時文件;
2) 刪除原來的文件,并創(chuàng)建一個新文件,命名為 demo.mp4;
3) 將臨時文件中的所有數(shù)據(jù)復(fù)制到 demo.mp4。
??????修改數(shù)據(jù)時,如果新數(shù)據(jù)和舊數(shù)據(jù)長度相同,那么設(shè)置好內(nèi)部指針,直接寫入即可;
??????如果新數(shù)據(jù)比舊數(shù)據(jù)長,相當(dāng)于增加新內(nèi)容,思路和插入數(shù)據(jù)類似;
??????如果新數(shù)據(jù)比舊數(shù)據(jù)短,相當(dāng)于減少內(nèi)容,思路和刪除數(shù)據(jù)類似。
???實際開發(fā)中,我們往往會保持新舊數(shù)據(jù)長度一致,以減少編程的工作量,所以我們不再討論新舊數(shù)據(jù)長度不同的情況。
總起來說,本節(jié)重點討論數(shù)據(jù)的插入和刪除。
文件復(fù)制函數(shù)
??????在數(shù)據(jù)的插入刪除過程中,需要多次復(fù)制文件內(nèi)容,我們有必要將該功能實現(xiàn)為一個函數(shù),如下所示:
/**
* fsource :要復(fù)制的源文件
* offsetSource:開發(fā)復(fù)制的位置
* len:復(fù)制數(shù)據(jù)的長度
* fTarget:數(shù)據(jù)復(fù)制到的目標(biāo)文件
* offsetTarget:放數(shù)據(jù)到目標(biāo)文件的這個位置
**/
long fcopy(FILE *fsource, long offsetSource, long len, FILE *fTarget,
long offsetTarget) {
int bufferLen = 4 * 1024;
char *buffer = (char *)malloc(bufferLen);
int readCount = 0; //每次讀取的數(shù)據(jù)長度
long nBtytes = 0; //記錄已經(jīng)讀了的數(shù)據(jù)長度
int n = 0; //需要調(diào)用多少次fread,源文件要被讀取多少次,能讀完數(shù)據(jù)
//重置源文件和目標(biāo)文件的位置指針
fseek(fsource, offsetSource, SEEK_SET);
fseek(fTarget, offsetTarget, SEEK_SET);
//開始復(fù)制內(nèi)容
if (len < 0) { //從開始位置開始,后面的所有內(nèi)容全部復(fù)制
while ((readCount = fread(buffer, 1, bufferLen, fsource)) > 0) {
nBtytes += readCount;
fwrite(buffer, readCount, 1, fTarget);
}
} else { //從開始位置復(fù)制len長度的數(shù)據(jù)
n = (int)ceil(
(double)((double)len / bufferLen)); // ceil函數(shù)向上取整,1.1===2 要include <math.h>
for (int i = 1; i <= n; i++) {
if ((len - nBtytes) < bufferLen) {
bufferLen = len - nBtytes;
}
readCount = fread(buffer, 1, bufferLen, fsource);
fwrite(buffer, readCount, 1, fTarget);
nBtytes += readCount;
}
}
fflush(fTarget); //刷新系統(tǒng)緩沖區(qū),保證數(shù)據(jù)都進(jìn)入到目標(biāo)文件的磁盤上去
free(buffer);
return nBtytes;
}
-------------該函數(shù)可以將原文件任意位置的任意長度的內(nèi)容復(fù)制到目標(biāo)文件的任意位置,非常靈活。
那么可以像這面這樣調(diào)用:
fcopy(fSource, 0, -1, fTarget, 0);
文件內(nèi)容插入函數(shù)
先看代碼:
/**
* filename:要插入內(nèi)容的文件名字,包含路徑
* offset:要插入內(nèi)容的位置
* buffer:要插入的內(nèi)容
* len:要插入的內(nèi)容的長度
* */
int finsert(char *filename, long offset, void *buffer, int len) {
//打開文件
FILE *fp = NULL;
if ((fp = fopen(filename, "r+b")) == NULL) {
puts("fail to open file!");
return -1; //操作失敗
}
//獲取目標(biāo)文件的長度
long fileSize = fsize(fp);
if (offset > fileSize || offset < 0 || len < 0) {
return -1;
}
if (offset == fileSize) { //插入的數(shù)據(jù)是直接在末尾
fseek(fp, offset, SEEK_SET); //位置指針移動到了文件的末尾
if (!fwrite(buffer, len, 1, fp)) {
return -1;
}
}
if (offset < fileSize) {
FILE *fpTemp =
tmpfile(); //自動創(chuàng)建臨時文件,可讀寫,fclose后,臨時文件會自動刪除
fcopy(fp, 0, offset, fpTemp, 0); //靈活的文件復(fù)制函數(shù)就被使用了
fwrite(buffer, len, 1, fpTemp); //把插入的內(nèi)容寫到臨時文件的尾巴上
fcopy(fp, offset, -1, fpTemp,
offset + len); //把源文件的后半部分復(fù)制到臨時文件的尾巴上
freopen(filename, "wb+", fp);
fcopy(fpTemp, 0, -1, fp, 0);
fclose(fpTemp);
}
fclose(fp);
return 0; //操作成功
}
代碼說明:
- fsize() 是前面自定義的函數(shù),用來獲取文件大?。ㄒ宰止?jié)計)。
- 第17行判斷數(shù)據(jù)的插入位置,如果是在文件末尾,就非常簡單了,直接用 fwrite() 寫入即可。
- 如果從文件開頭或中間插入,就得創(chuàng)建臨時文件。
tmpfile() 函數(shù)用來創(chuàng)建一個臨時的二進(jìn)制文件,可以讀取和寫入數(shù)據(jù),相當(dāng)于 fopen() 函數(shù)以"wb+"方式打開文件。該臨時文件不會和當(dāng)前已存在的任何文件重名,并且會在調(diào)用 fclose() 后或程序結(jié)束后自動刪除。
文件內(nèi)容刪除函數(shù)
看下面的代碼:
int fdelete(char *filename, long offset, int len) {
//打開文件
FILE *fp = NULL;
if ((fp = fopen(filename, "r+b")) == NULL) {
puts("fail to open file!");
return -1; //操作失敗
}
//獲取目標(biāo)文件的長度
long fileSize = fsize(fp);
if (offset > fileSize || offset < 0 || len < 0) {
return -1;
}
FILE *fpTemp =
tmpfile(); //自動創(chuàng)建臨時文件,可讀寫,fclose后,臨時文件會自動刪除
fcopy(fp, 0, offset, fpTemp, 0); //靈活的文件復(fù)制函數(shù)就被使用了
fcopy(fp, offset + len, -1, fpTemp,
offset); //把源文件的后半部分復(fù)制到臨時文件的尾巴上
freopen(filename, "wb+", fp);
fcopy(fpTemp, 0, -1, fp, 0);
fclose(fpTemp);
fclose(fp);
return 0; //操作成功
}
調(diào)用,我們設(shè)置了一個字符指針,字符有123456789,分別插入,再刪除,最終等于對demo.txt文件什么也沒做
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(){
char *str ="123456789";
//finsert("./demo.txt",3,str,9);
fdelete("./demo.txt",3,9);
return 0;
}
//編譯執(zhí)行,先插入,再刪除,分別注釋不同的代碼進(jìn)行測試
>gcc -g main.c -o main.exe
>./main
//demo.txt文件的變化
aaaaaaaaaa
aaa123456789aaaaaaa
aaaaaaaaaa
系統(tǒng)調(diào)用IO
什么是文件描述符
???????文件描述符:File descriptor,簡稱fd,當(dāng)應(yīng)用程序請求內(nèi)核打開/新建一個文件時(操作系統(tǒng)的核心叫內(nèi)核,是一個獨立的軟件),內(nèi)核會返回一個文件描述符用于對應(yīng)這個打開/新建的文件,其fd本質(zhì)上就是一個非負(fù)整數(shù)。
???????實際上,fd是一個索引值,指向內(nèi)核為每一個進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表。
???????操作系統(tǒng)會為每一個進(jìn)程維護(hù)了一個文件描述符表,該表的索引值都從從0開始的,所以在不同的進(jìn)程中可以看到相同的文件描述符,這種情況下相同的文件描述符可能指向同一個文件,也可能指向不同的文件,具體情況需要具體分析,下面用一張簡圖就可以很容易的明白了:

當(dāng)一個應(yīng)用程序剛剛啟動的時候,0是標(biāo)準(zhǔn)輸入,1是標(biāo)準(zhǔn)輸出,2是標(biāo)準(zhǔn)錯誤。
如果此時去打開一個新的文件,它的文件描述符會是3。
IEEE的可移植操作系統(tǒng)(POSIX)標(biāo)準(zhǔn)要求每次打開文件時必須使用當(dāng)前進(jìn)程中最小可用的文件描述符號
什么是inode:
???????文件存儲在硬盤上,硬盤的最小存儲單位叫做“扇區(qū)”(Sector)。每個扇區(qū)儲存512字節(jié)(相當(dāng)于0.5KB)。
???????操作系統(tǒng)讀取硬盤的時候,不會一個個扇區(qū)的讀取,這樣效率太低,而是一次性連續(xù)讀取多個扇區(qū),即一次性讀取一個“塊”(block)。這種由多個扇區(qū)組成的“塊”,是文件存取的最小單位?!皦K”的大小,最常見的是4KB,即連續(xù)八個sector組成一個block。
???????文件數(shù)據(jù)都儲存在“塊”中,那么很顯然,我們還必須找到一個地方儲存文件的“元信息”,比如文件的創(chuàng)建者、文件的創(chuàng)建日期、文件的大小等等。這種儲存文件元信息的區(qū)域就叫做inode,中文譯名為"索引節(jié)點"。
???????每一個文件都有對應(yīng)的inode,里面包含了與該文件有關(guān)的一些信息。
???????Unix/Linux系統(tǒng)內(nèi)部不使用文件名,而使用inode號碼來識別文件。對于系統(tǒng)來說,文件名只是inode號碼便于識別的別稱。
open()函數(shù)---對應(yīng)標(biāo)準(zhǔn)IO的fopen
需要引入頭文件:
#include <fcntl.h>
#include <sys/types.h>
#incldue <sys/stat.h>
功能:打開和創(chuàng)建文件(建立一個文件描述符,其他的函數(shù)可以通過文
件描述符對指定文件進(jìn)行讀取與寫入的操作。)
原型
int open(const char*pathname,int flags);
int open(const char*pathname,int flags,mode_t mode);
參數(shù)說明:
- pathname
要打開或創(chuàng)建的目標(biāo)文件 - flags,文件的狀態(tài)標(biāo)志
打開文件時,可以傳入多個參數(shù)選項,用下面的一個或者多個常量進(jìn)行“或”運(yùn)算,構(gòu)成falgs常用參數(shù):
O_RDONLY: 只讀打開
O_WRONLY: 只寫打開
O_RDWR: 讀,寫打開
上面三個常量,必須指定一個且只能指定一個。
O_APPEND: 追加寫,如果文件已經(jīng)有內(nèi)容,
這次打開文件所寫的數(shù)據(jù)附加到文件的末尾而不覆蓋原來的內(nèi)容。
O_NONBLOCK、SYNC、O_ASYNC:這些和網(wǎng)絡(luò)通信有關(guān),學(xué)到后面在了解。
O_CREAT: 若文件不存在,則創(chuàng)建它,需要使用mode選項,來指明新文件的訪問權(quán)限。
使用O_CREAT的時候需要配套第三個參數(shù)是設(shè)定該文件的權(quán)限:
我們將權(quán)限用數(shù)字表示,其中 r 表示4,w表示2,x表示1,分別是2的0次方,1次方,2次方。
那么我們可以這樣理解:
具有 rwx 權(quán)限的數(shù)字就是 7,具有 rw- 權(quán)限的數(shù)字是 6,具有 r-- 權(quán)限的數(shù)字是 4。
我們常用的權(quán)限的數(shù)字模式又這幾種:
644:rw-r--r--
755:rwxr-xr-x
777:rwxrwxrwx
返回值
成功:新打開的文件描述符
失?。?1
例子:
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main() {
int filefd = 0;
if ((filefd = open("./test.txt", O_CREAT | O_RDWR,0666)) == -1) {
printf("++++++++++++++=open file fail\n");
return -1;
}
return 0;
}
//在使用open()建立新文件時,該參數(shù)mode并非真正建立文件的權(quán)限,
//而是(mode&~umask)的權(quán)限值。
//例如,在建立文件時指定文件權(quán)限為0666,通常umask值默認(rèn)為022,
//則該文件的真正權(quán)限則為0666&~022=0644,也就是rw-r-r--。
close()函數(shù)---對應(yīng)標(biāo)準(zhǔn)IO的fclose
??????close用于關(guān)閉一個已打開文件。
進(jìn)程終止時,內(nèi)核會自動關(guān)閉它所有的打開文件,應(yīng)用程序經(jīng)常利用這一點而不顯式關(guān)閉文件。
需要引入頭文件:
#include <unistd.h>
原型
int close(int fd);
返回值:
成功返回0,失敗返回-1
例子:
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
int main(){
int fd=open("./test.txt",O_RDWR|O_CREAT,0666);
if(fd<0){
perror("open error");
return -1;
}
printf("%d\n",fd);
close(fd);
return 0;
}
read()函數(shù)
???read用于從打開文件中讀數(shù)據(jù)。
需要引入頭文件:
#include <unistd.h>
原型
ssize_t read(int fd, void *buf, size_t count);
參數(shù)說明:
???fd:open返回的文件描述符-文件操作句柄,,通過fd指定要往哪個文件讀數(shù)據(jù)
???buf:從文件中讀取數(shù)據(jù)放到哪塊緩沖區(qū)的首地址
???count:想要讀取的數(shù)據(jù)長度,注意這個count不能大于緩沖區(qū)的大小
返回值:
???成功返回讀到的字節(jié)數(shù);若讀到文件尾則返回0;失敗返回-1
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
int filefd = 0; //相當(dāng)于標(biāo)準(zhǔn)i/o中的 FILE *fp,用于后面的文件操作
if ((filefd = open("./test.txt", O_CREAT | O_RDWR,0666)) == -1) {
printf("++++++++++++++=open file fail\n");
return -1;
}
char buf[1024] = {0}; //設(shè)一個緩沖區(qū),最好賦一個初值,以免出現(xiàn)不必要的異常
//文件數(shù)據(jù)
int ret = read(filefd,buf,1024); //成功返回讀到的字節(jié)數(shù);若讀到文件尾則返回0;失敗返回-1
if(ret <0){
printf("讀取異常!\n");
exit(1);
}
printf("讀取文件的數(shù)據(jù)大小和內(nèi)容為: %d-\n%s\n",ret,buf);
printf("文件描述號: %d\n",filefd); //顯示文件描述符
close(filefd); //關(guān)閉文件
return 0;
}
//編譯與執(zhí)行
[root@localhost os_io]# gcc -g read.c -o read
[root@localhost os_io]# ./read
讀取文件的數(shù)據(jù)大小和內(nèi)容為: 60-
xiongshaowen熊少文
xuhuifeng
xionglinzhou
xionglinxiao
文件描述號: 3
write()函數(shù)
??????向文件寫入數(shù)據(jù)。
#include <unistd.h>
//成功返回寫入的字節(jié)數(shù),失敗返回-1
ssize_t write(int fd, const void *buf, size_t count);
參數(shù)
???fd:open返回的文件描述符-文件操作句柄,,通過fd指定要往哪個文件寫入數(shù)據(jù)
???buf:要寫入文件的數(shù)據(jù)的空間首地址
???count:要寫入的數(shù)據(jù)大小
???返回值:返回實際寫入文件的數(shù)據(jù)字節(jié)長度,錯誤返回-1
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
int fd = 0,fdb=0; //相當(dāng)于標(biāo)準(zhǔn)i/o中的 FILE *fp,用于后面的文件操作
if ((fd = open("./test.txt", O_CREAT | O_RDWR,0666)) == -1) {
printf("++++++++++++++=open file fail\n");
return -1;
}
char buf[1024] = {0}; //設(shè)一個緩沖區(qū),最好賦一個初值,以免出現(xiàn)不必要的異常
//讀文件數(shù)據(jù)
int ret = read(fd,buf,1024); //成功返回讀到的字節(jié)數(shù);若讀到文件尾則返回0;失敗返回-1
if(ret <0){
printf("讀取異常!\n");
exit(1);
}
printf("讀取文件的數(shù)據(jù)大小和內(nèi)容為: %d-\n%s\n",ret,buf);
//把buf中讀到的數(shù)據(jù)保存到另一個文件(testb.txt)中。
if((fdb=open("./testb.txt",O_RDWR | O_CREAT,0666)) == -1){
printf("file testb.txt open fail!\n");
return -1;
}
int retb = write(fdb,buf,strlen(buf));
if(retb <0){
printf("寫數(shù)據(jù)異常!\n");
exit(1);
}
printf("兩文件描述號: %d %d\n",fd,fdb); //顯示文件描述符
close(fd); //關(guān)閉文件
close(fdb);
return 0;
}
lseek()函數(shù)---標(biāo)準(zhǔn)IO中fseek函數(shù)
???lseek用于設(shè)置打開文件的偏移量。
#include <sys/types.h>
#include <unistd.h>
成功返回新的文件偏移量,失敗返回-1
off_t lseek(int fd, off_t offset, int whence);
對offset的解釋取決于whence的值:
???若whence == SEEK_SET,則將文件偏移量設(shè)為距文件開頭offset個字節(jié),此時offset必須為非負(fù)
???若whence == SEEK_CUR,則將文件偏移量設(shè)為當(dāng)前值 + offset,此時offset可正可負(fù)
???若whence == SEEK_END,則將文件偏移量設(shè)為文件長度 + offset,此時offset可正可負(fù)
注意:
??????lseek僅將新的文件偏移量記錄在內(nèi)核中,它并不引起任何IO操作,因此它不是系統(tǒng)調(diào)用IO,但該偏移量會用于下一次read/write操作。
dup()和dup2()函數(shù)
??????兩個函數(shù)都可以來復(fù)制一個現(xiàn)有的文件描述符,使多個文件描述符指向同一個文件,也可以把標(biāo)準(zhǔn)輸入和輸出換成某個文件的輸入輸出:
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
???關(guān)于dup函數(shù),當(dāng)我們調(diào)用它的時候,dup會返回一個新的描述符,這個描述一定是當(dāng)前可用文件描述符中的最小值。
??????我們知道,一般的0,1,2描述符分別被標(biāo)準(zhǔn)輸入、輸出、錯誤占用,所以在程序中如果close(1)關(guān)掉標(biāo)準(zhǔn)輸出,調(diào)用dup函數(shù),此時返回的描述符就是1。
???對于dup2,可以用fd2指定新描述符的值,如果fd2本身已經(jīng)打開了,則會先將其關(guān)閉。
返回fd2。如果fd剛好和fd2相等,就直接返回fd2,不關(guān)閉。
總之:這兩個函數(shù)返回的描述符與fd描述符所指向的文件共享同一文件表項。
dup和dup2的例子:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
int fd = 0; //相當(dāng)于標(biāo)準(zhǔn)i/o中的 FILE *fp,用于后面的文件操作
if ((fd = open("./test.txt", O_CREAT | O_RDWR,0666)) == -1) {
printf("++++++++++++++=open file fail\n");
return -1;
}
if(write(fd,"hello 111111\n",13)!=13){ //寫入之前如果文件存在,手動清除原內(nèi)容,
printf("寫錯誤!");
exit(1);
}
//int dup_fd = dup(fd); //或
int dup_fd = dup(fd,4); //復(fù)制文件描述符,重新指定描述符,
printf("fd:%d dup_fd:%d\n",fd,dup_fd); //fd:3 dup_fd:4,此時,4和3是描述指向同一個文件
if(write(4,"hello 222222\n",13)!=13){ //再往4描述符指向的文件中寫入數(shù)據(jù),是向test.txt寫入的
printf("寫錯誤!");
exit(1);
}
close(dup_fd);
close(fd);
return 0;
}

??????我們知道,一般0,1,2描述符分別被標(biāo)準(zhǔn)輸入、輸出、錯誤占用,我在如下代碼中,我讓fd指向1標(biāo)準(zhǔn)輸出,即讓test.txt代替了屏幕文件了。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main() {
int fd = 0; //相當(dāng)于標(biāo)準(zhǔn)i/o中的 FILE *fp,用于后面的文件操作
if ((fd = open("./test.txt", O_CREAT | O_RDWR,0666)) == -1) {
printf("++++++++++++++=open file fail\n");
return -1;
}
if(write(fd,"hello 111111\n",13)!=13){ //寫入之前如果文件存在,手動清除原內(nèi)容,
printf("寫錯誤!");
exit(1);
}
int dup_fd = dup2(fd,1); //復(fù)制文件描述符,重新指定描述符,
printf("fd:%d dup_fd:%d\n",fd,dup_fd); //fd:3 dup_fd:1,此時,1和3是描述指向同一個文件,此時為1,即打印到文件test.txt中了,而不是屏幕上
fflush(stdout); //刷新標(biāo)準(zhǔn)輸出
close(dup_fd);
close(fd);
return 0;
}

fcntl()和ioctl()函數(shù)
fcntl原型
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能
??????fcntl函數(shù)其實是File Control的縮寫,通過fcntl可以設(shè)置、或者修改已打開的文件性質(zhì)。
參數(shù)
???fd:指向打開文件
???cmd:控制命令,通過指定不同的宏來修改fd所指向文件的性質(zhì)。
F_DUPFD
用來查找大于或等于參數(shù) arg 的最小且仍未使用的文件描述符,并且復(fù)制參數(shù) fd 的文件描述符。
作用就和dup和dup2一樣,雖然能實現(xiàn)一樣的工程,但項目中推薦用dup和dup2哈!
返回值:返回復(fù)制后的新文件描述
F_GETFL、F_SETFL
取得/設(shè)置文件狀態(tài)標(biāo)志,通過第三個參數(shù)設(shè)置
可以更改的幾個標(biāo)志是:O_APPEND、O_NONBLOCK、SYNC、O_ASYNC
(O_RDONLY、O_WRONLY和O_RDWR不適用)
什么時候需要fcntl來補(bǔ)設(shè)?
當(dāng)文件描述符不是你自己open得到,而是調(diào)用別人給的函數(shù),別人的函數(shù)去open某個文件,
然后再將文件描述符返回給你用,在這種情況下,我們是沒辦法去修改被人的函數(shù),
在他調(diào)用的
