C、C++格式化字串
引言
在C和C++開發中,我們經常會用到printf來進行字串的格式化,例如printf("format string %d, %d", 1, 2);
,這樣的格式化只是用於列印除錯資訊。printf函式實現的是接收可變引數,然後解析格式化的字串,最後輸出到控制檯。那麼問題來了,當我們需要實現一個函式,根據傳入的可變引數來生成格式化的字串,應該怎麼辦呢?
你可以在這裡看到更好的排版
正文
可變引數
首先來一個可變引數使用示例,testVariadic
方法接收int行的可變引數,並以可變引數為-1表示結束。va_list用於遍歷可變引數,va_start
方法接收兩個引數,第一個為va_list
,第二個為可變引數前一個引數,下面的例子裡該引數為a。
/** 下面是 <stdarg.h> 裡面重要的幾個巨集定義如下: typedef char* va_list; void va_start ( va_list ap, prev_param ); // ANSI version type va_arg ( va_list ap, type ); void va_end ( va_list ap ); va_list 是一個字元指標,可以理解為指向當前引數的一個指標,取參必須通過這個指標進行。 <Step 1> 在呼叫引數表之前,定義一個 va_list 型別的變數,(假設va_list 型別變數被定義為ap); <Step 2> 然後應該對ap 進行初始化,讓它指向可變引數表裡面的第一個引數,這是通過 va_start 來實現的,第一個引數是 ap 本身,第二個引數是在變參表前面緊挨著的一個變數,即“...”之前的那個引數; <Step 3> 然後是獲取引數,呼叫va_arg,它的第一個引數是ap,第二個引數是要獲取的引數的指定型別,然後返回這個指定型別的值,並且把 ap 的位置指向變參表的下一個變數位置; <Step 4> 獲取所有的引數之後,我們有必要將這個 ap 指標關掉,以免發生危險,方法是呼叫 va_end,他是輸入的引數 ap 置為 NULL,應該養成獲取完引數表之後關閉指標的習慣。說白了,就是讓我們的程式具有健壯性。通常va_start和va_end是成對出現。 */ //-1表示可變引數結束 void receiveVariadic(int a, ...) { va_list list; va_start(list, a); int arg = a; while (arg != -1) { arg = va_arg(list, int); printf("%d ", arg); } printf("\n"); va_end(list); } //test void testVari() { printf("------%s------\n", __FUNCTION__); //-1表示可變引數結束 receiveVariadic(1, 2, 3, 4, 5, 6, -1); }
執行結果
------testVari------
2 3 4 5 6 -1
格式化字串
好了,我們已經介紹了怎樣實現一個接收可變引數的C函式,接下來介紹根據接收的可變引數來格式化字串。這裡介紹兩種方式,第一種是利用巨集定義,第二種通過函式的方式來實現。
通過巨集定義的方式
en…讓咱們先來看看第一個版本的巨集,這個巨集定義對於不熟悉巨集的人來說可能看著有點費勁,不過不要怕,稍後會做解釋,程式碼如下:
#define myFormatStringByMacro_WithoutReturn(format, ...) \ do { \ int size = snprintf(NULL, 0, format, ##__VA_ARGS__);\ size++; \ char *buf = (char *)malloc(size); \ snprintf(buf, size, format, ##__VA_ARGS__); \ printf("%s", buf); \ free(buf); \ } while(0)
巨集基礎知識
首先需要介紹巨集用到的知識:\
, 這個\
的作用是可換行定義巨集,畢竟如果一行很長的巨集可讀性很差,使用方式在換行時加上\
即可。第二個是介紹(format, ...)
,這裡的...
是預定義的巨集,用於接收可變引數,就像是printf
函式一樣。接著介紹##__VA_ARGS__
,同樣的__VA_ARGS__
也是預定義的巨集,表示接收到的...
傳入的可變引數。##
的作用是用來處理未傳入可變引數的情況,當沒有傳入可變引數的時候,編譯器或通過優化將snprintf(NULL, 0, format, ##__VA_ARGS__);
優化為snprintf(NULL, 0, format);
。你可以理解為沒有可變引數時,##
前的逗號,
與__VA_ARGS__
都被“幹掉了”。
你一定會覺得困惑,為什麼要寫do-while
語句呢?這是為了巨集的健壯性,如果使用巨集的人像下面這樣使用的話,就會出問題
#define testMarco(a, b) \
int _a = a + 1; \
int _b = b + 1; \
printf("\n%d", _a + _b); \
void test()
{
if (1 > 0)
testMarco(1, 2);
}
上面的程式碼連編譯都不會通過, 會報錯如下:
如果手動展開這個巨集的話,會變成這個樣子,問題就顯而易見了。但是如果if
語句加上了{}
的話,就不會有問題,可以看出規範寫法是多麼的重要