跳轉到內容

C 程式設計/流 I/O

來自 Wikibooks,開放世界中的開放書籍
上一個:錯誤處理 C 程式設計 下一個:字串

stdio.h 標頭檔案聲明瞭大量執行檔案和裝置(例如控制檯)輸入和輸出的函式。它是 C 庫中最早出現的標頭檔案之一。它宣告的函式比任何其他標準標頭檔案都多,也需要更多的解釋,因為函式背後有複雜的機制。

多年來,裝置無關的輸入輸出模型得到了顯著改進,但其成功卻鮮為人知。FORTRAN II 在 1960 年代被吹捧為一種機器無關的語言,但實際上,在不進行任何更改的情況下,幾乎不可能在體系結構之間遷移 FORTRAN 程式。在 FORTRAN II 中,您需要在 FORTRAN 程式碼中間的 FORTRAN 語句中直接指定要操作的裝置。因此,在面向磁帶的 IBM 7090 上,您會寫 READ INPUT TAPE 5,而在其他機器上,您會寫 READ CARD 來讀取卡片影像。FORTRAN IV 有更通用的 READWRITE 語句,指定了邏輯單元號(LUN)而不是裝置名稱。裝置無關 I/O 的時代到來了。

諸如印表機之類的外圍裝置仍然對所要求執行的操作有相當強的概念。隨後,發明了外設交換實用程式來處理奇異的裝置。當陰極射線管出現時,每家控制檯製造商都獨立地解決了諸如控制檯游標移動之類的難題,從而帶來了更多麻煩。

Unix 就是在這種環境下誕生的。Unix 的開發者肯·湯普森和丹尼斯·裡奇應得讚譽,他們在作業系統中融入了無數巧妙的想法。他們對裝置無關性的方法是最棒的想法之一。

ANSI C <stdio.h> 庫基於原始的 Unix 檔案 I/O 原語,但範圍更廣,以適應各種系統中最常見的特徵。

無論是對物理裝置(如終端和磁帶驅動器)還是對結構化儲存裝置上的檔案進行輸入或輸出,都對映到邏輯資料流,其屬性比各種輸入輸出更統一。支援兩種對映形式:文字流和二進位制流。

文字流包含一行或多行。文字流中的一行包含零個或多個字元,外加一個終止換行符。(唯一的例外是,在某些實現中,檔案的最後一行不需要終止換行符。)Unix 採用了一種所有文字流的標準內部格式。每行文字都以換行符結尾。這就是任何程式在讀取文字時所期望的,也是任何程式在寫入文字時所產生的。(這是最基本約定,如果它不能滿足連線到 Unix 機器上的面向文字的外設的需求,則修復操作將在系統邊緣進行。中間不需要進行任何更改。)進入或離開文字流的字元字串可能需要修改以符合特定約定。這會導致文字流中輸入的資料與輸出的資料之間存在可能的差異。例如,在某些實現中,當輸入中出現空格字元緊接換行符時,空格字元會從輸出中刪除。一般來說,當資料僅包含可列印字元和控制字元(如水平製表符和換行符)時,文字流的輸入和輸出是相等的。

與文字流相比,二進位制流非常直觀。二進位制流是字元的有序序列,可以透明地記錄內部資料。寫入二進位制流的資料將始終等於在相同實現下讀取的資料。但是,二進位制流可能在流末尾附加了實現定義數量的空字元。沒有其他需要考慮的約定。

Unix 中沒有任何內容可以阻止程式將任意 8 位二進位制程式碼寫入任何開啟的檔案,或者從適當的儲存庫中讀取它們而不進行更改。因此,Unix 消除了文字流和二進位制流之間長期存在的區別。

標準流

[編輯 | 編輯原始碼]

當 C 程式開始執行時,程式會自動開啟三個名為 stdinstdoutstderr 的標準流。這些流對於每個 C 程式都附加。

第一個標準流用於輸入緩衝,另外兩個用於輸出。這些流是位元組序列。

考慮以下程式

 /* An example program. */
 int main()
 {
     int var;
     scanf ("%d", &var); /* use stdin for scanning an integer from keyboard. */
     printf ("%d", var); /* use stdout for printing the integer that was just scanned in. */
     return 0;
 }
 /* end program. */

預設情況下,stdin 指向鍵盤,stdoutstderr 指向螢幕。在 Unix 下,甚至可能在其他作業系統下,可以將輸入從檔案重定向,或將輸出重定向到檔案,或兩者兼而有之。

指向流的指標

[編輯 | 編輯原始碼]
出於歷史原因,表示流的 C 資料結構的型別稱為 FILE,而不是 stream

<stdio.h> 標頭檔案包含一個 FILE 型別的定義(通常透過 typedef),該型別能夠處理控制流所需的所有資訊,包括檔案位置指示器、指向相關緩衝區(如果有)的指標、記錄是否發生讀/寫錯誤的錯誤指示器以及記錄是否已到達檔案結尾的檔案結束指示器。

除非程式設計師正在編寫 <stdio.h> 及其內容的實現,否則直接訪問 FILE 的內容被認為是不好的做法。透過 <stdio.h> 中的函式可以更好地訪問 FILE 的內容。可以說,FILE 型別是 面向物件程式設計 的早期示例。

開啟和關閉檔案

[編輯 | 編輯原始碼]

要開啟和關閉檔案,<stdio.h> 庫提供了三個函式:fopenfreopenfclose

開啟檔案

[編輯 | 編輯原始碼]
 #include <stdio.h>
 FILE *fopen(const char *filename, const char *mode);
 FILE *freopen(const char *filename, const char *mode, FILE *stream);

fopenfreopen 開啟名稱在 filename 指向的字串中的檔案,並將其與流關聯。兩者都返回一個指向控制流的物件的指標,或者,如果開啟操作失敗,則返回一個空指標。錯誤和檔案結束指示器被清除,如果開啟操作失敗,則設定錯誤。freopenfopen 的區別在於,stream 指向的檔案在已經開啟的情況下會被先關閉,並且會忽略任何關閉錯誤。

這兩個函式的 mode 都指向一個字串,該字串以以下序列之一開頭(序列後可能還有其他字元)

r           open a text file for reading
w           truncate to zero length or create a text file for writing
a           append; open or create text file for writing at end-of-file
rb          open binary file for reading
wb          truncate to zero length or create a binary file for writing
ab          append; open or create binary file for writing at end-of-file
r+          open text file for update (reading and writing)
w+          truncate to zero length or create a text file for update
a+          append; open or create text file for update
r+b or rb+  open binary file for update (reading and writing)
w+b or wb+  truncate to zero length or create a binary file for update
a+b or ab+  append; open or create binary file for update

如果檔案不存在或無法讀取,則以讀取模式(mode 引數中的第一個字元為“r”)開啟檔案將失敗。

