C 语言中的 printf 函数

printf 函数被设计用来显示格式串(format string)的内容,并且在该串中的指定位置插入可能的值。调用 printf 函数时必须提供格式串,格式串后面的参数是需要在显示时插入到该串中的值:

printf(格式串, 表达式1, 表达式2, ...);

显示的值可以是常量、变量或者更加复杂的表达式。调用 printf 函数一次可以打印的值的个数没有限制。

格式串包含普通字符和转换说明(conversion specification),其中转换说明以字符 % 开头。转换说明是用来表示打印过程中待填充的值的占位符。跟随在字符 % 后边的信息指定了把数值从内部形式(二进制)转换成打印形式(字符)的方法,这也就是“转换说明”这一术语的由来。例如,转换说明 %d 指定 printf 函数把 int 型值从二进制形式转换成十进制数字组成的字符串,转换说明 %ffloat 型值也进行类似的转换。

格式串中的普通字符完全如在字符串中出现的那样显示出来,而转换说明则要用待显示的值来替换。思考下面的例子:

1
2
3
4
5
6
7
8
9
int i, j;
float x, y;

i = 10;
j = 20;
x = 43.2892f;
y = 5527.0f;

printf("i = %d, j = %d, x = %f, y = %f\n", i, j, x, y);

这个 printf 函数调用会产生如下输出:

i = 10, j = 20, x = 43.289200, y = 5527.000000

格式串中的普通字符被简单复制给输出行,而变量 ijxy 的值则依次替换了 4 个转换说明。

C 语言编译器不会检测格式串中转换说明的数量是否和输出项的数量相匹配。下面这个 printf 函数调用所拥有的转换说明的数量就多于要显示的值的数量:

1
printf("%d %d\n", i);   /*** WRONG ***/

printf 函数将正确显示变量 i 的值,接着显示另一个(无意义的)整数值。函数调用带有太少的转换说明也会出现类似的问题:

1
printf("%d\n", i, j);   /*** WRONG ***/

在这种情况下,printf 函数会显示变量 i 的值,但是不显示变量 j 的值。

此外,C 语言编译器也不检测转换说明是否适合要显示项的数据类型。如果程序员使用不正确的转换说明,程序将会简单地产生无意义的输出。思考下面的 printf 函数调用,其中 int 型变量 ifloat 型变量x的顺序放置错误:

1
printf("%f %d\n", i, x);   /*** WRONG ***/

因为 printf 函数必须服从于格式串,所以它将如实地显示出一个 float 型值,接着是一个 int 型值。可惜这两个值都将是无意义的。

一、转换说明#

转换说明给程序员提供了大量对输出格式的控制方法。另一方面,转换说明很可能很复杂且难以阅读。事实上,在本节中想要完整详尽地介绍转换说明是不可能的,这里只是简要介绍一些较为重要的性能。

转换说明可以包含格式化信息。具体来说,我们可以用 %.1f 来显示小数点后带一位数字的 float 型值。更一般地,转换说明可以用 %m.pX 格式或 %-m.pX 格式,这里的 mp 都是整数常量,而 X 是字母。mp 都是可选的。如果省略 pmp 之间的小数点也要去掉。在转换说明 %10.2f 中,m 是 10,p 是 2,而 Xf。在转换说明 %10f 中,m 是 10,p(连同小数点一起)省去了;而在转换说明 %.2f 中,p 是 2,m 省去了。

最小字段宽度(minimum field width)m 指定了要显示的最少字符数量。如果要显示的数值所需的字符数少于 m,那么值在字段内是右对齐的。(换句话说,在值前面放置额外的空格。)例如,转换说明 %4d 将以 •123 的形式显示数 123(本文用符号 表示空格字符)。如果要显示的值所需的字符数多于 m,那么字段宽度会自动扩展为所需的尺寸。因此,转换说明 %4d 将以 12345 的形式显示数 12345,而不会丢失数字。在 m 前放上一个负号会导致左对齐;转换说明 %-4d 将以 123• 的形式显示 123。

精度(precision)p 的含义很难描述,因为它依赖于转换说明符(conversion specifier)X 的选择。X 表明在显示数值前需要对其进行哪种转换。对数值来说最常用的转换说明符有以下几个。

  • d ——表示十进制(基数为 10)形式的整数。p 指明了待显示的数字的最少个数(必要时在数前加上额外的零);如果省略 p,则默认它的值为 1。
  • e ——表示指数(科学记数法)形式的浮点数。p 指明了小数点后应该出现的数字的个数(默认值为 6)。如果 p 为 0,则不显示小数点。
  • f ——表示“定点十进制”形式的浮点数,没有指数。p 的含义与在说明符 e 中的一样。
  • g ——表示指数形式或者定点十进制形式的浮点数,形式的选择根据数的大小决定。p 说明可以显示的有效数字(没有小数点后的数字)的最大数量。与转换说明符f不同,g 的转换将不显示尾随的零。此外,如果要显示的数值没有小数点后的数字,g 就不会显示小数点。

