當我們構建函數時,如果我們希望得到的是不定的參數,那我們應該怎麼辦呢?下面幾個關鍵的macro上場:
#define _ADDRESSOF(v) (&reinterpret_cast<const char &>(v))
typedef char* va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) -1))
#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_end(ap) (ap = (va_list)0)
對於很多人來講,初讀這幾條macro,實在會有些摸不着頭腦的感覺,不過在複雜的宏也都是簡單的文本替換,所以一下子鬆了口氣。
#define _ADDRESSOF(v) (&reinterpret_cast<const char &>(v)
注:reinterpret 重新解釋的意思,reinterpret_cast<T>可用在任意指針(或引用)類型之間的轉換,以及指針與足夠大的整數類型之間的轉換,從整數類型(包括枚舉類型)到指針類型,這裏使用是爲了取出(char *)類型的指針,而使用引用是防止新對象的產生。 這個macro我們可以這樣用: char *p = _ADDRESSOF(s);取出char *指針指向s的地址。
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) -1))
注:這個macro我特別喜歡,在不知不覺中,你會發現這一行代碼會帶來這麼奇妙的東西。
(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) 假若sizeof(int) = 4 ,sizeof(n) =3,則簡化運算爲:7 & ~(3) = 4,即 0111b & 1100b = 0100b ,這個處理方式自動處理掉小於size(int) 的位,故得運算結果始終會爲 sizeof(int) 的倍數。
#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))
注:va_start 的v指向固定參數的最後一個參數,並取得地址通過v參數的大小,計算出第一個可變參數的地址。
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
注:va_arg的t是參數類型,粗略一看,嗯,我們可以用這個macro得到我們想要的所有類型的可變參數,於是愉快的寫出了這樣的代碼: C標準對於不帶原型聲明的函數的參數會有“默認實際參數提升(default argument promotions)"
即:
float -> double
char,short和對應的unsigned,signed ->int
int如果不足夠大, Int -> unsigned
所以我們的t參數根本不應該使用: float, char,short 以及相關unsigned,signed。
那我們如何取出char ,float呢?char value = (char)va_arg(ap,int);
float value =(float)va_arg(ap, double);
如果我們需要取出一個char str[] = "I'm Jovi",怎麼辦呢?
當然,我們瞭解到,函數傳參str實際上是傳首地址,所以依舊可以傳入一個int類型獲得參數大小(指針類型也可以)
即 char *str = (char *)va_arg(ap,int);
#define va_end(ap) (ap = (va_list)0)
注: ap不再指向堆棧,指向一個0指針,相當於NULL。
這也就是幾個基本macro的說明,下面我們來使用並寫出自己的printf函數。
#define _ADDRESSOF(v) (&reinterpret_cast<const char &>(v))
typedef char* va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) -1))
#define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_end(ap) (ap = (va_list)0)
int __cdecl MyPrintf(const char *fmt, ...)
{
va_list vaptr;
va_start(vaptr, fmt);
while (*fmt)
{
if (*fmt == '%')
{
switch (*++fmt)
{
case 'c':
{
char value;
//value = va_arg(vaptr, char); 錯誤 在可變長參數中,應用的是"加寬"原則。也就是float類型被擴展成double;char, short被擴展成int。
value = (char)va_arg(vaptr, int);
putchar(value);
break;
}
case 'd':
{
int value;
char buf[12];
char *pBuf = buf;
value = va_arg(vaptr, int);
if (value < 0)
{
value = -value;
buf[0] = '-';
pBuf = buf + 1;
}
int len = 1;
int temp = value;
while (temp / 10)
{
temp /= 10;
len++;
}
temp = value;
*(pBuf + len) = 0;
while (len)
{
int t = temp % 10;
*(pBuf - 1 + len--) = t + '0';
temp /= 10;
}
fputs(buf, stdout);
break;
}
case 'f':
{
float value;
char buf[32];
char *pBuf = buf;
value = (float)va_arg(vaptr, double);
if (value < 0)
{
value = -value;
buf[0] = '-';
pBuf = buf + 1;
}
//處理整數位
int intergerlen = 1;
int integer = (int)value;
while (integer / 10)
{
integer /= 10;
intergerlen++;
}
integer = (int)value;
int len = intergerlen;
while (len)
{
int t = integer % 10;
*(pBuf - 1 + len--) = t + '0';
integer /= 10;
}
*(pBuf + intergerlen) = '.';
pBuf = pBuf + intergerlen + 1;
//處理小數位
float decimals = value - (int)value;
//8位小數
decimals *= 100000000;
int decimalslen = 8;
int int_decimals = (int)decimals;
*(pBuf + decimalslen) = 0;
while (decimalslen)
{
int t = int_decimals % 10;
*(pBuf - 1 + decimalslen--) = t + '0';
int_decimals /= 10;
}
fputs(buf, stdout);
break;
}
case 's':
{
char *pValue;
//此處傳參爲地址 lea eax,[str]
pValue = (char *)va_arg(vaptr, int);
fputs(pValue, stdout);
break;
}
default:
break;
}
}
else
{
putchar(*fmt);
}
++fmt;
}
va_end(vaptr);
return 1;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a = -120;
char b = 'a';
char str[] = "I'm Jovi";
float f = 123.1415f;
MyPrintf("輸出爲:a=%d , b=%c , str=%s , f=%f ",a,b,str,f);
getchar();
return 0;
}
當然,實際的printf返回值是打印出字符個數,這裏我爲了簡化,在代碼中只返回了1。
簡單實現了 %d,%c,%s,%f 的輸出,當然對於可變參數函數實現還有很多,比如max(int n, ...)等等,不定參數定義時須使用"..."。運用va_start,va_arg,va_end實現對可變參數的訪問,將可變參數一一取出,進行處理。