可变参数列表分析及printf函数实现

- 从平均数实现看可变参数列表
- 可变参数实现查找最大值
- 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;
}

运行结果如下所示:
这里写图片描述

本文仅作为学习总结。凡有不足之处欢迎批评指正,谢谢!

猜你喜欢

转载自blog.csdn.net/Erica_ou/article/details/81239571
今日推荐