By qianghaohao(Xqiang)
在C語言中當(dāng)一個(gè)函數(shù)參數(shù)無法列舉出來,或者參數(shù)個(gè)數(shù)
不確定,這時(shí)我們將函數(shù)聲明為可變參數(shù)的形式,根據(jù)需
要傳適當(dāng)個(gè)數(shù)的參數(shù).舉例如下:
int fun(char *fmt, ...);
... 表示此函數(shù)fmt后面可以傳任意數(shù)目的參數(shù).
我們所熟悉的printf函數(shù)便是利用了這一特性,printf
函數(shù)聲明如下:
int printf(const char *, ...);
以下介紹如何利用此類函數(shù)打印函數(shù)的所有參數(shù),總共
介紹了三種方法:
以此函數(shù)聲明為例:int fun(char *fmt, ...);
在介紹之前得先搞清楚以下相關(guān)的函數(shù)(在stdarg.h中聲明):
1.va_list
定義一個(gè)va_list變量,后面的操作都和此變量有關(guān).
ex: va_list ap;
2.va_start
初始化va_list變量,讓ap指向可變參數(shù)表的第一個(gè)參數(shù),即
...參數(shù)中的第一個(gè)參數(shù).此函數(shù)第一個(gè)參數(shù)為ap,第二個(gè)參
數(shù)為第一個(gè)可變參數(shù)的前一個(gè)參數(shù),即...前的那一個(gè)參數(shù).
ex: va_start(ap, fmt);
3.va_arg
獲取可變參數(shù),此函數(shù)第一個(gè)參數(shù)為ap,第二個(gè)參數(shù)為要獲取
的類型.返回指定類型的的變量,然后讓ap指向可變參數(shù)表的
下一個(gè)參數(shù).
ex: va_arg(ap, char *);
4.va_end
使用完后釋放ap變量
ex: va_end(ap);
5. int vsprintf(char *string, char *format, va_list param);
按照format格式化輸出參數(shù)到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;
}
好了,現(xiàn)在有了前面的知識,現(xiàn)在正式開始介紹如何打印出可變參數(shù)函數(shù)的所有參數(shù):
一.第一種方法(比較笨的一種方法,我不推薦這么做,除非有特殊需求吧):
通過va_arg函數(shù)循環(huán)取出參數(shù)列表中的參數(shù)并打印,這種方法要求在
最后傳一個(gè)結(jié)束標(biāo)志,比如"",代表空串.
示例代碼如下:
- #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;
- }
二.第二種方法(有局限性,不過可以打印各種類型的參數(shù)):
通過vsprintf函數(shù)格式化輸出各個(gè)參數(shù)到緩沖字符串.
示例代碼如下:
- #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;
- }
三.第三種方法(這種方法能打印同種類型的任意多個(gè)參數(shù)):
以下示例代碼摘自W. Richard Stevens的<<TCP/IP詳解>>中使用
的sock程序的代碼.巧妙地利用了這一點(diǎn)打印錯誤提示信息.
代碼如下(為了演示,代碼略有修改):
- #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;
- }
總結(jié):
掌握了以上函數(shù)的用法后今后在寫程序中處理提示信息及幫助菜單的時(shí)候就可以直接運(yùn)用,,而不需要
手動調(diào)用一堆printf函數(shù)打印.當(dāng)然還有更多的好處,比如第三中方法中的條件編譯導(dǎo)致參數(shù)的不確定性,這
樣就可以直接用可變參數(shù)函數(shù)了.
|