13-printf()的转换说明修饰符
4.4.3 printf() 的转换说明修饰符
在%和转换字符之间插入修饰符可修饰基本的转换说明。表4.4和表4.5列出可作为修饰符的合法字符。如果要插入多个字符,其书写顺序应该与表4.4中列出的顺序相同。不是所有的组合都可行。表中有些字符是C99新增的,如果编译器不支持C99,则可能不支持表中的所有项。
| 修饰符 | 含义 |
| :----- | :----- | :----- | :----- |
| 标记 | 表4.5描述了5种标记( - 、 + 、空格、 # 和 0 ),可以不使用标记或使用多个标记 | 示例: "%-10d" |
| 数字 | 最小字段宽度 | 如果该字段不能容纳待打印的数字或字符串,系统会使用更宽的字段 | 示例: "%4d" |
| . 数字 | 精度 | 对于 %e 、 %E 和 %f 转换,表示小数点右边数字的位数 | 对于 %g 和 %G 转换,表示有效数字最大位数 | 对于 %s 转换,表示待打印字符的最大数量 | 对于整型转换,表示待打印数字的最小位数 | 如有必要,使用前导 0 来达到这个位数 | 只使用 . 表示其后跟随一个 0 ,所以 %.f 和 %.0f 相同 | 示例: "%5.2f" 打印一个浮点数,字段宽度为 5 字符,其中小数点后有两位数字 |
| h | 和整型转换说明一起使用,表示 short int 或 unsigned short int 类型的值 | 示例: "%hu" 、 "%hx" 、 "%6.4hd" |
| hh | 和整型转换说明一起使用,表示 signed char 或 unsigned char 类型的值 | 示例: "%hhu" 、 "%hhx" 、 "%6.4hhd" |
| j | 和整型转换说明一起使用,表示 intmax_t 或 uintmax_t 类型的值。这些类型定义在 stdint.h 中 | 示例: "%jd" 、 "%8jx" |
| l | 和整型转换说明一起使用,表示 long int 或 unsigned long int 类型的值 | 示例: "%ld" 、 "%8lu" |
| ll | 和整型转换说明一起使用,表示 long long int 或 unsigned long long int 类型的值(C99) | 示例: "%lld" 、 "%8llu" |
| L | 和浮点转换说明一起使用,表示 long double 类型的值 | 示例: "%Lf" 、 "%10.4Le" |
| t | 和整型转换说明一起使用,表示 ptrdiff_t 类型的值。 ptrdiff_t 是两个指针差值的类型(C99) | 示例: "%td" 、 "%12ti" |
| z | 和整型转换说明一起使用,表示 size_t 类型的值。 size_t 是 sizeof 返回的类型(C99) | 示例: "%zd" 、 "%12zd" |
注意 类型可移植性
sizeof运算符以字节为单位返回类型或值的大小。这应该是某种形式的整数,但是标准只规定了该值是无符号整数。在不同的实现中,它可以是unsigned int、unsigned long甚至是unsigned long long。因此,如果要用printf()函数显示sizeof表达式,根据不同系统,可能使用%u、%lu或%llu。这意味着要查找你当前系统的用法,如果把程序移植到不同的系统还要进行修改。鉴于此,C提供了可移植性更好的类型。首先,stddef.h头文件(在包含stdio.h头文件时已包含其中)把size_t定义成系统使用sizeof返回的类型,这被称为底层类型(underlying type)。其次,printf()使用z修饰符表示打印相应的类型。同样,C还定义了ptrdiff_t类型和t修饰符来表示系统使用的两个地址差值的底层有符号整数类型。
注意
float参数的转换 对于浮点类型,有用于double和long double类型的转换说明,却没有float类型的转换说明。这是因为在K&R C中,表达式或参数中的float类型值会被自动转换成double类型。一般而言,ANSI C不会把float自动转换成double。然而,有大量的现有程序都假设float类型的参数被自动转换成double类型,为了保护这些程序,printf()函数中所有float类型的参数(对未使用显式原型的所有C函数都有效)仍自动转换成double类型。因此,无论是K&R C还是ANSI C,都没有显示float类型值专用的转换说明。
| 标记 | 含义 |
| :----- | :----- | :----- | :----- |
| - | 待打印项左对齐。即,从字段的左侧开始打印该项 | 示例: "%-20s" |
| + | 有符号值若为正,则在值前面显示加号;若为负,则在值前面显示减号 | 示例: "%+6.2f" |
| 空格 | 有符号值若为正,则在值前面显示前导空格(不显示任何符号);若为负,则在值前面显示减号 + 标记并覆盖空格 | 示例: "%6.2f" |
| # | 把结果转换为另一种形式。如果是 %o 格式,则以 0 开始;如果是 %x 或 %X 格式,则以 0x 或 0X 开始;对于所有的浮点格式, # 保证了即使后面没有任何数字,也打印一个小数点字符。对于 %g 和 %G 格式, # 防止结果后面的 0 被删除 | 示例: "%#o" 、 "%#8.0f" 、 "%+#10.3e" |
| 0 | 对于数值格式,用前导 0 代替空格填充字段宽度。对于整数格式,如果出现 - 标记或指定精度,则忽略该标记 | 示例: "%010d" 和 "%08.3f" |
1.使用修饰符和标记的示例
接下来,用程序示例演示如何使用这些修饰符和标记。先来看看字段宽度在打印整数时的效果。考虑程序清单4.7中的程序。
程序清单4.7 width.c 程序
/* width.c -- 字段宽度 */
#include <stdio.h>
#define PAGES 959
int main(void)
{
printf("*%d*\n", PAGES);
printf("*%2d*\n", PAGES);
printf("*%10d*\n", PAGES);
printf("*%-10d*\n", PAGES);
return 0;
}
程序清单4.7通过4种不同的转换说明把相同的值打印了4次。程序中使用星号(*)标出每个字段的开始和结束。其输出结果如下所示:
*959*
*959*
* 959*
*959 *
第 1 个转换说明 %d 不带任何修饰符,其对应的输出结果与带整数字段宽度的转换说明的输出结果相同。在默认情况下,没有任何修饰符的转换说明,就是这样的打印结果。第 2 个转换说明是 %2d ,其对应的输出结果应该是 2 字段宽度。因为待打印的整数有 3 位数字,所以字段宽度自动扩大以符合整数的长度。第 3 个转换说明是 %10d ,其对应的输出结果有 10 个空格宽度,实际上在两个星号之间有 7 个空格和 3 位数字,并且数字位于字段的右侧。最后一个转换说明是 %-10d ,其对应的输出结果同样是 10 个空格宽度, - 标记说明打印的数字位于字段的左侧。熟悉它们的用法后,我们就能很好地控制输出格式。试着改变 PAGES 的值,看看编译器如何打印不同位数的数字。
接下来看看浮点型格式。请输入、编译并运行程序清单4.8中的程序。
程序清单4.8 floats.c 程序
// floats.c -- 一些浮点型修饰符的组合
#include <stdio.h>
int main(void)
{
const double RENT = 3852.99; // const变量
printf("*%f*\n", RENT);
printf("*%e*\n", RENT);
printf("*%4.2f*\n", RENT);
printf("*%3.1f*\n", RENT);
printf("*%10.3f*\n", RENT);
printf("*%10.3E*\n", RENT);
printf("*%+4.2f*\n", RENT);
printf("*%010.2f*\n", RENT);
return 0;
}
该程序中使用了 const 关键字,限定变量为只读。该程序的输出如下:
*3852.990000*
*3.852990e+03*
*3852.99*
*3853.0*
* 3852.990*
* 3.853E+03*
*+3852.99*
*0003852.99*
本例的第 1 个转换说明是 %f 。在这种情况下,字段宽度和小数点后面的位数均为系统默认设置,即字段宽度是容纳待打印数字所需的位数和小数点后打印 6 位数字。
第 2 个转换说明是 %e 。默认情况下,编译器在小数点的左侧打印 1 个数字,在小数点的右侧打印 6 个数字。这样打印的数字太多!解决方案是指定小数点右侧显示的位数,程序中接下来的 4 个例子就是这样做的。请注意,第 4 个和第 6 个例子对输出结果进行了四舍五入。另外,第 6 个例子用 E 代替了 e 。
第 7 个转换说明中包含了 + 标记,这使得打印的值前面多了一个代数符号( + )。 0 标记使得打印的值前面以 0 填充以满足字段要求。注意,转换说明 %010.2f 的第 1 个 0 是标记,句点( . )之前、标记之后的数字(本例为 10 )是指定的字段宽度。
尝试修改 RENT 的值,看看编译器如何打印不同大小的值。程序清单 4.9 演示了其他组合。
程序清单4.9 flags.c 程序
/* flags.c -- 演示一些格式标记 */
#include <stdio.h>
int main(void)
{
printf("%x %X %#x\n", 31, 31, 31);
printf("**%d**% d**% d**\n", 42, 42, -42);
printf("**%5d**%5.3d**%05d**%05.3d**\n", 6, 6, 6, 6);
return 0;
}
该程序的输出如下:
1f 1F 0x1f
**42** 42**-42**
** 6** 006**00006** 006**
第 1 行输出中, 1f 是十六进制数,等于十进制数 31 。第 1 行 printf() 语句中,根据 %x 打印出 1f , %X 打印出 1F , %#x 打印出 0x1f 。
第 2 行输出演示了如何在转换说明中用空格在输出的正值前面生成前导空格,负值前面不产生前导空格。这样的输出结果比较美观,因为打印出来的正值和负值在相同字段宽度下的有效数字位数相同。
第 3 行输出演示了如何在整型格式中使用精度( %5.3d )生成足够的前导 0 以满足最小位数的要求(本例是 3 )。然而,使用 0 标记会使得编译器用前导 0 填充满整个字段宽度。最后,如果 0 标记和精度一起出现, 0 标记会被忽略。
下面来看看字符串格式的示例。考虑程序清单4.10中的程序。
程序清单4.10 stringf.c 程序
/* stringf.c -- 字符串格式 */
#include <stdio.h>
#define BLURB "Authentic imitation!"
int main(void)
{
printf("[%2s]\n", BLURB);
printf("[%24s]\n", BLURB);
printf("[%24.5s]\n", BLURB);
printf("[%-24.5s]\n", BLURB);
return 0;
}
该程序的输出如下:
[Authentic imitation!]
[ Authentic imitation!]
[ Authe]
[Authe ]
注意,虽然第 1 个转换说明是 %2s ,但是字段被扩大为可容纳字符串中的所有字符。还需注意,精度限制了待打印字符的个数。 .5 告诉 printf() 只打印 5 个字符。另外, - 标记使得文本左对齐输出。
2.学以致用
学习完以上几个示例,试试如何用一个语句打印以下格式的内容:
The NAME family just may be $XXX.XX dollars richer!
这里, NAME 和 XXX.XX 代表程序中变量(如 name[40] 和 cash )的值。可参考以下代码:
printf("The %s family just may be $%.2f dollars richer!\n",name,cash);