天天看點

《UNIXLinux程式設計教程》一2.8 格式I/O

前幾節介紹的流i/o函數除了以字元或行方式進行讀寫外,并不對資料進行解釋,但在很多時候應用都會需要對輸入輸出資料進行解釋,因為資料在計算機内的表示和人們可讀的形式是不同的。資料在計算機内是二進制形式,在計算機外部常常為正文形式。例如,十進制數12在計算機内部的32位二進制表示是:00000000000000000000000000001100。當這個數在列印機上輸出或者在終端螢幕上顯示時,必須轉換為字元'1'和'2',它們的ascii編碼分别為00110001和00110010。反之,當從鍵盤讀入用ascii字元表示的十進制整數時,必須将它們轉換成計算機可處理的二進制表示。格式i/o函數能夠自動完成這種外部和内部格式之間的轉換工作,并且能夠對輸入輸出資料進行諸如資料類型、精度、位置等格式控制。

所有格式i/o函數都通過一個格式字元串來對其餘參數進行格式描述。格式字元串中用轉換區分符來描述待輸入輸出參數的類型、精度、外部形式以及占據的位元組寬度等。掌握好轉換區分符是使用格式i/o函數的關鍵。這一節介紹格式i/o函數,給出轉換區分符的文法成分,并通過例子說明它們的用法。

格式輸出由如下三種printf()函數來處理,它們是完成輸出最友善的函數。

這三個函數功能相同,都在格式字元串format的控制下輸出其他參數(這裡表示為“...”)。不同的隻是輸出的流不同:printf()輸出至标準輸出流;fprintf()輸出至參數stream指定的流;sprintf()不是輸出至一個檔案,而是輸出至參數buf所指的字元數組中,并且在buf的末尾自動添加一個空位元組。它們調用成功均傳回實際輸出的字元個數。

這些函數允許任意個數參數,格式字元串format中的轉換區分符隐含地指明後繼參數個數以及怎樣解釋和格式化這些參數。如果轉換區分符的個數少于後繼的參數個數,多餘的參數将被忽略;如果轉換區分符的個數多于後繼的參數個數,多餘的轉換區分符輸出的内容是不确定的。

格式字元串format由兩類成分組成:普通字元和轉換區分符。普通字元是除轉換區分符之外的其餘字元。輸出格式字元串中的普通字元簡單地按原樣寫至輸出流。

轉換區分符是以'%'開頭、按如下文法規則組成的連續字元:

其中隻有fmt是必需的。fmt是表2-1列出的字元之一,稱為轉換字元。轉換字元用于指明參數的類型和基本格式。形如%fmt的轉換區分符是最基本且使用最頻繁的形式。

《UNIXLinux程式設計教程》一2.8 格式I/O
《UNIXLinux程式設計教程》一2.8 格式I/O

下面是使用單個格式轉換字元輸出的例子:

《UNIXLinux程式設計教程》一2.8 格式I/O

為了更精确地對格式進行控制,在'%'和轉換字元之間也可以有選擇地寫上各種修飾符。例如,可以用修飾符posp$指定下一個要輸入輸出的參數,用width指定資料占據的最小域寬度,用prec指定浮點數小數點後保留的位數,還可以用标志flags指定結果在域中的對齊方式。表2-2給出了主要的修飾符及其作用。

《UNIXLinux程式設計教程》一2.8 格式I/O
《UNIXLinux程式設計教程》一2.8 格式I/O

例如,在轉換區分符'%-10.8ld'中,'-'是一個标志;'10'指明域寬,其精度為'8';字母'l'是類型修飾符(size),它指明參數所占存儲位元組大小;而最後一個字母'd'是轉換字元。這個特定的轉換區分符指明了在10字元寬度的域中以8位數左對齊方式列印一個類型為long int的十進制整數。

轉換區分符所允許的修飾符以及含義根據具體的轉換字元而變化,它們看起來似乎有點複雜。不過,在開始時我們幾乎總是能夠完全不用這些修飾符而實作相當自由的格式輸出。完全掌握轉換區分符并不困難,在掌握了它的文法和常用的轉換字元之後,便可以根據需要靈活使用修飾符對列印格式進行調整。修飾符大部分不過是用來使得輸出看起來更漂亮而已。下面是使用各種修飾符列印輸出的例子。

《UNIXLinux程式設計教程》一2.8 格式I/O
《UNIXLinux程式設計教程》一2.8 格式I/O

請注意這些例子中,當參數的實際字元個數超過域寬時,按參數需要的域寬輸出;例如,例4和例15雖然指明了域寬,但實際輸出都超出了域寬範圍。

在各種修飾符中,特别應當注意的是長度修飾符。長度修飾符是保證參數轉換正确性必不可少的。當參數的類型是長整型時,一定要注意使用長度修飾符,否則輸出的結果會不正确。對于浮點類型,由于預設情形下總是将它們提升為雙精度類型進行處理,是以除了精度丢失外不會出現大的錯誤。但是,對于整數類型,不論長度是什麼,預設情形下總是将它們處理成int類型。如果對長度占8位元組的long int類型參數不指定長度修飾符'l',printf()将視為4位元組的int類型進行格式轉換,因而導緻結果錯誤,如例16所示。