以追加模式(mode 引數中的第一個字元為“a”)開啟檔案會導致所有後續寫入檔案的操作都強制到當時的當前檔案結尾,而與對 fseek 函式的中間呼叫無關。在某些實現中,以追加模式(mode 引數中的第二個或第三個字元為上述列表中的“b”)開啟二進位制檔案可能會將流的檔案位置指示器最初定位到最後一個寫入資料的後面,因為有空字元填充。

當以更新模式(mode 引數值列表中的第二個或第三個字元為“+”)開啟檔案時,可以對關聯的流執行輸入和輸出。但是,輸出不能直接跟在輸入之後,除非在中間呼叫 fflush 函式或檔案定位函式(fseekfsetposrewind),並且輸入不能直接跟在輸出之後,除非輸入操作遇到檔案結尾,除非輸入操作遇到檔案結尾。在某些實現中,以更新模式開啟(或建立)文字檔案可能會改為開啟(或建立)二進位制流。

開啟時,當且僅當可以確定流不引用互動式裝置時,流才是完全緩衝的。

關閉檔案

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int fclose(FILE *stream);

fclose 函式會導致 stream 指向的流被重新整理,並關閉關聯的檔案。流的任何未寫入的緩衝資料將被傳遞到主機環境,以寫入檔案;任何未讀取的緩衝資料將被丟棄。流與檔案分離。如果關聯的緩衝區是自動分配的,則它將被釋放。如果流成功關閉,則函式返回零;如果檢測到任何錯誤,則返回 EOF

流緩衝函式

[編輯 | 編輯原始碼]

fflush 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int fflush(FILE *stream);

如果 stream 指向一個輸出流或一個更新流,其中最近的操作不是輸入,則 fflush 函式會導致該流的任何未寫入的資料被延遲到主機環境,以寫入檔案。對於輸入流,fflush 的行為是未定義的。

如果 stream 是一個空指標,則 fflush 函式會對所有行為如上所述定義的流執行此重新整理操作。

如果發生寫入錯誤,fflush 函式返回 EOF,否則返回零。

存在 fflush 函式的原因是,C 中的流可以具有緩衝的輸入/輸出;也就是說,寫入檔案的函式實際上寫入 FILE 結構內的緩衝區。如果緩衝區已滿,寫入函式將呼叫 fflush 以實際將緩衝區中的資料“寫入”檔案。由於 fflush 只是偶爾被呼叫一次,因此呼叫作業系統執行原始寫入的次數被最小化了。

setbuf 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 void setbuf(FILE *stream, char *buf);

除了不返回值之外,setbuf 函式等效於用 _IOFBF 作為 modeBUFSIZ 作為 size 呼叫 setvbuf 函式,或者(如果 buf 是一個空指標)用 _IONBF 作為 mode 呼叫 setvbuf 函式。

setvbuf 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int setvbuf(FILE *stream, char *buf, int mode, size_t size);

setvbuf 函式只能在 stream 指向的流與一個開啟的檔案關聯之後並且在對流執行任何其他操作之前使用。引數 mode 確定流將如何被緩衝,如下所示:_IOFBF 會導致輸入/輸出被完全緩衝;_IOLBF 會導致輸入/輸出被行緩衝;_IONBF 會導致輸入/輸出不被緩衝。如果 buf 不是一個空指標,則它指向的陣列可以代替 setvbuf 函式關聯的緩衝區。(緩衝區的生存期必須至少與開啟的流一樣長,因此在緩衝區具有自動儲存期限的緩衝區在塊退出時被釋放之前,應該關閉流。)引數 size 指定陣列的大小。陣列中任何時候的內容都是不確定的。

如果成功,setvbuf 函式返回零;如果為 mode 提供了無效值,或者無法滿足請求,則返回非零值。

修改檔案位置指示器的函式

[編輯 | 編輯原始碼]

stdio.h 庫有五個函式可以影響檔案位置指示器,除了那些執行讀寫操作的函式:fgetposfseekfsetposftellrewind

fseekftell 函式比 fgetposfsetpos 更早。

fgetposfsetpos 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int fgetpos(FILE *stream, fpos_t *pos);
 int fsetpos(FILE *stream, const fpos_t *pos);

fgetpos 函式將 stream 指向的流的檔案位置指示器的當前值儲存在 pos 指向的物件中。儲存的值包含 fsetpos 函式可用於將流重新定位到呼叫 fgetpos 函式時的位置的未指定資訊。

如果成功,fgetpos 函式返回零;如果失敗,fgetpos 函式返回非零值,並在 errno 中儲存一個實現定義的正值。

fsetpos 函式根據 pos 指向的物件的值設定 stream 指向的流的檔案位置指示器,該值應是從先前對相同流的 fgetpos 函式的呼叫中獲得的值。

fsetpos 函式的成功呼叫會清除流的檔案結束指示器,並撤消對相同流的 ungetc 函式的任何影響。在 fsetpos 呼叫之後,對更新流的下一個操作可以是輸入或輸出。

如果成功,fsetpos 函式返回零;如果失敗,fsetpos 函式返回非零值,並在 errno 中儲存一個實現定義的正值。

fseekftell 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int fseek(FILE *stream, long int offset, int whence);
 long int ftell(FILE *stream);

fseek 函式設定 stream 指向的流的檔案位置指示器。

對於二進位制流,新的位置(以檔案開頭處的字元數衡量)透過將offset新增到whence指定的位置獲得。stdio.h中的三個宏稱為SEEK_SETSEEK_CURSEEK_END擴充套件為唯一值。如果whence指定的位置為SEEK_SET,則指定的位置為檔案開頭;如果whenceSEEK_END,則指定的位置為檔案結尾;如果whenceSEEK_CUR,則指定的位置為當前檔案位置。二進位制流不必有意義地支援whence值為SEEK_ENDfseek呼叫。

對於文字流,offset應為零,或者offset應為先前對同一流呼叫ftell函式返回的值,而whence應為SEEK_SET

fseek函式僅對無法滿足的請求返回非零值。

ftell函式獲取stream指向的流的檔案位置指示器的當前值。對於二進位制流,該值是從檔案開頭的字元數;對於文字流,其檔案位置指示器包含未指定的資訊,可由fseek函式用於將流的檔案位置指示器返回到呼叫ftell時的位置;兩個這樣的返回值之間的差值不一定是寫入或讀取的字元數的有效度量。

如果成功,ftell函式將返回流的檔案位置指示器的當前值。如果失敗,ftell函式將返回-1L,並在errno中儲存實現定義的正值。

rewind函式

[edit | edit source]
 #include <stdio.h>
 void rewind(FILE *stream);

rewind函式將stream指向的流的檔案位置指示器設定為檔案開頭。它等效於

(void)fseek(stream, 0L, SEEK_SET)

除了流的錯誤指示器也被清除。

