printf 字符串格式化

在將各類類型的數據構形成字符串時,sprintf 的強大功能不多會讓你失望。因爲sprintf 跟printf 在用法上幾乎同樣,只是打印的目的地不一樣而已,前者打印到字符串中,後者則直接在命令行上輸出。這也致使sprintf 比printf 有用得多。數據庫

sprintf 是個變參函數,定義以下: 
int sprintf( char *buffer, const char *format [, argument] ... );除了前兩個參數類型固定外,後面能夠接任意多個參數。而它的精華,顯然就在第二個參數:格式化字符串上。數組

printf 和sprintf 都使用格式化字符串來指定串的格式,在格式串內部使用一些以"%"開頭的格式說明符(format specifications)來佔據一個位置,在後邊的變參列表中提供相應的變量,最終函數就會用相應位置的變量來替代那個說明符,產生一個調用者想要 的字符串。安全

1、格式化數字字符串


sprintf 最多見的應用之一莫過於把整數打印到字符串中,因此,spritnf 在大多數場合能夠替代itoa。網絡

如: 
//把整數123 打印成一個字符串保存在s 中。 
sprintf(s, "%d", 123); //產生"123"能夠指定寬度,不足的左邊補空格: 
sprintf(s, "%8d%8d", 123, 4567); //產生:" 123 4567"固然也能夠左對齊: 
sprintf(s, "%-8d%8d", 123, 4567); //產生:"123 4567"函數

也能夠按照16 進制打印: 
sprintf(s, "%8x", 4567); //小寫16 進制,寬度佔8 個位置,右對齊 
sprintf(s, "%-8X", 4568); //大寫16 進制,寬度佔8 個位置,左對齊編碼

這樣,一個整數的16 進制字符串就很容易獲得,但咱們在打印16 進制內容時,一般想要一種左邊補0 的等寬格式,那該怎麼作呢?很簡單,在表示寬度的數字前面加個0 就能夠了。 
sprintf(s, "%08X", 4567); //產生:"000011D7" 
上面以"%d"進行的10 進制打印一樣也可使用這種左邊補0 的方式。命令行

這裏要注意一個符號擴展的問題:好比,假如咱們想打印短整數(short)-1 的內存16 進製表示形式,在Win32 平臺上,一個short 型佔2 個字節,因此咱們天然但願用4 個16 進制數字來打印它: 
short si = -1; 
sprintf(s, "%04X", si); 
產 生"FFFFFFFF",怎麼回事?由於spritnf 是個變參函數,除了前面兩個參數以外,後面的參數都不是類型安全的,函數更沒有辦法僅僅經過一個"%X"就能得知當初函數調用前參數壓棧時被壓進來的到底 是個4 字節的整數仍是個2 字節的短整數,因此採起了統一4 字節的處理方式,致使參數壓棧時作了符號擴展,擴展成了32 位的整數-1,打印時4 個位置不夠了,就把32 位整數-1 的8 位16 進制都打印出來了。scala

若是你想看si 的原本面目,那麼就應該讓編譯器作0 擴展而不是符號擴展(擴展時二進制左邊補0 而不是補符號位): 
sprintf(s, "%04X", (unsigned short)si); 
就能夠了。或者: 
unsigned short si = -1; 
sprintf(s, "%04X", si);3d

sprintf 和printf 還能夠按8 進制打印整數字符串,使用"%o"。注意8 進制和16 進制都不會打 
印出負數,都是無符號的,實際上也就是變量的內部編碼的直接的16 進制或8 進製表示。指針

2、控制浮點數打印格式


浮點數的打印和格式控制是sprintf 的又一大經常使用功能,浮點數使用格式符"%f"控制,默認保 
留小數點後6 位數字,好比: 
sprintf(s, "%f", 3.1415926); //產生"3.141593" 
但有時咱們但願本身控制打印的寬度和小數位數,這時就應該使用:"%m.nf"格式,其中m 表 
示打印的寬度,n 表示小數點後的位數。好比: 
sprintf(s, "%10.3f", 3.1415626); //產生:" 3.142" 
sprintf(s, "%-10.3f", 3.1415626); //產生:"3.142 " 
sprintf(s, "%.3f", 3.1415626); //不指定總寬度,產生:"3.142"