编写程序时无法预知数的大小或者数值变化范围很大的情况下,说明符 g 对于数的显示是特别有用的。在用于显示大小适中的数时,说明符 g 采用定点十进制形式。但是,在显示非常大或非常小的数时,说明符 g 会转换成指数形式以便减少所需的字符数。

除了说明符 %d%e%f%g 以外,还有许多其他的说明符。我们将在后续的教程中陆续进行介绍。

程序 用 printf 函数格式化数

下面的程序举例说明了用 printf 函数以各种格式显示整数和浮点数的方法。

tprintf.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/* Prints int and float values in various formats */

#include <stdio.h>

int main(void)
{
  int i;
  float x;

  i = 40;
  x = 839.21f;

  printf("|%d|%5d|%-5d|%5.3d|\n", i, i, i, i);
  printf("|%10.3f|%10.3e|%-10g|\n", x, x, x);

  return 0;
}

在显示时,printf 函数格式串中的字符 | 只是用来帮助显示每个数所占用的空格数量;不同于 %\,字符 |printf 函数而言没有任何特殊意义。此程序的输出如下:

|40|    40|40   |   040|
|    839.210| 8.392e+02|839.21    |

下面仔细看一下上述程序中使用的转换说明。

  • %d ——以十进制形式显示变量 i,且占用最少的空间。
  • %5d ——以十进制形式显示变量 i,且至少占用 5 个字符的空间。因为变量 i 只占两个字符,所以添加了 3 个空格。
  • %-5d ——以十进制形式显示变量 i,且至少占用 5 个字符的空间。因为表示变量 i 的值不需要用满 5 个字符,所以在后续位置上添加空格(更确切地说,变量 i 在长度为 5 的字段内是左对齐的)。
  • %5.3d ——以十进制形式显示变量 i,且至少占用 5 个字符的空间并至少有 3 位数字。因为变量 i 只有 2 个字符长度,所以要添加一个额外的零来保证有 3 位数字。现在只有 3 个字符长度,为了保证占有 5 个字符,还要添加 2 个空格(变量 i 是右对齐的)。
  • %10.3f ——以定点十进制形式显示变量 x,且总共用 10 个字符,其中小数点后保留 3 位数字。因为变量 x 只需要 7 个字符(即小数点前 3 位,小数点后 3 位,再加上小数点本身 1 位),所以在变量 x 前面有 3 个空格。
  • %10.3e ——以指数形式显示变量 x,且总共用 10 个字符,其中小数点后保留 3 位数字。因为变量 x 总共需要 9 个字符(包括指数),所以在变量 x 前面有 1 个空格。
  • %-10g ——既可以以定点十进制形式显示变量 x,也可以以指数形式显示变量 x,且总共用 10 个字符。在这种情况下,printf 函数选择用定点十进制形式显示变量 x。负号的出现强制进行左对齐,所以有 4 个空格跟在变量 x 后面。

二、转义序列#

格式串中常用的代码 \n 被称为转义序列(escape sequence)。转义序列使字符串包含一些特殊字符而不会使编译器引发问题,这些字符包括非打印的(控制)字符和对编译器有特殊含义的字符(如")。后面会提供完整的转义序列表,现在先看一组示例。

  • 警报(响铃)符:\a
  • 回退符:\b
  • 换行符:\n
  • 水平制表符:\t

当这些转义序列出现在 printf 函数的格式串中时,它们表示在显示中执行的操作。在大多数机器上,输出 \a 会产生一声鸣响,输出 \b 会使光标从当前位置回退一个位置,输出 \n 会使光标跳到下一行的起始位置,输出 \t 会把光标移动到下一个制表符的位置。

字符串可以包含任意数量的转义序列。思考下面的 printf 函数示例,其中的格式串包含了 6 个转义序列:

1
printf("Item\tUnit\tPurchase\n\tPrice\tDate\n");

执行上述语句显示出一条两行的标题:

Item    Unit    Purchase
        Price   Date

另一个常用的转义序列是 \",它表示字符 "。因为字符 " 标记字符串的开始和结束,所以它不能出现在没有使用上述转义序列的字符串内。下面是一个示例:

1
printf("\"Hello!\"");

这条语句产生如下输出:

"Hello!"

附带提一下,不能在字符串中只放置单独一个字符 \,编译器将认为它是一个转义序列的开始。为了显示单独一个字符 \,需要在字符串中放置两个 \ 字符:

1
printf("\\");   /* prints one \ character */

(完)

comments powered by Disqus