可变参数函数简单解读(variadic function)
平时写代码接触最多函数大概就是 printf 函数了,printf 函数就是一个可变参数函数,第2个参数就是可变参数,第一个参数是格式化字符串。格式化字符串中的转换修饰符决定了可变参数的数量和类型。
可变参数函数(variadic function):函数参数变量可变的函数。可变参函数包括至少一个强制参数(mandatory argument)+数量可变的可选参数(optional argument),强制性参数在前,可变参数用省略号(…)表示。
实现可变参数函数需要包含**<stdarg.h>**头文件,他提供需要用到的下列宏定义:
typedef char* va_list;
void va_start(va_list argptr, lastparam);
type va_arg(va_list argptr, type);
void va_end(va_list argptr);
void va_copy(va_list dest, va_list src);
- typedef char va_list;*:简单理解就是指向就是存放可变参数列表的指针,访问这个列表可以得到每一个可变参数。
第一步:定义函数,并用这个类型定义一个变量:
int m_printf(const char *f_str,...)
{
va_list p_list;
}
- void va_start(va_list argptr, lastparam);:第一个参数为va_list类型的变量,第2个参数为可变参的数量。此函数会做可变参数链表的初始化等工作。
第2步:用va_start初始化 p_list变量。format为printf函数的第一个参数。
int m_printf(const char *f_str,...)
{
va_list p_list;
va_start(p_list,f_str);
}
- type va_arg(va_list argptr, type);:第一个参数为va_list类型的变量,第2个参数为可变参数的类型。此函数会按照从左到右的顺序从可变参数列表里面pop出一项可变参数的值,然后p_list指针会指向下一个可变参数。
第3步:用va_arg获取可变参参数的值。根据参数类型,type需要设置为不同的类型。
int m_printf(const char *f_str,...)
{
va_list p_list;
int value;
char* chs;
va_start(p_list,f_str);
... ...
/* pop出一个int类型的值 */
value = va_arg(p_args,int);
/* pop出一个字符串 */
chs = va_arg(p_args,char*);
}
- void va_end(va_list argptr);:参数列表指针不需要被使用的时候需要调用此函数完成清理等工作。
最后一步调用va_end完成清理工作。
int m_printf(const char *f_str,...)
{
va_list p_list;
int value;
char* chs;
va_start(p_list,f_str);
... ...
/* pop出一个int类型的值 */
value = va_arg(p_args,int);
/* pop出一个字符串 */
chs = va_arg(p_args,char*);
... ...
va_end(p_args);
}
- 下面是一个简单的可变参数函数
的实现代码:add()
/* num:表示可变参数的数量 */
int add(int num,...)
{
int sum,i;
va_list p_list;
int value;
sum = 0;
va_start(p_list,num);
for(i=0;i<num;i++)
{
value = va_arg(p_list,int);
sum +=value;
}
va_end(p_list);
return sum;
}
void add_t(void)
{
printf("%d %d %d \n",add(2,3,4),add(3,4,5,1),add(4,2,2,1,1));
}
printf函数实现原理简单解读
printf函数的格式化输出是使用百分号
%
控制输出的格式。printf第一个参数是格式化字符串,所以需要解析这个字符串,然后根据不同的输出格式输出对应字符。最终调用
putc()
等类似函数输出一个个字符。
- 目前本文实现了下面几种格式的输出。
控制符 | 说明 |
---|---|
%d | 十进制整型数据 |
%md | m 为指定的输出字段的宽度。如果数据的位数小于 m,则左端补以空格,若大于 m,则按实际位数输出。 |
%0md | m 为指定的输出字段的宽度。如果数据的位数小于 m,则左端补以0,若大于 m,则按实际位数输出。 |
%x | 十六进制整形数据 |
%c | 输出一个字符 |
%s | 输出字符串 |
%d实现(整形数值转换为十进制字符串)
一个整形数值可以按照下面的代码转换为十进制字符串:
int value= 1234;
char buff[32];
char ch;
buff[31] = 0;
int index = 30;
while(value!= 0)
{
ch = value % 10 + '0'; /* 第1步:求余数得到个位并获得数字对应的字符 */
value = value / 10; /* 第2步:丢弃个位 */
buff[index] = ch; /* 保存到缓冲区 */
index--;
}
上面代码的
ch = value % 10 + '0';
还可以简单的改为下面的代码:
static const char hex[] = "0123456789ABCDEF";
... ...
ch = hex[value % 10];
%x实现(整形数值转换为16进制字符串)
- 方法一:一个整形数值可以按照下面的代码转换为十六进制字符串:
int value= 1234;
char buff[32];
char ch;
buff[31] = 0;
int index = 30;
while(value!= 0)
{
ch = value % 16 + '0'; /* 第1步:求余数并获得数字对应的字符 */
value = value / 16; /* 第2步:丢弃低4位 */
buff[index] = ch; /* 保存到缓冲区 */
index--;
}
上面代码的
ch = value % 10 + '0';
还可以简单的改为下面的代码:
static const char hex[] = "0123456789ABCDEF";
... ...
ch = hex[value % 16];
转16进制字符串的代码实现和10进制类似,不同的只是求余和除法的10和16;
- 方法二:数值转16进制字符串的代码等价于下面的代码:
int value= 1234;
char buff[32];
char ch;
buff[31] = 0;
int index = 30;
static const char hex[] = "0123456789ABCDEF";
while(value!= 0)
{
ch = hex[value & 0xf]; /* 第1步:求余数并获得数字对应的字符 */
value = value >> 4; /* 第2步:右移4位,丢弃低4位 */
buff[index] = ch; /* 保存到缓冲区 */
index--;
}
整形数值转不同进制字符串的规律
- 假设一个
型的数值需要转换为不同进制类型的字符串,则可以按照下面的步骤:int
#define ARY 10 /* 不同数值表示不同的进制 */
int value= 1234;
char buff[32];
char ch;
buff[31] = 0;
int index = 30;
static const char hex[] = "0123456789ABCDEF";
while(value!= 0)
{
ch = hex[value % ARY]; /* 第1步:求余数并获得数字对应的字符 */
value = value / ARY; /* 第2步:除以进制 */
buff[index] = ch; /* 保存到缓冲区 */
index--;
}
- 因此一个数转换为2进制或者8进制的字符串只需要将上面代码的宏定义改为2,8就可以了。
十进制字符串转十进制数值
输出指定宽度的数值时需要将字符串转为10进制数值,
%md
其中的m,可以是字符串123或者10等,实现代码如下所示:
int width = 0;
char fmt[] = "123";
char *p = fmt;
while((*p>= '1') && (*p<= '9'))
{
width = width * 10 + *p - '0';
p++;
}
printf("width=%d",width);
%c实现(输出一个字符)
输出一个字符的代码实现就很简单了,printf最终也是也是调用字符输出函数将字符串输出。
- 在windows端可以调用
函数输出一个字符,调用putchar()
输出一个字符串。因为puts()
会默认输出一个换行符,所以可以自己简单实现一个输出字符串的函数。puts()
- 在MCU端则可以实现串口驱动,并实现输出一个字符及字符串的函数。
迷你 printf 函数整体实现代码
/* 实现迷你printf函数 */
void put_str(char* str)
{
while(*str != 0)
{
putchar(*str);
str ++;
}
}
#define BUFF_SIZE 32
int m_printf(const char *fmt,...)
{
int length = 0; /* 返回值:打印的字符长度 */
int index,width; /* index:缓冲区索引,width:字符输出宽度 */
int value,temp,stuff; /* stuff:指定宽度输出0或者空格 */
unsigned int value_x; /* %x 输出值为无符号整形 */
char* chs;
char buff[BUFF_SIZE] = {0}; /* 缓冲区 */
static const char hex[] = "0123456789ABCDEF";
va_list p_args;
va_start(p_args,fmt);
while(*fmt != '\0')
{
if(*fmt == '%') /* 格式化输出 */
{
fmt++;
index = 30;
width = 0;
/* 指定宽度填充0或者空格并向右靠齐 */
if(*fmt == '0')
{
stuff = '0';
fmt++;
}
else
{
stuff = ' ';
}
while((*fmt >= '1') && (*fmt <= '9'))
{
width = width *10 + *fmt - '0';
fmt++;
}
switch(*fmt)
{
case 'c':
value = va_arg(p_args,int);
putchar(value);
fmt++;
length ++;
break;
case 's':
chs = va_arg(p_args,char*);
put_str(chs);
fmt++;
length += strlen(chs);
break;
case 'd':
value = va_arg(p_args,int);
temp = value;
/* 负数变正数 */
if(value < 0)
{
value = -value;
}
/* int 型数字转为十进制字符串 */
while(value != 0)
{
buff[index] = hex[value % 10]; /* 求余数得到个位并获得数字对应的字符 */
value = value / 10; /* 丢弃个位 */
index--;
width --;
}
/* 负数添加负号 */
if(temp < 0)
{
buff[index] = '-';
index--;
width--;
}
fmt++;
break;
case 'x':
case 'X':
value_x = va_arg(p_args,unsigned int);
/* 数值转16进制字符串 */
while(value_x != 0)
{
buff[index] = hex[value_x % 16]; /* 求余数得到个位数 */
value_x = value_x / 16; /* 丢弃个位数 */
index --;
width--;
}
buff[index--] = 'x'; /* 添加 0x 前缀 */
buff[index--] = '0';
width -= 2;
fmt++;
break;
default:
break;
}
/* 按照指定宽度填充字符0或者空格,width可能为负数 */
if(width > 0)
{
while(width != 0)
{
buff[index] = stuff;
index --;
width --;
}
}
if(index != 30) /* 输出格式化字符串 */
{
put_str(&buff[++index]);
length += BUFF_SIZE - 1 - index;
}
}
else /* 直接输出字符 */
{
putchar(*fmt);
fmt++;
length ++;
}
}
va_end(p_args);
return length;
}
/* 函数功能测试 */
void test_m(void)
{
int r_value = 0;
m_printf("hello print! %c %s %7d %07d %9x %09x\n",'a',"this",1234,-5678,0x1122,0x5566);
r_value = m_printf("12345:%s %c %d %6d %x %6x\n","abcdef",'j',1234,567,0x1122,0x3344);
printf("m r_value:%d\n",r_value);
r_value = printf("12345:%s %c %d %6d %x %6x\n","abcdef",'j',1234,567,0x1122,0x3344);
printf("p r_value:%d\n",r_value);
}