注意一個問題,你猜 
int i = 100; 
sprintf(s, "%.2f", i); 
會打出什麼東東來?"100.00"?對嗎?本身試試就知道了,同時也試試下面這個: 
sprintf(s, "%.2f", (double)i); 
第 一個打出來的確定不是正確結果,緣由跟前面提到的同樣,參數壓棧時調用者並不知道跟i相對應的格式控制符是個"%f"。而函數執行時函數自己則並不知道當 年被壓入棧裏的是個整數,因而可憐的保存整數i 的那4 個字節就被不禁分說地強行做爲浮點數格式來解釋了,整個亂套了。不過,若是有人有興趣使用手工編碼一個浮點數,那麼倒可使用這種方法來檢驗一下你手工編 排的結果是否正確。

3、字符

/Ascii

碼對照

咱們知道,在C/C++語言中,char 也是一種普通的scalable 類型,除了字長以外,它與short,int,long 這些類型沒有本質區別,只不過被你們習慣用來表示字符和字符串而已。(或許當年該把這 個類型叫作"byte",而後如今就能夠根據實際狀況,使用byte 或short 來把char 經過typedef 定義出來,這樣更合適些)因而,使用"%d"或者"%x"打印一個字符,便能得出它的10 進制或16 進制的ASCII 碼;反過來,使用"%c"打印一個整數,即可以看到它所對應的ASCII 字符。如下程序段把全部可見字符的ASCII 碼對照表打印到屏幕上(這裏採用printf,注意"#"與"%X"合用時自動爲16 進制數增長"0X"前綴): 
for(int i = 32; i < 127; i++) { 
printf("[ %c ]: %3d 0x%#04X\n", i, i, i); 
}

4、鏈接字符串


sprintf 的格式控制串中既然能夠插入各類東西,並最終把它們"連成一串",天然也就可以鏈接字符串,從而在許多場合能夠替代strcat,但sprintf 可以一次鏈接多個字符串(天然也能夠同時在它們中間插入別的內容,總之很是靈活)。好比: 
char* who = "I"; 
char* whom = "CSDN"; 
sprintf(s, "%s love %s.", who, whom); //產生:"I love CSDN. " 
strcat 只能鏈接字符串(一段以''結尾的字符數組或叫作字符緩衝,null-terminated-string),但有時咱們有兩段字符緩衝區,他們並非以 ''結尾。好比許多從第三方庫函數中返回的字符數組,從硬件或者網絡傳輸中讀進來的字符流,它們未必每一段字符序列後面都有個相應的''來結尾。若是直接 鏈接,無論是sprintf 仍是strcat 確定會致使非法內存操做,而strncat 也至少要求第一個參數是個null-terminated-string,那該怎麼辦呢?咱們天然會想起前面介紹打印整數和浮點數時能夠指定寬度,字符串 也同樣的。好比: 
char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'}; 
char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'}; 
若是: 
sprintf(s, "%s%s", a1, a2); //Don't do that! 
十有八九要出問題了。是否能夠改爲: 
sprintf(s, "%7s%7s", a1, a2); 
也沒好到哪兒去,正確的應該是: 
sprintf(s, "%.7s%.7s", a1, a2);//產生:"ABCDEFGHIJKLMN" 
這 能夠類比打印浮點數的"%m.nf",在"%m.ns"中,m 表示佔用寬度(字符串長度不足時補空格,超出了則按照實際寬度打印),n 才表示從相應的字符串中最多取用的字符數。一般在打印字符串時m 沒什麼大用,仍是點號後面的n 用的多。天然,也能夠先後都只取部分字符: 
sprintf(s, "%.6s%.5s", a1, a2);//產生:"ABCDEFHIJKL" 
在許多時候,咱們或許還但願這些格式控制符中用以指定長度信息的數字是動態的,而不是靜態指定的,由於許多時候,程序要到運行時纔會清楚到底須要取字符數組 中的幾個字符,這種動態的寬度/精度設置功能在sprintf 的實現中也被考慮到了,sprintf 採用"*"來佔用一個原本須要一個指定寬度或精度的常數數字的位置,一樣,而實際的寬度或精度就能夠和其它被打印的變量同樣被提供出來,因而,上面的例子 能夠變成: 
sprintf(s, "%.*s%.*s", 7, a1, 7, a2); 
或者: 
sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2); 
實際上,前面介紹的打印字符、整數、浮點數等均可以動態指定那些常量值,好比: 
sprintf(s, "%-*d", 4, 'A'); //產生"65 " 
sprintf(s, "%#0*X", 8, 128); //產生"0X000080","#"產生0X 
sprintf(s, "%*.*f", 10, 2, 3.1415926); //產生" 3.14"

