7.3 变长参数表
以实现函数printf的一个最简单版本为例,介绍如何以可移植的方式编写可处理变长参数表的函数。因为我们的重点在于参数的处理,所以,函数minprintf只处理格式字符串和参数,格式转换则通过调用函数printf实现。
函数printf的正确声明形式为:
int printf(char* fmt, ...)
其中,省略号表示参数表中参数的数量和类型是可变的。省略号只能出现在参数表的尾部。因为minprintf函数不需要想printf函数一样返回实际输出的字符数,因此,我们将它声明为下列形式:
void minprintf(char* fmt, ...)
编写函数minprintf的关键在于如何处理一个甚至连名字都没有的参数表。标准头文件<stdarg.h>中包含一组宏定义,它们对如何遍历参数表进行了定义。改头文件的实现因不同的机器而不同,但提供的接口是一致的。
va_list类型用于声明一个变量,该变量将依次引用各参数。在函数minprintf中,我们将该变量称为ap,意思是“参数指针”。宏 va_start将ap初始化为指向第一个无名参数的指针。在使用ap之前,该宏必须被调用一次。参数表必须至少包括一个有名参数,va_start将最 后一个有名参数作为起点。
每次调用va_arg,该函数都将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型名来决定返回的对象类型、指针移动的步长。
va_list类型用于声明一个变量,该变量将依次引用各参数。在函数minprintf中,我们将该变量称为ap,意思是“参数指针”。宏 va_start将ap初始化为指向第一个无名参数的指针。在使用ap之前,该宏必须被调用一次。参数表必须至少包括一个有名参数,va_start将最 后一个有名参数作为起点。
每次调用va_arg,该函数都将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型名来决定返回的对象类型、指针移动的步长。
最后,必须在函数返回之前调用va_end,以完成一些必要的清理工作。
#include <stdio.h> #include <stdarg.h> void minprintf(char* fmt, ...) { va_list ap; char* p; char* sval; int ival; double dval; va_start(ap, fmt); for (p = fmt; *p; p++) { if (*p != '%') { putchar(*p); continue; } switch (*++p) { case 'd': ival = va_arg(ap, int); printf("%d", ival); break; case 'f': dval = va_arg(ap, double); printf("%f", dval); break; case 's': for (sval = va_arg(ap, char*); *sval; sval++) putchar(*sval); break; default: putchar(*p); break; } } va_end(ap); } int main() { int i = 12; double d = 4.3; char* str = "OK"; minprintf("test%d test%f test%s test%t test", i, d, str); printf(" debug "); minprintf("test%d test%f test%s test ", i, d, str); return 0; }
终端显示:
test12
test4.300000
testOK
testt
test
debug
test12
test4.300000
testOK
test
7.5 文件访问
#include <stdio.h> /* cat: concatenate files, version 1 */ main(int argc, char *argv[]) { FILE *fp; void filecopy(FILE *, FILE *); if(argc == 1)/* no args; copy standard input */ filecopy(stdin, stdout); else while(--argc > 0) if ((fp = fopen(*++argv, "r")) == NULL) { printf("cat: can't open %s ", *argv); return 1; } else { filecopy(fp, stdout); fclose(fp); } return 0; } /* filecopy: copy file ifp to file ofp */ void filecopy(FILE *ifp, FILE *ofp) { int c; while ((c = getc(ifp)) != EOF) putc(c, ofp); }
调试过程:
新建文件file,写入内容Today is Thursday.
终端显示:
ray@ray-Lenovo:~/Exec_important/c_pro/seven_chapter$ ./7_5_file_operate file
Today is Thursday.
ray@ray-Lenovo:~/Exec_important/c_pro/seven_chapter$
7.6. 错误处理——stderr 和 exit
#include <stdio.h> /* cat: concatenate files, version 2 */ main(int argc, char *argv[]) { FILE *fp; void filecopy(FILE *, FILE *); char *prog = argv[0]; /* program name for errors */ if (argc == 1 ) /* no args; copy standard input */ filecopy(stdin, stdout); else while (--argc > 0) if ((fp = fopen(*++argv, "r")) == NULL) { fprintf(stderr, "%s: can't open %s ", prog, *argv); exit(1); } else { filecopy(fp, stdout); fclose(fp); } if (ferror(stdout)) { fprintf(stderr, "%s: error writing stdout ", prog); exit(2); } exit(0); }