映月读书网 > C语言解惑 > 8.4 充分利用库函数printf的功能 >

8.4 充分利用库函数printf的功能

由于篇幅的限制,一般的教材只是以能满足基本编程为前提,简要介绍printf的基本功能。其实,printf函数还有许多有用的功能,本节除分析使用printf函数容易出现的问题之外,也将介绍一些其他功能,如添加修饰符和标志、使用替换符等。

【例8.8】能给出下面程序的正确输出结果吗?


#include <stdio.h>
int  main 
( 
)
{
      char c="this % is good
!"
;
      int a[3]={1
,3
,-5}
;
      printf 
( "%s\n"
, c 
);     //1
      printf 
( c 
);     //2
      printf 
( "\n10%%\n"
);     //3
      printf 
( "%d\n"
, printf 
( "How are you
?\n"
));     //4
      printf 
( "%.6d %06d\n"
, a[0
,1]
,a[1
,2]
);     //5
      printf 
( "-10%% is %.1f\n"
,-0.1
);     //6
      return 0
;
}
  

【解答】第1条输出语句是打印出任何以空字符结尾的字符串,而%是作为组成字符串的一个字符,所以语法没有歧义。第2个输出语句是将字符串c中的任何%字符作为格式说明的一部分,其后的字符被视为格式字符,但%后是空格和字符i,不能构成有效的格式说明,所以带来麻烦,无法判定它将会输出什么。第3个输出字符串中的“%%”是正确的,它用来输出一个“%”号,所以输出“10%”。

如果知道printf函数是int类型,也就能给出第4个输出语句的输出结果。它先输出字符串,然后把打印的个数(有\n则再加1)作为另一个printf语句的参数(输出13)。

第5个打印语句的输出量表使用了逗号运算符,a[0,1]和a[1,2]分别是a[1]和a[2]。其余的事情就是分析格式字符的含义。“.6”和“06”的含义一样,都是要求输出宽度为6位,因为a[2]是-5,所以只填4个0。

第6个很容易,输出小数为1位,即输出“-10%is-0.1”。下面给出在一特定机器上的输出结果供对照分析。


this % is good
!
this  2367460s good
!
10%
How are you
?
13
000003 -00005
-10% is -0.1
  

由此可见,要掌握printf的格式控制符的使用方法。为节省篇幅,本节以介绍使用方法及注意事项为主。

8.4.1 printf的函数原型


int printf
(const char *format
,...
);
  

...表示参数可变,printf的参数个数根据format来的,将在第2篇编写自己的printf函数时再介绍,现在仅仅知道它是可变参数,第1个参数是字符串即可。为此,将其简化为如下的一般格式。


printf
(格式控制符,输出量表);
  

格式控制符是用双引号("")括起来的字符串,也称控制字符串,它包括以下信息。

(1)格式说明由“%”和格式字符组成,如%d,%f等,它的作用是将输出的数据转换为指定格式输出。格式说明总是由“%”字符开始的。

(2)普通字符,即需要原样输出的字符。

(3)输出量表是需要输出的一些数据,也可以是表达式。

本节主要针对格式控制符,目的是用好printf提供的功能。在格式说明中,格式字符不一定要紧跟在%号之后,把紧跟%号之后的格式说明称为简单格式类型,把简单类型中的%号与格式字符中添加一些可选字符,这些可选字符可以各种方式修改转换,根据其作用可以分为添加修饰及标志等情况。为了查阅方便,下面将按此组成方式予以叙述。

8.4.2 printf函数的格式控制符

1.printf的函数的简单格式控制符

简单格式控制符有:%d、%o、%x、%X、%s、%c、%g、%f、%e、%%、%p和%n。

C语言取消了一些控制字符,也新增了一些,因为各个编译系统执行情况不一样,如果吃不准,最好不要使用已经取消的控制符。当然,可以测试一下它们的效果,以免进入歧途。

注意大写的格式字符,不要使用%D、%O、%U、%S和%P。

%u只用来输出正整数,也可以用来输出内存地址。

