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函数循环取出参数列表中的参数并打印,这种方法要求在
最后传一个结束标志,比如"",代表空串.
示例代码如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
-
- void Manue (char* msg, ...) {
- va_list argp;
- char* para;
- printf("%s", msg);
- va_start(argp,msg);
-
- while(1) {
- para = va_arg(argp, char *);
- if(strcmp(para, "")==0)
- break;
- printf("%s ", para);
- }
- va_end(argp);
- }
-
- int main() {
- Manue("usage:", "file1", " file2", "");
- return 0;
- }
二.第二种方法(有局限性,不过可以打印各种类型的参数):
通过vsprintf函数格式化输出各个参数到缓冲字符串.
示例代码如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
- void PrintFError ( const char * format, ... )
- {
- char buffer[256];
- va_list args;
- va_start (args, format);
- vsprintf (buffer,format, args);
- printf("%s\n", buffer);
- va_end (args);
- }
-
- int main ()
- {
- PrintFError ("%s%s%s", "usage:", "file1", " file2");
- return 0;
- }
三.第三种方法(这种方法能打印同种类型的任意多个参数):
以下示例代码摘自W. Richard Stevens的<<TCP/IP详解>>中使用
的sock程序的代码.巧妙地利用了这一点打印错误提示信息.
代码如下(为了演示,代码略有修改):
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
- static void
- err_doit(int errnoflag, const char *fmt, va_list ap)
- {
- int errno_save;
- char buf[4096];
-
- errno_save = errno; /* value caller might want printed */
- vsprintf(buf, fmt, ap);
-
- strcat(buf, "\n");
- fflush(stdout); /* in case stdout and stderr are the same */
- fputs(buf, stderr);
- fflush(stderr); /* SunOS 4.1.* doesn't grok NULL argument */
- return;
- }
-
- void
- /* $f err_msg $ */
- err_msg(const char *fmt, ...)
- {
- va_list ap;
-
- va_start(ap, fmt);
- err_doit(0, fmt, ap);
- va_end(ap);
- return;
- }
-
- int main() {
- err_msg(
- "usage: sock [ options ] <host> <port> (for client; default)\n"
- " sock [ options ] -s [ <IPaddr> ] <port> (for server)\n"
- " sock [ options ] -i <host> <port> (for \"source\" client)\n"
- " sock [ options ] -i -s [ <IPaddr> ] <port> (for \"sink\" server)\n"
- "options: -b n bind n as client's local port number\n"
- " -c convert newline to CR/LF & vice versa\n"
- " -f a.b.c.d.p foreign IP address = a.b.c.d, foreign port# = p\n"
- " -g a.b.c.d loose source route\n"
- " -h issue TCP half close on standard input EOF\n"
- " -i \"source\" data to socket, \"sink\" data from socket (w/-s)\n"
- #ifdef IP_ADD_MEMBERSHIP
- " -j a.b.c.d join multicast group\n"
- #endif
- " -k write or writev in chunks\n"
- " -l a.b.c.d.p client's local IP address = a.b.c.d, local port# = p\n"
- " -n n #buffers to write for \"source\" client (default 1024)\n"
- " -o do NOT connect UDP client\n"
- " -p n #ms to pause before each read or write (source/sink)\n"
- " -q n size of listen queue for TCP server (default 5)\n"
- " -r n #bytes per read() for \"sink\" server (default 1024)\n"
- " -s operate as server instead of client\n"
- #ifdef IP_MULTICAST_TTL
- " -t n set multicast ttl\n"
- #endif
- " -u use UDP instead of TCP\n"
- " -v verbose\n"
- " -w n #bytes per write() for \"source\" client (default 1024)\n"
- " -x n #ms for SO_RCVTIMEO (receive timeout)\n"
- " -y n #ms for SO_SNDTIMEO (send timeout)\n"
- " -A SO_REUSEADDR option\n"
- " -B SO_BROADCAST option\n"
- " -C set terminal to cbreak mode\n"
- " -D SO_DEBUG option\n"
- " -E IP_RECVDSTADDR option\n"
- " -F fork after connection accepted (TCP concurrent server)\n"
- " -G a.b.c.d strict source route\n"
- #ifdef IP_TOS
- " -H n IP_TOS option (16=min del, 8=max thru, 4=max rel, 2=min$)\n"
- #endif
- " -I SIGIO signal\n"
- #ifdef IP_TTL
- " -J n IP_TTL option\n"
- #endif
- " -K SO_KEEPALIVE option\n"
- " -L n SO_LINGER option, n = linger time\n"
- " -N TCP_NODELAY option\n"
- " -O n #ms to pause after listen, but before first accept\n"
- " -P n #ms to pause before first read or write (source/sink)\n"
- " -Q n #ms to pause after receiving FIN, but before close\n"
- " -R n SO_RCVBUF option\n"
- " -S n SO_SNDBUF option\n"
- #ifdef SO_REUSEPORT
- " -T SO_REUSEPORT option\n"
- #endif
- " -U n enter urgent mode before write number n (source only)\n"
- " -V use writev() instead of write(); enables -k too\n"
- " -W ignore write errors for sink client\n"
- " -X n TCP_MAXSEG option (set MSS)\n"
- " -Y SO_DONTROUTE option\n"
- " -Z MSG_PEEK\n"
- #ifdef IP_ONESBCAST
- " -2 IP_ONESBCAST option (255.255.255.255 for broadcast\n"
- #endif
- );
- return 0;
- }
总结:
掌握了以上函数的用法后今后在写程序中处理提示信息及帮助菜单的时候就可以直接运用,而不需要
手动调用一堆printf函数打印.当然还有更多的好处,比如第三中方法中的条件编译导致参数的不确定性,这
样就可以直接用可变参数函数了.
|