当我们构建函数时,如果我们希望得到的是不定的参数,那我们应该怎么办呢?下面几个关键的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实现对可变参数的访问,将可变参数一一取出,进行处理。