標籤:io c
ANSI C標準幾乎被所有的作業系統支援,ANSI C標準提供了完善的I/O函數,使用這些I/O操作我們可以控製程序的輸入輸出、讀寫系統磁碟檔案。本文記錄了使用者進程I/O緩衝介紹、檔案的讀寫、檔案定位操作等內容。
庫函數與系統調用
檔案是位於磁碟上的,如何在啟動並執行程式(進程)中控制檔案的讀寫,通過下面的這張圖,我們可以看到應用程式如何控制系統資源(包括磁碟中的檔案)的大概的原理。
作業系統協助我們管理硬體資源,封裝底層實現,以介面的形式(系統調用函數)供上層應用程式調用。直接使用作業系統提供的系統調用函數我們就可以控制檔案的讀寫,但是一般我們在應用程式中不提倡這樣做,因為會帶來效能上的問題。應用程式調用系統調用函數的時候,作業系統會從使用者態轉變成核心態,完成調用後,再從核心態轉成使用者態(Linux中是通過非強制中斷實現的),而頻繁的系統狀態切換是需要開銷的,會給使用者應用程式帶來效能上的問題。另外就是可移植性的問題,每個作業系統向外提供的介面是不盡相同的,如果直接在應用程式中使用系統調用函數,那麼程式的可移植性將會很差。在ANSI C標準中為我們提供了標準函數(庫函數)去作業系統調用函數,庫函數是一些能夠完成特定功能的函數,一般由某個標準組織發布,並形成一種公認的標準,所以使用庫函數可以屏蔽作業系統間對外介面的差異。
下面是網友總結的庫函數和系統調用函數之間的差異
緩衝和非緩衝
對於檔案的訪問操作,可以按照是否使用緩衝區,分為緩衝檔案操作和非緩衝檔案操作
緩衝檔案操作:進階檔案操作,如第一幅圖使用者應用程式的左分支,在使用者進程空間為開啟的檔案分配緩衝區。ANSI C標準就是使用的這種檔案操作。本文討論的正是這種。
非緩衝檔案操作:低級檔案操作,如第一幅圖使用者應用程式的右分支,在使用者進程空間不設檔案緩衝區。POSIX C標準的I/O就是使用的非緩衝檔案操作。
說明:這裡所說的緩衝全部指的是使用者進程空間的檔案緩衝,即使是非緩衝的檔案操作,在核心部分也是使用了多級緩衝,而不是直接存取磁碟(可以參考作業系統的儲存空間結構)。
檔案與檔案流
流是一種抽象的資料結構,是ANSI C用來高效的管理開啟了的檔案資訊的,在實際編程中的體現就是struct FILE結構體,在stdio.h標頭檔中定義,流對象最重要的機制就是緩衝區和格式轉換。在Linux系統中系統預設為每個進程開啟3個檔案,對應3個流就是標準輸入資料流、標準輸出資料流、標準錯誤流。除此之外,需要用到的其他檔案需要自己開啟和關閉。
檔案的I/O操作
按照讀/寫的對象、可以分為按字元、行、塊、格式化等幾種讀寫操作。
(1)按字元讀/寫檔案流
下面的這段程式是使用fgetc和fputc來讀取指定檔案中的內容到標準輸出(顯示器),運行時需要指定一個檔案
#include<stdio.h>int main(int argc,char *argv[]){ FILE *fp=NULL; char ch; if(argc<=1) { printf("check usage of %s \n",argv[0]); return -1; } if((fp=fopen(argv[1],"r"))==NULL) { printf("can not open %s\n",argv[1]); return -1; } while ((ch=fgetc(fp))!=EOF) fputc(ch,stdout); fclose(fp); return 0;}
運行程式:
(2)按行讀/寫檔案流
下面是一個使用fgets和fputs實現上述功能的程式
#include<stdio.h>int main(int argc,char *argv[]){ FILE *fp=NULL; char str[20]; if((fp=fopen(argv[1],"r"))==NULL) //按唯讀形式開啟檔案 { printf("can not open!\n"); return -1; } fgets(str,sizeof(str),fp); //從開啟檔案中讀取sizeof(str)個位元組到str中 fputs(str,stdout); //將str輸出到標準輸出 fclose(fp); //關閉檔案 return 0;}
(3)按照塊讀寫
下面是使用fread和fwrite來實現的按塊讀寫的程式
#include<stdio.h>int main(int argc,char *argv[]){ struct student { char name[10]; int number; }; FILE *fp=NULL; int i; struct student boya[2],boyb[2],*pp,*qq; if((fp=fopen("aa.txt","w+"))==NULL) //以可讀寫的方式開啟檔案;若該檔案存在則清空,若不存在就建立 { //開啟檔案失敗 printf("can not open!\n"); return -1; } pp=boya; qq=boyb; printf(“please input two students‘ name and number:\n"); for (i=0;i<2;i++,pp++) scanf("%s\%d",pp->name,&pp->number); pp=boya; fwrite(pp,sizeof(struct student),2,fp); //將從鍵盤輸入的資訊寫入到檔案流fp中 rewind(fp); //將讀寫位置定位到檔案頭 fread(qq,sizeof(struct student),2,fp); //從檔案流fp中讀兩個結構體到qq printf("name\t\t number\n"); for(i=0;i<2;i++,qq++) //輸出qq中的內容 printf("%s\t\t %d\n",qq->name,qq->number); fclose(fp); return 0;}
(4)按照格式化讀/寫
下面這段程式是使用sprintf和sscanf進行檔案讀/寫操作
#include<stdio.h>int main(){ FILE *fp = NULL; int i = 20; char ch = 'D'; if((fp=fopen("f2","r+"))==NULL) { printf("open file f2 failed."); return -1; } fprintf(fp,"%d:%c\n",i,ch); //按照指定格式寫檔案 int new_i = 0; char new_ch; rewind(fp); //使讀寫指標歸位 fscanf(fp,"%d:%c\n",&new_i,&new_ch); //按照指定格式讀檔案 printf("new_i=%d, new_ch=%c\n",new_i,new_ch); fclose(fp); return 0;}
程式運行結果:
再說緩衝區
可以發現上述4中形式的讀寫操作,都沒有指明緩衝區,但是它們都使用到了位於使用者進程空間的檔案緩衝區,這是因為,對於任意的流,如果沒有指明其緩衝區的類型,系統將指定預設類型的緩衝區。如果使用者希望自己指定緩衝區,可以使用setbuf( )或者setvbuf( )函數指定,這兩個函數的聲明如下:
extern void setbuf ( 流對象, 緩衝區);
如果將緩衝區設定為NULL,則關閉緩衝區。
extern int setvbuf (流對象,緩衝區, 模式,緩衝區大小)
setvbuf比setbuf更加靈活,其中模式可取值有0、1、2,分別表示全緩衝、行緩衝、無緩衝,如果模式是2,那麼將會忽視第二和第四個參數。
下面是一個修改緩衝區的程式:
/* Example show usage of setbuf() &setvbuf() */#include<stdio.h>#include<error.h>#include<string.h>int main( int argc , char ** argv ){int i;FILE * fp;char msg1[]="hello,wolrd\n";char msg2[] = "hello\nworld";char buf[128];//open a file and set nobuf(used setbuf).and write string to it,check it before close of flush the streamif(( fp = fopen("no_buf1.txt","w")) == NULL){perror("file open failure!");return(-1);}setbuf(fp,NULL);memset(buf,'\0',128);fwrite( msg1 , 7 , 1 , fp );printf("test setbuf(no buf)!check no_buf1.txt\n");printf("now buf data is :buf=%s\n",buf);printf("press enter to continue!\n");getchar();fclose(fp);//open a file and set nobuf(used setvbuf).and write string to it,check it before close of flush the streamif(( fp = fopen("no_buf2.txt","w")) == NULL){perror("file open failure!");return(-1);}setvbuf( fp , NULL, _IONBF , 0 );memset(buf,'\0',128);fwrite( msg1 , 7 , 1 , fp );printf("test setvbuf(no buf)!check no_buf2.txt\n");printf("now buf data is :buf=%s\n",buf);printf("press enter to continue!\n");getchar();fclose(fp);//open a file and set line buf(used setvbuf).and write string(include '\n') to it,////check it before close of flush the streamif(( fp = fopen("l_buf.txt","w")) == NULL){perror("file open failure!");return(-1);}setvbuf( fp , buf , _IOLBF , sizeof(buf) );memset(buf,'\0',128);fwrite( msg2 , sizeof(msg2) , 1 , fp );printf("test setvbuf(line buf)!check l_buf.txt, because line buf ,only data before enter send to file\n");printf("now buf data is :buf=%s\n",buf);printf("press enter to continue!\n");getchar();fclose(fp);//open a file and set full buf(used setvbuf).and write string to it for 20th time (it is large than the buf)//check it before close of flush the streamif(( fp = fopen("f_buf.txt","w")) == NULL){perror("file open failure!");return(-1);}setvbuf( fp , buf , _IOFBF , sizeof(buf) );memset(buf,'\0',128);fwrite( msg2 , sizeof(msg2) , 1 , fp );printf("test setbuf(full buf)!check f_buf.txt\n");printf("now buf data is :buf=%s\n",buf);printf("press enter to continue!\n");getchar();fclose(fp);}
其他檔案操作
下面介紹一些在檔案操作中常用的函數
開啟、關閉檔案操作fopen和fclose
fp = fopen(檔案名稱,檔案操作方式);
檔案的操作方式有以下這些
關閉檔案使用fclose(檔案指標); 或者 fcloseall()函數。
檔案流檢測
extern int feof(流對象)
用於判斷流對象是否讀到檔案尾部,如果是返回1,否則,返回0;
extern int ferror(流對象)
用於判斷流對象是否出現了錯誤,若沒有錯誤,則返回0,否則,返回非0。
extern long int ftell(流對象)
返回當前讀寫位置距離檔案開頭位置的位元組數,若執行失敗,返回-1
extern int fseek(流對象,位移距離,基準位置)
基準位置可取值有0、1、2分別表示檔案開頭、當前位置、檔案結尾
將讀寫位置移到,距離基準位置位移距離處。若成功,返回0;否則,返回,-1
extern void rewind(流對象)
將讀寫位置重設到檔案開始處。
C檔案IO