1. 程式人生 > >C 和 C++ 字串格式化

C 和 C++ 字串格式化

---------------------------------------------------------------------------------

程式碼如下:

int main(void)
{
    char url_str[1024];
    int url_len = 0;
    url_len = snprintf(url_str, sizeof(url_str), "%*.*s,5,5,"hello");
    printf("url_len is:%s\n", url_str);
    return 0;
}
剛開始看到 %*.*s 的時候楞了一下,半天沒反應過來,後來想了下,這個與"%5.5s,"hello"是等價的。
不過若是這裡的5和"hello"要是經常變化的值的話,用 %*.*s 就更方便了。
注: %6.9s 表示顯示一個長度不小於6且不大於9的字串。若大於9, 則第9個字元以後的內容將被刪除。

眾所周知,sprintf不能檢查目標字串的長度,可能造成眾多安全問題,所以都會推薦使用snprintf.

自從snprintf代替了sprintf,相信大家對snprintf的使用都不會少,函式定義如下:

int snprintf(char*str, size_t size,constchar*format, ...);

函式說明:

最多從源串中拷貝size1個字元到目標串中,然後再在後面加一個0。所以如果目標串的大小為size的話,將不會溢位。

函式返回值:

若成功則返回欲寫入的字串長度,若出錯則返回負值。

但是大家在使用snprintf的時候是否真的清楚引數裡size的意思呢?看下面的例子:
假設我想將一個char型別的變數列印成2進位制,我應該怎麼寫呢?

之前有個同事這樣寫的:

char a='a';
char str[20];
snprintf(str,2,"%02x",a);

對不對呢,錯了。
1). 2不應該是2,應該是3.
2). 也不建議直接寫3,應該寫sizeof(str)

所以建議的寫法是:

char a='a';
char str[3];//再大點也沒有問題
snprintf(str,sizeof(str),"%02x",a);

解釋如下:
size是限定最終生成的dest的字元數,最多拷貝size-1個字元; 一般情況下size會取sizeof(dest),這是為了dst不溢位.

在snprintf(dest, size, "str: %s\n", src)中如果size-1大於等於"str: %s\n"的長度,則把"str: %s\n"都拷貝到dst; 如果size-1小於"str: %s\n"的長度,則從"str: %s\n"拷貝size-1長度的字串到dst,並且末尾置\0.

就是說,拷貝的長度是size-1和源字串長度的最小值;