錯誤處理函式

[edit | edit source]

clearerr函式

[edit | edit source]
 #include <stdio.h>
 void clearerr(FILE *stream);

clearerr函式清除stream指向的流的檔案結尾指示器和錯誤指示器。

feof函式

[edit | edit source]
 #include <stdio.h>
 int feof(FILE *stream);

feof函式測試stream指向的流的檔案結尾指示器,如果且僅當檔案結尾指示器為stream設定時,返回非零值,否則返回零。

ferror函式

[edit | edit source]
 #include <stdio.h>
 int ferror(FILE *stream);

ferror函式測試stream指向的流的錯誤指示器,如果且僅當錯誤指示器為stream設定時,返回非零值,否則返回零。

perror函式

[edit | edit source]
 #include <stdio.h>
 void perror(const char *s);

perror函式將整數表示式errno中的錯誤號對映到錯誤訊息。它向標準錯誤流寫入一個字元序列,如下所示:首先,如果s不是空指標,並且s指向的字元不是空字元,則為s指向的字串,後跟冒號(:) 和一個空格;然後是適當的錯誤訊息字串,後跟一個換行符。錯誤訊息的內容與使用引數errno呼叫strerror函式返回的內容相同,這些內容是實現定義的。

其他檔案操作

[edit | edit source]

stdio.h庫包含各種函式,這些函式除了讀寫外還會對檔案執行一些操作。

remove函式

[edit | edit source]
 #include <stdio.h>
 int remove(const char *filename);

remove函式導致名為filename指向的字串的檔案不再可以透過該名稱訪問。隨後嘗試使用該名稱開啟該檔案將失敗,除非它被重新建立。如果檔案已開啟,則remove函式的行為是實現定義的。

如果操作成功,remove函式將返回零,如果失敗,則返回非零值。

rename函式

[edit | edit source]
 #include <stdio.h>
 int rename(const char *old_filename, const char *new_filename);

rename函式導致名為old_filename指向的字串的檔案從此以後被稱為new_filename指向的字串給出的名稱。名為old_filename的檔案不再可以透過該名稱訪問。如果在呼叫rename函式之前存在名為new_filename指向的字串的檔案,則行為是實現定義的。

如果操作成功,rename函式將返回零,如果失敗,則返回非零值,在這種情況下,如果該檔案先前存在,它仍然以其原始名稱而聞名。

tmpfile函式

[edit | edit source]
 #include <stdio.h>
 FILE *tmpfile(void);

tmpfile函式建立一個臨時二進位制檔案,該檔案將在關閉時或程式終止時自動刪除。如果程式異常終止,是否刪除開啟的臨時檔案是實現定義的。該檔案以"wb+"模式開啟以供更新。

tmpfile函式返回一個指向它建立的檔案流的指標。如果無法建立檔案,則tmpfile函式將返回一個空指標。

tmpnam函式

[edit | edit source]
 #include <stdio.h>
 char *tmpnam(char *s);

tmpnam函式生成一個有效的檔名字串,該字串不是現有檔案的名稱。

tmpnam函式每次呼叫時都會生成一個不同的字串,最多TMP_MAX次。(TMP_MAXstdio.h中定義的宏。)如果它被呼叫超過TMP_MAX次,則行為是實現定義的。

實現的行為就好像沒有庫函式呼叫tmpnam函式一樣。

如果引數為空指標,則tmpnam函式將其結果留在內部靜態物件中,並返回指向該物件的指標。後續對tmpnam函式的呼叫可能會修改同一個物件。如果引數不是空指標,則假定它指向一個至少包含L_tmpnam個字元的陣列(L_tmpnamstdio.h中的另一個宏);tmpnam函式將結果寫入該陣列,並返回引數作為其值。

TMP_MAX的值必須至少為 25。

從檔案讀取

[edit | edit source]

字元輸入函式

[edit | edit source]

fgetc函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int fgetc(FILE *stream);

fgetc 函式從由 stream 指向的流中獲取下一個字元(如果存在)作為 unsigned char 轉換為 int,並推進與流相關的檔案位置指示器(如果已定義)。

fgetc 函式返回由 stream 指向的流中的下一個字元。如果流處於檔案末尾或發生讀取錯誤,fgetc 返回 EOFEOF 是在 <stdio.h> 中定義的負值,通常為 (-1))。feofferror 必須用於區分檔案末尾和錯誤。如果發生錯誤,全域性變數 errno 將被設定為指示錯誤。

fgets 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 char *fgets(char *s, int n, FILE *stream);

fgets 函式從由 stream 指向的流中讀取最多比由 n 指定的字元數少一個字元,並將它們寫入由 s 指向的陣列中。在新行字元(保留)或檔案末尾之後不會讀取其他字元。一個空字元將寫入讀取到陣列中的最後一個字元之後。

如果成功,fgets 函式返回 s。如果遇到檔案末尾且沒有字元被讀取到陣列中,陣列的內容將保持不變,並將返回空指標。如果在操作期間發生讀取錯誤,陣列內容將不確定,並將返回空指標。

警告:不同的作業系統可能使用不同的字元序列來表示行尾序列。例如,一些檔案系統在文字檔案中使用終止符 \r\nfgets 可能會讀取這些行,刪除 \n 但保留 \r 作為 s 的最後一個字元。在使用該字串進行任何操作之前,應從字串 s 中刪除此偽字元(除非程式設計師不關心它)。Unix 通常使用 \n 作為其行尾序列,MS-DOS 和 Windows 使用 \r\n,而 Mac OSes 在 OS X 之前使用 \r。許多編譯器在除 Unix 或 Linux 之外的作業系統上將換行序列對映到文字檔案的 \n 中;檢查您的編譯器文件以瞭解它在這種情況下會做什麼。

 /* An example program that reads from stdin and writes to stdout */
 #include <stdio.h>

 #define BUFFER_SIZE 100

 int main(void)
 {
     char buffer[BUFFER_SIZE]; /* a read buffer */
     while( fgets (buffer, BUFFER_SIZE, stdin) != NULL)
     {
          printf("%s",buffer);
     }
     return 0;
 }
 /* end program. */

getc 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int getc(FILE *stream);

getc 函式等效於 fgetc,不同之處在於它可能被實現為宏。如果它被實現為宏,stream 引數可能被評估不止一次,因此該引數永遠不應該是一個具有副作用的表示式(即具有賦值、遞增或遞減運算子,或是一個函式呼叫)。

getc 函式返回由 stream 指向的輸入流中的下一個字元。如果流處於檔案末尾,流的檔案末尾指示器將被設定為 getc 並返回 EOFEOF 是在 <stdio.h> 中定義的負值,通常為 (-1))。如果發生讀取錯誤,流的錯誤指示器將被設定為 getc 並返回 EOF

getchar 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int getchar(void);