%d、%o、%x、%X分别输出10、8和16进制的整数,输出正整数时,不打印“+”标识符。%x和%X是有区别的,前者输出小写字符,后者输出大写字符。%d输出负数使用“-”号,8进制和16进制则不使用“-”,而是输出转换后的正整数。

%p和%n是新引入的格式字符。%p用于打印指针所指向的地址(16进制),或者内存储器地址值。%n用于将打印字符个数存入指定变量。注意计数字符数是指%n之前的数目。


int k=0
;
char c="OK"
;
printf 
("%s%n\n"
, c
,&k 
);     //\n
在%n
后面,不记入,k=2
printf 
("%d\n"
, k 
);     //
输出2
printf 
("%s\n %n "
, c
,&k 
);     //\n
在%
前面要记入,k=3
printf 
("%d\n"
, k 
);     //
输出3
  

因为是存入整数,所以使用地址符&,这与scanf读入数据的格式相同。下面给出一个演示正确和错误使用格式字符的例子,请对比它们的输出掌握其用法。

【例8.9】演示简单格式字符使用的例子。


#include <stdio.h>
int  main 
( 
)
{
     int n=200
,i
,j
,k
; 
     char c="OK"
;
     printf 
("%d %o %x %X %O %D\n"
, n
,n
,n
,n
,n
,n
);     //O
和D
不用
     printf 
( "%x %X %o %d\n"
,-1
,-1
,-1
,-1
);          //o
和x
转换为正数
     printf 
("%u %u %U\n"
, 55
,-55
,55 
);               //
仅用于正整数,不用U
     printf 
("%s %S\n"
, c
,c 
);                    //S
不可用
     printf 
("%c %C\n"
, c[0]
,c[1] 
);               //
均可
     printf 
("%p %u %P\n"
, &n
,&n
,&n 
);               //
不用P
,输出地址
     printf 
("How are you
?%n"
, &i 
);               //i=12
     printf 
("%s%n\n"
, c
,&j 
);                    //\n
在%n
后面,不计入,j=2
     printf 
("%c\n%n"
, c[1]
,&k 
);                    //\n
在前面要记入,k=2
     printf 
( "i=%d
,j=%d
,k=%d\n"
, i
,j
,k
);
     printf 
("100%%\n"
);                         //
输出%
     printf 
("%s%n\n"
, c
,&k 
);                    //\n
在%n
后面,不记入,k=2
     printf 
("%d\n"
, k 
);                         //
输出k=2
     printf 
("%s\n%n"
, c
,&k 
);                    //\n
在前面要记入,k=2
     printf 
("%d\n"
, k 
);                         //\n
在前面要记入,k=2
     return 0
;
}
  

程序输出结果如下:


200 310 c8 C8 O D
ffffffff FFFFFFFF 37777777777 -1
55 4294967241 U
OK
O K
0012FF7C 1245052 P
How are you
?OK
K
i=12
,j=2
,k=2
100%
OK
2
OK
3
  

%f、%e、%g用来输出浮点值。%f格式强制禁止使用指数形式表示浮点数,小数点后面有6位有效数字,只能用小写的f,不能用F。%e与%f正好相反,它要求一律显式地使用指数形式,小数点后面也是保留6位有效数字。%g用来输出实数,它根据数值的大小,自动选f格式或e格式。%g不是保留小数点后面6位有效数字,而是总共为6位数字,并且再打印浮点或双精度类型数值时,也会去掉该数值尾缀的零。例如下例中的2.5。对于指数部分而言,规定并不相同。下面的例子中,指数总共占5位(如e+007),其中“e”占1位,符号占1位,指数占3位。

【例8.10】演示使用简单格式字符输出浮点数的例子。


#include <stdio.h>
int  main 
( 
)
{
     float n=2.5
,k=15400440.0
,h=8.9808093f
; 
     printf 
("%f %e %E %g %G\n"
, n
,n
,n
,n
,n
,n
);
     printf 
("%f %e %g %G\n"
, k
,k
,k
,k
);
     printf 
("%f %e %g %G\n"
, h
,h
,h
,h
);
     printf 
("%g %g\n"
, 1.23456e-4
,1.23456e-5
); 
     printf 
("%g %g\n"
, 123456.0
,1234567.0
);
     return 0
;
}
  

