1、所有的printf 族函数
有两大类:
#include<stdio.h>
int printf(const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);
int dprintf(int fd, const char* format, ...);
int sprintf(char* str, const char* format, ...);
int snprintf(char* str, size_t size, const char* format, ...);
#include<stdarg.h>
int vprintf(const char* format, va_list ap);
int vfprintf(FILE* stream, const char* format, va_list ap);
int vdprintf(int fd, const char* format, va_list ap);
int vsprintf(char* str, const char* format, va_list ap);
int vsnprintf(char* str, size_t size, const char* format, va_list ap);
从函数名来看,下面的函数的名字都比上面多一个字母 v 。 从参数类型来看,下面函数的最后一个参数都是va_list 类型。 下面的函数在功能上与上面的函数一 一对应。
2、前驱知识
本小节从从最基础的 printf 入手,来讲解学习这些函数需要的前驱知识。
2.1 什么是: 格式化字符串format
这个其实我们每天都在用,只是不知道他叫这个名字。
int a = 1;
double b = 3.14;
printf("a = %d, b = %llf\n", a, b);
结合上面printf 函数的定义可知, "a = %d, b = %llf\n" 其实就是格式化字符串,即,参数format 的内容。而a 、b 合起来是可变参数参数,即,参数... 的内容。
2.2 va_list 是什么类型
va_list 是可变参数列表类型,它的含义和... 一样,都是代表很多个参数。但它们的区别也很大。
准确来说 ... 并不能算作一种类型,他只是在视觉上告诉我们这后面可以传入可变参数。而在函数的实现中我们并不能直接使用... 作为参数名来使用这些可变参数。那,我们想自己实现一个形如 my_printf(const char* format, ...) 的函数该怎么办呢??
va_list 类型就是用来解决这个问题的。在函数体中,我们使用va_list 类型的一个参数就可以代表... 中所有的可变参数。
好嘞,本小节标题的问题解决了。
3、可变参数列表(va_list )怎么用?
3.1 va_list 族函数
#include<stdarg.h>
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
coid va_end(va_list ap);
void va_copy(va_list dest, va_list src);
3.2 这些函数怎么用
直接以一个实际应用中的例子来说明。现在,我们的程序中需要一个这样的函数:func(int a, double b, ...);
首先,在函数体中定义一个va_list 类型的变量:va_list arg_list;
此时arg_list 还未初始化,类似一个空指针,接下来要把这个空指针指定到需要的位置。va_start 函数就用来完成这件事,使用va_list 类型时,该函数必须第一个被调用: va_start(arg_list, b); 调用该函数后,arg_list 就能代表传入func 的所有可变参数。va_start 函数中,参数last 没有类型,就是一个参数名,他是fun 中最后一个类型已知的参数的名字,本例中就是b 。
然后,arg_list 就能正式投入使用了。我们既可以将他看作一个整体,传入printf 函数的第二个参数,也可以把可变参数一个一个取出来用。 先来看前者:直接这样:printf(" 这里是格式化字符串 ", arg_list); 后者要复杂一些,需要使用va_arg 函数。进行 va_arg(arg_list, int) 调用就是从开始位置返回一个int (可能不是int 类型,根据实际情况确定类型),并把位置往前推1,下次调用该函数就返回后面的那个参数。这样访问需要提前知道可变参数的数量,类型以及顺序。
va_arg 函数本身并不知道在哪里结束,所以用while(1){ var_arg(args,int);} 会无限循环直到内存访问越界程序coredump。 最好就在传参时传入一个专门用于标识结尾的参数 比如12345678 。当返回值等于标识数时,结束访问。
访问完毕后,必须使用va_end 函数销毁刚刚初始化的arg_list :va_end(arg_list);
3.3 总结
① va_start :初始化(指定位置) ② va_arg :遍历访问 ③ va_end :销毁 ④ va_copy :复制
4、再看printf 族函数
#include<stdio.h>
int printf(const char* format, ...); 将输出写入标准输出 stdout
int fprintf(FILE* stream, const char* format, ...); 将输出写入给定的输出流
int dprintf(int fd, const char* format, ...); 将输出写入文件描述符
int sprintf(char* str, const char* format, ...); 将输出写入字符串
int snprintf(char* str, size_t size, const char* format, ...); 将定长输出写入字符串
使用va_list 类型的函数功能与上面的一 一对应,只是在调用时,把上面的... 变量用va_list 变量替代。
#include<stdarg.h>
int vprintf(const char* format, va_list ap);
int vfprintf(FILE* stream, const char* format, va_list ap);
int vdprintf(int fd, const char* format, va_list ap);
int vsprintf(char* str, const char* format, va_list ap);
int vsnprintf(char* str, size_t size, const char* format, va_list ap);
|