getchar 函式等效於帶有引數 stdingetc

getchar 函式返回由 stdin 指向的輸入流中的下一個字元。如果 stdin 處於檔案末尾,stdin 的檔案末尾指示器將被設定為 getchar 並返回 EOFEOF 是在 <stdio.h> 中定義的負值,通常為 (-1))。如果發生讀取錯誤,stdin 的錯誤指示器將被設定為 getchar 並返回 EOF

gets 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 char *gets(char *s);

gets 函式從由 stdin 指向的輸入流中讀取字元到由 s 指向的陣列中,直到遇到檔案末尾或讀取到換行符。任何換行符都會被丟棄,並且一個空字元將寫入讀取到陣列中的最後一個字元之後。

如果成功,gets 函式返回 s。如果遇到檔案末尾且沒有字元被讀取到陣列中,陣列的內容將保持不變,並將返回空指標。如果在操作期間發生讀取錯誤,陣列內容將不確定,並將返回空指標。

此函式和描述僅出於完整性而包含在此處。如今大多數 C 程式設計師都避免使用 gets,因為該函式無法知道程式設計師想要讀取到哪個緩衝區有多大。

Henry SpencerC 程式設計師的十誡(帶註釋版) 中的誡律 #5 寫道

你應該檢查所有字串(實際上是所有陣列)的陣列邊界,因為當你輸入 foo 時,有人總有一天會輸入 supercalifragilisticexpialidocious

它在註釋中提到了 gets

正如大蠕蟲的所作所為所證明的那樣,這條誡律的一個結果是,穩健的生產軟體永遠不應該使用 gets(),因為它確實是魔鬼的工具。你的介面應該始終告知你的僕人你的陣列的界限,而那些拒絕這種建議或默默地不遵守這種建議的僕人,應該立即被派遣到 Rm 之地,在那裡他們不會再對你造成傷害。

在 2018 年版的 C 標準之前,gets 函式已被棄用。希望程式設計師會改用 fgets 函式。

ungetc 函式

[編輯 | 編輯原始碼]
 #include <stdio.h>
 int ungetc(int c, FILE *stream);

ungetc 函式將由 c 指定的字元(轉換為 unsigned char)推回到由 stream 指向的輸入流中。推回的字元將按它們被推回的相反順序由該流上的後續讀取返回。對指向 stream 的流的成功干預呼叫(使用檔案定位函式(fseekfsetposrewind))將丟棄該流的任何推回字元。與流相對應的外部儲存將保持不變。

保證有一個字元的回退。如果在對同一個流進行干預讀取或檔案定位操作之前,ungetc 函式在同一個流上被呼叫了太多次,操作可能會失敗。

如果 c 的值等於宏 EOF 的值,則操作將失敗,輸入流將保持不變。

ungetc 函式的成功呼叫將清除流的檔案末尾指示器。在讀取或丟棄所有推回的字元後,流的檔案位置指示器的值應與其在字元被推回之前的值相同。對於文字流,在成功呼叫 ungetc 函式後,其檔案位置指示器的值在所有推回的字元被讀取或丟棄之前是不確定的。對於二進位制流,其檔案位置指示器在每次成功呼叫 ungetc 函式時都會遞減;如果其值在呼叫之前為零,則呼叫後它將是不確定的。

ungetc 函式在轉換後返回推回的字元,或者在操作失敗時返回 EOF

EOF 陷阱

[編輯 | 編輯原始碼]

使用 fgetcgetcgetchar 時,一個錯誤是將結果分配給 char 型別的變數EOF 進行比較之前。以下程式碼片段展示了這個錯誤,然後顯示了正確的方法(使用 int 型別)

錯誤 更正
char c;
while ((c = getchar()) != EOF)
    putchar(c);
int c;
while ((c = getchar()) != EOF)
    putchar(c);

考慮一個系統,其中 char 型別是 8 位寬,表示 256 個不同的值。getchar 可以返回 256 個可能的字元中的任何一個,它也可以返回 EOF 來指示檔案末尾,總共 257 個不同的可能返回值。

getchar的結果被賦值給一個char時,char只能表示 256 個不同的值,因此必然會丟失一些資訊——當把 257 個專案塞進 256 個槽位時,必然會發生碰撞EOF 值在轉換為 char 時,將無法與 256 個字元中的任何一個區分開來,因為它們具有相同的數值。如果該字元在檔案中找到,上面的示例可能會將其誤認為是檔案結尾指示符;或者,同樣糟糕的是,如果型別 char 是無符號的,那麼由於 EOF 是負數,它永遠不可能等於任何無符號 char,因此上面的示例不會在檔案結尾處終止。它將無限迴圈,重複列印將 EOF 轉換為 char 所產生的字元。

然而,如果 char 定義是有符號的(C 使預設 char 型別實現取決於實現),[1] 並且假設通常使用的 EOF 值為 -1,則不會發生這種迴圈失敗模式。但是,根本問題仍然存在,如果 EOF 值定義在 char 類型範圍之外,當它被賦值給 char 時,該值會被切片,並且不再與退出迴圈所需的完整 EOF 值匹配。另一方面,如果 EOFchar 的範圍內,這保證了 EOF 和 char 值之間的碰撞。因此,無論系統型別如何定義,在測試 EOF 時永遠不要使用 char 型別。

intchar 大小相同的系統上(即與 POSIX 和 C99 標準不相容的系統),即使是“良好”的示例也會受到 EOF 和某些字元值的無法區分的影響。處理這種情況的正確方法是在 getchar 返回 EOF 後檢查 feofferror。如果 feof 指示尚未到達檔案結尾,並且 ferror 指示沒有發生錯誤,那麼 getchar 返回的 EOF 可以被認為代表一個實際的字元。這些額外的檢查很少進行,因為大多數程式設計師假設他們的程式碼永遠不需要在這些“大 char”系統上執行。另一種方法是使用編譯時斷言來確保 UINT_MAX > UCHAR_MAX,這至少可以防止具有這種假設的程式在這樣的系統上編譯。


直接輸入函式:fread 函式

[edit | edit source]
 #include <stdio.h>
 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

fread 函式從 stream 指向的流中讀取最多 nmemb 個元素,這些元素的大小由 size 指定,並寫入 ptr 指向的陣列中。流的檔案位置指示器(如果已定義)將根據成功讀取的字元數進行前進。如果發生錯誤,流的檔案位置指示器的結果值將是不確定的。如果讀取部分元素,其值將是不確定的。

fread 函式返回成功讀取的元素數,如果遇到讀取錯誤或檔案結尾,則該數可能小於 nmemb。如果 sizenmemb 為零,fread 將返回零,陣列的內容和流的狀態將保持不變。

格式化輸入函式:scanf 函式系列