程序输出如下:


2.500000 2.500000e+000 2.500000E+000 2.5 2.5
15400440.000000 1.540044e+007 1.54004e+007 1.54004E+007
8.980809 8.980809e+000 8.98081 8.98081
0.000123456 1.23456e-005
123456 1.23457e+006
  

%g对大于999999的数和指数小于或等于-5的情况,才采用科学计数法输出。由此可见,%g很适于打印那些不需要按列对齐的浮点数。

假如有一个单个字符c和字符串s,语句


printf
("%s"
,s
);
  

并不等效于语句


printf
(s
);
  

只有当字符串中没有%号时,输出才是一样的。语句“printf(c);”是错误的,可以用


putchar
(c
);
  

代替它,但前者更加灵活。

printf语句的格式控制符可以分成多个书写,只要双引号内的语法正确即可,里面还可以包含多个换行符\n。例8.11给出两个例子。同理,输出变量表不仅可以使用表达式求值,而且可以使用多个表达式求值。例如:


printf 
("there %s %d item%s in the list.\n"
,
       i
!=1
? "are" 
: "is"
, i
, i
!=1
? "s"
:" "
);
  

当i=3时,第1个表达式是"are",第2个是"s",printf变为


printf 
("there %s %d item%s in the list.\n"
, "are" 
, "s"
);
  

语句,输出:


there are 3 items in the list.
  

对照下面的例子和输出结果,可以熟悉它们的用法。

【例8.11】演示%c和%s的例子。


#include <stdio.h>
int  main 
( 
)
{
     char c='A'
, s="ABC"
; 
     int i=3
,j=1
;
     printf 
("%c"
,c
);
     printf 
(s
);
     printf 
("%s\n"
,s
);
     printf 
("we""%s CB%c\n"
,s
,c
);
     printf 
("THIS IS ""%s\nchar '%c' is %d\n"
,s
,c
,c
);
     printf 
("there %s %d item%s in the list.\n"
,
            i
!=1
? "are" 
: "is"
, i
, i
!=1
? "s"
:" "
);
     printf 
("there %s %d item%s in the list.\n"
,
            j
!=1
? "are" 
: "is"
, j
, j
!=1
? "s"
:" "
);
     return 0
;
}
  

程序输出结果如下:


AABCABC
weABC CBA
THIS IS ABC
char 'A' is 65
there are 3 items in the list.
there is 1 item  in the list.
  

2.添加修饰符

在格式说明符%和格式字符之间加上辅助字符,可以构成长度修饰符、宽度修饰符和精度修饰符。

(1)长度修饰符l(或L,不分大小写)用来构成%ld、%1o、%lx和%lu。整数有short、long和正常长度3种。尽管当一个short整数作为一个函数的参数出现时,会被自动地扩展为一个正常长度的整数,但仍然需要一种通知printf函数,某个参数是long型整数。l修饰符只对用于整数的格式字符才有意义。%lu仍然是把long型正整数作为long型无符号整数打印出来。

【例8.12】演示使用修饰符l的例子。


#include <stdio.h>
int  main 
( 
)
{
     long n=1234567898
; 
     printf 
("%ld\t%Ld\n"
, n
,n
);
     printf 
("%lo\t%lx\t%lX\n"
, n
,n
,n
);
     printf 
("%lu\t%lu\t\t%u\n"
, n
,&n
,&n
);
     return 0
;
}
  

程序输出结果如下:


1234567898      1234567898
11145401332     499602da      499602DA
1234567898      1245052       1245052
  

(2)宽度修饰符用于在固定长度的域内打印数值,它处于“%”和格式字符之间,假设m是正整数,可以表示为“%m+格式字符”。m是指定要打印的字符数,又称为域宽。如果要打印的数值(或字符)不能填满m个位置,它的左侧就会被补上空格以使这个数值(或字符)的宽度满足要求。不过,宽度修饰符绝对不会截断一个输出域,因为一旦待打印的数值太大而超过给定的域宽m时,就会适当调整输出域的宽度m以容纳该数值。也就是说,若大于m,则按实际位数输出。

在一些编译系统中,宽度修饰符对所有的格式字符都有效,也有些编译系统对“%%”无效,但编译时并不给出警告信息(不影响产生执行文件)。

