引言
在c和c++開發中,我們經常會用到printf來進行字串的格式化,例如printf(「format string %d, %d」, 1, 2);,這樣的格式化只是用於列印除錯資訊。printf函式實現的是接收可變引數,然後解析格式化的字串,最後輸出到控制台。那麼問題來了,當我們需要實現乙個函式,根據傳入的可變引數來生成格式化的字串,應該怎麼辦呢?
你可以在這裡看到更好的排版
正文可變引數
首先來乙個可變引數使用示例,testvariadic方法接收int行的可變引數,並以可變引數為-1表示結束。va_list用於遍歷可變引數,va_start方法接收兩個引數,第乙個為va_list,第二個為可變引數前乙個引數,下面的例子裡該引數為a。
/**下面是 裡面重要的幾個巨集定義如下:
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 是乙個字元指標,可以理解為指向當前引數的乙個指標,取參必須通過這個指標進行。
在呼叫參數列之前,定義乙個 va_list 型別的變數,(假設va_list 型別變數被定義為ap);
然後應該對ap 進行初始化,讓它指向可變參數列裡面的第乙個引數,這是通過 va_start 來實現的,第乙個引數是 ap 本身,第二個引數是在變參表前面緊挨著的乙個變數,即「…」之前的那個引數;
然後是獲取引數,呼叫va_arg,它的第乙個引數是ap,第二個引數是要獲取的引數的指定型別,然後返回這個指定型別的值,並且把 ap 的位置指向變參表的下乙個變數位置;
獲取所有的引數之後,我們有必要將這個 ap 指標關掉,以免發生危險,方法是呼叫 va_end,他是輸入的引數 ap 置為 null,應該養成獲取完參數列之後關閉指標的習慣。說白了,就是讓我們的程式具有健壯性。通常va_start和va_end是成對出現。
*///-1表示可變引數結束
void receivevariadic(int a, …)
printf(」\n");
va_end(list);
}//test
void testvari()
執行結果
------testvari------
2 3 4 5 6 -1
格式化字串
好了,我們已經介紹了怎樣實現乙個接收可變引數的c函式,接下來介紹根據接收的可變引數來格式化字串。這裡介紹兩種方式,第一種是利用巨集定義,第二種通過函式的方式來實現。
通過巨集定義的方式
en…讓咱們先來看看第乙個版本的巨集,這個巨集定義對於不熟悉巨集的人來說可能看著有點費勁,不過不要怕,稍後會做解釋,**如下:
#define myformatstringbymacro_withoutreturn(format, …)
do 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語句加上了{}的話,就不會有問題,可以看出規範寫法是多麼的重要?(皮一下很開心)。
void test()
加上do-while以後就不一樣,加上do-while後的**如下:
#define testmarco(a, b)
do while(0)
void test()
預處理之後**如下:
//展開後的**
void test()
while(0);
}好了,巨集的基礎知識就介紹這麼多了,接下來進入正題。
#define myformatstringbymacro_withoutreturn(format, …)
do while(0)
首先,介紹一下snprintf()函式,此函式的定義如下:
@param __str 接收格式化結果的指標
@param __size 接收的size
@param __format 格式化的字串
@param … 可變引數
@return 返回格式化後實際上寫入的大小a,a <= __size
*/int snprintf(char * __restrict __str, size_t __size, const char * __restrict __format, …) __printflike(3, 4);
為了方便理解,使用方式是這個樣子的:
void testsnprintf()
執行結果:
------testsnprintf------
size:14
snprintf函式還有乙個用法是__str和__size分別傳入null和0,返回值會是格式化字串的實際長度,可以通過這個方式來獲取正確的格式化size,從而避免malloc多餘的空間,造成空間浪費。同時返回的size是不包含結束符\0的,所以真正寫入要buffer時,需要對size + 1。
相信通過我的解釋,你一定能看懂上面這段**了吧。哦,對了malloc的**一定要記得free(敲重點)。
到了這裡,如果細心思考的同學一定會問?這個巨集根本沒有實際用途好不好,我要的是能夠把格式化的字串作為返回值返回的,僅僅列印直接用printf不就好了。其實,這樣的巨集還是有作用的,比如說當你要記錄日誌時,你可以像這樣使用:
#define log_debug(format, …)
do while(0)
要將結果字串返回的話,需要用到gnu c的賦值擴充套件,使用方式如下:
int a = ();
這段**變數a最終值會是6。利用gnu這個擴充套件,將之前的巨集改造一下就能實現我們的需求,改造完成後是這個樣子的:
#define myformatstringbymacro_returnformatstring(format, …)
();呼叫巨集的**:
void testbymacro1()
原諒我的囉嗦,malloc開闢的空間一定要記得free。執行結果:
------testbymacro1------
format by macro, 123 well done
至此利用巨集的方式就介紹完了。
通過函式的方式
老規矩先上**
char *myformatstringbyfun(char *format, …)
size++;
//2. 復位va_list,將格式化字串寫入到buf
va_start(list, format);
char *buf = (char *)malloc(size);
vsnprintf(buf, size, format, list);
va_end(list);
return buf;
}
這裡利用的是vsnprintf函式,此函式的定義在stdio.h中的定義如下:
@param __str 目標字串
@param __size 要賦值的大小
@param __format 格式化字串
@param va_list 可變引數列表
@return 返回格式化後實際上寫入的大小a,a <= __size
*/int vsnprintf(char * __restrict __str, size_t __size, const char * __restrict __format, va_list) __printflike(3, 0);
vsnprintf的具體使用方式和之前介紹的snprintf是差不多的,這裡就不再詳細介紹了,不大明白的同學可以看看上面的介紹。哦,對了,這兩個函式都是定義在stdio.h這個標頭檔案下的
接下來就是試一下我們封裝的函式了
void testbyfun()
執行結果:
------testbyfun------
format by fun 321 nice
格式化字串的方法差不多介紹完了,不知道善於思考的你有沒想到直接用巨集定義來呼叫我們封裝的函式呢?我就在這直接給出巨集定義和使用方式了
template< typename… args >
std::string string_sprintf( const char* format, args… args )
其實和c語言版本的沒什麼差別,只是多了泛型的東西而已,相信聰明的你一定能看懂,看不懂的話,就去看看c++的泛型知識吧,哈哈哈。
參考資料
std::string formatting like sprintf
C C 字串格式化
與scanf 類似,但scanf 以鍵盤為輸入,但sscanf 以固定字串為輸入源 int sscanf char buffer,const char format,argument format 乙個或多個 type t n 非 符號 表示跳過此資料不讀入,如 d s 遇到整數不讀入 遇到字串不讀...
C C 格式化字串說明
說明 1 可以在 和字母之間插進數字表示最大場寬。例如 3d 表示輸出3位整型數,不夠3位右對齊。9.2f表示輸出場寬為9的浮點數,其中小數字為2,整數字為6,小數點佔一位,不夠9位右對齊。8s 表示輸出8個字元的字串,不夠8個字元右對齊。如果字串的長度 或整型數字數超過說明的場寬,將按其實際長度輸...
C C 格式化字串說明
1.格式化規定符 符號 作用 d 十進位制有符號整數 u 十進位制無符號整數 f 浮點數 s 字串 c 單個字元 p 指標的值 e 指數形式的浮點數 x,x 無符號以十六進製制表示的整數 0 無符號以八進位制表示的整數 g 自動選擇合適的表示法 說明 1 可以在 和字母之間插進數字表示最大場寬。例如...