1. 為什么使用文件
不使用文件,我們所寫的程序存在電腦內存中,程序結束,內存回收,數據就丟失了。再次運行程序也是看不到上次運行時的數據的,如果想要將數據進行持久化保存,就需要使用文件。
2. 文件分類(包含關系)
2.1 文件名
一個文件要有一個唯一的識別標識,以便用戶識別和引用。
文件名包含三部分:文件路徑+文件名主干+文件后綴。
為方便起見,文件標識常被稱為文件名。
磁盤(硬盤)上的內容稱為文件。在程序設計中,我們談到的文件有兩種,程序文件和數據文件。程序文件有數據文件又有文本文件和二進制文件之分。
2.2?各類文件定義
2.2.1 程序文件
程序文件是計算機程序相關文件的統稱,包括源程序文件(后綴為.c .cpp其他編程語言還有.java .py)、目標文件(windows環境后綴為.obj)、可執行文件(后綴為.exe)等等。
2.2.2 數據文件
文件的內容不一定是程序,而是程序運行時讀寫的數據,比如程序運行需要從中讀取數據的文件,或者輸出內容的文件。在以前各章所處理數據的輸入輸出都是以終端為對象的,即從終端的鍵盤輸入數據,運行結果顯示到顯示器上。其實有時候我們會把信息輸出到磁盤上,當需要的時候再從磁盤上把數據讀取到內存中使用,這里處理的就是磁盤上文件,也就是數據文件。根據數據的組織形式,數據文件被稱為文本文件或者二進制文件。
二進制文件:數據在內存中以二進制的形式存儲,如果不加轉換的輸出到外存的文件中,就是二進制文件。
文本文件;如果要求在外存上以ASCI碼的形式存儲,則需要在存儲前轉換。以ASCI字符的形式存儲的文件就是文本文件。
一個數據在文件中是怎么存儲的呢?
字符一律以ASCII形式存儲,數值型數據既可以用ASCII形式存儲,也可以使用二進制形式存儲。如有整數10000,如果以ASCII碼的形式輸出到磁盤,則磁盤中占用5個字節(每個字符一個字節),而二進制形式輸出,則在磁盤上只占4個字節。
3. 文件如何打開和關閉的?
3.1 流和標準流
3.1.1 流的定義
我們程序的數據需要輸出到各種外部設備,也需要從外部設備獲取數據,不同的外部設備的輸入輸出操作各不相同,為了方便程序員對各種設備進行方便的操作,我們抽象出了流的概念,我們可以把流想象成流淌著字符的河。C程序針對文件、畫面、鍵盤等的數據輸入輸出操作都是通過流操作的。一般情況下,我們要想向流里寫數據,或者從流中讀取數據,都是要打開流,然后操作。
3.1.2 標準流
為什么我們從鍵盤輸入數據,向屏幕上輸出數據,并沒有打開流呢?
那是因為C語言程序在啟動的時候,默認打開了3個流:
stdin:標準輸入流,在大多數的環境中從鍵盤輸入,scanf函數就是從標準輸入流中讀取數據
stdout:標準輸出流,大多數的環境中輸出至顯示器界面,printf函數就是將信息輸出到標準輸出流中
stderr:標準錯誤流,大多數環境中輸出到顯示器界面
這是默認打開了這三個流,我們使用scanf、printf等函數就可以直接進行輸入輸出操作的。stdin、stdout、stderf三個流的類型是:"FILE*,通常稱為文件指針。C語言中,就是通過:FILE*的文件指針來維護流的各種操作的。
3.2 文件指針
緩沖文件系統中,關鍵的概念是“文件類型指針”,簡稱“文件指針”。每個被使用的文件都在內存中開辟了一個相應的文件信息區,用來存放文件的相關信息(如文件的名字,文件狀態及文件當前的位置等)。這些信息是保存在一個結構體變量中的。該結構體類型是由系統聲明的,重命名為FILE。不同編譯器中FILE類型不完全相同,但大同小異。
每打開一個文件,系統會根據文件情況自動創建一個FILE結構的變量,自動填充其中的信息,并返回一個FILE*的指針指向該變量,使用者不必關心結構體內容細節。我們可以通過FILE*的指針找到對應文件的文件信息區(FILE類型的結構體),通過文件信息區就可以訪問該文件。
3.3 文件的打開和關閉(fopen、fclose)
文件在讀寫之前應該先打開文件,使用結束之后應該關閉文件。打開文件就會自動返回一個FILE*類型的指針,建立了指針和文件的關系。ANSIC規定使用fopen函數打開文件,fclose關閉文件。
//打開文件
FILE * fopen ( const char * filename, const char * mode );//關閉文件
int fclose ( FILE * stream );
fopen第一個參數是一個字符串,內容是:文件名,第二個參數mode表示文件打開模式。
? ? ? ? ? ? ?
4. 文件順序讀寫
4.1 各類輸入輸出函數圖解
函數 | 功能 | 適用于 |
fgetc | 字符輸入函數 | 所以輸入流 |
fputc | 字符輸出函數 | 所以輸出流 |
fgets | 文本行輸入函數 | 所以輸入流 |
fputs | 文本行輸出函數 | 所以輸出流 |
fscanf | 格式化輸入函數 | 所以輸入流 |
fprintf | 格式化輸出函數 | 所以輸出流 |
fread | 二進制輸入 | 文件輸入流 |
fwrite | 二進制輸出 | 文件輸入流 |
4.2 各類輸入輸出函數介紹
注:stream指向輸入(出)流的FILE對象的指針
4.2.1 fgetc
int fgetc ( FILE * stream );
?從流中獲取當前字符,并推進位置指示器+1
如果調用時流處于文件末尾或發生錯誤,返回EOF并設置流的末尾(錯誤)指示器
4.2.2?fputc
int fputc ( int character, FILE * stream );
?將字符寫入流,并推進位置指示器+1
character存放字符的整型提升,在寫入時轉變為無符號字符
成功寫入返回寫入的字符,失敗返回EOF并設置錯誤指示器(ferror)
4.2.3 fgets
char * fgets ( char * str, int num, FILE * stream );
?從流中讀取字符并將其存儲到str中,直到(num-1)個字符或者到達換行符或者到文件末尾,使fgets停止讀取,此字符(換行符或其他字符)也會作為有效字符復制到str中,同時最后一個字符放終止空字符'\0'
成功時返回str,發生錯誤,返回NULL,設置錯誤指示器(ferror)
4.2.4 fputs
int fputs ( const char * str, FILE * stream );
將str中的字符串寫入流 ,從指定的地址開始(字符串首元素地址),復制直到終止空字符'\0'為止,終止空字符不會復制到流中
成功時返回一個非負值,失敗返回EOF并設置錯誤指示器(ferror)
4.2.5 fscanf
int fscanf ( FILE * stream, const char * format, ...(附加參數) );
與scanf函數類似,scanf是從標準輸入流(stdin)中讀取格式化數據而fscanf是從所以輸入流(標準和文件輸入流)中讀取格式化數據,并根據參數格式將他們存儲到附件參數指向的位置
成功后,函數返回成功填充的參數列表的項數,讀取發生錯誤或讀到文件末尾,返回EOF
4.2.6 fprintf
int fprintf ( FILE * stream, const char * format, ... );
與printf函數類似,printf是向標準輸出流(stdout)中輸出格式化數據而fprintf是向所有輸出流(標準和文件輸出流)中輸出格式化數據
成功后,返回寫入的字符總數,發生錯誤,返回負數
附:sscanf函數是從字符串中讀取格式化數據。
sprintf函數是將格式化數據輸出到一個字符串中。
4.2.7 fread
fread和fwrite中參數:ptr是待寫入(讀取)的數組的指針;size是單個元素的大小;count是元素的個數。
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
從文件輸入流中讀取count個大小為size的元素放在ptr數組中
返回成功寫入的元素總數,如果寫入元素總數與count不同,會寫入錯誤阻止函數完成
4.2.8 fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
?將ptr數組中count個大小為size的元素輸出到文件輸出流中
返回成功寫入的元素總數,如果寫入元素總數與count不同,會寫入錯誤阻止函數完成
5. 文件隨機讀寫
5.1 fseek
int fseek ( FILE * stream, long int offset, int origin );
offset是從origin位置偏移的字節數;
origin是源位置,有三種選擇:
SEEK_SET:文件起始位置
SEEK_CUR:文件當前位置
SEEK_END:文件末尾
重新定義流位置指示器,如果成功返回0,否則返回非0值
5.2 ftell
long int ftell ( FILE * stream );
?返回位置指示器相對于起始位置的偏移量,失敗返回-1L(長整型的-1)并設置errno
5.3 rewind
void rewind ( FILE * stream );
?讓位置指示器回到文件起始位置,無返回值
7. 文件讀取結束判斷
7.1 feof作用
feof作用是當文件讀取結束時,判斷文件讀取結束是不是因為遇到文件末尾(也就是檢測有沒有設置末尾指示器)。
不能用feof的返回值直接判斷函數文件讀取是否結束。
附:ferror可以判斷文件讀取結束是不是因為遇到錯誤(也就是檢查有沒有設置錯誤指示器)。
7.2 如何判斷文件讀取是否結束
文本文件可以判斷返回值是否為EOF(fgetc)或為NULL(fgets)
二進制文件可以返回值是否小于實際要讀個數
?示例:文本文件
#include <stdio.h>
#include <stdlib.h>int main(void)
{int c = 0; // 注意:int,?char,要求處理EOFFILE* fp = fopen("test.txt", "r");if(fp == NULL) {perror("File opening failed");return;}//fgetc 當讀取失敗的時候或者遇到?件結束的時候,都會返回EOFwhile ((c = fgetc(fp)) != EOF) { putchar(c);}//判斷是什么原因結束的//ferror遇到錯誤會返回一個非0值//feof遇到文件末尾會返回一個非0值if (ferror(fp))puts("Error when reading");else if (feof(fp))puts("End of file reached successfully");fclose(fp);
}
8. 文件緩沖區
ANSIC 標準采用“緩沖文件系統”處理的數據文件的,所謂緩沖文件系統是指系統自動地在內存中為程序中每一個正在使用的文件開辟一塊“文件緩沖區”。從內存向磁盤輸出數據會先送到內存中的緩沖區,裝滿緩沖區后才一起送到磁盤上。如果從磁盤向計算機讀入數據,則從磁盤文件中讀取數據輸入到內存緩沖區(充滿緩沖區),然后再從緩沖區逐個地將數據送到程序數據區(程序變量等)。緩沖區的大小根據C編譯系統決定的。文件緩沖區的存在一定程度上讓操作系統更高效。
因為有緩沖區的存在,C語言在操作文件的時候,需要做刷新緩沖區或者在文件操作結束的時候關閉文件(關閉文件也是一次文件緩沖區的刷新)。如果不做,可能導致讀寫的問題。
fflush函數可以刷新文件緩沖區。