【例8.13】演示使用修饰符指定域宽宽度的例子。


#include <stdio.h>
int  main 
( 
)
{
     int num=25
;
     printf 
("%2d %2d\n"
, 1
,2
);     //1 
     printf 
("%2o %2X\n"
, 9
,15
);     //2
     printf 
("%2c %3c\n"
, 'a'
,'b'
);     //3
     printf 
("%8%%3%\n"
);     //4 
本编译系统无效
     printf 
("%2d %2d\n"
, 97
,98
);     //5  
     printf 
("%2d %2d\n"
, 100
,99
);     //6
     printf 
("%2x %2d\n"
, 100
,99
);     //7
     printf 
("%2s %3s\n"
, "ab"
,"a b c d"
);     //8
     printf 
("%10p %10u\n"
, &num
, &num
);     //9
     printf 
("%2p %2u\n"
, &num
, &num
);     //10
     return 0
;
}
  

运行结果如下:


 1  2
11  f
 a   b
%%
97 98
100 99
64 99
ab a b c d
   0012FF7C    1245052
0012FF7C 1245052
  

从运行结果可见,不足宽度则以空格补充,如第1行的输出结果。第6行自动调整宽度,不影响输出100,第8行的字符串“a b c d”也是如此处理。第4行输出“%”时,没有用空格填充,表明本编译系统不适合%%。第9行填充空格,而第10行自动调整不影响输出。

(3)精度修饰符用于控制一个数值表示中将要出现的数字位数,或者用于限制将要打印的字符串中应该出现的字符数。假设n是一个正整数,精度修饰符可以表示为“.n”,即精度修饰符包括一个小数点和正整数n,可以表示为“%m+格式字符”。精度修饰符的确切含义与格式字符有关。在实际使用时,它还会与(2)中介绍的宽度修饰符m配合,下面将讲解具体的使用方法和实例。

①对于整数%.nd、%.no、%.nx和%.nu(%.np特殊)来说,n指定了打印数字的最少位数。如果要打印的数值并不需要n位数字来表示,则在它的前面补0。如果也使用了宽度m,当m大于n时才起作用,它用空格补足m位。语句


printf 
("%.2d/%.2d/%.4d\n"
, 15
,5
,2014
);
printf 
("%4.3d/%4.2d/%4.4d\n"
, 15
,5
,2014
);
  

将输出:


15/05/2014
 015/  05/2014
  

%p打印的是16进制地址,地址使用固定长度,书写时不能随便在地址前面补0。所以对于“%.np”而言,仅符合上述补空格的规律。例8.14演示了它们的区别。

②对于字符串,n指定了要从字符串中打印的字数。如果字符串中包含的字符数少于n,则仅将全部字符输出(不在左边填空格),如果字符串中包含的字符数大于n,则仅将全部字符中的前n个字符截断输出(舍去其他字符)。如果使用“%m.ns”的格式,当m≤n时,m不起作用,按“.n”处理。当m>n时,仅输出字符中的前n个字符,字符左边用m-n个空格填充以保持宽度为m个字符。这种方法在某些场合特别有用,例如用字符数组name[N]存储文件名,如果恰巧文件名的长度为N,这就使name失去结束符。但可以使用“%.Ns”正确打印文件名。如果有必要,还可以在文件名左边填充空格。例8.14给出示范。

③对于单字符,不理睬“%m.nc”的形式,一律输出单字符,但也遵守在字符左边填充空格的规律。

【例8.14】演示使用精度修饰符的例子及输出结果。


