zoukankan      html  css  js  c++  java
  • 浅谈C语言参数可变函数的实现

     

    1.需要的头文件:stdarg.h

      需要的宏:va_start(a,b)    va_arg(a,b)    va_end(a)

      需要的类型别名: va_list

    2.基本用法

      (1)写函数头 

    1 return_type function_name(first_argument,...);

      eg1:

    1 int fun1(int a,...);                //right
    2 void fun2(double b,...);             //right
    3 char *fun3(int a, double b,...);   //right
    4 int printf(const char *format,...);  //right
    5 int wrong_function(...);             //wrong

      参数数目可变的函数,其必须明确定义至少一个参数(且必须放在最前面),之后跟...表示剩下的参数数目、类型是不确定的。

      (2)定义va_list变量(定义几个?根据需求而定)

      eg2

    1 va_list parg;

      (3)va_start(parg,first_argument);这里parg是(2)中定义的va_list类型变量,first_argument是参数列表中的第一个参数(如果在参数列表中只明确定义   了一个变量的话,那first_argument确实是参数列表中的第一个变量。但是,如果参数列表中明确定义了不止一个变量,那么first_argument指的是明确定义的       最后一个变量).

      eg3:

    1 int fun1(int a,...)
    2 {
    3     va_list pa;
    4     va_start(pa,a);
    5     //……
    6 }

    eg4:

     1 int fun1(int a,int b,int c,…)
     2 {
     3     va_list pa;
     4     va_start(pa,c);
     5     //……
     6 }

      (3)用va_arg(a,b)获取不确定的参数

    eg5: 

    1 int   a=va_arg(parg,int);          //int是通过分析得出的
    2 char *s=va_arg(parg,char *);       //char *是通过分析得出的

      分析得出?什么意思呢?由于参数数目可变函数在传递参数的时候,并没有传递参数的类型,也没有传递参数的个数,所以需要以某种方式让函数知道到底传递了多少个参数,到底每个参数是什么类型的。

      eg6:

    1 prinf(“there are %d birds,%d dogs,10 %s”,5,3,”pigs”);

      输出:there are 5 birds,3 dogs,10 pigs

      printf是怎么知道有3个参数,怎么知道参数分别是int int char*型的参数呢?其实可以发现,当我们刚开始用printf的时候,就有%d%c%s%f之类的奇怪东西跟着。在eg6中,因为有%d %d %s这3个带%的东西,printf才知道有4(第一个参数+后面的三个参数)个参数;同样因为%d %d %s,printf才知道是int,int,char*类型的变量。Printf正是通过分析%的数目以及%后面跟的字母,让printf得以分析出参数的个数和类型。

      (4)va_end(parg);

      这个没什么好说的,只要记住函数最后要加这么一句话就对了。

    3.完整的例子

    eg7:

     1 int add(int n,…)//计算n个数的和
     2 {
     3     int sum=0;
     4     va_list parg;
     5 
     6     va_start(parg,n);
     7     while(n--)//循环n次
     8         sum+=va_arg(parg,int);
     9 
    10     va_end(parg);
    11     return sum;
    12 }

    eg8:

     1 int my_easy_printf(char *format,…)//%d %c %s %h %u
     2 {
     3     int i_a;
     4     double dbl_b;
     5     unsigned u_c;
     6     char *p_d;
     7     //这里没有定义char c_e,但是却支持%c的操作,下面会讲到
     8     int print_arg=0;
     9     va_list parg;
    10 
    11     va_start(parg,format);
    12 
    13     while(*format)
    14     {
    15         if(*format!=’%’)
    16         putchar(*format);
    17         else
    18         switch(*++format)
    19         {
    20             case '%':
    21                     putchar(‘%’);
    22                     format++;
    23                     break;
    24         case 'd':
    25         case 'h':
    26             i_a=va_arg(parg,int);
    27             print_int(i_a);//这是一个输出有符号整数的函数
    28             print_arg++;
    29             break;
    30         case 'u'31             u_c=va_arg(parg,unsigned int);
    32             print_uint(u_c); //这是一个输出无符号整数的函数
    33             print_arg++;
    34             break;
    35         case 'c':
    36             c_c=va_arg(parg,int);
    37             putchar(c_c);
    38             print_arg++;
    39             break;
    40         case 's':
    41             p_d=arg(parg,char *);
    42             while(*p_d)
    43                 putchar(*p_d++);
    44             print_arg++;
    45             break;    
    46         default:
    47             va_end(parg);
    48             return -1;
    49         }
    50     Format++;
    51     }
    52     va_end(parg);
    53     return print_arg;
    54 }
    55                                      
    View Code

    ps:me_easy_printf虽然支持%c的输出,但是并没有定义char类型的变量,这是因为char类型变量在参数传递的过程中会进行类型提升,提升为int,相当于char类型变量都被类型转换成int型变量了。

    类似的

    (unsigned/signed)char、short ---> (unsigned/signed)int

      float ---> double

    4.va_list、va_start、va_arg、va_end的一种实现方式

    这四个东西(宏和类型别名)的实现和具体环境有关,并非一成不变,下面内容仅供参考,在实际使用中不要自己编写va_start、va_arg、va_end,不要自己声明类型别名va_list,请使用官方提供版本。

    (1)va_list

    1 typedef char * va_list

    va_list 定义的变量用于存储变量的指针

    (2)va_start

    1 va_start(parg,first_arg) (parg=(va_list)&first_arg+sizeof(first_arg))

    (3)va_arg

    1 #define _SIZEOFINT(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))//按sizeof(int)的整数倍不减对齐

    _SIZEOFINT(n)的目的是,求得一整数k,使

    k%sizeof(int)==0 ----------1

    k>=sizeof(n)   ----------2

    k是满足1、2的最小整数

       这里以8位举例_SIZEOFINT的实现方法,假设sizeof(int)为4,虽然int是32(16)位,但原理一样:

    假如我们要把m向4的整数倍对齐,那么只需让m的二进制后两位为0即可,即可满足能被4(sizeof(int))整除的条件,方法是m&11111100,而~sizeof(int)-1即为11111100。但是不满足条件2(除非n刚好能被4整除),因为把1变为0的话,数一定是减小的,所以要把(m+(4-1))&11111100才行,而4-1就是sizeof(int)-1,并且m就是sizeof(n)。综上,为

    ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))

    1 #define va_arg(parg,t) (parg=(va_list)&parg+SIZEOFINT(t));
    2 //取得第一个可变参数的指针,并存储在parg中

    上面的_SIZEOFINT 主要是用于需要内存对齐的系统,倘若系统不需要内存对其,则

    1 #define _SIZEOFINT(n) sizeof(n)
    1 #define va_arg(parg,t) (*(t*)((parg+=SIZEOFINT(t))-SIZEOFINT(t)))
    2 //parg+=SIZEOFINT(t)使得parg指向下一个可变参数
    3 //取得下一个可变参数的指针,并返回这个指针上的值

    (4)va_end

    1 #define va_end(parg) (parg=(va_list)0)
    2 //让parg指向NULL,防止对指针的错误使用

    上面谈的是参数数目和类型都不确定的函数,在C语言里,还有一种参数数目确定但是类型不确定的函数

    比如文件操作函数fwrite的第一个参数,貌似不需要具体的类型,先看一下fwrite的声明:

    1 size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

    eg9:

     1 //下面函数用于对两个操作数进行+操作,
     2 //如果mod为1,则进行整形+操作,
     3 //如果mod为2则进行字符串操作,出错返回ERROR,否则返回OK
     4 int add(int mod,void *opd1,void *opd2,void*result)
     5 {
     6     char *popd1,popd2,presult;
     7 
     8     if(1==mod)
     9     {
    10         * (int *)result=*(int *)opd1+*(int *)opd2;
    11         return OK;
    12     }
    13     else if(2==mod)
    14     {
    15         popd1=(char *)opd1;
    16         popd2=(char*)opd2;
    17         presult=(char *)result;
    18 
    19         while(*presult++=*opd1++);
    20             presult--;
    21         while(*presult++=*opd2++);
    22         *presult=’\0’;
    23         return OK;
    24     }
    25     else
    26         return ERROR;
    27 }
    28                 
    View Code

    本人萌新,错误难免,请叔叔们指教。

  • 相关阅读:
    Oracle 左连接、右连接、全外连接、(+)号作用
    ORA-01940:无法删除当前已链接的用户(转)
    博客正式开通
    python扫描内网存活主机(scapy与nmap模块)
    Python学习之扫描网段主机与开启端口(避坑)
    Python学习之服务器与客户端的交互
    python渗透测试编程之kali linux2的AptanaStudio3安装
    Shellcodeloader免杀过火绒
    Cracer之Waf绕过
    30 Day Challenge Day 16 | Leetcode 692. Top K Frequent Words
  • 原文地址:https://www.cnblogs.com/inori/p/4972717.html
Copyright © 2011-2022 走看看