[edit | edit source]
 #include <stdio.h>
 int fscanf(FILE *stream, const char *format, ...);
 int scanf(const char *format, ...);
 int sscanf(const char *s, const char *format, ...);

fscanf 函式從 stream 指向的流中讀取輸入,在 format 指向的字串的控制下,該字串指定允許的序列以及如何轉換它們以進行賦值,使用後續引數作為指向接收轉換輸入的物件的指標。如果格式引數不足,則行為未定義。如果格式用盡而引數仍然存在,則會評估多餘的引數(與往常一樣),但會被忽略。

格式應為多位元組字元序列,以其初始移位狀態開始和結束。格式由零個或多個指令組成:一個或多個空白字元;一個普通的多位元組字元(既不是%也不是空白字元);或一個轉換說明符。每個轉換說明符都以字元%開頭。在%之後,以下內容按順序出現

  • 一個可選的賦值抑制字元*.
  • 一個可選的非零十進位制整數,指定最大欄位寬度。
  • 一個可選的h, l(ell)或L指示接收物件的尺寸。轉換說明符d, in應以h開頭,如果相應的引數是指向 short int 的指標而不是指向 int 的指標,或者以l開頭,如果它是指向 long int 的指標。類似地,轉換說明符o, ux應以h應以l開頭,如果相應的引數是指向 unsigned short int 的指標而不是 unsigned int 的指標,或者以開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, ef應以lgL應以h, l開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以L開頭,如果它是指向 long double 的指標。如果

與任何其他格式說明符一起出現,則行為未定義。

指定要應用的轉換型別的字元。有效的轉換說明符如下所述。

fscanf 函式依次執行格式的每個指令。如果指令失敗,如以下所述,fscanf 函式將返回。失敗被描述為輸入失敗(由於輸入字元不可用)或匹配失敗(由於輸入不合適)。

由空白字元組成的指令透過讀取輸入直到第一個非空白字元(保持未讀)或直到沒有更多字元保持未讀來執行。

