C語言文件操作


對計算機來說,一切皆數據,超女的信息是數據、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)
作者:碼農有道

如果文章有錯別字,或者內容有錯誤,或其他的建議和意見,請您留言指正,非常感謝!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM