表示結果是解決任何問題中很重要的一部分 本章將會深入介紹 scanf 和 printf 的格式化功能 這兩個函式分別可以用來從標準輸入資料流 (standard input stream) 輸入資料, 以及從標準輸出資料流 (standard output stream) 輸出資料 第 8 章曾討論過其他四個使用標準輸入和標準輸出的函式 -gets puts getchar 和 putchar 呼叫上述的函式時, 必須含入 stdio.h 標頭
所有的輸入和輸出都是以資料流 (streams) 來完成, 資料流也就是位元組所形成的序列 當輸入操作時, 位元組就會從裝置 ( 例如, 鍵盤 磁碟機或者網路連線 ) 流向主記憶體 在輸出操作當中, 就是從主記憶體流向一個裝置 ( 例如顯示器 印表機 磁碟機 網路連線 ) 的位元組串流 當程式開始執行時, 三種資料流會自動連到程式
通常標準輸入資料流會連到鍵盤, 而標準輸出資料流則會連到螢幕 作業系統通常允許將這些資料流重新導向 (redirected) 到其他的裝置 第三個資料流, 標準錯誤資料流 (standard error stream) 也是連接到顯示器 錯誤訊息會輸出到標準錯誤資料流
精確的格式化輸出是以 printf 來完成的 每一個 printf 呼叫都包含一個格式控制字串 (format control string), 它會用來描述輸出的格式 格式控制字串也包含轉換指定詞 旗標 欄位寬度 精確度和字元常數 再加上使用 ( % ), 就構成轉換指定 (conversion specifications)
printf 函式可以執行以下列出的格式化功能, 本章將討論所列出的每一種功能 捨入 (Rounding) 浮點數值到指定的小數位數 對齊 (Aligning) 有多行數字的小數欄位 將輸出的結果向右對齊 (Right-justification) 或向左對齊 (left-justification) 在一行輸出當中精確的位置插入字元常數 (Inserting literal characters) 以指數格式表示浮點數 (floating-point numbers) 以八進位制和十六進位制的格式來表示無號整數 ( 請參閱附錄 C 數字系統, 以便獲得更多關於八進位和十六進位的資訊 ) 用固定的欄位寬度和精確度來顯示所有型別的資料
printf 函式有以下格式 : printf( format-control-string, other-arguments ); 其中 format-control-string 描述輸出格式, 而 other-arguments( 不一定要有 ) 則對應到 format-control-string 中的每一種轉換指定 每個轉換指定都是以 % 開始, 並且用轉換指定詞當做結束 同一個格式控制字串中可能具有許多個轉換指定
整數是不含小數的數字, 例如 776 0 或 52 整數值可以使用幾種格式中的一種來加以顯示 圖 9.1 描述整數轉換 (integer conversion) 指定詞
圖 9.2 的程式使用整數轉換指定詞來顯示整數 請注意它只有顯示出負號, 而不會顯示正號 還有使用 %u 讀取 -455 時 ( 第 15 行 ), 它會轉換成無號值 4294966841
浮點數是指包含小數的數值, 例如 33.5 0.0 或 - 657.983 浮點數值可以使用幾種格式當中的一種來加以顯示 圖 9.3 描述浮點數轉換指定詞 轉換指定詞 (conversion specifiers) e 和 E 會將浮點數以指數記號 (exponential notation) 顯示 指數表示法在電腦中和數學的科學記號 (scientific notation) 是一樣的
例如,150.4582 可以用科學記號表示如下 : 1.504582 10 2 也可以用電腦將指數記號表示成 1.504582E+02 這個記號代表 1.504582 乘上 10 的二次方 (E+02) 其中 E 代表 指數
用轉換指定詞 e E 和 f 顯示出來的值, 預設小數點右邊會有 6 位數的精確度 ( 例如 1.04592); 但我們也可明確地指定不一樣的精確度 轉換指定詞 f 總是至少會在小數點前面顯示一位 轉換指定詞 e 和 E 會分別在指數前面顯示小寫的 e 和大寫的 E, 它們都只在小數點前面顯示一位 轉換指定詞 g ( 或 G ) 會顯示沒有補上 0 的 e( E ) 或 f 格式 ( 例如 1.234000 會顯示成 1.234)
如果數值轉換成指數記號之後, 該數值的指數小於 -4 或是大於等於指定的精準度 ( 也就是 g 和 G 預定的六位有效位數 ), 則這個數值就會使用 e (E) 顯示 ; 不然就會使用轉換指定詞 f 顯示這個數值 使用 g 或 G 輸出數值的小數部分補上的 0 不會顯示 但至少會輸出一個小數位數
使用轉換指定詞 g, 數值 0.0000875 8750000.0 8.75 87.50 和 875 將分別會顯示成 8.75e-05 8.75e+06 8.75 87.5 和 875 數值 0.0000875 使用了 e 的表示法, 因為當它轉換成指數記號時, 它的指數會小於 -4 ( 也就是 -5) 數值 8750000.0 也使用 e 的表示法, 因為它的指數 ( 6 ) 等於預設的精準度
轉換指定詞 g 和 G 指出顯示的最大有效數字, 這包括小數點左邊的有效數字 如果使用 %g, 數值 1234567.0 將會顯示為 1.23457e+06 ( 請記住, 所有的浮點數轉換指定詞的預設精確度都是 6) 請注意, 這個結果中共有 6 位有效數字 當數值以指數記號顯示時,g 和 G 之間的差別與 e 和 E 之間的差別是相同的 - 小寫的 g 會輸出小寫的 e, 大寫的 G 會輸出大寫的 E
圖 9.4 示範每個浮點數轉換指定詞的用法 請注意,%E %e 以及 %g 轉換指定詞會使輸出的數值四捨五入, 而轉換指定詞 %f 則不會 某些編譯器的輸出中, 指數會以加號再加兩位數字的形式出現
轉換指定詞 c 和 s 分別是用來顯示個別的字元和字串 轉換指定詞 c 必須使用 char 引數, 轉換指定詞 s 的引數是指向 char 的指標 轉換指定詞 s 會不斷顯示字元, 直到遇到結束的空 ('\0') 字元為止 圖 9.5 中的程式說明使用指定詞 c 和 s 的字元和字串
剩下的三種轉換指定詞是 p n 和 % ( 圖 9.6) 轉換指定詞 %n 會儲存目前 printf 敘述式已輸出的字元個數 - 相對應的引數是一個指向整數變數的指標, 而字元個數儲存在該變數中 使用 %n 時不會顯示任何東西 轉換指定詞 % 則使百分比符號用來輸出
圖 9.7 的程式當中,%p 顯示了 ptr 的值以及 x 的位址 ; 因為 ptr 的值設定為 x 的位址, 所以這兩個值是相同的 接著 %n 將第三個 printf 敘述式 ( 第 15 列 ) 顯示過的字元個數存放到整數變數 y, 然後顯示 y 的值 最後一個 printf 敘述式 ( 第 21 行 ) 則使用了 %% 來顯示字元字串當中的 % 字元
要注意每個 printf 呼叫都會傳回一個值 - 如果輸出發生錯誤, 就會傳回一個負值 ; 不然就會傳回輸出的字元個數 [ 請注意 : 本範例無法在微軟的 Visual C++ 環境中執行, 微軟由於 安全因素 限制了 %n 的使用 ]
顯示在欄位當中資料的位數是由欄位寬度 (field width) 來指定 如果欄位寬度大於要顯示的資料, 則程式的資料在欄位中會靠右對齊 代表欄位寬度的整數, 可以加入轉換指定詞中的百分比符號 ( % ) 與轉換指定詞之間 ( 例如 :%4d) 圖 9.8 中的程式顯示兩組整數, 每一組有 5 個整數 若要顯示的數值位數小於欄位寬度, 則此數字會靠右對齊 若要顯示的數值的位數大於欄位寬度, 則程式會自動增加欄位寬度 負數的負號則會佔欄寬的一個位置 欄位寬度可以與各種轉換指定詞一起使用
printf 函式也提供指定顯示資料精確度的功能 精確度對於不同的資料型別有不同的意義 和整數轉換指定詞一起使用時, 精確度指出程式至少必須顯示多少個位數 如果顯示數值的位數小於指定的精確度, 則多出來的位數就會補上 0 假如精準度並非以零或是小數點來表示, 則會補上空白
整數預設的精確度是 1 當和浮點數轉換指定詞 e E 和 f 一起使用的時候, 精確度代表小數點後面應該有幾個位數 和轉換指定詞 g 和 G 一起使用的時候, 精準度代表的是顯示的最大有效數字位數 和轉換指定詞 s 一起使用的時候, 精確度代表的是從這個字串顯示的字元個數的最大值 當使用精確度時, 放一個小數點 (. ), 接著, 在轉換指定詞和百分比符號之間放置一個表示精確度的整數
圖 9.9 中的程式示範如何在格式控制字串中使用精確度 要注意, 顯示浮點數值時, 假如所指定的精確度小於浮點數的小數位數, 則此浮點數就會四捨五入
欄位寬度和精確度可以一起使用, 方法是先寫欄位寬度, 再寫一個小數點, 最後才寫精確度 例如以下敘述式 printf( "%9.3f", 123.456789 ); 會顯示 123.457, 小數點右邊有三位, 並且在寬度為 9 的欄位當中向右對齊 程式也可以在格式控制字串後面的引數列中, 使用整數運算式來指定欄位寬度和精確度
要使用這項功能, 你必須加入星號 ( * ) 取代欄位寬度或精確度 ( 或是二者 ) 引數列中和 int 相對的引數會先計算出來, 並且放置在星號的位置 欄位寬度的數值可以是正的或負的 ( 負的欄位寬度會讓顯示的資料靠左對齊, 我們會在下一節討論它 ) 敘述式 printf( "%*.*f", 7, 2, 98.736 ); 用 7 代表欄位寬度 2 代表精準度, 並且將輸出的數值 98.74 靠右對齊
printf 函式還提供了旗標來輔助它的格式化輸出功能 共有 5 種旗標可以用於格式控制字串中 ( 圖 9.10) 要在格式化控制字串中使用旗標, 請直接在百分記號右邊放置旗標 同一個轉換指定詞中可以同時使用數個旗標
圖 9.11 的程式示範字串 整數 字元以及浮點數的靠右對齊和靠左對齊
圖 9.12 的程式分別使用有 + 旗標 (flag) 和沒有 + 旗標的方式, 顯示一個正數和一個負數 在兩種情況中都會顯示負號, 但是正號只有在使用 + 旗標的時候才會顯示出來
圖 9.13 的程式使用空白旗標 (space flag) 在一個正數前面增加一格空白 這對於具有同樣位數的正數和負數的對齊很有幫助 請注意, 因為負號的關係, 輸出值 -547 之前不會放置一個空白
圖 9.14 的程式使用 # 旗標 (flag) 在八進位制數值的前面加上一個 0, 在十六進位制數值前面加上 0x 和 0X, 並強迫以 g 顯示的數值顯示小數點
圖 9.15 的程式結合了 + 旗標和 0 ( 零 ) 旗標, 在 9 位的欄位當中將 452 顯示成具有 + 號和前導零, 然後只使用 0 旗標和 9 個欄位將 452 再顯示一次
大多數想用 printf 敘述式顯示的字元常數都可以放在格式控制字串中 但是有許多有 問題 的字元, 像是用來隔開格式控制字串的雙引號 ( " ) 新行和水平跳格都必須用跳脫序列 (escape sequences) 來表示 跳脫序列會用反斜線 ( \ ) 加上一個特別的跳脫字元 (escape character) 來加以表示 圖 9.16 列出所有的跳脫序列以及它們執行的動作
精確的輸入格式化會使用 scanf 來加以完成 每個 scanf 敘述式都包含格式控制字串, 它會用來描述要輸入資料的格式 格式控制字串包含轉換指定詞和字元常數 scanf 函式具有以下的輸入格式化功能 : 輸入所有型別的資料 從輸入資料流當中輸入指定的字元 跳過輸入資料流中的某些指定字元
scanf 函式會寫成以下的形式 : scanf( format-control-string, other-arguments ); 其中 format-control-string 描述輸入的格式, other-arguments 則是一些指向變數的指標, 這些變數會用來存放輸入的資料
圖 9.17 整理用來輸入所有型別的資料的轉換指定詞 本節剩餘的部分會提供程式來示範如何使用各 scanf 轉換指定詞來讀取資料
圖 9.18 的程式使用不同的引數轉換指定詞來讀取數個引數, 然後以十進位制將這些引數顯示出來 注意, 其中的 %i 可以用來輸入十進位制 八進位制 以及十六進位制的整數
要輸入浮點數的時候, 程式可以使用浮點轉換指定詞 e E f g 或 G 圖 9.19 的程式使用三種浮點轉換指定詞讀進三個浮點數 並且將這三個數字以轉換指定詞 f 顯示出來 程式的輸出說明浮點數值的不精確性, 你可以從第三個顯示的數值察覺這項事實
字元和字串分別用轉換指定詞 c 和 s 來進行輸入 圖 9.20 的程式會提示使用者輸入一個字串 這個程式使用 %c 來讀取字串中的第一個字元, 將它存放到字元變數 x, 然後用 %s 讀入字串的剩餘部分, 並且將它存到字元陣列 y
一連串的字元可以用掃瞄集 (scan set) 來進行輸入 掃瞄集是格式控制字串中, 一些由中括號 [] 括起來, 並且前面加上百分比符號的字元 掃瞄集會掃瞄輸入資料流中的字元, 找出屬於此掃瞄集的字元 每次找到一個字元時, 程式就會存放到掃瞄集對應的引數中 - 也就是指向字元陣列的指標 當遇到一個不包含在掃描集中的字元時, 掃描集就停止輸入字元
如果輸入資料流的第一個字元不在掃瞄集中, 則只有 null 字元會存放到陣列中 圖 9.21 的程式會使用掃瞄集 [aeiou] 來掃瞄輸入資料流中的母音字母 注意, 程式會讀入前 7 個字母 第 8 個字母 ( h ) 不屬於掃瞄集, 所以掃瞄的動作到此結束
我們也可以使用反掃瞄集 (inverted scan set) 來掃瞄不在掃瞄集中的字元 只要在掃瞄集中的掃瞄字元的前面加上一個脫字符號 (^) 就可建立反掃瞄集 這會使 scanf 儲存沒有出現在掃描集輸入的字元 當遇到第一個屬於反掃瞄集中的字元時, 這個輸入動作就會結束 圖 9.22 的程式使用反掃瞄集 [^aeiou] 來找尋子音字母 - 更適當的說法是尋找 非母音 字母
欄位寬度也可以用於 scanf 的轉換指定詞中, 用來從輸入資料流讀取指定個數的字元 圖 9.23 的程式會輸入一連串的數字字元, 其中前兩個字元讀取成兩位數的整數, 其他的字元則讀取成另一個整數
我們常需要跳過輸入資料流中的某些字元 例如日期可能會輸入為 11-10-1999 日期中的每一個數字都需要儲存起來, 但分隔數字的一劃則可以丟棄 為了消去不需要的字元, 我們可以將這些字元放到 scanf 格式控制字串中 ( 空白字元如空格 新行和跳格 - 這些會跳過所有前導的空白 )
例如, 要在輸入中跳過橫線, 你可以使用敘述式 scanf( "%d-%d-%d", &month, &day, &year ); 雖然這個 scanf 可以消去上述輸入的橫線, 但是日期也可能會以下面的方式進行輸入 10/11/1999 在這種情形, 前一個 scanf 將無法去除不需要的字元 基於這個原因,scanf 提供設定禁止字元 (assignment suppression character)*
這個字元可使 scanf 從輸入的資料流中讀進任何型別的資料, 並將它丟棄而不設定給變數 圖 9.24 的程式在 %c 轉換指定中使用了設定禁止字元, 表示應該從輸入資料流中讀入一個字元並且將它丟棄 所以程式只會儲存月 日 年 將這些變數的值顯示出來以證明它們都是正確輸入的 每個 scanf 呼叫的引數列上沒有變數會對應到使用設定禁止字元的轉換指定詞, 因為程式並不會對這種轉換指定執行任何的指定動作 對應的字元只會丟棄