- 从平均数实现看可变参数列表
- 可变参数实现查找最大值
- printf函数实现
在我们平时的编码过程中,每当调用函数时基本都会给函数传入定量的且类型确定的参数,并且在函数设计时也会遵从这一习惯。但是真的每个函数都需要定量且类型确定的参数吗?答案一定是否定的。如果注意观察一定不难发现我们基本上每次编码都会使用到的printf就是一个典型的可变参数函数,本文的目的是通过一个简单的例子对可变参数列表进行学习,从而实现自己的printf函数。
从平均数实现看可变参数列表
以一个求平均数的函数为例,调用函数时向其传入不等数量的参数,看看结论如何。
#include<stdio.h>
#include<stdarg.h>
int average(int n,...)
{
va_list arg;
int i = 0;
int sum = 0;
va_start(arg,n);
for(i=0;i<n;i++)
{
sum += va_arg(arg,int);
}
return sum/n;
va_end(arg);
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
int avg1 = average(2,a,b);
int avg2 = average(3,a,b,c);
printf("avg1 = %d\n",avg1);
printf("avg2 = %d\n",avg2);
return 0;
}
代码的逻辑并不难,只是对average函数的细节问题需要进行分析。
- 参数列表:average(int n,…) 列表中的int n并不难理解,代表一个整形的变量,其中存储的是所需传入参数的个数(这里并不包括n哦!) “ … ”则没有具体的含义,因为n后面的参数是可变的所以这里不管放什么都难以服众,那就用“ … ”代替一下吧(个人理解,仅供参考)。尽管并无实际含义但“ … ”一定不能省略不写!
- va_list:根据语句va_list arg;我们基本可以猜到va_list应该是一个类型,转到定义可以看到 typedef char * va_list; 所以va_list是char * 的一个别名。
- va_start(arg,n);这个函数稍稍有些麻烦,因为它穿的马甲实在是太多了!转到定义:
va_start(arg,n); //#define va_start _crt_va_start
//_crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
//#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) );
//#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
a. 先对宏函数( &reinterpret_cast < const char &>(v) )进行分析:reinterpret_cast 是强制类型转化,所以整个语句也就是 (char*)&v;
b. 宏函数( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) );分别取sizeof(n)=1,4,5;经计算,当取1时,表达式=4;当取4时,表达式=4;当去5时,表达式为8;所以这个表达式的目的就是当传入的参数类型大小<=4时返回4;(>4)&&(<=8)时返回8,返回的值是4的倍数(因为参数压栈时的基本单位是一个字节,4个bit)
通过对宏函数的分析,我们可以得出结论,va_start(arg,n)函数的作用就是取出可变参数列表中第一个参数的地址。根据函数调用过程可知,形参实例化后压栈时中间并没有空间浪费,所以在找到了一个参数的地址后通过加减合适的字节数(4的倍数)就可以获取到其他的参数内容。以当前函数在内存中的存储为例做出图示:
- va_arg(arg,int): 同样转到定义,发现它也是一个宏函数,进行如下分析:
va_arg(arg,int);//#define va_arg _crt_va_arg
//define _crt_va_arg(ap,t) \
// ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
了解到 _INTSIZEOF(t)函数返回的是占空间访问偏移量,假设这里返回值均为4,则对函数体进行改写为:((int ) (arg+=4) -4)。其意义就是arg向下偏移访问下一个形式参数,而返回值返回的确实上一个形式参数的值。
那么函数体内的for循环则是通过内存访问到了n个形式参数的内容并将其相加,最后除以个数返回最终值。需要注意的是当访问完毕最后一个可变参数之后,需要调用va_end。最后除以个数返回最终值。
总结起来:可变参数列表可通过宏来实现,这些宏定义在stdarg.h头文件中,它是标准库的一部分。这个头文件声明了一个类型va_list和三个宏函数va_start,va_arg,va_end.,与这几个宏配合使用通过内存访问获取到访问参数的值。参数列表的可变部分位于一个或多个普通参数的后面(即参数列表中至少要有一个命名参数),它在函数原型中以一个省略号表示。
查找最大值
最后附加一个使用可变参数列表求最大值的应用,代码如下:
#include<stdio.h>
#include<stdarg.h>
int Max(int n,...)
{
va_list arg;
int max,tmp,i = 0;
va_start(arg,n);
max = va_arg(arg,int);//给max赋初值时最好使用待比较元素的值,不要直接赋0
for(i=1;i<n;i++)
{
tmp = va_arg(arg,int);
//va_arg()函数虽返回了当前值但指针arg已经指向下一个元素,\
//为避免元素遗漏应该进行暂时的保存。
if(tmp>max)
max = tmp;
}
return max;
va_end(arg);
}
int main()
{
//int a = 1;
//int b = 2;
//int c = 3;
int max1 = Max(2,1,2);
int max2 = Max(3,1,2,3);
printf("max1 = %d\n",max1);
printf("max2 = %d\n",max2);
return 0;
}
运行结果如下:
printf函数实现
可变参数列表在平时的编码过程中最常被用到的例子就是printf函数,做为对可变参数列表的应用现自主实现一个my_print函数,源码如下所示:
#include<stdio.h>
#include<string.h>
#include<stdarg.h>
//打印整型数据,运用递归将整数的各个位取下来并转化为char型进行打印。
void show_int(int n)
{
if(n>9)
{
show_int(n/10);
}
putchar(n%10+'0');
}
void show_char(char ch)//打印单个字符
{
putchar(ch);
}
void show_str(char* str)//打印字符串
{
while(*str)
{
putchar(*str);
str++;
}
}
//打印浮点数(小数点后6位),将整数部分与小数部分分开分别进行打印。
void show_float(const float flt)
{
int ret_int = (int)flt;
int ret_flt = (int)(1000000 * (flt - ret_int));
show_int(ret_int);
putchar('.');
show_int(ret_flt);
}
void my_print(const char *format, ...)
{
va_list arg;
va_start(arg,format);
while(*format)
{
if((*format) != '%')//没有遇到%时正常打印
{
putchar(*format);
format++;
}
else//遇到%则说明百分号后一位是标记位,跳过%不打印,对标记位进行判断。
{
format++;
switch(*format)
{
case'd':
{
int ret = va_arg(arg,int);
show_int(ret);
}break;
case'c':
{
char ch = va_arg(arg,char);
show_char(ch);
}break;
case's':
{
char *sch = va_arg(arg,char*);
show_str(sch);
}break;
case'f':
{
float ret = va_arg(arg,double);
show_float(ret);
}break;
default:
putchar(*format);
break;
}format++;
}
}
}
int main()
{
int a = 10;
char b = 'A';
char* str = "abcdef";
float c = 3.14159;
my_print(" a = %d\n b = %c\n str = %s\n c = %f\n", a,b,str,c);
return 0;
}
运行结果如下所示:
本文仅作为学习总结。凡有不足之处欢迎批评指正,谢谢!