2010年11月4日 星期四

sprintf用法解析

作者:晨星     http://www.cnitblog.com/liaoqingshan/archive/ 2008/03/06 /40573.html

1
sprintf 最常見的應用之一莫過於把整數列印到字串中,所以,spritnf在大多數場合可以替代itoa
這樣,一個整數的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 進制都列印出來了。如果你想看si 的本來面目,那麼就應該讓編譯器做0 擴展而不是符號擴展(擴展時二進位左邊補0 而不是補符號位元):

sprintf(s, "%04X", (unsigned short)si);
就可以了。或者:

unsigned short si = -1;
sprintf(s, "%04X", si);


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:
連接字串

sprintf
的格式控制串中既然可以插入各種東西,並最終把它們連成一串,自然也就能夠連接字串,從而在許多場合可以替代strcat,但sprintf 能夠一次連接多個字串(自然也可以同時在它們中間插入別的內容,總之非常靈活)。比如:
char* who = "I";
char* whom = "CSDN";
sprintf(s, "%s love %s.", who, whom); //產生:"I love CSDN. "

strcat
只能連接字串(一段以’\ 0’結尾的字元陣列或叫做字元緩衝,null-terminated-string),
但有時我們有兩段字元緩衝區,他們並不是以’\ 0’結尾。比如許多從第三方庫函數中返回的字元陣列,從硬體或者網路傳輸中讀進來的字元流,它們未必每一段字元序列後面都有個相應的’\ 0’來結尾。如果直接連接,不管是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函數將輸出寫入到字串s中,並以'\0'結束,所以生成的s中有'\0',所以可以用printf(s),而不用擔心會出錯)
在許多時候,我們或許還希望這些格式控制符中用以指定長度資訊的數位是動態的,而不是靜態指定的,因為許多時候,程式要到運行時才會清楚到底需要取字元陣列中的幾個字元,這種動態的寬度/精度設置功能在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"

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。