摘要:二什么是文件磁盤上的文件就是文件。文件指針變量定義是一個指向類型數據的指針變量。表示向何種流中輸出,可以是標準輸出流,也可以是文件流。文件結構體指針,將要讀取的文件流。
提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔
我們前面學習結構體時,寫了通訊錄的程序,當通訊錄運行起來時,可以給通訊錄增加、刪除數據,此時的數據是存放在內存中的,當程序退出時,通訊錄的數據就被銷毀了,下次運行通訊錄時,數據又得重新錄入,如果使用這樣的通訊錄就會非常的坐牢。
所以這些通訊錄數據我們僅僅放在內存里是不行的,大家都知道,我們電腦的C盤、D盤、E盤等等里面存放的文件,你不進行刪除,它就一直在那里,那我們可以試著把通訊錄的數據存入磁盤里,來實現數據的持久性。
磁盤上的文件就是文件。
但是在程序設計中,我們一般談的文件有兩種:
1.程序文件
2.數據文件
(以文件功能來劃分)
包括源程序文件(后綴為.c),目標文件(windows環境后綴為.obj),可執行程序(windows環境后綴為.exe)。
文件的內容不一定是程序,而是程序運行時讀寫的數據,比如程序運行需要從中讀取數據的文件,或者輸出內容的文件。
本文著重討論數據文件
在以前各章所處理的數據的輸入輸出都是以終端為對象的,即從終端的鍵盤輸入數據,運行的結果顯示到顯示器上,其實有時候我們會把信息輸出到鍵盤上,當需要的時候再從鍵盤上把數據讀取到內存中使用,這里處理的就是磁盤上的文件。
下圖是數據文件與程序文件的交互簡圖
一個文件要有一個唯一的文件標識,便于用戶的識別與引用。
文件名包含3個部分:文件路徑+文件名主干+文件后綴
栗子:E:/c-language-notes/test.txt
這里的E:/c-language-notes/叫作文件路徑,是在E盤c-language-notes這個路徑底下
test叫作文件主干
.txt叫做文件后綴
為了方便起見,文件標識常被稱為文件名
緩沖文件系統中,關鍵的概念是“文件類型指針”,簡稱文件指針
每個被使用的文件都在內存中開辟了一個相應的文件信息區,用來存放文件的相關信息(如文件的名字,文件狀態及文件當前的位置等)。這些信息是保存在一個結構體變量中的。該結構體類型是由系統聲明的,結構體類型名為FILE(下文會提到)
如上圖,當你想要操作一個數據文件時,你不可避免地會經歷3個操作:打開文件、讀/寫文件、關閉文件。只要你打開文件,系統會自動生成一個叫做文件信息區的東西,也就是會創建一個 FILE 類型結構體變量,上圖以f作為示例,實際也可能是其他的,那么創建完成后,f就會和data.txt文件強關聯了,f會記錄data文件名、文件有多大、文件在哪個位置、文件的狀態是怎樣的。。。
vs2013的編譯器環境提供的stdio.h的頭文件中有以下的文件類型聲明:
struct _iobuf{ char*_ptr; int _cnt; char*_base; int _flag; int _file; int _charbuf; int _bufsiz; char*_tmpfname;};typedef struct _ibuf FILE//把上述結構體重命名為FILE
不同編譯器的FILE類型包含的內容不一定完全相同,但基本都是大同小異,每當打開一個文件時,系統會根據文件的情況自動創建一個FILE結果的變量,并填充其中的信息,我們使用者不必過度關心細節,按周總理說的“求同存異”即可。
一般都是通過一個FILE類型的指針來維護FILE結構的變量,這樣使用起來更加方便。
FILE*pf;//文件指針變量
定義pf是一個指向FILE類型數據的指針變量。可以使pf指向某個文件的文件信息區(是一個結構體變量)。通過該文件信息區中的信息就可以訪問該文件。也就是說,通過文件指針變量能找到與它關聯的文件如下圖所示:
pf是指向文件信息區的,而文件信息區又可以確切的找到與它關聯的文件,這樣你就可以通過pf找到所需文件并進行相關操作。
文件在讀寫之前應該先打開文件,在使用后應該關閉文件。
在編寫程序時,打開文件的同時,都會返回一個FILE*的指針變量指向該文件,也相對于建立了指針和文件的聯系。
ANSIC規定使用fopen函數來打開文件,fclose來關閉文件
FILE *fopen(const char* filename, const char* moede);//打開文件//fopen第一個參數是文件名,第二個參數是打開模式//比如你傳一個test.txt到一個參數里,傳的其實是首字母t的地址//打開模式是這樣的,你是想給這個文件寫點東西還是想讀取這個文件的一些東西int fclose(FILE*stream);//關閉文件
關于打開模式如下圖,比如你打開模式是r,那你的打開方式就是讀,如果你的文件不存在或者沒有被找到,那fopen函數就會調用失敗
(圖片來自比特就業課,這里只舉r一個打開方式,其他打開方式讀者可自行對照上表)
fopen打開模式打開data.txt文件會返回一個FILE*的指針,該指針是指向與data.txt文件相關聯的文件信息區的起始地址,如果打開失敗會返回空指針。
fclose關閉文件相對fopen就簡單很多了,你要關閉哪個文件,我們直接傳那個文件關聯的文件信息區的指針即可,也就是上圖的pf
打開和關閉實際操作代碼示例如下:
比如我現在要打開E:/c-language-notes/test.21.10.9路徑下的data.txt文件
int main(){ //打開文件 //fopen函數 FILE*pf=fopen("E://c-language-notes//test.21.10.9//data.txt", "r"); //這里的"/"可能會與后面的字母構成轉義字符,我們用/對/進行轉義一下,讓/單純是一個/ //fopen函數會返回一個FILE*的指針,打開失敗返回空指針 if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //讀文件。。。 //關閉文件 fclose(pf); pf = NULL; return 0;}
需要注意的是,fclose關閉文件是不會把pf置為空指針的,我們需要手動操作置為空指針
(圖片來自比特就業課)
所有輸入流包括:istream 類連續文本模式輸入使用、ifstream磁盤文件輸入、istringstream 類從內存字符串的輸入。
fgetc和fputc函數分別是讀入一個字符和輸出一個字符
如上圖,我們寫一個程序時,會產生一些數據,數據會存放在內存中,如果你想把數據寫入(輸出)到文件里,或者你想把文件里的信息讀到內存中,叫讀(輸入操作)。我們用輸入/輸出操作就用的是fgetc與fputc
fputc函數示例:
// int fputc(int c, FILE *stream);函數聲明int main(){ FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "w");//以“只寫w”的模式打開文件 if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //寫文件 fputc("b", pf); fputc("i", pf); fputc("t", pf); fclose(pf); pf = NULL; return 0;}
運行完上述代碼后,相關文件自動出現fputc函數寫入的三個字
fgetc函數示例:
//int fgetc(FILE *filename);函數聲明int main(){ FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "r");//以“只讀r”的模式打開文件 if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //讀文件 int ch1=fgetc(pf); printf("%c/n", ch1); int ch2 = fgetc(pf); printf("%c/n", ch2); int ch3 = fgetc(pf); printf("%c/n", ch3); fclose(pf); pf = NULL; return 0;}
假設我們現在相關文件里有3個字母abc
我們運行上述程序,程序自動從文件里獲取三個字母abc
我們再來看一看文本行輸入/輸出函數
fputs函數示例:
//int fputs(const char *s, FILE *stream);函數聲明//s 代表要輸出的字符串的首地址,可以是字符數組名或字符指針變量名。//stream 表示向何種流中輸出,可以是標準輸出流 stdout,也可以是文件流。標準輸出流即屏幕輸出,printf 其實也是向標準輸出流中輸出的。int main(){ FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "w");//以“只寫W”的模式打開文件 if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //寫文件(寫一行) fputs("hello world/n", pf); fputs("hello bit/n", pf); fclose(pf); pf = NULL; return 0;}
和fputc差不多,fputc是寫一個字母,fputs是寫一行,運行完上述程序,相關文件夾出現hello world 和hello bit
fgets函數示例:
//char *fgets(char *buf, int bufsize, FILE *stream);//*buf: 字符型指針,指向用來存儲所得數據的地址。//bufsize: 整型數據,指明存儲數據的大小。//*stream: 文件結構體指針,將要讀取的文件流。int main(){ FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "r");//以“只讀r”的模式打開文件 if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //讀文件(讀一行) char arr[20] = { 0 }; fgets(arr,5,pf); printf("%s/n", arr);//打印hell,說是最多讀5個其實是讀到第四個,然后第五個補/0 fclose(pf); pf = NULL; return 0;}
我們原先文件里有hello world 和hello bit,我們讀一行中的5個字符到arr里
說是打印5個字符,其實是打印4個字符,第五個字符是自動補/0
格式化輸入輸出也就是按某種格式寫入或讀取
fprintf函數示例:
struct S{ int n; double d;};int main(){ //int fprintf(FILE *filename, const char *string, . . . .);函數聲明 //看起來函數聲明有點麻煩,我們再來看一下常見的printf函數聲明 //int printf( const char *format, … ); //對比一下很容易發現也就是比printf函數多一個指針參數而已,其他的都按printf來即可 struct S s = { 100,3.14 }; FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "w");//以“只寫w”的模式打開文件 if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //寫文件 fprintf(pf,"%d %lf", s.n, s.d); fclose(pf); pf = NULL; return 0;}
fprintf就是printf函數多一個pf指針,其他的和printf都是一樣的,上述代碼運行一下,相關文件出現100,和3.140000(浮點型默認6位小數)
fscanf函數示例:
struct S{ int n; double d;};int main(){ //int fscanf(FILE *stream, char *format,[argument...]);函數聲明 //和fprintf一樣,就是scanf函數前面多一個pf指針參數 struct S s = { 0}; FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "r");//以“只讀r”的模式打開文件 if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //讀文件 fscanf(pf,"%d %lf", &(s.n), &(s.d)); printf("%d %lf/n", s.n, s.d); fclose(pf); pf = NULL; return 0;}
原先文件里有100 3.140000,運行程序后讀取出這兩個數
fwrite函數聲明如下
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
ptr-- 這是指向要被寫入的元素數組的指針。
size-- 這是要被寫入的每個元素的大小,以字節為單位。
nmemb-- 這是元素的個數,每個元素的大小為 size 字節。
stream-- 這是指向 FILE 對象的指針,該 FILE 對象指定了一個輸出流。
fwrite函數示例:
//size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)//ptr-- 這是指向要被寫入的元素數組的指針。//size-- 這是要被寫入的每個元素的大小,以字節為單位。//nmemb-- 這是元素的個數,每個元素的大小為 size 字節。//stream-- 這是指向 FILE 對象的指針,該 FILE 對象指定了一個輸出流。struct S{ int n; double d; char name[20];};int main(){ struct S s = { 100,3.14,"zhangsan" }; FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "wb");//以“只寫wb”的模式打開文件 if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //寫文件-二進制方式寫 fwrite(&s, sizeof(s), 1, pf); //關閉文件 fclose(pf); pf = NULL; return 0;}
這里要注意的是,我們以二進制fwrite寫入,產生的是二進制文件,所以我們以二進制的wb模式打開,文件里的內容如下
因為是二進制文件,我們直接看是看不懂的,但是我們知道可以二進制讀,也就是下面的fread函數
函數聲明:size_t fread( void *buffer, size_t size, size_t count, FILE *stream )
buffer 是讀取的數據存放的內存的指針(可以是數組,也可以是新開辟的空間,buffer就是一個索引)
size 是每次讀取的字節數
count 是讀取次數
strean 是要讀取的文件的指針
fread函數示例:
struct S{ int n; double d; char name[20];};int main(){ struct S s = {0}; FILE*pf = fopen("E://c-language-notes//test.21.10.9//data.txt", "rb");//以“只寫wb”的模式打開文件 //wb if (pf == NULL) { perror("fopen");//perror函數顯示錯誤信息 return -1; } //讀文件-二進制方式讀 fread(&s, sizeof(struct S), 1, pf); //打印 printf("%d %lf %s/n", s.n, s.d, s.name); fclose(pf); pf = NULL; return 0;}
我們現在文件里是這些東西,我們運行程序讀一下
可以讀出之前寫入的東西
函數聲明:int fseek(FILE *stream, long offset, int fromwhere);
函數設置文件指針stream的位置。如果執行成功,stream將指向以fromwhere為基準,偏移offset個字節的位置。如果執行失敗(比如offset超過文件自身大小),則不改變stream指向的位置。執行成功返回0,否則返回其他數。
fromwhere有三個值:
SEEK_CUR 文件指針當前位置
SEEK_END 文件的末尾
SEEK_SET 文件的起始
比如a的地址就是文件的起始,f就是文件的末尾,假如我現在fgetc讀了一個,指針從a往后移動一位,指針指向b,那b所在位置就是SEEK_CUR,也就是文件指針當前位置
注意:隨機讀寫不是亂讀,而是想讀哪里讀哪里,比如我想讀文件第三個字母,我就可以直接用隨機讀寫讀取第三個字母。我們仍以上面這個文本文檔為例:
現在我要讀第三個,按照常規的順序讀寫,是有一個指針指向a,然后每讀一個,指針往后移一位,讀第三個要移動2次。fseek函數就是可以快速根據指針的位置和偏移量來定位文件指針,大白話講就是fseek函數可以快速找到第三個字母的指針。
#define _CRT_SECURE_NO_WARNINGS#include //fseek函數int main(){ //1.打開文件 FILE*pf=fopen("E://c-language-notes//test.21.10.11//data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //2.讀文件(隨機讀寫) //讀c fseek(pf, 2, SEEK_SET);//現在我要讀c,剛開始cur和set都是起始位置,用cur也可 int ch = fgetc(pf); printf("%c/n", ch); //讀b fseek(pf, -2, SEEK_CUR); ch = fgetc(pf); printf("%c/n", ch); //3.關閉文件 return 0;}
關于讀b,因為我們讀c之后,指針會自動往后移一位,所以cur是指向d的,b關于d的偏移量是-2,所以我們用fseek(pf, -2, SEEK_CUR);讀取
函數聲明:long int ftell(FILE*filenname);
返回文件指針相對起始位置的偏移量
#include //fseek函數int main(){ //1.打開文件 FILE*pf=fopen("E://c-language-notes//test.21.10.11//data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //2.讀文件(隨機讀寫) //讀c fseek(pf, 2, SEEK_SET);//現在我要讀c,剛開始cur和set都是起始位置,用cur也可 int ch = fgetc(pf); printf("%c/n", ch); //讀b fseek(pf, -2, SEEK_CUR); ch = fgetc(pf); printf("%c/n", ch); int a=ftell(pf);//b讀完之后指針自動往后一位到c,c相對a偏移量為2 printf("%d", a);//打印2 //3.關閉文件 return 0;}
繼上一段代碼,我們讀完b之后指針自動往后移動一位指向c,c相對起始位置a的偏移量為2,所以ftell會返回2
函數聲明:void rewind(FILE *stream);
不管pf現在在什么位置,傳過去,pf重新指向文件起始位置
根據數據的組織形式,數據文件被稱為文本文件或者二進制文件
數據在內存中以二進制的形式存儲,如果不加轉換的輸出到外存,就是二進制文件
如果要求在外存上以ASCII碼的形式存儲,則需要在存儲前進行轉換。以ASCII碼的形式存儲的文件就是文本文件
一個數據在內存中是怎么存儲的呢?
字符一律以ASCII碼的形式進行存儲,數值型的數據即可用ASCII碼的形式存儲,也可以用二進制形式存儲,如下,我們進行10000的存儲
(圖片來自比特就業課)
如果我們按ASCII形式存儲,把10000共5位,我們把每位上的數字看做一個字符,共要5個字節
如果我們直接按二進制形式進行存儲,二進制的10000,是
00000000 00000000 00100111 00010000共需占4個字節(1個整形)
牢記:在文件讀取過程中,不能使用feof函數的返回值直接來判斷文件的結束與否,而是應用于當文件讀取結束的時候,判斷是讀取失敗結束,還是遇到文件尾結束
1.文本文件讀取是否結束,判斷返回值是否為EOF(fgetc),或者NULL(fgets)
*fgetc判斷是否為EOF
fgetc讀到一個字符返回int,如果文件結束沒讀到或者遇到錯誤,返回EOF
原文件里有abcdef5個字符,現在我們怎么利用fgetc進行打印,并判斷是否結束呢?
代碼如下:
#include int main(){ //打開文件 FILE*pf=fopen("E://c-language-notes//test.21.10.11//data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/122562.html
摘要:語言在設計中考慮了函數的高效性和易用性兩個原則。在語言中,最常見的當屬函數了。以上就是一個函數,它被稱為語言的入口函數,或者主函數。例如和都是函數名。形式參數當函數調用完成之后就自動銷毀了。 ...
摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術專家我看過哪些技術類書籍。 大家好,我是...
摘要:時間永遠都過得那么快,一晃從年注冊,到現在已經過去了年那些被我藏在收藏夾吃灰的文章,已經太多了,是時候把他們整理一下了。那是因為收藏夾太亂,橡皮擦給設置私密了,不收拾不好看呀。 ...
摘要:層確保數據一致性和可靠性。保證集群的相關組件在同一時刻能夠達成一致,相當于集群的領導層,負責收集更新和發布集群信息。元數據服務器,跟蹤文件層次結構并存儲只供使用的元數據。啟迪云-高級開發工程師 ?侯玉彬前言上一次簡單的介紹Ceph的過去和未來的發展。這一節將詳細介紹Ceph的構件以及組件。Ceph存儲架構Ceph 存儲集群由幾個不同的daemon組成,每個daemon負責Ceph 的一個獨特...
閱讀 3209·2023-04-26 02:27
閱讀 2138·2021-11-22 14:44
閱讀 4082·2021-10-22 09:54
閱讀 3195·2021-10-14 09:43
閱讀 748·2021-09-23 11:53
閱讀 12675·2021-09-22 15:33
閱讀 2704·2019-08-30 15:54
閱讀 2681·2019-08-30 14:04