一個普通多位元組字元的指令透過讀取流的下一個字元來執行。如果其中一個字元與指令中的一個字元不同,則指令失敗,並且不同的字元和後續字元將保持未讀。[, 一個轉換說明符指令定義了一組匹配的輸入序列,如以下對每個說明符的描述。轉換說明符按以下步驟執行開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以n除非說明符包含

cn說明符,否則將跳過輸入空白字元(如 isspace 函式所指定)。(空白字元不會計入指定的欄位寬度。)

除非說明符包含%說明符,否則將從流中讀取一個輸入項。輸入項被定義為最長的匹配輸入字元序列,除非它超過了指定的欄位寬度,在這種情況下,它將是該序列中長度為初始子序列。如果存在,則輸入項後的第一個字元將保持未讀。如果輸入項的長度為零,則指令的執行將失敗;此情況是匹配失敗,除非錯誤阻止了流的輸入,在這種情況下,它是輸入失敗。除了說明符的情況外,輸入項(或在*%n

指令的情況下,輸入字元數)將轉換為適合轉換說明符的型別。如果輸入項不是匹配序列,則指令的執行將失敗;此情況是匹配失敗。除非由

d
指示賦值抑制,否則轉換結果將放在 format 引數後的第一個尚未接收轉換結果的引數所指向的物件中。如果此物件沒有合適的型別,或者轉換結果不能在提供的空間中表示,則行為未定義。
i
以下轉換說明符有效
o
匹配一個可選的帶符號十進位制整數,其格式與 strtol 函式的主題序列(使用 10 作為 base 引數的值)的預期格式相同。相應的引數應是指向整數的指標。
u
匹配一個可選的帶符號整數,其格式與 strtol 函式的主題序列(使用 0 作為 base 引數的值)的預期格式相同。相應的引數應是指向整數的指標。
x
匹配一個可選的帶符號八進位制整數,其格式與 strtoul 函式的主題序列(使用 8 作為 base 引數的值)的預期格式相同。相應的引數應是指向無符號整數的指標。
開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, e, f
匹配一個可選的帶符號十進位制整數,其格式與 strtoul 函式的主題序列(使用 10 作為 base 引數的值)的預期格式相同。相應的引數應是指向無符號整數的指標。
匹配一個可選的帶符號十六進位制整數,其格式與 strtoul 函式的主題序列(使用 16 作為 base 引數的值)的預期格式相同。相應的引數應是指向無符號整數的指標。
匹配一個可選的帶符號浮點數,其格式與 strtod 函式的主題字串的預期格式相同。相應的引數將是指向浮點數的指標。
[
s]匹配一個非空白字元序列。(沒有為多位元組字元提供特殊規定。)相應的引數應是指向足夠大的陣列的初始字元的指標,以容納該序列和一個終止空字元,該字元將被自動新增。^匹配來自一組預期字元(掃描集)的非空字元序列(沒有為多位元組字元提供特殊規定)。相應的引數應是指向足夠大的陣列的初始字元的指標,以容納該序列和一個終止空字元,該字元將被自動新增。轉換說明符包含 format 字串中的所有後續字元,直到且包括匹配的右括號([])。括號之間的字元(掃描列表)構成掃描集,除非左括號後的字元是插入符([^]),在這種情況下,掃描集包含所有不在插入符和右括號之間的掃描列表中的字元。如果轉換說明符以-字元在掃描列表中,並且不是第一個字元,也不是第一個字元為 a 的第二個字元。^,也不是最後一個字元,行為是實現定義的。
一個轉換說明符指令定義了一組匹配的輸入序列,如以下對每個說明符的描述。轉換說明符按以下步驟執行
匹配由欄位寬度指定的數字(如果指令中沒有欄位寬度,則為 1)的字元序列(對於多位元組字元沒有特殊規定)。相應的引數應是指向足夠大的陣列的初始字元的指標,以接受該序列。不會新增空字元。
p
匹配一組實現定義的序列,這組序列應與函式%p轉換可能產生的序列相同。相應的引數應是指向 void 的指標。輸入的解釋是實現定義的。如果輸入項是同一程式執行期間先前轉換的值,則結果指標應與該值相等;否則,%p轉換的行為是未定義的。
n
不使用任何輸入。相應的引數應是指向整數的指標,該整數將寫入迄今為止透過此呼叫 fscanf 函式從輸入流中讀取的字元數。執行除了指令不會增加在執行 fscanf 函式完成時返回的賦值計數。
%
匹配單個%;不會發生轉換或賦值。完整的轉換說明應為%%.

如果轉換說明無效,則行為未定義。

轉換說明符E, GX也是有效的,並且分別與開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, fx.

如果在輸入期間遇到檔案結束符,則轉換將終止。如果在讀取與當前指令匹配的任何字元(除了允許的領先空白字元)之前發生檔案結束符,則當前指令的執行將以輸入失敗終止;否則,除非當前指令的執行以匹配失敗終止,否則以下指令(如果有)的執行將以輸入失敗終止。

如果轉換在衝突的輸入字元上終止,則有問題的輸入字元將留在輸入流中未讀取。尾隨空白字元(包括換行符)將保留未讀取,除非與指令匹配。除透過除了指令外,無法直接確定文字匹配和抑制賦值的成功與否。

如果在任何轉換之前發生輸入失敗,fscanf 函式將返回宏 EOF 的值。否則,fscanf 函式將返回分配的輸入項數,這可能少於提供的項,甚至為零,如果發生早期匹配失敗。

scanf 函式等效於 fscanf,但引數 stdin 插入在 scanf 的引數之前。它的返回值類似於 fscanf

sscanf 函式等效於 fscanf,只是引數 s 指定要從中獲取輸入的字串,而不是來自流。到達字串的末尾等效於遇到 fscanf 函式的檔案結束符。如果在重疊的物件之間進行復制,則行為未定義。

寫入檔案

[edit | edit source]

字元輸出函式

[edit | edit source]

fputc 函式

[edit | edit source]
#include <stdio.h>
int fputc(int c, FILE *stream);

fputc 函式將 c 指定的字元(轉換為 unsigned char)寫入 stream 指向的流,位置由關聯的檔案位置指示器(如果定義)指示,並適當地向前移動指示器。如果檔案不支援定位請求,或者流以追加模式開啟,則字元將追加到輸出流。該函式將返回寫入的字元,除非發生寫入錯誤,在這種情況下,流的錯誤指示器將被設定,並且 fputc 將返回 EOF

fputs 函式

[edit | edit source]
#include <stdio.h>
int fputs(const char *s, FILE *stream);

fputs 函式將 s 指向的字串寫入 stream 指向的流。不會寫入終止的空字元。如果發生寫入錯誤,該函式將返回 EOF,否則它將返回一個非負值。

putc 函式

[edit | edit source]
#include <stdio.h>
int putc(int c, FILE *stream);

putc 函式等效於 fputc,只是如果它被實現為宏,它可能多次評估 stream,因此引數永遠不應是具有副作用的表示式。該函式將返回寫入的字元,除非發生寫入錯誤,在這種情況下,流的錯誤指示器將被設定,並且該函式將返回 EOF

putchar 函式

[edit | edit source]
#include <stdio.h>
int putchar(int c);

putchar 函式等效於帶有第二個引數 stdoutputc。它將返回寫入的字元,除非發生寫入錯誤,在這種情況下,stdout 的錯誤指示器將被設定,並且該函式將返回 EOF

puts 函式

[edit | edit source]
#include <stdio.h>
int puts(const char *s);

puts 函式將 s 指向的字串寫入 stdout 指向的流,並在輸出中附加一個換行符。不會寫入終止的空字元。如果發生寫入錯誤,該函式將返回 EOF;否則,它將返回一個非負值。

直接輸出函式:fwrite 函式

[edit | edit source]
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

fwrite 函式從 ptr 指向的陣列中寫入,最多 nmemb 個大小由 size 指定的元素到 stream 指向的流。流的檔案位置指示器(如果定義)將按成功寫入的字元數向前移動。如果發生錯誤,流的檔案位置指示器的結果值是不確定的。該函式將返回成功寫入的元素數,這將小於 nmemb,只有在遇到寫入錯誤時才會這樣。

格式化輸出函式:printf 函式系列

[edit | edit source]
#include <stdarg.h>
#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);
int printf(const char *format, ...);
int sprintf(char *s, const char *format, ...);
int vfprintf(FILE *stream, const char *format, va_list arg);
int vprintf(const char *format, va_list arg);
int vsprintf(char *s, const char *format, va_list arg);

注意:一些長度說明符和格式說明符是在 C99 中新增的。這些可能在舊的編譯器和 stdio 庫版本中不可用,這些版本遵循 C89/C90 標準。在可能的情況下,新說明符將用 (C99) 標記。

fprintf 函式在 format 指向的字串的控制下,將輸出寫入 stream 指向的流,該字串指定如何為輸出轉換後續引數。如果引數不足以滿足格式,則行為未定義。如果格式在引數仍存在的情況下耗盡,則將評估多餘的引數(與往常一樣),但其他方面將被忽略。fprintf 函式在遇到格式字串的結尾時返回。

格式應為多位元組字元序列,在初始移位狀態下開始和結束。格式由零個或多個指令組成:普通多位元組字元(不是%),這些字元將原樣複製到輸出流中;以及轉換說明,每個說明都將導致獲取零個或多個後續引數,如果適用,則根據相應的轉換說明符進行轉換,然後將結果寫入輸出流。

每個轉換說明都由字元%開頭。在%之後,以下內容按順序出現

  • 零個或多個標誌(以任何順序),它們修改轉換說明的含義。
  • 可選的最小欄位寬度。如果轉換後的值的字元數少於欄位寬度,則將在左側(如果已給出左側調整標誌,如後所述,則在右側)用空格(預設情況下)填充到欄位寬度。欄位寬度採用星號形式*(稍後描述)或十進位制整數。(請注意,0 被視為一個標誌,而不是一個欄位寬度的開始。)
  • 可選的精度,它給出要顯示的最小位數d, i, o, u, xX轉換、顯示在小數點字元後面的位數a, A, 開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, E, eF轉換、對fG轉換的有效位數的最大值,或從字串中寫入的最大字元數匹配一個可選的帶符號十六進位制整數,其格式與 strtoul 函式的主題序列(使用 16 作為 base 引數的值)的預期格式相同。相應的引數應是指向無符號整數的指標。轉換。精度採用句點(.)後跟星號*(稍後描述)或可選的十進位制整數;如果只指定句點,則精度將取為零。如果精度與任何其他轉換說明符一起出現,則行為未定義。浮點數將舍入以適合精度;即printf("%1.1f\n", 1.19);產生1.2.
  • 可選的長度修飾符,它指定引數的大小。
  • 指定要應用的轉換型別的轉換說明符字元。

如上所述,欄位寬度或精度或兩者都可以用星號 (*) 表示。在這種情況下,int 引數提供欄位寬度或精度。指定欄位寬度或精度或兩者的引數應(按此順序)出現在要轉換的引數(如果有)之前。負欄位寬度引數被視為-標誌後跟一個正欄位寬度。負精度引數被視為精度被省略。

標誌字元及其含義如下:

-
轉換結果在欄位中左對齊。(如果未指定此標誌,則右對齊。)
+
帶符號轉換的結果始終以加號或減號開頭。(如果未指定此標誌,則僅當轉換負值時,它才以符號開頭。負零的所有浮點轉換的結果以及舍入為零的負值的結果都包含減號。)
空格
如果帶符號轉換的第一個字元不是符號,或者帶符號轉換導致沒有字元,則在結果之前新增一個空格。如果空格和+標誌都出現,則忽略空格標誌。
#
結果將轉換為“備用形式”。對於o轉換,如果且僅當必要時,它會增加精度以強制結果的第一個數字為零(如果值和精度都為 0,則列印單個 0)。對於x(或X)轉換,非零結果將0x(或0X)作為字首新增。對於a, A, 開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, E, e, F, fG轉換,結果始終包含小數點字元,即使沒有數字跟隨它。(通常,只有在數字跟隨它時,這些轉換的結果才會出現小數點字元。)對於fG轉換,尾隨零不會從結果中刪除。對於其他轉換,行為未定義。
0
對於d, i, o, u, x, X, a, A, 開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, E, e, F, fG轉換,前導零(在任何符號或基數指示之後)用於填充到欄位寬度;不執行空格填充。如果0-標誌都出現,則0標誌被忽略。對於d, i, o, u, xX轉換,如果指定了精度,則0標誌被忽略。對於其他轉換,行為未定義。

長度修飾符及其含義如下:

hh
(C99)指定以下d, i, o, u, x開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以X轉換說明符應用於 signed charunsigned char 引數(引數將根據整數提升進行提升,但其值應在列印之前轉換為 signed charunsigned char);或者以下n轉換說明符應用於指向 signed char 引數的指標。
h
指定以下d, i, o, u, x開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以X轉換說明符應用於 short intunsigned short int 引數(引數將根據整數提升進行提升,但其值應在列印之前轉換為 short intunsigned short int);或者以下n轉換說明符應用於指向 short int 引數的指標。
l(ell)
指定以下d, i, o, u, x開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以X轉換說明符應用於 long intunsigned long int 引數;以下n轉換說明符應用於指向 long int 引數的指標;(C99)以下一個轉換說明符指令定義了一組匹配的輸入序列,如以下對每個說明符的描述。轉換說明符按以下步驟執行轉換說明符應用於 wint_t 引數;(C99)以下匹配一個可選的帶符號十六進位制整數,其格式與 strtoul 函式的主題序列(使用 16 作為 base 引數的值)的預期格式相同。相應的引數應是指向無符號整數的指標。轉換說明符應用於指向 wchar_t 引數的指標;或者對以下a, A, 開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, E, e, F, f開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以G轉換說明符沒有影響。
ll(ell-ell)
(C99)指定以下d, i, o, u, x開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以X轉換說明符應用於 long long intunsigned long long int 引數;或者以下n轉換說明符應用於指向 long long int 引數的指標。
j
(C99)指定以下d, i, o, u, x開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以X轉換說明符應用於 intmax_tuintmax_t 引數;或者以下n轉換說明符應用於指向 intmax_t 引數的指標。
z
(C99)指定以下d, i, o, u, x開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以X轉換說明符應用於 size_t 或相應的帶符號整數型別引數;或者以下n轉換說明符應用於指向與 size_t 引數對應的帶符號整數型別的指標。
t
(C99)指定以下d, i, o, u, x開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以X轉換說明符應用於 ptrdiff_t 或相應的無符號整數型別引數;或者以下n轉換說明符應用於指向 ptrdiff_t 引數的指標。
L
指定以下a, A, 開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, E, e, F, f開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以G轉換說明符應用於 long double 引數。

如果長度修飾符與除上面指定的以外的任何轉換說明符一起出現,則行為未定義。

轉換說明符及其含義如下:

d, i
int 引數將轉換為帶符號十進位制,格式為 []dddd。精度指定要出現的最小位數;如果要轉換的值可以用更少的位數表示,則它將用前導零擴充套件。預設精度為 1。將精度為零的零值轉換的結果為無字元。
o, u, x, X
unsigned int 引數將轉換為無符號八進位制(o),無符號十進位制(u)或無符號十六進位制表示法(x)。括號之間的字元(掃描列表)構成掃描集,除非左括號後的字元是插入符(X)格式為 dddd;字母abcdef用於x轉換和字母ABCDEF用於X轉換。精度指定要出現的最小位數;如果要轉換的值可以用更少的位數表示,則它將用前導零擴充套件。預設精度為 1。將精度為零的零值轉換的結果為無字元。
e, F
表示(有限)浮點數的 double 引數將轉換為十進位制表示法,格式為 []ddd.ddd,其中小數點字元後的位數等於精度規範。如果精度缺失,則預設為 6;如果精度為零且#標誌未指定,則不出現小數點字元。如果出現小數點字元,則在其之前至少出現一位數字。該值將舍入到適當的位數。
(C99)表示無窮大的 double 引數將轉換為以下格式之一 [-]inf[-]infinity— 哪個格式由實現定義。表示 NaN 的 double 引數將轉換為以下格式之一 [-]nan[-]nan(n-char-sequence)— 哪個格式以及任何 n-char-sequence 的含義由實現定義。該F轉換說明符生成INF, INFINITY開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以NAN而不是inf, infinity開頭,如果相應的引數是指向 double 的指標而不是指向 float 的指標,或者以nan,分別。(當應用於無窮大和 NaN 值時,該-, +,和 空格 標誌具有其通常的含義;該#0標誌沒有效果。)
開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, E
表示(有限)浮點數的 double 引數將轉換為以下格式 []d.ddddd,其中小數點字元之前有一位數字(如果引數非零,則該數字非零),小數點字元之後出現的位數等於精度;如果精度缺失,則預設為 6;如果精度為零且#標誌未指定,則不出現小數點字元。該值將舍入到適當的位數。該E轉換說明符生成一個帶有E而不是開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符引入指數。指數始終包含至少兩位數字,並且僅包含表示指數所需的更多位數。如果值為零,則指數為零。
(C99)表示無窮大或 NaN 的 double 引數將轉換為e)。括號之間的字元(掃描列表)構成掃描集,除非左括號後的字元是插入符(F轉換說明符沒有影響。
f, G
表示(有限)浮點數的 double 引數將轉換為以下格式e)。括號之間的字元(掃描列表)構成掃描集,除非左括號後的字元是插入符(開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符(或以以下格式F)。括號之間的字元(掃描列表)構成掃描集,除非左括號後的字元是插入符(EG轉換說明符的情況下),精度指定有效數字的個數。如果精度為零,則預設為 1。使用的格式取決於轉換的值;格式開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符(或E)僅在從這種轉換產生的指數小於 –4 或大於或等於精度時使用。尾隨零將從結果的小數部分刪除,除非#標誌指定;只有在小數點字元後跟有數字時才會出現小數點字元。
(C99)表示無窮大或 NaN 的 double 引數將轉換為e)。括號之間的字元(掃描列表)構成掃描集,除非左括號後的字元是插入符(F轉換說明符沒有影響。
a, A
(C99)表示(有限)浮點數的 double 引數將轉換為以下格式 []0xh.hhhhd,其中小數點字元之前有一位十六進位制數字(如果引數是標準化的浮點數,則該數字非零,否則未指定),小數點字元之後出現的十六進位制數字個數等於精度;如果精度缺失並且 FLT_RADIX 是 2 的冪,則精度足以精確表示該值;如果精度缺失並且 FLT_RADIX 不是 2 的冪,則精度足以區分(精度 p 足以區分源型別的值,如果 16p–1 > bn,其中 bFLT_RADIX,而 n 是源型別有效數字中的以 b 為基數的位數。取決於實現確定小數點字元左側數字的方案,較小的 p 可能就足夠了。)型別 double 的值,但尾隨零可能會被省略;如果精度為零且#標誌未指定,則不出現小數點字元。字母abcdef用於a轉換和字母ABCDEF用於A轉換。該A轉換說明符生成一個帶有XP而不是xp。指數始終包含至少一位數字,並且僅包含表示 2 的十進位制指數所需的更多位數。如果值為零,則指數為零。
表示無窮大或 NaN 的 double 引數將轉換為e)。括號之間的字元(掃描列表)構成掃描集,除非左括號後的字元是插入符(F轉換說明符沒有影響。
一個轉換說明符指令定義了一組匹配的輸入序列,如以下對每個說明符的描述。轉換說明符按以下步驟執行
如果沒有l長度修飾符存在,則 int 引數將轉換為 unsigned char,並將生成的字元寫入。
(C99)如果l長度修飾符存在,則 wint_t 引數將轉換為ls轉換規範,沒有精度,並且引數指向包含 wint_t 引數的兩個元素的 wchar_t 陣列的初始元素,第一個元素包含 wint_t 引數到lc轉換規範,第二個元素是空寬字元。
匹配一個可選的帶符號十六進位制整數,其格式與 strtoul 函式的主題序列(使用 16 作為 base 引數的值)的預期格式相同。相應的引數應是指向無符號整數的指標。
如果沒有l如果存在長度修飾符,則引數應是指向字元型別陣列的初始元素的指標。(對於多位元組字元沒有特殊規定。)從陣列中寫入字元,直到(但不包括)終止空字元。如果指定了精度,則最多寫入那麼多字元。如果未指定精度或精度大於陣列大小,則陣列應包含一個空字元。
(C99)如果l如果存在長度修飾符,則引數應是指向 wchar_t 型別陣列的初始元素的指標。陣列中的寬字元被轉換為多位元組字元(每個字元都像呼叫 wcrtomb 函式一樣,轉換狀態由一個在第一個寬字元轉換之前初始化為零的 mbstate_t 物件描述),直到幷包括終止空寬字元。生成的 多位元組字元被寫入,直到(但不包括)終止空字元(位元組)。如果沒有指定精度,則陣列應包含一個空寬字元。如果指定了精度,則最多寫入那麼多字元(位元組)(包括任何移位序列),並且陣列應包含一個空寬字元,如果要等於由精度給出的多位元組字元序列長度,該函式將需要訪問陣列末尾的一個寬字元。在任何情況下都不會寫入部分多位元組字元。(如果多位元組字元具有狀態相關編碼,則可能會導致冗餘的移位序列。)
p
引數應是指向 void 的指標。指標的值以實現定義的方式轉換為可列印字元序列。
n
引數應是指向有符號整數的指標,該指標寫入到目前為止對該 fprintf 呼叫寫入到輸出流中的字元數。沒有引數被轉換,但會消耗一個引數。如果轉換說明包含任何標誌、欄位寬度或精度,則行為未定義。
%
A%寫入一個字元。沒有引數被轉換。完整的轉換說明應為%%.

如果轉換說明無效,則行為未定義。如果任何引數的型別與相應的轉換說明不匹配,則行為未定義。

在任何情況下,不存在或小的欄位寬度都不會導致欄位截斷;如果轉換的結果比欄位寬度更寬,則欄位會擴充套件以包含轉換結果。

對於aA轉換,如果 FLT_RADIX 是 2 的冪,則該值將被正確舍入為具有給定精度的十六進位制浮點數。

建議的做法是,如果 FLT_RADIX 不是 2 的冪,則結果應為具有給定精度的十六進位制浮點樣式中的兩個相鄰數字之一,並額外規定錯誤應具有當前舍入方向的正確符號。

建議的做法是,對於開頭,如果它是指向 unsigned long int 的指標。最後,轉換說明符, E, e, F, fG轉換,如果有效小數位數最多為 DECIMAL_DIG,則結果應被正確舍入。(對於二進位制到十進位制的轉換,結果格式的值是可以使用給定格式說明符表示的數字。有效位數由格式說明符決定,對於定點轉換,還由源值決定。)如果有效小數位數超過 DECIMAL_DIG 但源值可以用 DECIMAL_DIG 位數精確表示,則結果應為帶有尾隨零的精確表示。否則,源值將被兩個相鄰的十進位制字串 L < U 限制,兩者都具有DECIMAL_DIG有效位數;結果十進位制字串 D 的值應滿足 L ≤ D ≤ U,並額外規定錯誤應具有當前舍入方向的正確符號。

fprintf 函式返回傳輸的字元數,如果發生輸出或編碼錯誤,則返回負值。

printf 函式等效於 fprintf,只是在 printf 的引數之前插入了引數 stdout。它返回傳輸的字元數,如果發生輸出錯誤,則返回負值。

sprintf 函式等效於 fprintf,不同之處在於引數 s 指定一個數組,生成的輸入將寫入該陣列,而不是寫入流。在寫入的字元末尾寫入一個空字元;它不計入返回的總和。如果在重疊的物件之間進行復制,則行為未定義。該函式返回寫入陣列中的字元數,不包括終止空字元。

vfprintf 函式等效於 fprintf,只是可變引數列表被 arg 替換,arg 應已由 va_start 宏(以及可能隨後的 va_arg 呼叫)初始化。vfprintf 函式不會呼叫 va_end 宏。該函式返回傳輸的字元數,如果發生輸出錯誤,則返回負值。

vprintf 函式等效於 printf,只是可變引數列表被 arg 替換,arg 應已由 va_start 宏(以及可能隨後的 va_arg 呼叫)初始化。vprintf 函式不會呼叫 va_end 宏。該函式返回傳輸的字元數,如果發生輸出錯誤,則返回負值。

vsprintf 函式等效於 sprintf,只是可變引數列表被 arg 替換,arg 應已由 va_start 宏(以及可能隨後的 va_arg 呼叫)初始化。vsprintf 函式不會呼叫 va_end 宏。如果在重疊的物件之間進行復制,則行為未定義。該函式返回寫入陣列中的字元數,不包括終止空字元。


參考資料

[edit | edit source]
  1. C99 §6.2.5/15
上一個:錯誤處理 C 程式設計 下一個:字串
華夏公益教科書