分享

C语言可变参数函数的使用及相关函数介绍

 CodeNutter 2016-06-16

            By qianghaohao(Xqiang)

       在C语言中当一个函数参数无法列举出来,或者参数个数
 不确定,这时我们将函数声明为可变参数的形式,根据需
 要传适当个数的参数.举例如下:
int fun(char *fmt, ...);
... 表示此函数fmt后面可以传任意数目的参数.
我们所熟悉的printf函数便是利用了这一特性,printf
函数声明如下:
int printf(const char *, ...);
以下介绍如何利用此类函数打印函数的所有参数,总共
介绍了三种方法:

以此函数声明为例:int fun(char *fmt, ...);
在介绍之前得先搞清楚以下相关的函数(在stdarg.h中声明):
1.va_list
  定义一个va_list变量,后面的操作都和此变量有关.
  ex: va_list ap;
2.va_start
  初始化va_list变量,让ap指向可变参数表的第一个参数,即
  ...参数中的第一个参数.此函数第一个参数为ap,第二个参
  数为第一个可变参数的前一个参数,即...前的那一个参数.
  ex: va_start(ap, fmt);
3.va_arg
  获取可变参数,此函数第一个参数为ap,第二个参数为要获取
  的类型.返回指定类型的的变量,然后让ap指向可变参数表的
  下一个参数.
  ex: va_arg(ap, char *);
4.va_end
  使用完后释放ap变量
  ex: va_end(ap);
5. int vsprintf(char *string, char *format, va_list param);
   按照format格式化输出参数到string,format类似于printf的
   格式化串,param是va_list变量.
   ex: ---摘自百度百科
    #include <stdarg.h>
    #include<stdio.h>
    char buffer[80];
    int vspf(char *fmt, ...)
    {
        va_list argptr;
        int cnt;
        va_start(argptr, fmt);
        cnt = vsprintf(buffer, fmt, argptr);
        va_end(argptr);
        return(cnt);
    }
    int main(void)
    {
        int inumber = 30;
        float fnumber = 90.0;
        char string[4] = "abc";
        vspf("%d %f %s", inumber, fnumber, string);
        printf("%s\n", buffer);
        return 0;
    }

 好了,现在有了前面的知识,现在正式开始介绍如何打印出可变参数函数的所有参数:
 一.第一种方法(比较笨的一种方法,我不推荐这么做,除非有特殊需求吧):
      通过va_arg函数循环取出参数列表中的参数并打印,这种方法要求在
      最后传一个结束标志,比如"",代表空串.
      示例代码如下:
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <stdarg.h>  
  4.   
  5. void Manue (char* msg, ...) {  
  6.     va_list argp;  
  7.     char* para;  
  8.     printf("%s", msg);  
  9.     va_start(argp,msg);  
  10.   
  11.     while(1) {  
  12.         para = va_arg(argp, char *);  
  13.         if(strcmp(para, "")==0)  
  14.             break;  
  15.         printf("%s ", para);  
  16.     }  
  17.     va_end(argp);  
  18. }  
  19.   
  20. int main() {  
  21.   Manue("usage:", "file1", " file2", "");  
  22.   return 0;  
  23. }  
二.第二种方法(有局限性,不过可以打印各种类型的参数):
    通过vsprintf函数格式化输出各个参数到缓冲字符串.
   示例代码如下:
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <stdarg.h>  
  4. void PrintFError ( const char * format, ... )  
  5. {  
  6.   char buffer[256];  
  7.   va_list args;  
  8.   va_start (args, format);  
  9.   vsprintf (buffer,format, args);  
  10.   printf("%s\n", buffer);  
  11.   va_end (args);  
  12. }  
  13.   
  14. int main ()  
  15. {  
  16.   PrintFError ("%s%s%s", "usage:", "file1", " file2");  
  17.   return 0;  
  18. }  
三.第三种方法(这种方法能打印同种类型的任意多个参数):
    以下示例代码摘自W. Richard Stevens的<<TCP/IP详解>>中使用
