1.C語言中的文件是什么?
????????所謂文件(file)一般指存儲在外部介質上數據的集合,比如我們經常使用的txt、bmp、jpg、exe、rmvb等等。這些文件各有各的用途,我們通常將它們存放在磁盤或者可移動盤等介質中。
文件無非就是一段數據的集合,這些數據可以是有規則的集合,也可以是無序的集合。操作系統也就是以文件為單位對數據進行管理的。也就是說,要訪問外部介質上的數據,必須先按照文件名進行查找,然后從該文件中讀取數據。要想寫數據到外部介質,必須得建立一個文件,然后再寫入。因此,你眼前的文件只是數據的集合。
1.1文件一般包括三要素:
????????文件路徑、文件名、后綴。
由于在C語言中''一般是轉義字符的起始標志,故在路徑中需要用兩個''表示路徑中目錄層次的間隔,也可以使用'/'作為路徑中的分隔符。
例如:
"D:\\123\\test.c"或者"D:/123/test.c",表示文件test.c保存在D盤123目錄下。
"tu.txt"表示當前目錄下的文件tu.txt。
文件路徑:可以顯式指出其絕對路徑,如上面的"D:\\"或者"D:/"等。如果沒有顯式指出其路徑,默認認為當前路徑。也就相對路徑。
數據的輸入和輸出幾乎伴隨著每個C語言程序,所謂輸入就是從"源端"獲取數據,所謂輸出可以理解為向"終端"寫入數據。這里的源端可以是鍵盤、鼠標、硬盤、光盤、掃描儀等輸入設備,終端可以是顯示器、硬盤、打印機等輸出設備。在C語言中,把這些輸入和輸出設備也看作"文件"。
1.2?C語言文件系統中的類型
FILE:對象類型,足以保有控制?C?I/O?流所需的全部信息
fpos_t:非數組完整對象類型,足以唯一指定文件的位置和多字節剖析狀態
每個?FILE?對象直接或間接保有下列信息:
●??(C95)字符寬度:未設置、窄或寬。
●??(C95)多字節與寬字符間轉換的分析狀態(mbstate_t類型對象)
●??緩沖狀態:無緩沖、行緩沖、全緩沖。
●??緩沖區,可為外部的用戶提供緩沖區所替換。
●??I/O?模式:輸入、輸出或更新(兼具輸入與輸出)。
●??二進制/文本模式指示器。
●??文件尾指示器。
●??錯誤狀態指示器。
●??文件位置指示器,可作為fpos_t類型對象訪問,對于寬流包含剖析狀態。
●??(C11)在多個線程讀、寫、尋位或查詢流時避免數據競爭的再入鎖。
1.3?預定義標準流
●?stdin?與標準輸入流關聯的?FILE*?類型表達式
●?stdout?與標準輸出流關聯的?FILE*?類型表達式
●?stderr?與標準錯誤輸出流關聯的?FILE*?類型表達式
1.4 宏常量
2.流的概念及分類
????????I/O設備的多樣性及復雜性,給程序設計者訪問這些設備帶來了很大的難度和不便。為此,ANSIC的I/O系統即標準I/O系統,把任意輸入的源端或任意輸出的終端,都抽象轉換成了概念上的“標準I/O設備”或稱“標準邏輯設備”。程序繞過具體設備,直接與該“標準邏輯設備”進行交互,這樣就為程序設計者提供了一個不依賴于任何具體I/O設備的統一操作接口,通常把抽象出來的“標準邏輯設備”或“標準文件”稱作“流”。
把任意I/O設備,轉換成邏輯意義上的標準I/O設備或標準文件的過程,并不需要程序設計者感知和處理,是由標準I/O系統自動轉換完成的。故從這個意義上,可以認為任意輸入的源端和任意輸出的終端均對應一個“流”。
●流按方向分為:輸入流和輸出流。從文件獲取數據的流稱為輸入流,向文件輸出數據稱為輸出流。
●流按數據形式分為:文本流和二進制流。文本流是ASCII碼字符序列,而二進制流是字節序列。
●流是一種抽象的概念,負責在數據的產生者和數據的使用者之間建立聯系,并管理數據的流動。
3.文本文件和二進制文件到底有什么區別:
根據文件中數據的組織形式的不同,可以把文件分為:文本文件和二進制文件。
●?文本文件:把要存儲的數據當成一系列字符組成,把每個字符的?ASCII?碼值存入文件中。每個?ASCII?碼值占一個字節,每個字節表示一個字符。故文本文件也稱作字符文件或?ASCII?文件,是字符序列文件。
●?二進制文件:把數據對應的二進制形式存儲到文件中,是字節序列文件。
4.C語言與文件讀寫
文件庫函數stdio.h鏈接:http://www.cplusplus.com/reference/cstdio/
C語言操作文件分為三步,1)打開文件,2)讀寫文件,3)關閉文件。
4.1?打開文件函數原型:FILE?*?fopen?(?const?char?*?filename,?const?char?*?mode?);?
函數參數:?
filename:文件名,包括路徑,如果不顯式含有路徑,則表示當前路徑。例如,"D:\\text.txt"表示D盤根目錄下的文件text.txt文件。
mode:文件打開模式,指出對該文件可進行的操作。常見的打開模式如"r"表示只讀,"w"表示只寫,"rw"表示讀寫,"a"表示追加寫入。
返回值:
打開成功,返回該文件對應的?FILE?類型的指針;打開失敗,返回?NULL。故需定義?FILE?類型的指針變量,保存該函數的返回值。可根據該函數的返回值判斷文件打開是否成功。
4.2?關閉函數fclose的原型:int?fclose(FILE*?stream);
函數參數
stream:指向要關閉流對象的指針。
返回值:
如果流被成功關閉,返回0值。失敗時,返回EOF(-1)。即使調用失敗,作為參數傳遞的流將不再與文件或其緩沖區關聯。
4.3?字符串格式化函數原型int?sprintf(char*?str,?const?char*?format,?...);
函數參數
str:指向緩沖區指針,緩沖區足夠大。
format:格式化字符串,該字符串遵循與printf中的格式相同的規范。
...:附加參數(根據格式化字符串的不同,函數可能需要一系列附加參數,每個參數都包含一個值,用于替換格式字符串中的格式說明符)。
返回值:
如果成功,將返回寫入的字符總數。此計數不包括自動附加在字符串末尾的額外空字符。失敗返回負數。
注意:Windows?OS?上的?C?流在輸出時將'\n'轉換為'\r\n',輸入時將'\r\n'轉換為'\n'。
4.4?格式化寫入函數int?fprintf?(?FILE?*?stream,?const?char?*?format,?...?);
輸出函數
int?printf?(?const?char?*?format,?...?);
示例:
?
4.5?從流中讀取格式化數據函數int?fscanf(FILE*?stream,?const?char*?format,?...?);
函數參數
stream:指向文件對象的指針,該對象標識要從中讀取數據的輸入流。
format:格式化字符串,該字符串遵循與scanf中的格式相同的規范。
返回值
成功賦值的接收參數的數量(可以為零,在首個接收用參數賦值前匹配失敗的情況下),或者若輸入在首個接收用參數賦值前發生失敗,則為EOF。
#include <stdlib.h>
#include <stdio.h>
int main()
{const int n = 10;int ar[n]={0};int i = 0;FILE *fpr = fopen("Test.txt","r");if(NULL == fpr){printf("open file failter \n");exit(EXIT_FAILURE);}for(i = 0;i<n;++i){fscanf(fpr,"%d ",&ar[i]);}fclose(fpr);fpr = NULL;return 0;
}
4.6?二進制文件的讀寫
????????塊數據寫入函數:size_t?fwrite?(?const?void?*?ptr,?size_t?size,?size_t?count,?FILE?*?stream?);
函數參數:
ptr:這是指向要被寫入的元素數組的指針。
size:這是要被寫入的每個元素的大小,以字節為單位。
count:這是元素的個數,每個元素的大小為?size?字節。
stream:這是指向?FILE?對象的指針,該?FILE?對象指定了一個輸出流。
返回值:
返回成功寫入元素的個數,若出現錯誤或到達文件末尾,則可能小于?count。如果返回的數值與?count?參數值不同,則寫入錯誤將阻止函數完成。在這種情況下,將為流設置錯誤指示器(ferror)。
塊數據讀出函數:size_t?fread?(?void?*?ptr,?size_t?size,?size_t?count,?FILE?*?stream?);
函數參數:
ptr:指向大小至少為(size*count)字節的內存塊的指針,從流中讀出的數據存儲到ptr指向的內存。
size:讀取元素的大小,unsigned?int。
count:讀取取元素的個數。
stream:是指向?FILE?對象的指針,該?FILE?對象指定了一個輸入流。
返回值:
返回成功讀取的對象個數,若出現錯誤或到達文件末尾,則可能小于?count。若?size?或?count?為零,則?fread?返回零且不進行其他動作。fread?不區分文件尾和錯誤,因此調用者必須用?feof?和?ferror?才能判斷發生了什么。
#include <stdlib.h>
#include <stdio.h>
#define ARSIZE 10void Save_Ar(int *ar, int n)
{FILE *fpw = NULL;int len = 0;if(NULL == ar || n < 1) return ;fpw = fopen("Test2.txt", "wb");if(NULL == fpw){printf("open file failure \n");exit(EXIT_FAILURE);}len = fwrite(ar, sizeof(int), n, fpw);fclose(fpw);fpw = NULL;
}void Load_Ar(int *br, int n)
{FILE *fpr = NULL;int len = 0;if(NULL == br || n < 1) return;fpr = fopen("Test2.txt", "rb");if(NULL == fpr){printf("open file failure \n");exit(EXIT_FAILURE);}len = fread(br, sizeof(int), n, fpr);fclose(fpr);fpr = NULL;
}int main()
{int ar[ARSIZE] = {12, 23, 34, 45, 56, 67, 78, 89, 90, 100};int br[ARSIZE] = {0};int n = ARSIZE;Save_Ar(ar, n);Load_Ar(br, n);return 0;
}
5.緩沖和非緩沖文件系統
????????在ANSI?C標準中,使用的是“緩沖文件系統”。所謂緩沖文件系統指系統自動地在內存為每一個正在使用的文件名開辟一個緩沖區,從內存向磁盤輸出數據必須先送到內存中的緩沖區,裝滿后再一起送到磁盤去。反向也是如此。vs2012?stdio.h中的FILE結構體:
?int?fflush(?FILE?*stream?);
功能:清除讀寫緩沖區,在需要立即把輸出緩沖區的數據進行物理寫入時
函數說明
如果指針指向一個輸出流或者是一個最近的一次操作不是輸入的更新流,輸出刷新將會創造任意未寫入的數據給將要被寫入文件的流和最近的數據被修改流,并且最后的文件狀態改變應該被標記為更新的基礎文件的時間戳。
對于打開以使用基礎文件描述進行讀取的流,如果文件尚未處于EOF,并且該文件是能夠搜索的文件,則基礎打開文件描述的文件偏移量應設置為流的文件位置,并且任何未被從流中讀取的?ungetc?()?或?ungetwc?()?推回到流上的字符都將被丟棄(不再進一步改變文件偏移量)。
如果?stream?是空指針,則?fflush?()?將對上面定義了行為的所有流執行此刷新操作。
返回值
如果成功刷新,?fflush?返回?0。指定的流沒有緩沖區或者只讀打開時也返回?0?值。返回?EOF?指出一個錯誤。
注意:如果?fflush?返回?EOF,數據可能由于寫錯誤已經丟失。當設置一個重要錯誤處理器時,最安全的是用?setvbuf?函數關閉緩沖或者使用低級?I/O?例程,如?open、close?和?write?來代替流?I/O?函數。
其他用法
fflush(stdin)刷新標準輸入緩沖區,把輸入緩沖區里的東西丟棄[非標準]
fflush(stdout)刷新標準輸出緩沖區,把輸出緩沖區里的東西打印到標準輸出設備上
注意事項
C?和?C++?的標準里從來沒有定義過?fflush(stdin)。也許有人會說:"可是我用?fflush(stdin)?解決了這個問題,你怎么能說是錯的呢?"?的確,某些編譯器(如?VC6)支持用?fflush(stdin)?來清空輸入緩沖,但是并非所有編譯器都要支持這個功能(linux?下的?gcc?就不支持),因為標準中根本沒有定義?fflush(stdin)。
MSDN?文檔里也清楚地寫著:fflush?on?input?stream?is?an?extension?to?the?C?standard?(?fflush?操作輸入流是對?C?標準的擴充)。
以下是?C99?對?fflush?函數的定義:int?fflush(FILE?*stream);
如果?stream?指向輸出流或者更新流?(update?stream),并且這個更新流最近執行的操作不是輸入,那么?fflush?函數將把任何未被寫入的數據寫入?stream?指向的文件(如標準輸出文件?stdout)。否則,?fflush?函數的行為是不確定的。?fflush?(NULL)?清空所有輸出流和上面提到的更新流。如果發生寫錯誤,?fflush?函數會給那些流打上錯誤標記,并且返回?EOF,否則返回?0。
由此可知,如果?stream?指向輸入流(如?stdin),那么?fflush?函數的行為是不確定的。故而使用?fflush(stdin)?是不正確的。
void?setbuf(FILE?*stream,?char?*buffer);
功能:設置用于流操作的內部緩沖區。其長度至少應該為BUFSIZ個字符。
若buffer非空,則等價于setvbuf(stream,buffer,_IOFBF,BUFSIZ)
若buffer為空,則等價于setvbuf(stream,NULL,_IOBNF,0),這會關閉緩沖。
參數:
stream:要設置緩沖區的文件流
buffer:指向文件流所用的緩沖區的指針。若提供空指針,則關閉緩沖。
返回值無。
注解
若BUFSIZ不是適合的緩沖區大小,則能用setvbuf更改它。
setvbuf亦應當用于檢測錯誤,因為setbuf不指示成功或失敗。
此函數僅可在已將stream關聯到打開的文件后,但要在任何其他操作(除了對setbuf/setvbuf的失敗調用)前使用。
一個常見錯誤是設置stdin或stdout的緩沖區為生存期在程序終止前結束的數組:
int?main(void)?
{char?buf[BUFSIZ];setbuf(stdin,buf);
}?//buf的生存期結束,未定義行為
int main()
{ char buff[256]; int a = 10, b = 20; FILE *pf = fopen("yhp.txt","w"); setbuf(pf,buff); fprintf(pf,"a = %d b = %d \n",a,b); fclose(pf); pf = NULL; return 0;
}
函數原型
int?setvbuf(?FILE?*stream,?char?*buffer,?int?mode,?size_t?size?);
功能
以?mode所指示值更改給定文件流?stream的緩沖模式。
緩沖策略
??若?buffer為空指針??:重設內部緩沖區大小為?size。
??若?buffer非空指針??:指示流使用始于?buffer而大小為?size的用戶提供緩沖區。
必須在?buffer所指向的數組的生存期結束前(用?fclose關閉流)。
成功調用?setvbuf后,數組內容不確定,任何使用它的嘗試是未定義行為。
參數
??stream??:要設置緩沖的文件流。
??buffer??:指向要使用的流緩沖區的指針;若僅更改大小和模式則為空指針。
??mode??:使用的緩沖模式(可選值):
_IOFBF:??全緩沖??(當緩沖區為空時從流讀入數據;緩沖區滿時向流寫入數據)。
_IOLBF:??行緩沖??(每次從流中讀入一行數據或向流中寫入一行數據)。
_IONBF:??無緩沖??(直接從流讀入或寫入數據;緩沖設置無效)。
??size??:緩沖區的大小。
返回值
成功時返回?0。失敗時返回??非零值??。
注意
此函數僅可在已將?stream關聯到打開的文件后使用(在任何其他操作前,除對?setbuf/setvbuf的失敗調用)。實際緩沖區大小可能向下取整(如到?2?的倍數、頁面大小的倍數等);并非所有?size字節都用于緩沖。??行緩沖限制??:多數實現中,行緩沖(_IOLBF)僅對終端輸入流可用。
??一個常見錯誤是設置stdin或stdout的緩沖區為生存期在程序終止前結束的數組:
// 錯誤示例:局部數組作緩沖區導致未定義行為
int main(void) {char buf[BUFSIZ];setvbuf(stdin, buf, _IOFBF, BUFSIZ);
} // buf 的生存期結束,后續操作未定義
????????期待默認緩沖區大小BUFSIZ為實現上文件I/O的最高效緩沖區大小,但POSIX fstat經常提供更好的估計。