#include <stdio.h>
int  main 
( 
)
{
     int num=25
,i=0
;
     char name[8]
;
     printf 
("%.2d/%.2d/%.2d\n"
, 15
,5
,2014
);
     printf 
("%4.3d/%4.2d/%3.2d\n"
, 15
,5
,2014
);
     printf 
("%.18%/%4.2%\n"
);
     printf 
("%.8s
,%.2s\n"
, "ABCDEF"
,"ABCDEF"
);
     printf 
("%2.4s
,%2.2s\n"
, "ABCDEF"
,"ABCDEF"
);
     printf 
("%8.4s
,%8.2s\n"
, "ABCDEF"
,"ABCDEF"
);
     printf 
("%.2p/%.2u\n"
, &num
,&num
);
     printf 
("%.18p/%.18u\n"
, &num
,&num
);
     printf 
("%12.2p/%12.2u\n"
, &num
, &num
);
     printf 
("%.18c/%4.2c\n"
, 'A'
,'A'
);
     for
(i=0
;i<8
;i++
)
     name[i]='a'+i
;
     printf 
("%.8s\n"
,name
);
     printf 
("%10.8s\n"
,name
);
     return 0
;
}
15/05/2014
 015/  05/2014
%/%
ABCDEF
,AB
ABCD
,AB
   ABCD
,        AB
0012FF7C/1245052
0012FF7C/000000000001245052
    0012FF7C/     1245052
A/   A
abcdefgh
  abcdefgh
  

④对于实数%.ne、%.nE、%.nx和%.nu来说,精度修饰符.n中的n指定了小数点后应该出现的数字位。只有精度大于0时,打印的数值中才会出现小数点。也就是说,“%.0f”和“%.0e”将数值按四舍五入取整打印,而且不打印小数点“.”。下面的语句


printf 
("%.0f %.0f %.0f %.1f %.1f\n"
, 1.
,2.2
,2.5
,2.5
,1.
);
printf 
("%.0e %.0e %.0E %.1E %.1E\n"
, 1.
,2.2
,2.5
,2.5
,1.
);
  

将给出如下输出结果:


1 2 3 2.5 1.0
1e+000 2e+000 3E+000 2.5E+000 1.0E+000
  

如果给定小数点后面的数字小于n,则在尾部用“0”填充,使总宽度达到n位。

如果使用“m.n”配合时,当m大于给定实数小数点前面的位数,假设位数为i,在m>i+n时,左边使用空格填充,使其整体占m宽度。注意负数的“-”也占一位。

其实,打印实数时,是需要左边用空格,还是右边补0均可。右边补0是“.n”决定,左边填空格是因为使用“m”引起。但e是个特例,它不采用空格,小数位数完全决定n。本节提供的例题,演示了各种情况,供对比以加深理解。如果记不得了,将这些例题再运行一下,就完全清楚了。下面是例8.15及其输出结果,最后一行还给出e的典型情况。

【例8.15】演示实数使用精度修饰符的例子。


#include <stdio.h>
int  main 
( 
)
{
     float f=1234.567f
;
     printf 
("%.0f %.0f %.0f %.1f %.1f\n"
, 1.
,2.2
,2.5
,2.5
,1.
);
     printf 
("%.0e %.0e %.0E %.1E %.1E\n"
, 1.
,2.2
,2.5
,2.5
,1.
);
     printf 
("%4.2f
,%.3f
,%8.12f\n"
, 12345678976.2245
,0.2
,.2
);
     printf 
("%4.2f
,%10.2f\n"
, 12345.2245
,1232.5151
);
     printf 
("%4.2e
,%10.2e\n"
, 12345.2245
,1232.5151
);
     printf 
("%4.2e
,%.3e
,%8.12e\n"
, 12345678976.2245
,0.2
,.2
);
     printf 
("%8.2f
,%5.2f\n"
, -123.45
,-123.45
);
     printf 
("%8.2e
,%5.2e\n"
, -123.45
,-123.45
);
     printf 
( "%e
,%10e
,%10.2e
,%.2e\n"
,f
,f
,f
,f
);
     return 0
;
}
  

程序输出结果如下:


1 2 3 2.5 1.0
1e+000 2e+000 3E+000 2.5E+000 1.0E+000
12345678976.22
,0.200
,0.200000000000
12345.22
,   1232.52
1.23e+004
, 1.23e+003
1.23e+010
,2.000e-001
,2.000000000000e-001
 -123.45
,-123.45
-1.23e+002
,-1.23e+002
1.234567e+003
,1.234567e+003
, 1.23e+003
,1.23e+003
  