的sock程序的代码.巧妙地利用了这一点打印错误提示信息.
    代码如下(为了演示,代码略有修改):
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <stdarg.h>  
  4. static void  
  5. err_doit(int errnoflag, const char *fmt, va_list ap)  
  6. {  
  7.     int     errno_save;  
  8.     char    buf[4096];  
  9.   
  10.     errno_save = errno;     /* value caller might want printed */  
  11.     vsprintf(buf, fmt, ap);  
  12.   
  13.     strcat(buf, "\n");  
  14.     fflush(stdout);     /* in case stdout and stderr are the same */  
  15.     fputs(buf, stderr);  
  16.     fflush(stderr);     /* SunOS 4.1.* doesn't grok NULL argument */  
  17.     return;  
  18. }  
  19.   
  20. void  
  21. /* $f err_msg $ */  
  22. err_msg(const char *fmt, ...)  
  23. {  
  24.     va_list     ap;  
  25.   
  26.     va_start(ap, fmt);  
  27.     err_doit(0, fmt, ap);  
  28.     va_end(ap);  
  29.     return;  
  30. }  
  31.   
  32. int main() {  
  33.     err_msg(  
  34. "usage: sock [ options ] <host> <port>              (for client; default)\n"  
  35. "       sock [ options ] -s [ <IPaddr> ] <port>     (for server)\n"  
  36. "       sock [ options ] -i <host> <port>           (for \"source\" client)\n"  
  37. "       sock [ options ] -i -s [ <IPaddr> ] <port>  (for \"sink\" server)\n"  
  38. "options: -b n  bind n as client's local port number\n"  
  39. "         -c    convert newline to CR/LF & vice versa\n"  
  40. "         -f a.b.c.d.p  foreign IP address = a.b.c.d, foreign port# = p\n"  
  41. "         -g a.b.c.d  loose source route\n"  
  42. "         -h    issue TCP half close on standard input EOF\n"  
  43. "         -i    \"source\" data to socket, \"sink\" data from socket (w/-s)\n"  
  44. #ifdef  IP_ADD_MEMBERSHIP  
  45. "         -j a.b.c.d  join multicast group\n"  
  46. #endif  
  47. "         -k    write or writev in chunks\n"  
  48. "         -l a.b.c.d.p  client's local IP address = a.b.c.d, local port# = p\n"  
  49. "         -n n  #buffers to write for \"source\" client (default 1024)\n"  
  50. "         -o    do NOT connect UDP client\n"  
  51. "         -p n  #ms to pause before each read or write (source/sink)\n"  
  52. "         -q n  size of listen queue for TCP server (default 5)\n"  
  53. "         -r n  #bytes per read() for \"sink\" server (default 1024)\n"  
  54. "         -s    operate as server instead of client\n"  
  55. #ifdef  IP_MULTICAST_TTL  
  56. "         -t n  set multicast ttl\n"  
  57. #endif  
  58. "         -u    use UDP instead of TCP\n"  
  59. "         -v    verbose\n"  
  60. "         -w n  #bytes per write() for \"source\" client (default 1024)\n"  
  61. "         -x n  #ms for SO_RCVTIMEO (receive timeout)\n"  
  62. "         -y n  #ms for SO_SNDTIMEO (send timeout)\n"  
  63. "         -A    SO_REUSEADDR option\n"  
  64. "         -B    SO_BROADCAST option\n"  
  65. "         -C    set terminal to cbreak mode\n"  
  66. "         -D    SO_DEBUG option\n"  
  67. "         -E    IP_RECVDSTADDR option\n"  
  68. "         -F    fork after connection accepted (TCP concurrent server)\n"  
  69. "         -G a.b.c.d  strict source route\n"  
  70. #ifdef  IP_TOS  
  71. "         -H n  IP_TOS option (16=min del, 8=max thru, 4=max rel, 2=min$)\n"  
  72. #endif  
  73. "         -I    SIGIO signal\n"  
  74. #ifdef  IP_TTL  
  75. "         -J n  IP_TTL option\n"  
  76. #endif  
  77. "         -K    SO_KEEPALIVE option\n"  
  78. "         -L n  SO_LINGER option, n = linger time\n"  
  79. "         -N    TCP_NODELAY option\n"  
  80. "         -O n  #ms to pause after listen, but before first accept\n"  
  81. "         -P n  #ms to pause before first read or write (source/sink)\n"  
  82. "         -Q n  #ms to pause after receiving FIN, but before close\n"  
  83. "         -R n  SO_RCVBUF option\n"  
  84. "         -S n  SO_SNDBUF option\n"  
  85. #ifdef  SO_REUSEPORT  
  86. "         -T    SO_REUSEPORT option\n"  
  87. #endif  
  88. "         -U n  enter urgent mode before write number n (source only)\n"  
  89. "         -V    use writev() instead of write(); enables -k too\n"  
  90. "         -W    ignore write errors for sink client\n"  
  91. "         -X n  TCP_MAXSEG option (set MSS)\n"  
  92. "         -Y    SO_DONTROUTE option\n"  
  93. "         -Z    MSG_PEEK\n"  
  94. #ifdef  IP_ONESBCAST  
  95. "         -2    IP_ONESBCAST option (255.255.255.255 for broadcast\n"  
  96. #endif  
  97. );  
  98.   return 0;  
  99. }  
 总结:
    掌握了以上函数的用法后今后在写程序中处理提示信息及帮助菜单的时候就可以直接运用,而不需要
手动调用一堆printf函数打印.当然还有更多的好处,比如第三中方法中的条件编译导致参数的不确定性,这
样就可以直接用可变参数函数了.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多