柒
***流***
流是什么?形象的比喻——水流,**`文件`和`程序`之間連接一個管道**,水流就在之間形成了,自然也就出現了方向:可以流進,也可以流出。 ? ?
便于理解,這么定義流: 流就是一個管道里面有流水,這個管道連接了文件和程序。
UNIX系統認為一切皆文件,所有的外部設備都被看做文件。
***文件***
***文本文件和二進制文件***
實質:在計算機底層只有0和1。
**何謂文件?文件就是一些相關信息位的集合。**
文本文件只不過是把其文件存儲空間按字節分割,即:它以字節為解釋信息的單位,每個字節中存儲的是一個ASCII碼的小整數。
二進制文件則是把文件存儲空間當做內存一樣,可在其中按數據類型定義并存儲數據。
例:把數據123存儲到文件中。
????? 若是按文本的方式來存儲則會占用3字節,每個字節中的內容是:0x31 0x32 0x33 ?(即:數字49,50,51)它們對應著ASCII字符’1’ ‘2’ ‘3’
????? 若是按二進制方式來存儲則會占用4字節,這4字節即一個int型數據123
**信息就是:位+上下文**
**在計算機內部只有位,是我們根據需要賦予了這些位不同的解釋規則(即上下文),所以會呈現出不同的結果。**
****
**★文本文件**就是我們用ASCII碼的規則編碼,然后再用ASCII碼的規則解釋,這樣信息就可以反映出我們的實際意愿。
**★二進制文件**就是我們用程序制定的自定義的一套規則編碼(程序會設置這些位代表的含義,比如哪些位表示的是一個浮點數,哪些位表示的是一個整數等),最后我們用先前自定義的規則來解釋這些位,同樣可以得到正確的信息。【實際上是,我們把文件當做內存區一樣,按C語言定義的數據類型規則,該占多少位就占多少位,對數據不用轉換處理】
所以:**文本文件和二進制文件的區別就是它們編碼解碼的規則不同,文本文件是按ASCII碼的規則來編碼解碼的,而二進制文件是按自定義規則來編碼解碼的。**
故,用二進制的方式也可以建造一個文本文件,[只要我們按照ASCII碼來編碼即可。]
***打開和關閉文件***
fopen(文件名,文件打開方式)
有以文本模式打開文件,和以二進制模式打開文件。(每種模式又有很多種打開方式)
實質:這兩種模式沒有任何區別!它們都是向系統申請一段磁盤空間用以存儲待寫的數據。
而如何存儲 即如何編碼,將會導致它們是成為文本文件還是二進制文件。
不要根據文件的后綴名來判斷一個文件的類型,后綴名只是方便應用程序識別文件。.txt文件也可以以二進制的形式編碼,我們也可以把用ASCII編碼的數據寫入exe文件中。
后綴名只是文件名的一部分罷了。
fclose(文件指針) ; 關閉文件指針所指向的文件。
fcloseall( ) ;??? ??? 關閉所有已打開的文件。
為什么要關閉文件:
1、操作系統允許打開的文件數是有限制的。若某程序忘記關閉文件,若其被多個程序調用,則可能導致文件數資源耗盡,其它程序無法打開文件。
2、若文件操作方式為“寫”方式,則系統首先把該文件緩沖區(在內存中)中的剩余數據全部輸出到文件中,然后使文件指針fp與文件斷開聯系。
若對文件操作后不關閉文件,則可能導致文件中數據的丟失。
【**無論是動態分配內存還是文件操作,使用完資源后要馬上釋放資源!**】
C語言系統定義了三個默認的文件指針:[它們都是文本文件,此即為一切皆文件的思想]
1、stdin ?即標準輸入文件,與鍵盤連接。(即把鍵盤當做文件)
2、stdout 即標準輸出文件,與屏幕連接。(即把屏幕當做文件)
3、stderr 即標準出錯文件,與屏幕連接。
(它們都是常量指針,故不能被重定向)
注意:
stdout和stderr是不是同設備描述符。stdout是塊設備,stderr則不是。**對于塊設備,只有當下面幾種情況下才會被輸入**,1)遇到回車,2)緩沖區滿,3)flush被調用。而stderr則不會。
例:fprintf(stdout,"hello-std-out");//不一定會輸出
?? ?fprintf(stderr,"hello-std-err");//一定會輸出
***文本文件的讀寫操作***
①用fgetc()讀取一個字符
int fget(FILE * stream) ;
功能:從文件stream中讀一個字符,并把它作為函數返回值
②用fputc()寫入一個字符
int?fputc (int?ch,?FILE?*stream)? ;
功能:把字符ch寫入到文件stream中。
③用fgets()從文件中讀取字符串
char?* fgets (char?*string, ?int?n,?FILE?*stream)? ;
功能:從文件stream中讀取n-1個字符放入以string為首地址的空間里。讀入結束后,系統將自動在最后加’\0’,并以string作為函數值返回。
④用fputs()把一個字符串寫入到文件中
int? fputs (char?*string,? FILE?*stream)? ;
功能:把字符串string寫入到文件stream中
文本格式化的輸入輸出
⑤用fscanf函數,從文本文件中按格式讀取數據?
fscanf(FILE?*stream, 格式控制字符串, 參數列表) ;
[與從鍵盤輸入的形式 格式 規則 一樣]
⑥用fprintf函數,按格式向文本文件寫入字符數據
fprintf(FILE?*stream, 格式控制字符串, 參數列表) ;
[與輸出到屏幕的形式 格式 規則 一樣]
***二進制文件的讀寫操作***
①int?fread (void?*ptr,?int?size,?int?nitems,?FILE?*stream);
從文件stream中讀取nitems個size大小的數據放到ptr指向的緩沖區中。
②int?fwrite (void?*ptr,?int?size,?int?nitems,?FILE?*stream);
從ptr指向的緩沖區中取出nitems個size大小的數據寫到文件stream中
【這種類型的I/O效率很高,因為每個值中的位直接從流中讀取或寫入,不需要任何轉換】
【實際上,二進制I/O函數也可以用來建立文本文件,只是在格式化I/O時,操作不太方便。
文本I/O函數也可以用來建立二進制文件,(要清楚數據的實際位表示情況),操作很不方便】
因為,在底層任何類型的文件都是用0和1表示的,只是文本I/O函數按照ASCII碼的規則編碼解碼,但我們同樣可以用二進制I/O函數 根據ASCII碼的規則編碼解碼 也能做成文本文件,只是略顯復雜。
所以,雖然這些函數可以完成其它的工作,但是讓它們做適合它們的工作,我們操作起來會更方便,更順手。
【**把數據寫到文件中,效率最高的方法是用二進制的形式寫入。**二進制的輸出避免了在數值轉換為字符過程中所涉及的開銷和精度損失。但二進制文件非人眼所能閱讀,我們必須記住它的存儲格式才能讀取其中的數據。】
【**注意:文件是流!故當讀或寫時,文件指針隨著讀寫的流向而移動,它不是一直指向文件頭部的!**】
文件指針的流動,是系統完成的,它有一些隱藏的復雜機制,不是像一般指針那樣通過加減整數來實現的。fp++;的意思是指針移動一個文件的長度(因為它指向的對象是一個文件)
【文件是流, 字符串不是流。】
***文件指針定位***
因為文件指針比較特殊,我們無法通過取地址,增量操作來移動指針,故要改變指針的位置,需要利用專門的庫函數。
這些函數一般用于二進制流文件,因為二進制流文件是值的真實反映,可精確定位到某位置,而文本流用以下函數定位指針,可能會出現偏差。
①int?fseek (FILE?*stream,? long? offset,? int?fromwhere) ;
把文件指針定位到,相對于fromwhere處距離offset字節的位置處
fromwhere 取值0 代表文件的開始位置;取值1 代表當前文件指針的位置;取值2代表文件尾部位置。
②long?ftell (FILE?*stream) ;
返回當前文件指針的位置(據文件頭部的位置)
【在二進制流中,這個值準確反映了當前位置距離文件起始位置之間的字節數。但在文本流中,這個值不一定能準確表示當前位置距離文件起始位置之間的字節數。】
③int?rewind (FILE?*stream) ;
將文件指針重新指向一個流的開頭位置
【若在主函數中打開一個流,把流指針傳給多個函數調用時,注意:每個被調用函數的開始部分,最好用rewind(FILE *)把指針復位到文件開頭[因為多次調用,可能會改變流指針的位置]】
檢測流文件上的文件結束符:
int? feof (FILE?*stream) ;
若遇到文件結束符,函數feof返回1,否則返回0
【**注意:在C語言中,只有輸入程序試圖讀取并失敗以后才能得到EOF**】
故:依靠feof函數來檢測文件結束,可能會產生BUG!
while( !feof( infp ) )
{???? fgets(buf, MAXLINE, info ) ;
????? fputs(buf, outfp ) ;
}//因為程序讀取文件失敗后才能檢測到文件結束,故此代碼中的語句會多執行一次,產生BUG
【**一般情況,完全沒有必要使用feof,我們可以通過檢查文件I/O函數的返回值來判斷文件是否結束**】
例:while(fgets( buf, MAXLINE, infp ) != NULL )
???????????? …… ;
***輸入輸出函數***
【注意:文件是一種流,輸入輸出的字符也是流。流的性質:你只能順序地訪問并提取流中的數據,未提取的數據只能阻塞在流中,等待下次被訪問或提取。】
***一、格式化I/O函數***
scanf函數
①用scanf(“%s”,……) ; 讀入字符串時,
前導空白將被丟棄忽略,當遇到空格回車等分隔符時,讀入結束。空格或回車符留在流中,等待下次讀入。
②用scanf(“%d”,……)讀入數字;scanf(“%c”,……)讀入字符時
一次讀取一個數據,剩余的字符留在流中,等待下次讀入。
停留在流中的字符可能會影響到下次的正確讀入,丟棄輸入流中字符的方法:
⑴輸入結束后,把流中剩余的垃圾字符都讀掉。
?? while( (ch = getchar())!=EOF && ch!= ‘\n’)
?????????????? NULL ;
⑵用函數fflush(FILE?*stream); 清除一個流。
對于輸入流為fflush(stdin);
int?scanf( char const *format, …… ) ;
int?fscanf( FILE *stream, char const *format, …… ) ;
int?sscanf( char const *string, char const *format, ……) ;
//以上函數的讀入處理規則都相同,不同的是它們讀取的流不同,一個是從鍵盤讀取、一個從文件流讀取、一個從字符串讀取。(注意:字符串不是流,其沒有流指針保存讀取位置)
int?printf( char const *format, …… ) ;
int?fprintf ( FILE *stream, char const *format, …… ) ;
int?sprintf ( char const *string, char const *format, ……) ;
//以上函數的輸出字符規則都相同,不同的是它們寫入的流不同,一個是向屏幕寫入、一個向文件流寫入、一個向字符串寫入。
【注意:sprintf函數是一個潛在的錯誤根源,它可能會導致緩沖區溢出。(要提前估計好緩沖區的大小)】
【注意:**printf返回值是輸出的字符個數**。】
~~~
#include <stdio.h>
int main()
{
???int i=43;
???printf("%d\n",printf("%d",printf("%d",i)));
???return 0;
}//程序會輸出4321
~~~
***二、未格式化I/O函數***
fgetc( ) 接受一個輸入流作為參數,它從這個流中讀取一個字符(可讀入回車等空白符),如果發生錯誤或流已到結尾,則返回EOF。
char * fgets( char *s, int n, FILE *fp ) ;
A、若輸入由于遇到換行符而終止,則這個換行符也存儲于緩沖數組中(就在’\0’符之前)
B、若未遇到換行符或文件尾,就讀取了n-1個字符。則在緩沖數組末尾添加’\0’符。
gets( )和fgets( )不同,gets( )會丟棄換行符,并不把它存儲在緩沖數組中。
但gets( )對于輸入長度沒有限制,很可能導致輸入長度超過緩沖數組的長度,導致緩沖區溢出。
【故:**我們一般用fgets( )函數來接收用戶輸入。(這樣可允許用戶輸入任意字符)再在程序中分析用戶輸入,提取數據**】