函数原型:列出了函数期望收到的参数数目及类型,但是它只能显示“固定数目”的参数。

可变参数列表:让一个函数在不同的时刻接受“不同数目”的参数可变参数列表是通过宏来实现的,这些宏都在stdarg.h这个头文件中,所以使用可变参数列表时要引用头文件#include<stdarg>。



例如:求寻找一组整数中最小的值,因为整数的个数不确定,所以函数在传参的时候也是不确定的,因此需要用到可变参数列表:

利用可变参数列表求最小值:来看看可变参数列表的形式:


int min_num(int val, ...)

{

                 va_list arg;

                 va_start(arg, val );

                 int min = va_arg (arg, int);

                 for (int i = 0; i < val-1; i++)

                {

                                 int tmp = va_arg (arg, int);

                                 if (min>tmp)

                                                min = tmp;

                }

                va_end(arg);

                 return min;

}


int main()

{

                

                 int ret=min_num(5,1,0,2,4,5);

                printf( "%d\n", ret);

                system( "pause");

                 return 0;

}


分析:

int min_num(int val, ...)  

//注意可变参数列表至少要有一个参数,这是因为你要通过这个参数的地址才能访问后面的参数,而这个参数就叫做命名参数(也就是“ ... ”前面的参数)

va_list arg;                       

//va_list 是一个char *类型(typedef  char *  va_list),声明一个char *类型的指针,便于向后访问.


 va_start(arg, val );          

  //初始化arg,可以看到,va_start有两个参数,一个是va_list所声明的指针arg,另一个参数列表中第一个参数 val.

通过:#define _crt_va_start (ap,v)  ( ap = (va_list) _ADDRESSOF(v) + _INTSIZEOF (v) )

看出,va_start的功能就是让指针arg向后偏移sizeof(val)个大小,即让arg指向参数列表中val之后的下一个参数的位置。(va_start(arg,val)实际上是让指针arg指向参数列表中第一个可变参数,所以va_start的第二个参数应该是“    ...   ”的前一个参数,因为本题中“  ...   ”前面只有一个参数val,所以va_start的第二个参数是val).





 int min = va_arg (arg, int);

//va_arg(arg,int)      用来获取参数,即va_start(arg,int)表示的就是*arg;

通过:#define _crt_va_arg (ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

           (ap += _INTSIZEOF(t))         - _INTSIZEOF(t)    所表达的意思可以分成两个语句:

            第一个语句: ap+=_INTSIZEOF(t)       这时ap已经指向了下一个参数

            第二个语句: ap-_INTSIZEOF(t)           整个表达式的值并没有保存,所以整个表达式*引用后指向的还是当前的值。

可以看到每当使用一次ap之后ap都向后偏移sizeof(int)个字节,指向下一个参数。


 int tmp = va_arg (arg, int);            

//这个也是一样,用来获取参数,但是到什么时候停下来呢???这时候val就派上作用,val代表   “...”中被传递过来实参的个数,例如在本题中,要找出5个数中最小值,val就是5;同理要是在100个数中找,val代表的是100

 va_end(arg);      


//因为arg是一个指针,所以在使用完后最好将它赋成空。

     #define _crt_va_end (ap)      ( ap = (va_list)0 )


va_list;是一个类型代表char *


va_start();初始化arg;实际上是:arg=&val+sizeof(aize_t val),让指针arg找到第一个可变参数


va_arg();获取下一个参数:就是引用*arg,用完之后再让arg加一(注意每使用一次va_arg(),arg都会自动指向下一个可变参数


va_end();将arg赋成0;把指针赋成0.

总结:可变参数列表通过参数之间的地址进行访问


可变参数列表的限制:

1、只能从第一个可变参数一次向后访问(这是移位他实际上是通过前一个参数的地址向后偏移找到下一个参数,依次向后递推着访问

2、因为可变参数列表是通过宏实现的,而宏无法判断参数的类型和可变参数的个数,所以我们必须使用命名参数。


#define _crt_va_start (ap,v)  ( ap = (va_list) _ADDRESSOF(v) + _INTSIZEOF (v) )

#define _crt_va_arg (ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

#define _crt_va_end (ap)      ( ap = (va_list)0 )


例:模拟实现prinf函数,实现:   print(arr, "s ccc.\n","hello" ,'b','i','t');

#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>

char * print(char *arr,char *format, ...)
{
      char *p = arr ;
      va_list arg;
      va_start(arg,format );
      while (*format )
       {
             if (*format == 's')
             {
                   char *p3 = va_arg (arg, char *);
                     while (*p3)
                     {
                         *p++=*p3++;
                     }
                         p--;
              }                          
               else if (*format == 'c')
                     *p = va_arg(arg, char );
               else
                    *p= *format;
                    
               format++;
               p++;
        }
        *p = '\0';
        va_end(arg);
        return arr ;
}
int main()
{
       char arr[100];
       print(arr, "s ccc.\n","hello" ,'b','i','t');
       printf( "%s",arr);
       system( "pause");
       return 0;
}