WangYu::Space

Study, think, create, and grow. Teach yourself and teach others.

C 语言中格式化打印

分类:C/C++创建时间:2021-05-06 00:00:00

C 语言头文件 stdio.h 中包含 printf 系列函数,一直只了解其基本的用法,这次详细探究一番。

printf("str: %s, num: %d\n", s, n);

本文的目的在于了解这里 %s%d 等的语法。

语法

格式化项的语法如下,包含 6 个部分,方括号里面的是可忽略的,只有最后的 type 是必须的。

fmt       ::= %[parameter][flags][width][.precision][length]type
parameter ::= <digit+>"$"
flags     ::= "-" | "+" | " " | "0" | "'" | "#"
width     ::= <digit+>
precision ::= <digit+> | "*"
length    ::= "hh" | "h" | "l" | "ll" | "L" | "z" | "j" | "t"
type      ::= "d" | "i" | "u" | "f" | "F" | "e" | "E" | "g" | "G" | "x" | "X" | "o" | "s" | "c" | "p" | "a" | "A" | "n"

parameter

这不是 C99 的标准,而是一个 POSIX 的扩展,它的形式很简单,下面是一个例子:

printf("%2$d  %1$d \n", 1, 2);  //  2  1 

parameter 的形式为 n$,其中 n 是一个数字。%2$d 表示使用第二个参数来做格式化。如此格式的参数就不再是按顺序了。而且同一个参数可以被使用多次。但一旦其中一个格式化项使用了 parameter 其他的也都要使用。

flags

CharacterDescription
-左对齐(默认是右对齐,需要配合 width 使用)
+在正数前面加上 + 号,默认正数是不加符号的。
空格默认情况下,负数前面有一个 - 号,正数前面什么都没有。使用 % d 则会在正数前面加一个空格。
0在整数前面使用 0 填充。
# (hash)Alternate form: For g and G types, trailing zeros are not removed. For f, F, e, E, g, G types, the output always contains a decimal point. For o, x, X types, the text 0, 0x, 0X, respectively, is prepended to non-zero numbers.

width

一个数字,指出当前格式化项的最小宽度。

precision

如果格式化的是浮点数,precision 指出小数点的精度。如果格式化的是字符串,指出格式化的长度。

printf("%.2f  %.6f", M_PI, M_PI); // 3.14  3.141593

精度也可以动态地指出:

printf("%.*f", 4, M_PI); // 3.1415

使用 .* 表示精度是动态获取的,这里第一个参数 4 就指出了精度,指出精度的哪一位需要是 int 型的。

const char *s = "hello world!";
printf("%.*s", 5, s); // hello

length

printf 系列函数是可变参函数,大致实现原理是这样的,在调用的时候多个参数被压入栈中,在格式化的时候,根据格式化字符串中指定的参数类型,去栈中对应偏移量取不同大小的数据,并进行格式化。传给 printf 系列函数的整数有很多中,比如 shortintlong 等等,因此需要有一种方法来说明待格式化的参数的字节长度。

CharacterDescription
hhchar 型整数
hshort 型整数
llong 型整数
lllong long 型整数
Llong double
zsize_t
jintmax_t
tptrdiff_t

为了跨平台,你可能使用了 stdint.h 中定义的形如 int64_t 这样的类型,在 32 位和 64 位的系统上,格式化的 length 是不同的。ISO C99 标准引入了 inttypes.h 头文件,这里面定义了一系列的宏,它使用了条件编译,平台不同其内容不同,使用这些宏可以保证 printf 在格式化整数时的跨平台正确性。用法为: printf("%" PRId64 "\n", t);,可使用的宏如下:

MacroDescription
PRId32Typically equivalent to d
PRId64Typically equivalent to lld (32-bit platforms) or ld (64-bit platforms)
PRIi32Typically equivalent to i
PRIi64Typically equivalent to lli (32-bit platforms) or li (64-bit platforms)
PRIu32Typically equivalent to u
PRIu64Typically equivalent to llu (32-bit platforms) or lu (64-bit platforms)
PRIx32Typically equivalent to x
PRIx64Typically equivalent to llx (32-bit platforms) or lx (64-bit platforms)

type

type 支持待格式化的参数的类型,下表给出了说明:

CharacterDescription
d, i十进制有符号整数
o八进制的无符号整数
x,X十六进制显示的无符号整数
u十进制无符号整数
f,F浮点数。(对浮点数的格式化并不区分 float 和 double,因为在可变参数中,float 都被提升为 double 了)f 和 F 的区别是只存在于格式化 nan 和 inf 的时候。f 和 F 的结果分别是 (inf, infinity and nan) 和 (INF, INFINITY and NAN)
e,E浮点数的指数表示
a,A十六进制浮点数
g,G自动选择浮点数的表示方式
s字符串
cchar
p指针
n不打印任何内容,而是把当前已经格式的字符串的长度写入到对应的参数(int*)中。

下面是表中给出了 length 和 type 配合使用的时候表示的类型:

Modifier          d, i           o, u, x, X            n
hh                signed char    unsigned char         signed char *
h                 short          unsigned short        short *
l (ell)           long           unsigned long         long *
ll (ell ell)      long long      unsigned long long    long long *
j                 intmax_t       uintmax_t             intmax_t *
t                 ptrdiff_t      (see note)            ptrdiff_t *
z                 (see note)     size_t                (see note)
Modifier    a, A, e, E, f, F, g, G
l (ell)     double (ignored, same behavior as without it)
L           long double

评论 (评论内容仅博主可见,不会公开显示)