⑤对于%.ng和%.nG来说,它与f和e添加精度修饰符的含义有所不同,这里是指定打印数值中的有效数字位数n。如果小数点后面不跟数字,则小数点也将被删除。请对比下面例题中的输出结果以分辨它们的区别。

【例8.16】演示对比使用精度修饰符的例子。


#include <stdio.h>
int  main 
( 
)
{
     float f=1234.567f
;
     printf 
("%.0f %.0f %.0f %.1f %.1f\n"
, 1.
,2.2
,2.5
,2.5
,1.
);
     printf 
("%.0g %.0g %.0G %.1G %.1G\n"
, 1.
,2.2
,2.5
,2.5
,1.
);
     printf 
("%4.2f
,%.3f
,%8.12f\n"
, 12345678976.2245
,0.2
,.2
);
     printf 
("%4.2g
,%10.2g\n"
, 12345.2245
,1232.5151
);
     printf 
("%4.2e
,%10.2e\n"
, 12345.2245
,1232.5151
);
     printf 
("%4.2E
,%.3E
,%8.12E\n"
, 12345678976.2245
,0.2
,.2
);
     printf 
("%4.2G
,%.3G
,%8.12G\n"
, 12345678976.2245
,0.2
,.2
);
     printf 
("%8.2f
,%5.2f\n"
, -123.45
,-123.45
);
     printf 
("%8.2e
,%5.2e\n"
, -123.45
,-123.45
);
     printf 
("%8.2g
,%5.2g\n"
, -123.45
,-123.45
);
     printf 
( "%e
,%10e
,%10.2e
,%.2e\n"
,f
,f
,f
,f
);
     printf 
( "%g
,%10g
,%10.2g
,%.2g\n"
,f
,f
,f
,f
);
     return 0
;
}
 

程序输出结果如下:


1 2 3 2.5 1.0
1 2 3 3 1
12345678976.22
,0.200
,0.200000000000
1.2e+004
,  1.2e+003
1.23e+004
, 1.23e+003
1.23E+010
,2.000E-001
,2.000000000000E-001
1.2E+010
,0.2
,     0.2
 -123.45
,-123.45
-1.23e+002
,-1.23e+002
-1.2e+002
,-1.2e+002
1.234567e+003
,1.234567e+003
, 1.23e+003
,1.23e+003
1234.57
,   1234.57
,  1.2e+003
,1.2e+003
  

3.添加标志

可以在%符号和域宽修饰符m之间插入标志字符,以微调格式字符的效果。常用的标志为-、+、#和空白字符。

(1)标志符“-”的作用是将显示方式改为左端对齐,在右端填充空白字符。标志符“-”仅当有域宽修饰符m存在时才有意义,而且m要大于数值宽度才起作用。在下面的例子中,每行均使用字符a作为右边界标志。

【例8.17】演示使用标志符“-”的例子。


#include <stdio.h>
int  main 
( 
)
{
     float f=1234.567f
;
     char c="abcdefghijklmnop"
;
     int num=123456
;
     printf
("%s\n"
,c
);
     printf 
("%-4d
,%-4d
,%c\n"
, num
,num
, c[0]
);  //
域宽4
小于num
长度,不起作用
     printf 
("%-8d
,%-8d
,%c\n"
, num
,num
, c[0]
);
     printf 
("%-8d
,%-8d
,%c\n"
, -123
,-4567
,c[0]
);
     printf
("%-8.4s
,%-16.16s
,%c\n"
,c
,c
,c[0]
);
     printf 
("%-8.3g
,%-12.2g
,%c\n"
, 123.45
,123.45
,c[0]
);
     printf 
( "%-16e
,%-16e
,%c\n"
,f
,f
,c[0]
);
     printf 
( "%-16.2e
,%-16.2e
,%c\n"
,f
,f
,c[0]
);
    printf 
( "%-16g
,%-16g
,%c\n"
,f
,f
,c[0]
);
    printf 
( "%-16.2g
,%-16.2g
,%c\n"
,f
,f
,c[0]
);
    return 0
;
}
  

输出结果如下:


