C可变参数函数 实现

先看例子程序。该函数至少有一个整数参数,其后占位符…,表示后面参数的个数不定. 在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值. 
函数代码如下: 

//示例代码1:可变参数函数的使用 
#include "stdio.h" 
#include "stdarg.h" 
void simple_va_fun(int start, ...) 
{ 
     va_list arg_ptr; 
     int nArgValue =start; 
     int nArgCout=0;      //可变参数的数目 
     va_start(arg_ptr,start); //以固定参数的地址为起点确定变参的内存起始地址。 
     do { 
        ++nArgCout; 
         printf("the %d th arg: %d
",nArgCout,nArgValue);      //输出各参数的值 
         nArgValue = va_arg(arg_ptr,int);                     //得到下一个可变参数的值 
     } while(nArgValue != -1);                 
     return; 
} 
int main(int argc, char* argv[]) 
{ 
     simple_va_fun(100,-1); 
     simple_va_fun(100,200,-1); 
     return 0; 
} 

下面解释一下这些代码 
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤: 
⑴由于在程序中将用到以下这些宏: 
     void va_start( va_list arg_ptr, prev_param ); 
     type va_arg( va_list arg_ptr, type ); 
     void va_end( va_list arg_ptr ); 
va在这里是variable-argument(可变参数)的意思. 
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件. 
⑵函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。 
⑶然后用va_start宏初始化⑵中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数. 
⑷然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。 
⑸设定结束条件,这里的条件就是判断参数值是否为-1。注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。

这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.同理,我们写一个简单的可变参数类型为char*的函数

#include <stdio.h>;  
#include <string.h>;  
#include <stdarg.h>;  

/* ANSI标准形式的声明方式,括号内的省略号表示可选参数 */  

int demo(char *msg, ... )  
{  
    va_list argp;                    /* 定义保存函数参数的结构 */  
    int argno = 0;                    /* 纪录参数个数 */  
    char *para;                        /* 存放取出的字符串参数 */  
    
                                    /* argp指向传入的第一个可选参数,      msg是最后一个确定的参数 */  
    va_start( argp, msg );  
    
    while (1) 
    {  
        para = va_arg( argp, char *);                 /*      取出当前的参数,类型为char *. */  
        if ( strcmp( para, "/0") == 0 )  
                                                      /* 采用空串指示参数输入结束 */  
            break;  
        printf("Parameter #%d is: %s/n", argno, para);  
        argno++;  
    }  
    va_end( argp );                                   /* 将argp置为NULL */  
    return 0;  
}


void main( void )  
{  
demo("DEMO", "This", "is", "a", "demo!" ,"333333", "/0");  


}  

  可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型.

我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: 
int printf( const char* format, ...); 
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点"…"做参数占位符),实际调用时可以有以下的形式: 
     printf("%d",i); 
     printf("%s",s); 
     printf("the number is %d ,string is:%s", i, s);   

 有人会问:printf中不是实现了智能识别参数吗?那是因为函数 printf是从固定参数format字符串来分析出参数的类型,再调用va_arg 的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.

下面是一个简单的printf函数的实现,参考了<The C Programming Language>中的156页的例子,读者可以结合书上的代码与本文参照。 

#include "stdio.h" 
#include "stdlib.h" 
void myprintf(char* fmt, ...)         //一个简单的类似于printf的实现,//参数必须都是int 类型 
{ 
     char* pArg=NULL;                //等价于原来的va_list 
     char c; 
     pArg = (char*) &fmt;     //注意不要写成p = fmt !!因为这里要对//参数取址,而不是取值 
     pArg += sizeof(fmt);          //等价于原来的va_start           
    do 
     { 
         c =*fmt; 
         if (c != ’%’) 
         { 
             putchar(c);             //照原样输出字符 
         } 
         else 
{ 
//按格式字符输出数据 
             switch(*++fmt) 
{ 
             case ’d’: 
                 printf("%d",*((int*)pArg));            
                 break; 
             case ’x’: 
                 printf("%#x",*((int*)pArg)); 
                 break; 
             default: 
                 break; 
             } 
             pArg += sizeof(int);                //等价于原来的va_arg 
         } 
         ++fmt; 
     }while (*fmt != ’0’); 
     pArg = NULL;                                //等价于va_end 
     return; } 
int main(int argc, char* argv[]) 
{ 
     int i = 1234; 
     int j = 5678; 
     myprintf("the first test:i=%d
",i,j); 
     myprintf("the secend test:i=%d; %x;j=%d;
",i,0xabcd,j); 
     system("pause"); 
     return 0; 
} 

二、可变参类型陷阱

下面的代码是错误的,运行时得不到预期的结果:

view plaincopy to clipboardprint?
va_start(pArg, plotNo);   
fValue = va_arg(pArg, float);  // 类型应改为double,不支持float   
va_end(pArg);  
va_start(pArg, plotNo);
fValue = va_arg(pArg, float);  // 类型应改为double,不支持float
va_end(pArg);

下面列出va_arg(argp, type)宏中不支持的type:

—— char、signed char、unsigned char
—— short、unsigned short
—— signed short、short int、signed short int、unsigned short int
—— float

在C语言中,调用一个不带原型声明的函数时,调用者会对每个参数执行“默认实际参数提升(default argument promotions)”。该规则同样适用于可变参数函数——对可变长参数列表超出最后一个有类型声明的形式参数之后的每一个实际参数,也将执行上述提升工作。

提升工作如下:
——float类型的实际参数将提升到double
——char、short和相应的signed、unsigned类型的实际参数提升到int
——如果int不能存储原值,则提升到unsigned int

然后,调用者将提升后的参数传递给被调用者。

所以,可变参函数内是绝对无法接收到上述类型的实际参数的。


关于该陷井,C/C++著作中有以下描述:


在《C语言程序设计》对可变长参数列表的相关章节中,并没有提到这个陷阱。但是有提到默认实际参数提升的规则:
在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转换为double类型。
                ——《C语言程序设计》第2版  2.7 类型转换 p36

在其他一些书籍中,也有提到这个规则:

事情很清楚,如果一个参数没有声明,编译器就没有信息去对它执行标准的类型检查和转换。
在这种情况下,一个char或short将作为int传递,float将作为double传递。
这些做未必是程序员所期望的。
脚注:这些都是由C语言继承来的标准提升。
对于由省略号表示的参数,其实际参数在传递之前总执行这些提升(如果它们属于需要提升的类型),将提升后的值传递给有关的函数。——译者注
                ——《C++程序设计语言》第3版-特别版 7.6 p138

…… float类型的参数会自动转换为double类型,short或char类型的参数会自动转换为int类型 ……
                ——《C陷阱与缺陷》 4.4 形参、实参与返回值 p73

这里有一个陷阱需要避免:
va_arg宏的第2个参数不能被指定为char、short或者float类型。
因为char和short类型的参数会被转换为int类型,而float类型的参数会被转换为double类型 ……
例如,这样写肯定是不对的:
c = va_arg(ap,char);
因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:
c = va_arg(ap,int);
                ——《C陷阱与缺陷》p164

原文地址:https://www.cnblogs.com/jzxx/p/6138242.html