對計算機來說,一切皆數據,超女的信息是數據、C語言源代碼文件是數據、編譯后的可執行程序也是數據,數據的存放方式有很多種,如內存、文件、數據庫等,文件是極其重要的一種。
根據文件中數據組織形式的不同,可以把文件分為文本文件和二進制文件,C語言源代碼是文本文件,編譯后的可執行程序是二進制文件。
一、文本數據和二進制
1、文本數據
文本數據由字符串組成,存放了每個字符的 ASCII碼值,每個字符占一個字節,每個字節存放一個字符。
例如數字 123,如果用文本格式存放,數據內容是'1'、'2'、'3'三個字符,占三個字節,如下表所示。
字符 | '1' | '2' | '3' |
---|---|---|---|
ASCII(十進制) | 49 | 50 | 51 |
ASCII(二進制) | 00110001 | 00110010 | 00110011 |
2、二進制數據
二進制數據是字節序列,數字123的二進制表示是01111011,如果用二進制格式形式存儲,字符、短整型、短整型、長整型都可以存儲123,存儲方式分別如下:
1)字符型一個字節
01111011
2)短整型2個字節
00000000 01111011
3)整型4個字節
00000000 00000000 00000000 01111011
4)長整型8個字節
00000000 00000000 00000000 00000000 00000000 00000000 00000000 01111011
3、文本文件和二進制文件
按文本格式存放數據的文件稱為文本文件或ASCII文件,文件可以用vi和記事本打開,看到的都是ASCII字符。
按二進制格式存放數據的文件稱為二進制文件,如果用vi打開二進制文件,看到的是亂碼,沒有意義。
二、打開文件
C 語言對文件進行操作必須先“打開”文件,操作(讀和寫)完成后,再“關閉”文件。
1、文件指針
打開文件的時候,C語言為打開的文件分配一個文件信息區,該信息區中包含文件描述信息、緩沖區位置、緩沖區大小、文件讀寫到的位置等基本信息,這些信息保存在一個結構體類型變量中struct_IO_FILE),這個結構體有一個別名FILE(typedef struct _IO_FILE FILE),FILE結構體和對文件操作的庫函數在 stdio.h 頭文件中聲明的。
打開文件的時候,調用fopen函數時會動態分配一個FILE結構體,並把FILE結構體的地址作為函數的返回值,程序中用FILE結構體指針存放這個地址。調用關閉文件的函數fclose時候,除了關閉文件,還會釋放文件指針占用的內存空間。
FILE結構體指針習慣稱為文件指針。
2、打開文件
我們可以使用 C語言提供的庫函數fopen來創建一個新的文件或者打開一個已存的文件,調用fopen函數成功后,返回一個文件指針( FILE *),函數的原型如下:
FILE *fopen( const char * filename, const char * mode );
參數filename 是字符串,表示需要打開的文件名,可以包含目錄名,如果不包含路徑就表示程序運行的當前目錄。實際開發中,采用文件的全路徑。
參數mode也是字符串,表示打開文件的方式(模式),打開方式可以是下列值中的一個。
方式 | 含 義 | 說 明 |
---|---|---|
r | 只讀 | 文件必須存在,否則打開失敗。 |
w | 只寫 | 如果文件存在,則清除原文件內容;如果文件不存在,則新建文件。 |
a | 追加只寫 | 如果文件存在,則打開文件,如果文件不存在,則新建文件。 |
r+ | 讀寫 | 文件必須存在。在只讀 r 的基礎上加 '+' 表示增加可寫的功能。 |
w+ | 讀寫 | 在只寫w的方式上增加可讀的功能。 |
a+ | 讀寫 | 在追加只寫a的方式上增加可讀的功能。 |
英文單詞:read簡寫r、write簡寫w、append簡寫a。
注意了,不同教材中對文件打開的方式有不同的說法。
有的說打開文本文件的方式要用"rt"、"wt"、"at"、"rt+"、"wt+"、"at+","t"是text的簡寫,"t"可以省略不寫。
有的說打開二進制文件的方式要用"rb"、"wb"、"ab"、"rb+"、"wb+"、"ab+","b"是binary的簡寫。
准確的說,在Linux平台下,打開文本文件和二進制文件的方式沒有區別。
在windows平台下,如果以“文本”方式打開文件,當讀取文件的時候,系統會將所有的"/r/n"轉換成"/n";當寫入文件的時候,系統會將"/n"轉換成"/r/n"寫入, 如果以"二進制"方式打開文件,則讀和寫都不會進行這樣的轉換,真是羅嗦。
3、關閉文件
fclose庫函數用於關閉文件,函數的原型:
int fclose(FILE *fp);
fp為fopen函數返回的文件指針。
示例(book108.c)
/*
* 程序名:book108.c,此程序用於演示文件打開和關閉
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
int main()
{
FILE *fp=0; // 定義文件指針變量fp
// 以只讀的方式打開文件/home/wucz/demo/book1.c
if ( (fp=fopen("/home/wucz/demo/book1.c","r")) == 0 )
{
printf("打開文件/home/wucz/demo/book.c失敗。\n"); return -1;
}
/* 上代碼等同於以下代碼
fp=fopen("/oracle/c/book1.c","r");
if (fp==0)
{
printf("打開文件/home/wucz/demo/book.c失敗。\n"); return -1;
}
*/
/* 不信用這個代碼來測試
printf("fp=%p\n",(fp=fopen("/home/wucz/demo/book1.c","r")));
printf("fp=%p\n",fp);
*/
// 關閉文件
fclose(fp);
}
對初學者來說,以下代碼可能難以理解。
if ( (fp=fopen("/home/wucz/demo/book1.c","r")) == 0 )
其實(fp=fopen("/home/wucz/demo/book1.c","r"))
表達式的值就是fp,我在講if分支語句的時候就討論過了,估計大家都沒把它放在心上,我們可以用代碼來測試它。
如果還不理解,就這么抄吧,抄多了就熟了。
4、注意事項
1)調用fopen打開文件的時候,一定要判斷返回值,如果文件不存在、或沒有權限、或磁盤空間滿了,都有可能造成打開文件失敗。
2)文件指針是調用fopen的時候,系統動態分配了內存空間,函數返回或程序退出之前,必須用fclose關閉文件指針,釋放內存,否則后果嚴重。
3)如果文件指針是空的,用fclose關閉它相當於操作空指針,后果嚴重。
三、文本文件的讀寫
在實際開發中,文本文件以行的形式存放字符串,如C程序的源代碼,一段文字等,所以一般是按行寫入和讀取數據。
1、向文件中寫入數據
C語言向文件中寫入數據庫函數有fputc、fputs、fprintf,在實際開發中,fputc和fputs沒什么用,只介紹fprintf就可以了。fprintf函數的聲明如下:
int fprintf(FILE *fp, const char *format, ...);
fprintf函數的用法與printf相同,只是多了第一個參數文件指針,表示把數據輸出到文件。
程序員不必關心fprintf函數的返回值。
示例(book111.c)
/*
* 程序名:book111.c,此程序用於演示向文件中寫入文本數據
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
int main()
{
int ii=0;
FILE *fp=0; // 定義文件指針變量fp
// 以只寫的方式打開文件/tmp/test1.txt
if ( (fp=fopen("/tmp/test1.txt","w")) == 0)
{
printf("fopen(/tmp/test1.txt) failed.\n"); return -1;
}
for (ii=0;ii<3;ii++) // 往文件中寫入3行
{
fprintf(fp,"這是第%d條數數據。\n",ii+1);
}
// 關閉文件
fclose(fp);
}
編譯book111.c程序並執行,采用cat命令查看/tmp/test1.txt的內容,如下:
可以看到/tmp/test1.txt中有3行記錄,程序book111不管執行多少次,文件/tmp/test1.txt的記錄都是3行記錄,因為文件打開的方式是"w",每次打開文件的時候都會清空原文件中的記錄。
大家可以試一下把文件打開方式設置為"a",看看程序執行的效果。
2、從文件中讀取數據
C語言從文件中讀取數據的庫函數有fgetc、fgets、fscanf,在實際開發中,fgetc和fscanf沒什么用,只介紹fgets就可以了。fgets函數的原型如下:
char *fgets(char *buf, int size, FILE *fp);
fgets的功能是從文件中讀取一行。
參數buf是一個字符串,用於保存從文件中讀到的數據。
參數size是打算讀取內容的長度。
參數fp是待讀取文件的文件指針。
如果文件中將要讀取的這一行的內容的長度小於size,fgets函數就讀取一行,如果這一行的內容大於等於size,fgets函數就讀取size-1字節的內容。
調用fgets函數如果成功的讀取到內容,函數返回buf,如果讀取錯誤或文件已結束,返回空,即0。如果fgets返回空,可以認為是文件結束而不是發生了錯誤,因為發生錯誤的情況極少出現。
示例(book113.c)
/*
* 程序名:book113.c,此程序用於演示從文本文件中讀取數據
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp=0; // 定義文件指針變量fp
char strbuf[301]; // 存放從文件中讀取到的一行的內容
// 以只讀的方式打開文件/tmp/test1.txt
if ( (fp=fopen("/tmp/test1.txt","r")) == 0)
{
printf("fopen(/tmp/test1.txt) failed.\n"); return -1;
}
// 逐行讀取文件的內容,輸出到屏幕
while (1)
{
memset(strbuf,0,sizeof(strbuf));
if (fgets(strbuf,301,fp)==0) break;
printf("%s",strbuf);
}
// 關閉文件
fclose(fp);
}
運行效果
需要重點說明的是,在讀取到 size-1個字符之前如果出現了換行,或者讀到了文件末尾,則讀取結束。
不管 size 的值多大,fgets函只讀取一行數據,不能跨行。
在實際開發中,可以將 size 的值設置地足夠大,確保每次都能讀取到一行完整的數據。
四、二進制文件的讀寫
二進制文件沒有行的概念,沒有字符串的概念。
我們把內存中的數據結構直接寫入二進制文件,讀取的時候,也是從文件中讀取數據結構的大小一塊數據,直接保存到數據結構中。注意,這里所說的數據結構不只是結構體,是任意數據類型。
1、向文件中寫入數據
fwrite函數用來向文件中寫入數據塊,它的原型為:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
參數的說明:
ptr:為內存區塊的指針,存放了要寫入的數據的地址,它可以是數組、變量、結構體等。
size:固定填1。
nmemb:表示打算寫入數據的字節數。
fp:表示文件指針。
函數的返回值是本次成功寫入數據的字節數,一般情況下,程序員不必關心fwrite函數的返回值。
示例(book115.c)
/*
* 程序名:book115.c,此程序用於演示向文件中寫入二進制數據
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
struct st_girl
{
char name[50]; // 姓名
int age; // 年齡
int height; // 身高,單位:厘米cm
char sc[30]; // 身材,火辣;普通;飛機場。
char yz[30]; // 顏值,漂亮;一般;歪瓜裂棗。
};
int main()
{
struct st_girl stgirl; // 定義超女數據結構變量
FILE *fp=0; // 定義文件指針變量fp
// 以只寫的方式打開文件/tmp/test1.dat
if ( (fp=fopen("/tmp/test1.dat","w")) == 0)
{
printf("fopen(/tmp/test1.dat) failed.\n"); return -1;
}
strcpy(stgirl.name,"西施"); stgirl.age=18; stgirl.height=170;
strcpy(stgirl.sc,"火辣"); strcpy(stgirl.yz,"漂亮");
fwrite(&stgirl,1,sizeof(stgirl),fp);
strcpy(stgirl.name,"芙蓉妹妹"); stgirl.age=38; stgirl.height=166;
strcpy(stgirl.sc,"膘肥體壯"); strcpy(stgirl.yz,"讓人終生不忘");
fwrite(&stgirl,1,sizeof(stgirl),fp);
// 關閉文件
fclose(fp);
}
編譯並運行程序,得到數據文件/tmp/test.dat,用vi命令打開文件,顯示如下:
可以看到很多亂碼,其實並不是文件的內容亂,而是vi無法識別文件的格式,把內容當成ASCII碼顯示,如果內容剛好是ASCII碼,就能正確顯示,如果不是ASCII碼(如年齡和身高是整數),就無法正常顯示了。
2、從文件中讀取數據
fread函數用來從文件中讀取數據塊,它的原型為:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);
ptr:用於存放從文件中讀取數據的變量地址,它可以是數組、變量、結構體等。
size:固定填1。
nmemb:表示打算讀取的數據的字節數。
fp:表示文件指針。
調用fread函數如果成功的讀取到內容,函數返回讀取到的內容的字節數,如果讀取錯誤或文件已結束,返回空,即0。如果fread返回空,可以認為是文件結束而不是發生了錯誤,因為發生錯誤的情況極少出現。
示例(book117.c)
/*
* 程序名:book117.c,此程序用於演示從文件中讀取二進制數據
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
struct st_girl
{
char name[50]; // 姓名
int age; // 年齡
int height; // 身高,單位:厘米cm
char sc[30]; // 身材,火辣;普通;飛機場。
char yz[30]; // 顏值,漂亮;一般;歪瓜裂棗。
};
int main()
{
struct st_girl stgirl; // 定義超女數據結構變量
FILE *fp=0; // 定義文件指針變量fp
// 以只讀的方式打開文件/tmp/test1.dat
if ( (fp=fopen("/tmp/test1.dat","rb")) == 0)
{
printf("fopen(/tmp/test1.dat) failed.\n"); return -1;
}
while (1)
{
// 從文件中讀取數據,存入超女數據結構變量中
if (fread(&stgirl,1,sizeof(struct st_girl),fp)==0) break;
// 顯示超女數據結構變量的值
printf("name=%s,age=%d,height=%d,sc=%s,yz=%s\n",\
stgirl.name,stgirl.age,stgirl.height,stgirl.sc,stgirl.yz);
}
// 關閉文件
fclose(fp);
}
運行效果
3、注意事項
1)我對fread和fwrite函數的size和nmemb以及它們的返回值的解釋是不准確的,這么做的原因是為了方便大家的學習,正確的解釋會把大家搞暈,等您功力提升之候,我們再討論它的准確含義。
2)fwrite和fread函數也可以寫入和讀取文本文件,但是沒有換行的概念,不管是換行符或其它的特殊字符,無區別對待。
3)一般來說,二進制文件有約定的數據格式,程序必須按約定的格式寫入/讀取數據,book115.c寫入的是超女結構體,book117.c就要用超女結構體來存放讀取到的數據。這道理就像圖片查看軟件無法打開音頻文件,音頻播放軟件也無法打開圖片文件,因為音頻文件和圖片文件的格式不同。
五、文件定位
在文件內部有一個位置指針,用來指向當前讀寫的位置。在文件打開時,如果打開方式是r和w,位置指針指向文件的第一個字節,如果打開方式是a,位置指針指向文件的尾部。每當從文件里讀n個字節或文件里寫入n個字節之后位置指針也會向后移動n個字節。
文件位置指針與C語言中的指針不是一回事。位置指針僅僅是一個標志,表示文件讀寫到的位置,不是變量的地址。文件每讀寫一次,位置指針就會移動一次,它不需要您在程序中定義和賦值,而是由系統自動設置,對程序員來說是隱藏的。
在實際開發中,偶爾需要移動位置指針,實現對指定位置數據的讀寫。我們把移動位置指針稱為文件定位。
C語言提供了ftell、rewind和fseek三個庫函數來實現文件定位功能。
1、ftell函數
ftell函數用來返回當前文件位置指針的值,這個值是當前位置相對於文件開始位置的字節數。它的聲明如下:
long ftell(FILE *fp);
2、rewind函數
rewind函數用來將位置指針移動到文件開頭,它的聲明如下:
void rewind ( FILE *fp );
3、fseek函數
fseek() 用來將位置指針移動到任意位置,它的聲明如下:
int fseek ( FILE *fp, long offset, int origin );
參數說明:
1)fp 為文件指針,也就是被移動的文件。
2)offset 為偏移量,也就是要移動的字節數。之所以為 long 類型,是希望移動的范圍更大,能處理的文件更大。offset 為正時,向后移動;offset 為負時,向前移動。
3)origin 為起始位置,也就是從何處開始計算偏移量。C語言規定的起始位置有三種,分別為:0-文件開頭;1-當前位置;2-文件末尾。
fseek(fp,100,0); // 從文件的開始位置計算,向后移動100字節。
fseek(fp,100,1); // 從文件的當前位置計算,向后移動100字節。
fseek(fp,-100,2); // 從文件的尾部位置計算,向前移動100字節。
4、注意事項
當offset是向文件尾方向偏移的時候,無論偏移量是否超出文件尾,fseek都是返回0,當偏移量沒有超出文件尾的時候,文件指針式指向正常的偏移地址的,當偏移量超出文件尾的時候,文件指針是指向文件尾的,不會返回偏移出錯-1值。
當offset是向文件頭方向偏移的時候,如果offset沒有超出文件頭,是正常偏移,文件指針指向正確的偏移地址,fseek返回值為0,當offset超出文件頭時,fseek返回出錯-1值,文件指針還是處於原來的位置。
六、文件緩沖區
在操作系統中,存在一個內存緩沖區,當調用fprintf、fwrite等函數往文件寫入數據的時候,數據並不會立即寫入磁盤文件,而是先寫入緩沖區,等緩沖區的數據滿了之后才寫入文件。還有一種情況就是程序調用了fclose時也會把緩沖區的數據寫入文件。
在實際開發中,如果程序員想把緩沖區的數據立即寫入文件,可以調用fflush庫函數,它的聲明如下:
int fflush(FILE *fp);
函數的參數只有一個,即文件指針,返回0成功,其它失敗,程序員一般不關心它的返回值。
七、標准輸入、標准輸出和標准錯誤
Linux操作系統為每個程序默認打開三個文件,即標准輸入stdin、標准輸出stdout和標准錯誤輸出stderr,其中0就是stdin,表示輸入流,指從鍵盤輸入,1代表stdout,2代表stderr,1,2默認是顯示器。
printf("Hello world.\n");
等同於
fprintf(stdout,"Hello world.\n");
這幾個文件指針沒什么用,讓大家了解一下就行。在實際開發中,我們一般會關閉這幾個文件指針。
八、課后作業
在實際開發中,文件操作極其重要,本章節的課后作業一定要認真完成。
1)編寫示例程序,從界面上輸入五名超女的數據,存放在struct st_girl結構體數組中,然后把結構體數組以二進制的方式寫入文件。
2)編寫示例程序,把上一題寫入的數據從二進制文件中讀取出來,存入struct st_girl結構體中,然后在界面上顯示出來。
3)編寫示例程序,從界面上輸入五名超女的數據,存放在struct st_girl結構體數組中,然后把結構體數組以xml字符串的方式寫入文本文件。文件內容的格式如下:
<name>西施</name><age>20</age><height>166</height><sc>一般</sc><yz>漂亮</yz>
<name>王昭君</name><age>18</age><height>160</height><sc>火辣</sc><yz>一般</yz>
<name>楊玉環</name><age>22</age><height>177</height><sc>一般</sc><yz>漂亮</yz>
<name>陳圓圓</name><age>26</age><height>159</height><sc>火辣</sc><yz>不行</yz>
4)編寫示例程序,把上一題寫入的數據從文本文件中讀取出來,並解析xml,存入struct st_girl結構體中,然后在界面上顯示出來。
5)編寫示例程序,實現文件復制的功能,文本文件用fget和fprintf讀寫?二進制文件用fread和fwrite讀寫?用fread和fwrite讀寫文本文件是什么效果?
6)編寫示例程序,測試文件定位函數ftell、rewind和fseek的使用。
7)編寫示例程序,測試文件緩沖函數fflush的使用。
九、版權聲明
C語言技術網原創文章,轉載請說明文章的來源、作者和原文的鏈接。
來源:C語言技術網(www.freecplus.net)
作者:碼農有道
如果文章有錯別字,或者內容有錯誤,或其他的建議和意見,請您留言指正,非常感謝!!!