abcdefghijklmnop
123456
,123456
,a
123456  
,123456  
,a
123456
,123456
,a
-123    
,-4567   
,a
abcd    
,abcdefghijklmnop
,a
123     
,1.2e+002    
,a
1.234567e+003   
,1.234567e+003   
,a
1.23e+003       
,1.23e+003       
,a
1234.57         
,1234.57         
,a
1.2e+003        
,1.2e+003        
,a
  

(2)标志符“+”规定在打印数值时,都用数值的符号(正数使用“+”号,负数使用“-”号)作为第1个字符,而0作为+0打印。8和16进制对负数另有规定,所以对它们不能使用这个标志符。

这个标志符“+”与上面(1)中讲述的标志符“-”之间没有任何关系,是分别独立的标志符。如果要同时使用它们,必须把“+”放在前面,例如%+-4d。下面给出它们的用法。

【例8.18】演示使用标志符“+”的例子。


#include <stdio.h>
int  main 
( 
)
{
     double f[3]={1
,0
,-1}
;
     int n[3]={1
,0
, -23}
;
     printf
("%+d %+d %+d\n"
,n[0]
,n[1]
,n[2]
);
     printf
("%+-4d%+-4d %+-4d\n"
,n[0]
,n[1]
,n[2]
);
     printf
("%+g %+g %+g\n"
,f[0]
,f[1]
,f[2]
);
     printf
("%+-18g%+-18g %+-18g\n"
,f[0]
,f[1]
,f[2]
);
     printf
("%+e %+e %+e\n"
,f[0]
,f[1]
,f[2]
);
     printf
("%+-18e%+-18e%+-18e\n"
,f[0]
,f[1]
,f[2]
);
     printf
("%+d\n%+d\n"
,-123
,123
);
     return 0
;
}
  

程序运行结果如下:


+1 +0 -23
+1  +0   -23
+1 +0 -1
+1                 +0                   -1
+1.000000e+000 +0.000000e+000 -1.000000e+000
+1.000000e+000     +0.000000e+000     -1.000000e+000
-123
+123
  

(3)空白字符又称空格("")符,也是标志符之一。它的作用在正数和0之前不打印“+”字符,而是用空格代替“+”号。如例8.17输出的最后两行为例,虽然希望正负数据对齐,但不希望打印“+”号,这时就可以使用空格标志符。由此可见,如果希望固定栏内的数值向左对齐,而又不想用标志符“+”(即输出有“-”号而没有“+”号),则可使用空格标志符。

注意:如果标志符“+”与空格字符同时出现在控制说明中,最终的效果以标志符“+”为准。

【例8.19】演示使用空格和“+”标志符的例子。


#include <stdio.h>
int  main 
( 
)
{
     double f
;
     int i
;
     for
(i=-1
;i<2
;i++
)
           printf
("% d %+d %+ d\n"
,i
,i
,i
);
     for
(f=-1
;f<2
;f++
)
           printf
("% f %+f %+ f\n"
,f
,f
,f
);
     for
(f=-1
;f<2
;f++
)
           printf
("% e %+e %e\n"
,f
,f
,f
);
     return 0
;
}
  

程序运行结果如下:


-1 -1 -1
 0 +0 +0
 1 +1 +1
-1.000000 -1.000000 -1.000000
 0.000000 +0.000000 +0.000000
 1.000000 +1.000000 +1.000000
-1.000000e+000 -1.000000e+000 -1.000000e+000
 0.000000e+000 +0.000000e+000  0.000000e+000
 1.000000e+000 +1.000000e+000  1.000000e+000
  

从运行结果可看出“+”标志和空格标志的区别。当两者相遇时,则决定“+”标志。

用%e不能保证小数点对齐,而%e和%+e能很好地解决这个问题,所以这两种格式要比正常的%e格式有用得多。

(4)标志符“#”的作用是针对数值输出的,而且具体的方式与特定的格式字符有关。

①%#o是让输出的第1个数字前加0,以便让8进制数值输出的格式与大多数C程序员惯用的方法一致。例如将10进制100输出为0144。当然也可以使用0%o在输出前加0,但这与%#o并不等效。当输出8进制的0时,用%#o格式输出0,而0%o输出00。

②%#x和%#X在要打印的16进制数值前面分别加上0x和0X。

③标志符“#”对浮点数输出格式的影响之一是要求小数点必须被打印出来,即使小数点后面没有数字也是如此。