标準i/o庫中還有另外三個格式輸出函數vprintf()、vfprintf()、vsprintf(),它們與上述三個函數類似,不同之處僅在于用可變參數指針數組arg替代了輸出變量參數表。

常用的格式輸入函數有如下三個:

這三個函數都按format規定的格式讀資料,不同的是scanf()讀标準輸入流,fscanf()讀stream指定的流,sscanf()不是讀檔案而是讀參數s指定的字元數組。每一個函數根據format參數讀取若幹位元組,按指定的格式解釋它們并存儲結果于對應的參數中。位于format之後的可選參數應當是指向存放接收輸入值的變量的指針,記住這一點非常重要。這些函數的正常傳回值是成功讀入了值的參數個數。

format字元串由三類成分組成:一至多個連續的空白符,普通字元(不包括'%'和空白符),轉換區分符。這裡特别提請讀者注意區分術語“空白符”、“空格符”和“空字元”。空白符是空格符' '、制表符't'、水準制表符'v'、換行符'r'和走紙符'f'的統稱,即isspace()傳回值為真的字元的統稱;空格符隻指' '(即'040');空字元是null(即'0')。

格式字元串中的空白符導緻scanf()跳過輸入流中的空白字元,直至遇到一個未讀過的非空白字元或者遇到檔案尾為止。輸入流中的空白字元不必完全與格式字元串中的空白符相同。例如格式字元串" , "識别輸入流中一個前後有任意空白的逗号。

格式字元串中的普通字元指明必須出現在輸入流中的字元,它必須完全與輸入流中的下一字元相比對,如果不比對将導緻比對失敗。例如:

僅當在标準輸入流中目前的五個連續字元是“hello”時,scanf()調用才會成功。在此之後,如果後面的字元形成了一個可識别的十進制數,則該數将被讀出并存儲在變量num中。這意味着對如下兩種輸入scanf()調用都将成功并指派1234給num:

格式字元串中的轉換區分符指導輸入域的轉換。輸入域定義為輸入流中非空白字元組成的字元序列,其長度直至遇到一個不合适的字元或者到達指定的域寬為止。轉換後的結果存儲在對應的參數中,除非轉換區分符指明了禁止指派标志'*'。

輸入轉換區分符的一般形式為:

其中fmt是表2-1給出的轉換字元,表2-3列出了轉換區分符的常用輸入修飾符。

《UNIXLinux程式設計教程》一2.8 格式I/O

同輸出轉換一樣,輸入轉換也可通過指明域寬來限制輸入吸收的字元數,可以用長度修飾符指明接收輸入值參數的類型長度;也可以用%n$輸入轉換指定将輸入結果存放在第n個參數所指對象中而不是下一個參數對象中;禁止指派标志'*'可以指明要跳過的輸入。下面是使用輸入轉換區分符的一些例子。

對于輸入“hello, world” (注意,在逗号和單詞world之間有一個空格),用%10c讀将産生“hello, wor”,它隻讀入10個字元,結尾沒有空字元;但用%10s讀卻産生“hello,”,它停止在第一個空格字元處并在尾部添加空字元。

如下代碼:

對于輸入“25 54.32e-1 thompson”,将導緻25賦給變量i(因為25之後有一空格,盡管域寬為4),5.432賦給變量x,而name中将包含字元串“thompson”,并且變量n将得到值3。

對于輸入“56789 0123 45a72”,将指派56給i,789.0給x,跳過0123,并放置字元“450”于name,這對應于“%8[0-9]”轉換,它要求讀入8個數字,但是隻讀到第2個數字之後便遇到了字元'a',故實際隻讀入“45”。然後,讀入的字元個數賦給變量n,其值為13。下一個待讀入的字元是'a'。

注意,%n轉換是确定文字比對成功與否的唯一手段。如果%n位于比對失敗點之後則不會有值存入其内,因為scanf()在處理%n之前就已傳回。如果在調用scanf()之前先存儲–1至%n對應的那個參數,則在調用scanf()之後若其值仍為–1就表明到達%n之前已遇到錯誤。

對于輸入值“567890123456 567890123456”,将産生輸出:

結果顯示讀入至變量dx1中的值不對,dx1的值本應與dx2的相同。錯誤原因是dx1對應的轉換區分符沒有指定'l'标志。當參數類型長度與轉換的預設類型長度不一緻時,一定要注意指明長度标志。

格式輸入函數的使用不像格式輸出函數那麼頻繁,部分原因是其用法十分不靈活,正确地使用它比對輸入需要特别仔細,稍不留意便可能出現錯誤。另一個原因是它難以從比對錯誤中恢複。

例2-9 計算機程式常常會要做十–二轉換或二–十轉換,借用格式輸入輸出可以很友善地做到這一點。程式2-9是利用格式輸入輸出實作這種轉換的例子。該程式根據讀入的圓半徑計算圓的面積,計算結果以字元形式存儲在數組buf中。

《UNIXLinux程式設計教程》一2.8 格式I/O