C文件I/O超詳細教程

来源:http://www.cnblogs.com/mrblug/archive/2016/08/16/5774657.html
-Advertisement-
Play Games

本文主要參考了C Primer Plus (5th & 6th Edition) 您可以選擇本文的部分內容來讀,有些內容對於不熟悉MS-DOS的讀者可能過於晦澀難懂。 C語言文件基本知識 文件通常是在磁碟或固態硬碟上的一段已命名的存儲區。所有的文件內容都以二進位形式儲存。文件分為文本文件和二進位文件 ...


本文主要參考了C Primer Plus (5th & 6th Edition)

您可以選擇本文的部分內容來讀,有些內容對於不熟悉MS-DOS的讀者可能過於晦澀難懂。

C語言文件基本知識

  文件通常是在磁碟或固態硬碟上的一段已命名的存儲區。所有的文件內容都以二進位形式儲存。文件分為文本文件和二進位文件。

文件格式 定義 保存內容 示例
文本文件 文本文件就是最初使用二進位編碼字元(如ASCII或Unicode)表示文本的文件 文本內容 .TXT文件
二進位文件 二進位文件就是文件中的二進位值代表機器語言代碼或數值數據的文件 二進位內容 圖片文件,可執行文件

  C語言提供兩種途徑訪問文件:文本模式二進位模式。文本模式,顧名思義就是以文本形式訪問文件,該文件通常是文本文件。不同的系統,處理文本文件的方式不同。UNIX系統使用'\n'表示換行,而早期的MS-DOS使用'\r'和'\n'組合換行,用Ctrl + Z表示文件結尾。舊式的OS X Macintosh卻使用'\r'表示新的一行。這給程式員在不同系統中操作使用文件帶來了諸多不便。還好,C語言提供了轉換機制。例如在早期的MS-DOS中以文本模式打開某個文件file1.txt,它會自動把\r\n組合轉換成'\n',如果要往該文件寫入內容時又會把'\n'轉換成\r\n組合。當以二進位模式打開文件時,程式將訪問到文件的每一個位元組,程式員如果需要處理文本文件,就必鬚根據操作系統的不同而採取不同措施。

  文件的結尾標志著文件內容的結束,C語言用巨集EOF(End of File)表示文件的結尾,其值通常為-1。

  C程式自動打開3個文件,它們是:

文件 通常使用的預設設備 C程式中的表示法
標準輸入 鍵盤 stdin
標準輸出 顯示屏 stdout
標準錯誤輸出 顯示屏 stderr

  那麼如何更改這些文件的預設設備呢?我們可以通過重定向的方法。