對於返回值,需要注意的是snprintf的返回值欲寫入的字串(即源字串長度,而不是實際寫入的字串度。如:

char test[8];int ret= snprintf(test,5,"1234567890"); printf("%d|%s\n",ret,test);

執行結果為:
10|1234

linux和windows下是不同的

linux下用的是snprintf();
而windows下用的是_snprintf();

1. 前言

  在gcc程式設計中,我們比較經常用到的字元格式化輸出函式是printf的,實際上gcc繼承了c語言處理字元具有強大功能的風格,它提供了一系列的格式化輸出函式,主要存在兩個庫函式檔案stdio.h/ stdarg.h中,具體函式如下:

NAME
       -------- formatted output conver --------
       printf,  fprintf,  dprintf, sprintf, snprintf, vprintf, vfprintf, vdprintf, vsprintf, vsnprintf 
       sion


SYNOPSIS
       #include <stdio.h>

       int printf(const char *format, ...);
       int fprintf(FILE *stream, const char *format, ...);
       int dprintf(int fd, const char *format, ...);
       int sprintf(char *str, const char *format, ...);
       int snprintf(char *str, size_t size, const char *format, ...);

       #include <stdarg.h>

       int vprintf(const char *format, va_list ap);
       int vfprintf(FILE *stream, const char *format, va_list ap);
       int vdprintf(int fd, const char *format, va_list ap);
       int vsprintf(char *str, const char *format, va_list ap);
       int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  其中:
  printf和vprintf函式主要是向一個標準輸出裝置或標準的字元流輸出格式化後的字元。
  Fprintf和vfprintf 函式主要是向一個給定的字元流裝置輸出格式化後的字元。
  而sprintf, snprintf, vsprintf 和vsnprintf函式則是將格式化後的結果賦值給一個字串。
  雖然函式的功能上有差異,返回的值的型別也不盡相同,但是在建立匹配格式的語法上還是相同的,這幾個函式都有一些共同特徵,就是你要設計好模板和格式化的字串。這些函式格式化字串的命令主要是通過模板字串中跟在“%”後面的字元來控制的。
  如下一個例子中:
int pct = 37;
char filename[] = "foo.txt";
printf ("Processing of `%s' is %d%% finished.Please be patient.",filename, pct);
  顯然,這個例子的列印結果就是如下:
Processing of `foo.txt' is 37% finished.Please be patient.
  如上例子我們可以看出一般格式化函式的語法特點就是包含格式化匹配的字串,輸出的字串和變數組合的結構。

2. 引數詳細介紹

  由於大部分函式在如何格式化字串部分的語法非常相似,我們先研究他們的共同特點,然後再應用不同的例子來分析其不同特點:此類函式一般的呼叫格式為:printf(";", ;);
  其中格式化字串包括兩部分內容: 一部分是正常字元, 這些字元將按原樣輸出; 另一部分是格式化規定字元, 以"%"開始, 後跟一個或幾個規定字元,用來確定輸出內容格式。參量表是需要輸出的一系列引數, 其個數必須與格式化字串所說明的輸出引數個數一樣多, 各引數之間用","分開, 且順序一一對應, 否則將會出現意想不到的錯誤。

Gcc提供的格式化規定符如下:

%d        十進位制有符號整數
%i        十進位制有符號整數
          注:上面這兩個函式在格式化輸出時用途是相同的,但在輸入時卻是不一樣的,
             %i可以接受任何形式的整數,而%d卻不能。
%u         十進位制無符號整數
%f         輸出浮點數
%s         輸出字串
%c         輸出單個字元
%p         輸出指標的值
%e, %E     指數形式的浮點數 ,其中:%e是以小寫形式輸出的 %E是以大寫形式輸出的
%x, %X     無符號以十六進位制表示的整數,其中:%x是以小寫形式輸出的 %X是以大寫形式輸出的
%g, %G     根據輸出資料的大小需要決定用普通形式還是指數形式的輸出方式,
           其中: %g是以小寫形式輸出的 %G是以大寫形式輸出的
%o         無符號以八進位制表示的整數
%c         輸出單個字元
%n         得到輸出字元的個數,但是本引數不產生任何的輸出效果。
%m         輸出錯誤時的相應的字串提示。
%%         輸出正文字元中的“%”字元

說明: 可以在"%"和字母之間插進數字表示最大場寬。例如: %3d 表示輸出3位整型數, 不夠3位右對齊。%9.2f 表示輸出場寬為9的浮點數, 其中小數位為2, 整數位為6,小數點佔一位, 不夠9位右對齊。%8s 表示輸出8個字元的字串, 不夠8個字元右對齊。如果字串的長度、或整型數位數超過說明的場寬, 將按其實際長度輸出。但對浮點數, 若整數部分位數超過了說明的整數位寬度, 將按實際整數位輸出;若小數部分位數超過了說明的小數位寬度, 則按說明的寬度以四捨五入輸出。另外, 若想在輸出值前加一些0, 就應在場寬項前加個0。例如: %04d 表示在輸出一個小於4位的數值時, 將在前面補0使其總寬度為4位。如果用浮點數表示字元或整型量的輸出格式, 小數點後的數字代表最大寬度,小數點前的數字代表最小寬度。
例如: %6.9s 表示顯示一個長度不小於6且不大於9的字串。若大於9, 則第9個字元以後的內容將被刪除。
         可以在"%"和字母之間加小寫字母l, 表示輸出的是長型數。
例如: %ld 表示輸出long整數%lf 表示輸出double浮點數
         可以控制輸出左對齊或右對齊, 即在"%"和字母之間加入一個"-" 號可
         說明輸出為左對齊, 否則為右對齊。例如: %-7d 表示輸出7位整數左對齊%-10s表示輸出10個字元左對齊

一些特殊規定字元

3. 格式化轉換的具體細則

3.1 整數轉換部分
  整數轉換部分主要是'%d', '%i', '%o', '%u', '%x', 和 '%X'這幾個引數命令的,由於引數的不同,可以輸出不同格式的結果。如上表所列: '%d', '%i'是輸出一個帶符號的十進位制的數,'%o', '%u', and '%x'是輸出一個不帶符號的數,而'%X是'%x''的大寫形式。其中,針對這幾種不同輸出選擇還有如下幾個引數項:

'-’ 表示是左對齊,一般都是右對齊的。 
'+’ 是對'%d', '%i'兩個引數而言的,是指以'+’符號表示正數 
' ' 是對'%d', '%i'兩個引數而言的,如果輸出不是以'+’'-’開頭的,那麼用空格做開頭。 
'#' 是對'%o'引數而言的,將在輸出的結果強制加上'0’為開頭。 
''' 將輸出的數字以LC_NUMERIC的分類法用’,’隔開。 
'0' 將空格的地方用'0'填入。 
  如果沒有特別指明,被格式化的引數被預設當作整數處理,或者可以用以下的型別指定引數來進行修改,如下:
'h' 指定傳入引數是 short int 或unsigned short int型別的 
'l' 指定傳入引數是 long int或unsigned long int型別的 
'q' 指定傳入引數是 long long int型別的 
'Z' 指定傳入引數是size_t.。 
  為了方便理解給出一個例子:
對於如下的格式化匹配字串:
"|%5d|%-5d|%+5d|%+-5d|% 5d|%05d|%5.0d|%5.2d|%d|"
將產生類似如下的輸出:
| 0|0 | +0|+0 | 0|00000| | 00|0|
| 1|1 | +1|+1 | 1|00001| 1| 01|1|
| -1|-1 | -1|-1 | -1|-0001| -1| -01|-1|
|100000|100000|+100000| 100000|100000|100000|100000|100000|
對於如下的格式化匹配字串:
"|%5u|%5o|%5x|%5X|%#5o|%#5x|%#5X|%#10.8x|"
將產生類似如下的輸出:
| 0| 0| 0| 0| 0| 0x0| 0X0|0x00000000|
| 1| 1| 1| 1| 01| 0x1| 0X1|0x00000001|
|100000|303240|186a0|186A0|0303240|0x186a0|0X186A0|0x000186a0|

3.2 浮點數的轉換部分
  浮點數轉換部分主要是'%f', '%e', '%E', '%g', 和 '%G' '這幾個引數命令的,由於引數的不同,可以輸出不同格式的結果。如上表所列: '%f'是輸出一個比較固定形式的浮點數……其中,針對這幾種不同輸出選擇還有如下幾個引數項:

'-’ 表示是左對齊,一般都是右對齊的。 
'+’ 是指以'+’符號表示正數 
' ' 如果輸出不是以'+’'-’開頭的,那麼用空格做開頭 
'#' 是對'%g'和'%G’引數而言的,將在輸出的結果強制加上'0’為開頭。 
''' 將輸出的數字以LC_NUMERIC的分類法用’,’隔開。 
'0' 將空格的地方用“0'填入。 
  如果沒有特別指定,傳入的被格式化的引數預設是double型別的,可以用'L’表示是一個long double型別的。
  如下例子可以看出浮點數格式化的字串:
  如下的格式字串:
"|%12.4f|%12.4e|%12.4g|"
  可能產生如下的輸出:
| 0.0000| 0.0000e+00| 0|
| 1.0000| 1.0000e+00| 1|
| -1.0000| -1.0000e+00| -1|
| 100.0000| 1.0000e+02| 100|
| 1000.0000| 1.0000e+03| 1000|
| 10000.0000| 1.0000e+04| 1e+04|
| 12345.0000| 1.2345e+04| 1.234e+04|
| 100000.0000| 1.0000e+05| 1e+05|
| 123456.0000| 1.2346e+05| 1.234e+05|

3.3 其他格式的轉換部分
  這部分的函式比較簡單一些,具體如下:
'%c’是指輸出一個單個的字串,預設的輸出的被格式化的引數是unsigned char型別的,可以用'-’表示左對齊的。沒有的別的引數,比如:
printf ("%c%c%c%c%c", 'h', 'e', 'l', 'l', 'o');
顯示結果為: 'hello'
'%s’是輸出一個字串,. 預設的輸出的被格式化的引數是char * (or const char *). 型別的,可以用'-’表示左對齊的。沒有的別的引數,比如:
printf ("%3s%-6s", "no", "where");
顯示結果: ' nowhere '.
  注: 如果你用這個引數來格式化輸出一個指標型別的引數時,有可能會得到一個'(null)'的輸出值。不過有時候用於指標為空的緣故程式執行時會產生“Segmentation fault”的錯誤,下面一個例子就會產生這樣的錯誤:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

void main()
{
    char a;
    a = inet_addr("192.168.1.1");
    if(a!=-1)
    {
        printf("ip:%s",a);/* 這裡的%s可能會產生錯誤,應改用用%p比較好一些*/
    }
}
'%m’是輸出error資訊的。如下例子:
fprintf (stderr, "can't open '%s': %m", filename);
等於如下的輸出命令:
fprintf (stderr, "can't open '%s': %s", filename, strerror (errno));
“%p”是輸出指標型別引數的,顯然被格式化的輸入蠶室必須是指標,可以用“-”來表示左對齊的。
“%n”是比較特殊的引數,它不對格式化輸出影響,而是得到輸出結果的字元長度,可以用型別指定引數'h' 和 'l'來分別指定輸出的引數分別是short int *和 long int *型別的。如下面的例子:
int nchar;
printf ("%d %s%n", 3, "bears", &nchar);
輸出結果:
3 bears
同時將7的值賦給變數nchar。
'%%'是輸出“%”的字元。

4. 函式具體介紹

4.1printf()函式
  printf()函式是格式化輸出函式系列中比較有具有普遍特點的, 一般用於向標準輸出裝置按規定格式輸出資訊。在編寫程式時經常會用到此函式。printf()函式的呼叫格式為:printf(";", ;);

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char c, s[20], *p;
    int a=1234, *i;
    float f=3.141592653589;
    double x=0.12345678987654321;
    p = "How do you do";
    strcpy(s, "Hello, Comrade");
    *i=12;
    c='x41';
    printf("a=%d", a);        /* 結果輸出十進位制整數a=1234 */
    printf("a=%6d", a);       /* 結果輸出6位十進位制數a= 1234 */
    printf("a=%06d", a);      /* 結果輸出6位十進位制數a=001234 */
    printf("a=%2d", a);       /* a超過2位, 按實際值輸出a=1234 */
    printf("*i=%4d", *i);     /* 輸出4位十進位制整數*i= 12 */
    printf("*i=%-4d", *i);    /* 輸出左對齊4位十進位制整數*i=12 */
    printf("i=%p", i);        /* 輸出地址i=06E4 */
    printf("f=%f", f);        /* 輸出浮點數f=3.141593 */
    printf("f=6.4f", f);      /* 輸出6位其中小數點後4位的浮點數f=3.1416 */
    printf("x=%lf", x);       /* 輸出長浮點數x=0.123457 */
    printf("x=%18.16lf", x);  /* 輸出18位其中小數點後16位的長浮點數x=0.1234567898765432  */
    printf("c=%c", c);        /* 輸出字元c=A */
    printf("c=%x", c);        /* 輸出字元的ASCII碼值c=41 */
    printf("s[]=%s", s);      /* 輸出陣列字串s[]=Hello, Comrade */
    printf("s[]=%6.9s", s);   /* 輸出最多9個字元的字串s[]=Hello,Co */
    printf("s=%p", s);        /* 輸出陣列字串首字元地址s=FFBE */
    printf("*p=%s", p);       /* 輸出指標字串p=How do you do */
    printf("p=%p", p);        /* 輸出指標的值p=0194 */
    getch();
    retunr 0;
}
  上面結果中的地址值在不同計算機上可能不同。
  例子中第一條語句#include的含義是呼叫另一個檔案stdio.h, 這是一個頭檔案, 其中包括全部標準輸入輸出庫函式的資料型別定
義和函式說明。對每個庫函式便用的變數及函式型別都已作了定義與說明, 放在相應標頭檔案"*.h"中, 使用者用到這些函式時必須要
用#include;或#include"*.h" 語句呼叫相應的標頭檔案, 以供若沒有用此語句說明, 則連線時將會出現錯誤。


4.2 fprintf()函式
  fprintf( ) 函式中格式化的規定與printf( ) 函式相同, 所不同的只是fprintf()函式是向檔案中寫入。而printf()是向螢幕輸出。
  下面介紹一個例子, 執行後產後一個test.dat的檔案。

#include <stdio.h>
#include <stdlib.h>

void main()
{
    char *s="That's good news"}; /*定義字串指標並初始化*/
    int i=617;   /*定義整型變數並初始化*/
    FILE *fp;    /*定義檔案指標*/
    fp=fopne("test.dat", "w");           /*建立一個文字檔案只寫*/
    fputs("Your score of TOEFLis", fp);  /*向所建檔案寫入一串字元*/
    fputc(':', fp);                      /*向所建檔案寫冒號:*/
    fprintf(fp, "%d", i);                /*向所建檔案寫一整型數*/
    fprintf(fp, "%s", s);                /*向所建檔案寫一字串*/
    fclose(fp);                          /*關閉檔案*/
}
用CAT命令顯示TEST.DAT的內容如下所示:螢幕顯示
Your score of TOEFL is: 617
That's good news


4.3 sprintf() 函式
  sprintf(string, fmt, ...)傳回的是string的型別的陣列,並以空字元結尾。不過,該函式有可能超過為字元分配的長度。比較危險。下面是一個sprintf()的事例。

//根據傳進來的Mission資料結構,建立socket連結,取得檔案的大小。
int get_size_of_url(struct Mission* pms)
{
    int s;    
    struct sockaddr_in sin;    
    struct hostent* phe;    
    char cmd[256];    
    char msg_hdr[1000];    
    char* p;
    
    //準備http中GET 方法的請求。    
    sprintf(cmd,"GET %s HTTP/1.0", pms->;url);
    
    //建立socket    
    if((s=socket(PF_INET,SOCK_STREAM,0))<0) return -1;
    
    //取得遠端主機的IP地址,失敗函式返回-1   
    if((phe = gethostbyname(pms->;host)) == NULL) return -1;
    
    memset(&sin,0,sizeof(sin));
    memcpy(&sin.sin_addr,phe->h_addr,sizeof(struct in_addr));   
    sin.sin_family=AF_INET;    
    sin.sin_port=htons(pms->;port);
    
    //跟遠端機器建立連線,失敗函式返回-1    
    if(connect(s,(struct sockaddr*)&sin,sizeof(sin))==-1) return -1;
    
    //傳送GET請求    
    if(write(s,cmd,strlen(cmd))<0) return 0;
    
    //從連結描述符(連線管道)中讀取傳送過來的資料    
    if(read(s, msg_hdr, 300)<0) return 0;    
    close(s);    
    printf("%s",msg_hdr);    
    //讀到該檔案的大小    
    if((p=strstr(msg_hdr,"Content-Length"))||(p=strstr(msg_hdr,"Content-length:"))) p+=16;    
    else return 0;
    
    //返回大小
    return atoi(p))  
}	                 
  注:在大部份的Unix系統上,sprintf(string, fmt, ...)傳回的是string的指標,然而,這方面Linux(遵循ANSI)傳回的卻是放入string內的字元數目.進行移植時,尤其是針對SunOS,需有警覺的心。


4.4 Snprintf()函式
  Snprintf()函式與Sprintf()函式極為相似,但是該函式多了size引數來表示最大的字元數目,該函式返回一個整數值表示被儲存的字元的數目,如果返回-1則表示輸出的字元空間不夠。如下例子:

char *make_message (char *name, char *value)
{
    /* 預分配100個字元空間. */   
    int size = 100;  
    char *buffer = (char *) xmalloc (size);    
    while (1)    
    {    
        /* 輸出格式化的字元到給定的空間中. */    
        int nchars = snprintf (buffer, size,"value of %s is %s",name, value);    
		
        /* 判斷是否返回真值 */    
        if (nchars < size) return buffer;    
		
        /* 如果空間不夠,加大預分配空間到2倍 */    
        size *= 2;    
        buffer = (char *) xrealloc (size, buffer);    
    }
}
4.5 asprintf()函式
  int asprintf (char **ptr, const char *template, ...)
  本函式跟sprintf()函式很類似,只是它將字串的分配改成動態分配的形式,引數ptr是指一個char *物件的地址函式返回指向一個新建的指標。如下例子:
/* Construct a message describing the value of a variable whose name is name and whose value is value. */
char *make_message (char *name, char *value)
{
    char *result;
    asprintf (&result, "value of %s is %s", name, value);
    return result;
}

4.6 Vprintf()函式
  int vprintf (const char *template, va_list ap)
  本函式跟printf函式很類似,只是將引數的數目可變的,變成了一個指標的列表。
4.7 Vfprintf()函式
  int vfprintf (FILE *stream, const char *template, va_list ap)
  本函式跟fprintf函式很類似,只是將引數的數目可變的,變成了一個指標的列表。
4.8 vfprintf()函式
  int vsprintf (char *s, const char *template, va_list ap)
  本函式跟sprintf函式很類似,只是將引數的數目可變的,變成了一個指標的列表。
4.9 vsnprintf()函式
  int vsnprintf (char *s, size_t size, const char *template, va_list ap)
  本函式跟snprintf函式很類似,只是將引數的數目可變的,變成了一個指標的列表。
4.10 vasprintf()函式
  int vasprintf (char **ptr, const char *template, va_list ap)
  本函式跟asprintf函式很類似,只是將引數的數目可變的,變成了一個指標的列表。

字串格式化函式 sprintf 和 printf

在將各種型別的資料構造成字串時,sprintf 的強大功能很少會讓你失望。由於sprintf 跟 printf 在用法上幾乎一樣只是列印的目的地不同而已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 進位制都打印出來了。

如果你想看si 的本來面目,那麼就應該讓編譯器做0 擴充套件而不是符號擴充套件(擴充套件時二進位制左邊補0 而不是補符號位):
sprintf(s, "%04X", (unsigned short)si);
就可以了。或者:
unsigned short si = -1;
sprintf(s, "%04X", si);

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. 字元

在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 <stdio.h>
#include <stdlib.h>
#include <time.h>

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)。