5、打印地址信息


有時調試程序時,咱們可能想查看某些變量或者成員的地址,因爲地址或者指針也不過是個32 位的數,你徹底可使用打印無符號整數的"%u"把他們打印出來: 
sprintf(s, "%u", &i); 
不過一般人們仍是喜歡使用16 進制而不是10 進制來顯示一個地址: 
sprintf(s, "%08X", &i); 
然而,這些都是間接的方法,對於地址打印,sprintf 提供了專門的"%p": 
sprintf(s, "%p", &i); 
我以爲它實際上就至關於: 
sprintf(s, "%0*x", 2 * sizeof(void *), &i); 
利用sprintf 的返回值 
較少有人注意printf/sprintf 函數的返回值,但有時它倒是有用的,spritnf 返回了本次函數調用最終打印到字符緩衝區中的字符數目。也就是說每當一次sprinf 調用結束之後,你無須再調用一次strlen 便已經知道告終果字符串的長度。如:int len = sprintf(s, "%d", i);對於正整數來講,len 便等於整數i 的10 進制位數。下面的是個完整的例子,產生10 個[0, 100)之間的隨機數,並將他們打印到一個字符數組s 中,以逗號分隔開。 
#include 
#include 
#include 
int main() { 
srand(time(0)); 
char s[64]; 
int offset = 0; 
for(int i = 0; i < 10; i++) { 
offset += sprintf(s + offset, "%d,", rand() % 100); 

s[offset - 1] = '\n';//將最後一個逗號換成換行符。 
printf(s); 
return 0; 

設想當你從數據庫中取出一條記錄,而後但願把他們的各個字段按照某種規則鏈接成一個字符串時,就可使用這種方法,從理論上講,他應該比不斷的strcat 效率高,由於strcat 每次調用都須要先找到最後的那個''的位置,而在上面給出的例子中,咱們每次都利用sprintf 返回值把這個位置直接記下來了。

6、使用

sprintf

的常見問題


sprintf 是個變參函數,使用時常常出問題,並且只要出問題一般就是能致使程序崩潰的內存訪問錯誤,但好在由sprintf 誤用致使的問題雖然嚴重,卻很容易找出,無非就是那麼幾種狀況,一般用眼睛再把出錯的代碼多看幾眼就看出來了。

  1. 緩衝區溢出 
    第一個參數的長度過短了,沒的說,給個大點的地方吧。固然也多是後面的參數的問題,建議變參對應必定要細心,而打印字符串時,儘可能使用"%.ns"的形式指定最大字符數。
  2. 忘記了第一個參數低級得不能再低級問題,用printf 用得太慣了。//偶就常犯。
  3. 變參對應出問題 
    一般是忘記了提供對應某個格式符的變參,致使之後的參數通通錯位,檢查檢查吧。尤爲是對應"*"的那些參數,都提供了嗎?不要把一個整數對應一個"%s",編譯器會以爲你欺她太甚了(編譯器是obj 和exe 的媽媽,應該是個女的,:P)。

7、其餘

strftime sprnitf 還有個不錯的表妹:strftime,專門用於格式化時間字符串的,用法跟她表哥很像,也是一大堆格式控制符,只是畢竟小姑娘家心細,她還要調用者指定緩衝區的最大長度,多是爲了在出現問題時能夠推卸責任吧。這裏舉個例子: time_t t = time(0); //產生"YYYY-MM-DD hh:mm:ss"格式的字符串。 char s[32]; strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime(&t)); sprintf 在MFC 中也能找到他的知音:CString::Format,strftime 在MFC 中天然也有她的同道: CTime::Format,這一對因爲從面向對象哪裏獲得了贊助,用以寫出的代碼更覺優雅

相關文章
相關標籤/搜索