④标志符“#”对浮点数输出格式的另一个影响是针对%g和%G的。%g会把尾缀的0去掉,例如将5.0打印为5,而%#g则打印出尾缀的0,即输出5.00000。

不要混淆宏定义中“#”字符的作用。printf语句中的“#”是对数值输出的格式进行微调,在宏定义中是作为要求输出“#”后面的单变量的字符。例如定义如下宏定义:


#define PRINT
(x
) printf
(#x "= %d\n"
,x
)
  

当i=3,调用


PRINT
(i
);
  

时,将输出


i= 3
  

除了+标志和空格标志之外,其余的标志符都是独立的。

【例8.20】演示使用“#”标志符的例子。


#include <stdio.h>
#define PRINT
(x
) printf
("<debug>"#x "= %d\n"
,x
)
int  main 
( 
)
{
    int i
;
    printf
("%#d %#o %#x %#X\n"
,100
,100
,100
,100
);
    printf
("%#o 0%o %#x %#X\n"
,0
,0
,0
,0
);
    printf
("%.0f %#.0f %g %#g\n"
,5.0
,5.0
,5.0
,5.0
);
    for
( i=1
;i<3
;i++
)
         PRINT
(i
);
    return 0
;
}
  

程序运行结果如下:


100 0144 0x64 0X64
0 00 0 0
5 5. 5 5.00000
<debug>i= 1
<debug>i= 2
  

4.“*”替换符

printf函数允许间接指定域宽和精度。“*”替换符用来替换域宽修饰符或精度修饰符中的任意一个,或者两者都替换。在这种情况下,printf函数首先从输出量表中取得将要使用的域宽或精度的实际数值,然后用这个数值来完成输出任务。

注意有些编译系统的%%不能使用%m.n%的形式。例如语句


printf
("%*%\n"
,12
);
  

的含义是右对齐输出%号,即别把它替换为域宽12,向右移动11个空格后输出%号。但有些编译系统则把这个语句当做


printf
("%*%\n"
,12
);
  

语句执行,仅仅左对齐输出一个%号。所以在使用时,一定要验证一下所使用的编译系统如何处理它们。

替代参数可以是多个,参数赋值的顺序要按参数的固有顺序给出,先给替换参数,然后是要打印的参数,替换参数也可以是表达式,详见下面的例题。

【例8.21】演示使用“*”替换符的例子。


#include <stdio.h>
#include <string.h>
int  main 
( 
)
{
     const int k1=12
;
     int k2=8
,k3=2
,k4=7
,j
;
     double i
;
     char st="How are you
?"
;
     printf
("%.*s\n"
,strlen
(st
),st
);
     printf
("%*.*s\n"
,12
,8
,st
);
     printf
("%*.*s\n"
,12
,12
,st
);
     printf
("%*.*s\n"
,k1
,k2
,st
);
     printf
("%*%\n"
,12
);          //
本系统对%
号不起作用
     printf
("%*c\n"
,12
,'A'
);     
     printf
("%*d\n"
,12
,123
);     
     printf
("%-*d/%+*d\n"
,6
,0
,6
,0
);     
     printf
("%+*d\n"
,12
,123
);     
     for
( i=-1
;i<2
;i++
)
           printf
("% .*e %+.*e %.*e\n"
,k3
,i
,k3
,i
,k3
,i
);
     for
( j=-1
;j<2
;j++
)
           printf
("%+*d %*d\n"
,3
,j
,-3
,j
);
     printf
("%*.*s %*.*s\n"
,k2
,k4
,st
,k1
,k1
,st
);
     printf
("%+*d %*.*e\n"
,k1
,123
,k2
,k3
,(double
)k1
);     
     return 0
;
}
  

程序运行结果如下:


How are you
?
    How are
How are you
?
    How are
%
           A
         123
0    /    +0
        +123
-1.00e+000 -1.00e+000 -1.00e+000
 0.00e+000 +0.00e+000  0.00e+000
 1.00e+000 +1.00e+000  1.00e+000
 -1 -1
 +0 0
 +1 1
 How are How are you
?
         +123 1.20e+001