关于printf函数
printf函数将数据写到标准输出,fprintf函数将数据写到任何文件,sprintf函数格式化字符串。这三个函数的返回值都是已经处理的字符数。
sprintf函数作为输出数据结束标志的空字符不计入返回值,printf和fprintf函数在试图写入时出现的I/O错误,将返回一个负值,此时我们无法得出究竟有多少字符已经被写出。sprintf不进行I/O操作,因此不会返回负值。当然也不排除因为C语言实现的某种原因返回负值。
对printf所输出的字符串,遵循通常的C语言习惯,即以空字符结束,因此,要用双引号括起来,把这个字符串写成字符串常量的形式,能够自动保证它以空字符结尾。该函数把格式说明字符串中的字符逐一复制到标准输出中,直到格式字符串结束或者遇到%字符,此时printf查看%后紧跟的字符而不打印%字符,以获得有关如何转换其下一个参数的指示。
sprintf和printf的第一个参数不一样,其他都一样。
关于printf函数的参数,有如下说明:
%o,%x,%X都是以无符号数据打印的。
%s,格式项对于的输出字符串必须以空字符结束,舍此别无他法。如果不是以空字符结束,则printf函数将不断打印其后的字符,直到内存某处找到空字符,这种情况下,最终打印的数据将会是乱码,且可能会很长很长。
%g,用来打印那些不需要按列对齐的浮点数。即会舍弃数值尾的零,保留6位有效数字。如果实际数据超过6位有效数字,则用科学计数法表示。对于较小的数字,只有该数的指数小于等于5时,采用科学计数法处理。
printf("%g\n", 3.14159e-4);0.000314159
printf("%g\n", 3.14159e-5);3.14159e-005
%e,打印小数点后6位有效数字,用0填充。%f也是 如此。
修饰符
长度修饰符l只对用于整型的格式码有效。
宽度修饰符对所有的格式码都有效,甚至%%也不例外。如果不能填满设置的宽度域,左边用空格字符填充,超过则挤占同一行右侧紧邻数值的位置。
精度修饰符与格式码有关,对于整型,指定了打印的数字的最少位数。不够则补0,对于%e,%E,%f,表示小数点后的位数,%.0f,除非标志另有说明,否则不打印小数点。%g,%G精度修饰符指定了打印数值中的有效数字位数,除非标志另有说明,非有效数字的0被去掉,如果小数点后不跟数字,则小数点被删除。对于%s,精度修饰符指定将要从相应的字符串中打印的字符数。
标志符:
在显示宽度大于被显示的位数时,数据尾部都是以显示区的右端对齐的,标识符-的作用是将其改为左端对齐。因此,仅当域宽修饰符存在时,标识符-才有意义。
标识符+的作用是,规定每个待打印的数值在输出时都应该以它的符号(正负)作为第一个字符。
+和空白字符一起出现时,以+的作用为主。
标示符#的作用与具体的格式项有关:
给%o加上#的作用是,当有必要时增加数值的输出精度。
%#x,%#X,输出0xXX,,,0XXX,,,,
%#o,输出0XXX
%#f,要求小数点必须被打印出来,即使小数点后没有数字。
%#g,打印出的数值尾缀不会被去掉,共6位有效数字。
可变域宽与精度
printf("%*.*s\n", 12, 5, str);
%p,打印指针地址
%n,打印打印出的字符数。
可变参数列表设计
类似于printf函数的可变参数设计,其机制是类似的,只需要知道第一个参数的类型,就可以对其进行存取,一旦第n个参数被成功的存取,第n+1个参数就可以在仅知道类型的情况下进行存取,并且按这种方式存取一个参数的时间不应该太多。需要注意的是,对于参数的存取顺序,都没有必要,另外参数列表是否结束通常也不必要。
大多数C语言实现上述机制,都是通过一组varargs的宏定义。虽然这些宏的实现与特定的C语言实现有关,但是只要运用得到,还是能够在大多数机器上使用可变参数列表。
首先,包含这组宏:
#include <varargs.h>
这组宏包含:
va_list :该宏是用来定义一个数据类型,我们用该宏申明一个对象ap,则只需要给定ap与第1个参数的类型就可以确定第1个参数的值。实际上,在任何一个C实现中,对于可变参数列表的第n个参数,在已知其类型的情况下,要对其进行存取还需要知道一些信息,这些信息通过已经可以存取的第一个参数到第n-1个参数而间接得到,因此可以把ap看成是指向参数列表内部的指针。通过va_list存取一个参数后,va_list被更新,指向下一个参数。
va_start:初始化ap,即指向参数列表头
va_end:清空ap
va_arg:获取参数
va_alist:该宏将扩展为特定C实现所要求的参数列表,这样函数就能够处理变长参数。
va_dcl:va_alist 的声明(UNIX version only) ,即将扩展为与参数列表对应的申明。
也就是说:
如果要定义一个可变参数的函数,则定义如下:
int fun(int first, ...); ANSI 版本
int fun(va_alist) va_dcl ;UNIX版本
这里举例说明:
#define END -1
int va_sum (int first_num, ...)
{
// (1) 定义参数列表
va_list ap;
// (2) 初始化参数列表
va_start(ap, first_num);
int result = first_num;
int temp = 0;
// 获取参数值
while ((temp = va_arg(ap, int)) != END) //va_arg 用于对一个参数进行存取,它的两个参数为va_list变量名和希望存取的参数类型,va_arg将取得这个参数,并更新va_list指向下一个参数、
{
result += temp;
}
// 关闭参数列表,一定要调用该宏,在大多数C实现上,调用va_end与否并无区别,但某些机器上,为了方便va_list遍历,实际采用动态分配内存。
va_end(ap);
return result;
}
vprintf,vfprintf,vsprintf与相应的函数在使用方式上类似,只是用va_list替换了格式字符串后的参数序列。用法如下:
char *format;
va_start(ap);
format = va_arg(ap, char *);
fprintf(stderr, "error: ");
vfprintf(stderr, format, ap);
va_end(ap);
实现varargs.h
typedef char *va_list;
#define va_dcl int va_alist;
#define va_start(list) list = (char *)va_alist
#define va_end(list)
#define va_arg(list, mode) \
((mode *) (list += sizeof(mode)))[-1]
因此,
int fun(va_alist) va_dcl ;
被扩展为:
int fun(va_alist) int va_alist ;
实际上这种实现方式隐含了要求函数参数在内存中连续储存。
va_arg有个陷阱要注意:
第2个参数不能用char ,short, float类型,因为char short类型被自动换成int类型,float会被换成double类型。
ANSI 版varargs.h
ANSIC对varargs.h 做了一种扩展,但前者能够运行的环境更多,可移植性更强,ANSIC的头文件为stdarg.h
其用法见例子,stdarg.h中并没有va_alist,va_dcl宏。