MS-DOS重定向

  使用帶有MS-DOS6.22系統的電腦,假定才C:\user中有如下文件:

  

  文件defin.txt中有如下內容:

  

  insort.exe是一個插入排序的程式。

  重定向輸入為defin.txt,然後執行程式:

  

  發現其實重定向輸入就是把defin.txt的內容與sdtin流相關聯。其中'<'是重定向運算符。這樣的優勢是,如果遇到大量數據輸入時,可以先把數據輸入到文件,再重定向輸入運行程式,十分方便且易於檢查和修改錯誤。可是我們看到,程式運行後死機,原因是程式結尾處有代碼getch();,而stdin流已經與defin.txt相關聯,本例中defin.txt的末尾處沒有可以使getch();執行的內容,且鍵盤不再是stdin流的預設設備,所以,程式將永遠無法退出(單任務純DOS環境)。由此可見,重定向輸入也是有風險的。(同時提醒各位讀者,在設計單任務純DOS下運行的程式時,如無必要,不需要在程式末尾添加暫停指令。必須要有相應的退出指令

  同樣,我們也可以重定向輸出:

  

  這樣,所有的輸出都被髮送到defout.txt中。其中'>'是重定向輸出運算符,它把defout.txt和stdout相關聯。打開defout.txt我們可以看到:

  

  這樣,我們就把原本輸出到stdin預設設備---顯示屏上的內容都輸入到文件defout.txt中了。但是,你看不到輸出的內容,所以重定向輸出只在特殊情況下使用,如UNIX伺服器。

  我們還可以使用組合重定向

  

  其中,<defin.txt和>defout.txt可以調換位置。符號左右的空格如果系統允許可以省略。

  需要註意的是,我們不能同時重定向輸入或輸出多個文件或重定向輸入輸出與可執行文件關聯,使用重定向與文件關聯時要保證文件有結尾EOF(End Of File),使用>重定向輸出時,輸出的文件內的數據將被覆蓋。

文件打開fopen()和關閉fclose()函數

  使用重定向輸入輸出來操作文件不僅十分繁瑣而且還存在相應的危險。C語言為程式員提供了更加直觀方便的文件訪問機制,即程式員可以在程式中直接操作文件而不需要去做類似重定向這樣的“低級”行為。

  我們如果要在程式中操作一個文件,這個文件同該程式在相對的同目錄下(如果使用IDE就要到IDE預設路徑創建),就可以像這樣打開文件:

FILE * fp; //聲明文件指針
fp = fopen("file1.txt", "r"); //以只讀模式打開文件
if(fp == NULL)
    exit(EXIT_FAILURE); //如果打開失敗就退出

  fopen()函數在成功打開後返回指向該文件指針,否則返回NULL。接受兩個參數,第一個參數表示文件的名稱(包含文件名的字元串地址),第二個參數表示打開模式(這與“文本模式和二進位模式”是一樣的,只是細分了這些模式。),常用的打開模式如下:

  此外C11還增加了x模式,為了不增加讀者的負擔,本文不詳述。

  和動態記憶體分配一樣,我們需要檢測文件是否成功打開,而且在執行完相應操作後有必要及時關閉文件。關閉文件我們用fclose(),和free()一樣簡單,沒有返回值:

fclose(fp);//關閉文件
if(fp != 0) 
    exit(EXIT_FAILURE);//如果關閉失敗就退出

  但是,有時候,文件不能正常關閉,例如程式運行時硬碟有故障或被拔出。因此,我們十分有必要在關閉之後檢查一下狀態。

文本模式文件I/O

操作單字元---getc()和putc()函數

  C Primer Plus(Sixth Edition)對這兩個函數的解釋非常清楚:{

  getc()和putc()函數與getchar()和putchar()函數類似,不同的是,要告訴getc()和putc()函數使用哪一個文件。下麵這條語句的意思是“從標準輸入中獲取一個字元”:

ch = getchar();

  而下麵這條語句的意思是“從fp指定的文件中獲取一個字元”:

ch = getc(fp);

   與此類似,下麵語句的意思是“把字元ch放入FILE指針fpout指定的文件中”:

putc(ch, fpout);

  }

  這裡我們需要註意getc()的返回值是int類型的,這樣是為了返回EOF,雖然有些系統把char類型預設定義signed char,但為了保證程式最大的可移植性,上面代碼中ch字元實際上是int類型的

  C程式只有在讀到超過文件末尾之後才會發現文件結尾(EOF),所以為了避免空文件被錯誤讀取,我們應在執行相應操作之前嘗試讀取文件,再根據是否讀到EOF 來執行相應操作。

 

  這裡插一段:有些讀者朋友會問getc()和fgetc()以及putc()和fputc()之間有什麼關係?其實在查閱相關文檔之後,兩組函數幾乎是一模一樣的,只不過或許getc()和putc()是fgetc()和fputc()的巨集實現,原話如下:

  

  

  所以在調用getc()和putc()時,其參數不能是具有副作用的表達式。例如get(fp++)這樣的,這是不允許的!

格式化輸出輸入到文件---fprintf()和fscanf()函數

  fprintf(),fscanf()和printf(),scanf()類似,只不過在原有基礎上加了一個參數來確定輸出或輸入的目標文件。例如,如果有一個FILE指針fp已經指定一個有效的文件,並以"w"模式打開它,我們這樣做可以將Hello World輸出到這個文件上:

fprintf(fp, "Hello World");

  同樣,如果有一個字元串"string"被保存在FILE指針fp指定的有效文件的最開始,並以"r"模式打開它,我們這樣做將"string"輸入到數組st上:

fscanf(fp, "%s", st);

  請留意這裡的輸入輸出,它們並不指從標準輸入輸出設備獲得輸入輸出,而是一種傳遞的關係:

  我們通過C Primer Plus(Fifth Edition)上的一個相關示例來演示這兩個函數以及rewind()的具體應用:

字元串輸入輸出到文件---fgets()和fputs()函數

  fgets()函數和fputs()函數之前已在《字元串的輸入輸出》這篇博文介紹過,只不過現在將它的用途擴展到全體文件。我們假定fp是一個有效的FILE指針,那麼從fp指定的文件中讀取一段長度為SIZE - 1的字元串(不包括'\0')到數組st,我們可以這樣:

fgets(st, SIZE, stdin); 
//fgets()函數讀取SIZE-1個字元或遇到'\n'結束,並把最後一個字元或'\n'之後的字元賦值為'\0',也就是說在某些情況下它保存了'\n'。

  同理,fputs()將字元串"Hello World\n"輸出到fp所指定的文件:

fputs(fp, "Hello World\n");
//因為fgets()保存了'\n'所以fput()不會自動在字元串末尾加上'\n'

二進位模式文件I/O

文件定位---fseek()和ftell()函數

  如果部分讀者讀到這裡可能會吃不消,因為我們發現,與文件操作這塊內容相關的函數非常多,很難記憶,不過沒有關係,你不必去死記硬背,只要記住函數的大致功能,用到時再去查C標準庫參考,久而久之就慢慢會記住了

  ftell()函數返回一個long類型的值,該值表示文件當前位置距離文件開始處的位元組數目,以此來確定文件指針當前指向的位置。它接受一個參數,該參數表示一個有效的文件指針。如下代碼所示,我們假定fp是一個有效的FILE指針:

long int addr;
addr = ftell(fp);

  addr就返迴文件的當前位置,如下圖所示:

  由此可見,ftell()將文件當成數組來處理,我們就可以用處理數組的方法來處理文件。但是,這有一個重要的前提,就是文件必須用二進位模式打開,否則將會出現毫無意義的結果。

  fseek()函數可以改變文件的當前位置,它實現的前提也應是以二進位模式打開文件。它接受三個參數,第一個參數指明要進行操作的文件指針,第二個參數我們稱作偏移量,這是一個long型的參數,如果往文件開始處偏移就是負值,否則為正。第三個參數是模式,用來表示偏移的起點。模式有三種:

  我們通過書中的示例來解釋這兩個函數的應用(因為是以二進位模式打開,所以針對不同的系統,讀取文本文件就要有不同的實現代碼):

 二進位文件I/O---fread()和fwrite()函數

  我們先前瞭解到,如果C程式以二進位模式訪問一個文件,那麼它將可以訪問該文件的所有位元組。文件定位的示例中演示了這個特性。那麼,為什麼需要以二進位模式對文件進行操作呢?假設我們要在文件中存儲1/3這個值,我們選擇文本模式,那麼存儲的精度就會大大降低,也更加地麻煩(因為要將數字轉換成字元串就必須指定一個精度,讀取時也必須指定同樣的精度,如果將1/3以0.33存入文件,下次讀取就不能恢復它原來的精度)。所以,我們最好的選擇就是用相同的位格式來存儲值,我們可以使用sizeof(double)個位元組的空間來存儲1/3,這和程式在記憶體中存儲double變數的位格式相同,相當於拷貝了一個值為1/3的double變數到文件中,讀取時按照原本寫入時的精度去讀取,就可以恢復最大的精度。為了完成上述操作,我們可以使用fread()和fwrite()函數。

  fwrite()的原形是:

  size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

  其中,ptr指定要寫入的數據塊的地址(原地址),size表示寫入數據塊的大小,nmemb表示寫入數據塊的個數(這能很方便地寫入數組),stream指定要寫入的文件(目標地址)。例如我們要把1/3這個數以最大精度寫入文件pro.dat,我們需要這樣做(這一節只給出核心代碼):

FILE * fp;
double num = 1.0 / 3.0;
    
fp = fopen("pro.dat", "wb"); //以二進位寫模式打開文件
if( !fp )
    exit(EXIT_FAILURE);

fwrite(&num, sizeof(double), 1, fp); //寫入二進位數據

if( fclose(fp) != 0 ) //關閉文件
    exit(EXIT_FAILURE);

  然後,我們查看相對與程式同目錄的文件夾發現pro.dat創建成功,並且大小為8位元組,正是當前系統double類型變數的大小。

  現在,如果我們要讀取pro.dat文件里的內容,我們就要使用fread()函數了,因為以文本模式打開這個文件會出現亂碼。fread()函數的原形如下:

  size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

  同樣是四個參數,第一個參數指名要讀取到的數據塊地址(目標地址),第二三個參數與fwrite()的中對應參數相同,表示讀取的大小,最後一個參數指明要讀取的文件。

  下麵的代碼演示瞭如何使用pro.dat文件內容:

FILE * fp;
double get;
    
fp = fopen("pro.dat", "rb"); //以二進位讀模式打開文件
if( !fp )
    exit(EXIT_FAILURE);

fread(&get, sizeof(double), 1, fp); //讀取文件內容到get

if( fclose(fp) != 0 ) //及時關閉文件
    exit(EXIT_FAILURE);

printf("get * 3.0 = %lf",get * 3.0); //輸出

  如無意外,代碼運行之後會顯示:

  

結語

  這篇文章我概括地寫出了有關C語言文件的基本操作,如果您手頭上正好有一本C Primer Plus(第5或6版),請最好結合著書看本文。

  通過本文,我們知道了:

  〉〉什麼是文件?

  〉〉MS-DOS系統下重定向的有關操作和反映問題

  〉〉文本的打開和關閉fopen()和fclose()

  〉〉文本模式I/O:getc()和putc(),fprintf()和fscanf(),fgets()和fputs()

  〉〉文件定位:fseek()和ftell()

  〉〉二進位模式I/O:fread()和fwrite()

  在本文的編撰過程中,由於本文的篇幅較大,圖片較多,部分內容偏難,我耗費了很長時間才完成。文中的錯誤也是不可避免的,如果您在讀完之後發現有什麼錯誤,或是有什麼建議,歡迎批評指出!

附錄:其他標準I/O函數(選自C Primer Plus 5)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 提交git代碼的時候報的錯誤 這是因為修改的東西太少的原因,應該多修改一些就可以提交了 例如:只是刪除了一個空格或者一個字元就提交git代碼的話就會提示這個錯誤 解決方法:多多的改變一下代碼,比如增加一下回車 ...
  • 直接看圖吧: ...
  • 在設計資料庫的時候,經常碰到那些表示狀態或類型的欄位,比如訂單的狀態,或者支付的類型。要為這一類數據選擇合適的數據類型,比較常用的有以下兩種方法。 方法一:tinyint+byte(枚舉) 資料庫中類型:tinyint c#中類型:byte,如代碼: 方法二:varchar(xx)+string(c... ...
  • 快捷鍵說明 1.Ctrl+B可以選擇顯示或隱藏左邊的項目導航框; 2.Ctrl+\可以實現在右邊再打開一個編輯工作區域。配合利用Ctrl+1,2,3可以快速切換編輯視窗 3.Ctrl+P快速打開搜索框搜索文件 4.Ctrl+Shift+P命令編輯。 5.Ctrl+Tab列出你所打開的所有文件列表。 ...
  • 目錄索引 【無私分享:ASP.NET CORE 項目實戰】目錄索引 簡介 在Asp.net Core VS2015中,我們發現還有很多不太簡便的地方,比如右擊添加視圖,轉到試圖頁等功能圖不見了,雖然我們可以通過工具欄的自定義命令,把這兩個右擊菜單添加上,但是貌似是灰色的不能用。 其實,這樣也好,通過 ...
  • 之前是打算寫一篇文章叫:Taurus.MVC 從入門到精通,一篇完事篇!後來轉指一念,還是把教程集在這個企業站項目上吧,之前發過一個幫師妹寫的企業站:“最近花了幾個夜晚幫師妹整了一個企業網站”技術風格是:文本資料庫(txt)+WebForm 這次轉型風格:文本資料庫(txt)+Taurus.MVC ...
  • 今天做了一個讀取PE文件導出表的小程式,用來學習。 參考了《Windows PE權威指南》一書。 首先, PE文件的全稱是Portable Executable,可移植的可執行的文件,常見的EXE、DLL、OCX、SYS、COM都是PE文件。 我們知道,一個Windows程式,它所實現的所有功能最終 ...
  • 本篇文章講解了電腦的原碼, 反碼和補碼. 並且進行了深入探求了為何要使用反碼和補碼, 以及更進一步的論證了為何可以用反碼, 補碼的加法計算原碼的減法. 論證部分如有不對的地方請各位牛人幫忙指正! 希望本文對大家學習電腦基礎有所幫助! ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...