15.10 格式化的行I/O
“格式化的行I/O”这个名字从某种意义上说并不正确,因为scanf和printf函数家族并不仅限于单行,它们也可以在行的一部分或多行上执行I/O操作。
15.10.1 scanf家族
scanf函数家族的原型如下所示。每个原型中的省略号表示一个可变长度的指针列表。从输入转换而来的值逐个存储到这些指针参数所指定的内存位置。
int fscanf( FILE *stream, char const *format, ... );
int scanf( char const *format, ... );
int sscanf( char const *string, char const *format, ... );
这些函数都从输入源读取字符并根据format字符串给出的格式代码对它们进行转换。fscanf的输入源就是作为参数给出的流,scanf从标准输入读取,而sscanf则从第1个参数所给出所给出的字符串中读取字符。
当格式化字符串到达末尾或者读取的输入不再匹配格式字符串所指定的类型时,输入就停止。在任何一种情况下,被转换的输入值的数目作为函数的返回值返回。如果在任何输入值被转换之前文件就已到达尾部,函数就返回常量值EOF。
警告:
为了能让这些函数正常运行,指针参数的类型必须是对应格式化代码的正确类型。函数无法验证它们的指针参数是否为正确的类型,所以函数就假定它们是正确的,于是继续执行并使用它们。如果指针类型是不正确的,那么结果值就会是垃圾,而邻近的变量有可能在处理过程中被改写。
警告:
现在,大家对于scanf函数的参数前面为什么要加一个&符号应该是比较清楚的了。由于C的传值参数传递机制,把一个内置位置作为参数传递给函数的唯一方法就是传递一个指向该位置的指针。在使用scanf函数时,一个非常容易出现的错误就是忘了加上&符号。省略这个符号将导致变量的值作为参数传递给函数,而scanf函数(或其他两个)却把它解释为一个指针。当它被解引用时,要
么导致程序终止,要么导致一个不可预料的内存位置的数据被改写。
15.10.2 scanf格式代码
scanf函数家族中的format字符串参数可能包含下列内容:
空白字符---它们与输入中的零个或多个空白字符匹配,在处理过程中将被忽略;
格式代码---它们指定函数如何解释接下来的输入字符;
其他字符---当任何其他字符出现在格式字符串时,下一个输入字符必须与它匹配。如果匹配,该输入字符随后就被丢弃。如果不匹配,函数就不再读取而是直接返回。
scanf函数家族的格式代码都以一个百分号开头,后面可以是一个可选的星号、一个可选的宽度、一个可选的限定符、格式代码。星号将使转换后的值被丢弃而不是被存储。这个技巧可以用于跳过不需要的输入字符。宽度以一个非负的整数给出,用于限制将被读取用于转换的输入字符的个数。如果未给出宽度,函数就连续读入字符,直到遇见输入中的下一个空白字符。限定符用于修改
有些格式代码的含义,如表15.4所示。
表15.4 scanf限定符
使用限定符的结果
格式代码 h l L
d、i、n short long
o、u、x unsigned short unsigned long
e、f、g double long double
警告:
限定符的目的是为了指定参数的长度。如果整型参数比缺省的整型值更短或更长,在格式代码中省略限定符就是一个常见的错误。浮点类型也是如此。如果省略了限定符,可能会导致一个较长变量只有部分被初始化,或者一个较短变量的邻近变量也被修改,这些都取决于这些类型的相对长度。
提示:
在一个整型长度和short相同的机器上,在转换一个short值时,限定符h并非必须的。但是对于那些整型长度比short长的机器,这个限定符是必须的。因此,如果在转换所有的short、long型整数值和long double型变量时都使用适当的限定符,可以使程序更具有可移植性。
scanf函数家族的例子。这里只显示与这些函数有关的部分代码。它从输入流成对地读取数字并对它们进行一些处理。当读取到文件末尾时,循环就终止。
int a, b;
while( fscanf( input, "%d %d", &a, &b ) == 2 ){
/*
**Process the values a and b.
*/
}
/*
** scanf格式代码。
*/
/*
** 在同一目录下创建的value.txt 文件的内容如下:
** 1, 2, 3, 4, 5, 6, 7, 8, 9
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
int a, b;
FILE *input;
input = fopen( "value.txt", "r" );
if( !input ){
printf( "fail to open value.txt file.\n" );
perror( "reason:" );
exit( EXIT_FAILURE );
}
while( fscanf( input, "%d %d", &a, &b ) == 2 ){
/*
**Process the values a and b.
*/
printf( "%d %d\n", a, b );
}
fclose( input );
return EXIT_SUCCESS;
}
/* 输出:

*/
这段代码并不精致,因为从流中输入的任何非法字符都将导致循环终止。同样,由于fscanf跳过空白字符,因此它没有办法验证这两个值是位于同一行还是分属两个不同的输入行。要解决这个问题,可以使用一种技巧,参加在后面的例子。
下一个例子使用了字段宽度:
nfields = fscanf( input, "%4d %4d %4d", &a, &b, &c );
这个宽度参数把整数值的宽度限制为4个数字或者更少。使用下面的输入:
1 2
a的值将是1,b的值将是2,c的值没有改变,nfields的值是2。但是,如果使用下面的输入:
12345 67890
a的值将是1234,b的值是5,c的值是6789,而nfields的值是3。输入中的最后一个0将保持在未插入状态。
/*
** scanf格式代码。
*/
/*
** value.txt 文件的内容如下:
** 1, 2, 3, 4, 5, 6, 7, 8, 9
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
int a, b, c;
/*
FILE *input;
input = fopen( "value.txt", "r" );
if( !input ){
printf( "fail to open value.txt file.\n" );
perror( "reason:" );
exit( EXIT_FAILURE );
}
*/
int nfields;
int i = 0;
while( i < 2 ){
nfields = fscanf( stdin, "%4d %4d %4d", &a, &b, &c );
printf( "nfields = %d, a = %d, b = %d, c = %d\n", nfields, a, b, c );
++i;
}
return EXIT_SUCCESS;
}
/* 输出:

*/
使用fscanf时,在输入中保持行边界的同步是很困难的,因为它把换行符也当作空白字符跳过。
例如,假定有一个程序读取的输入是由4个值组成的一组值。这些值然后通过某种方式进行处理,然后再读取接下来的4个值。在这类程序中准备输入的最简单方法是把每组的4个值放在一个单独的输入行,这就很容易观察哪些值形成一组。但如果某个行包含了太多或太少的值,程序就会产生混淆。例如,考虑下面这个输入,它的第2行包含了一个错误:
1 1 1 1
2 2 2 2 2
3 3 3 3
如果使用了fscanf按照一次读取4个值的方式读取这些数据,头两组数据是正确的,但第3组读取的数据将是(2,3,3,3),接下来的各组数据也都将不正确。
表15.5 scanf格式码
代码 参数 含义
c char * 读取和存储单个字符。前导的空白字符并不跳过。如果给出宽度,就读 取和存储这个数目的字符。字符后面不会添加一个NUL字节。参数必须 指向一个足够大的字符数组
i,d int * 一个可选的有符号整数被转换。d把输入解释为十进制数;i根据它的第一 个字符决定值的基数,就像整型字面值常量的表示形式一样
u,o,x unsigned * 一个可选的有符号整数被转换,但它按照无符号数存储。如果使用u,值 被解释为十进制数;如果使用o,值被解释为八进制数;如果使用x,值被 解释为十六进制数(X和x同义)
e,f,g float * 期待一个浮点值。它的形式必须像一个浮点型字面值常量,但小数点并非 必须的(E和G分别与e和g同义)
s char * 读取一串非空白字符。参数必须指向一个足够大的字符数组。当发现空 白时输入就停止,字符串后面会自动加上NUL终止符
[xxx] char * 根据给定组合的字符从输入中读取一串字符。参数必须指向一个足够大的 字符数组。当遇到第1个不在给定组合中出现的字符时,输入就停止。字 符串后面会自动加上NUL终止符。代码%[abc]表示字符组合a、b和c。如 果列表中以一个^字符开头,表示字符组合是所列出的字符的补集,所 以%[^abc]表示字符组合为a、b、c之外的所有字符。右方括号也可以出 现在字符列表中,但它必须是列表的第1个字符。至于横杠是否用于指定 某个范围的字符(例如%[a-z]),则因编译器而异
p void * 输入预期为一串字符,诸如那些由printf函数的%p格式代码所产生的输 出。它的转换方式因编译器而异,但转换结果将和按照上面描述的进行打 印所产生的字符的值相同
n int * 到目前为止通过调用scanf函数从输入读取的字符数被返回。%n转换的字 符并不计算在scanf函数的返回值之内,它本身并不消耗任何输入
% (无) 这个代码与输入中的一个%相匹配,该%符号将被丢弃
程序15.4使用一种更为可靠的方法读取这种类型的输入。这个方法的优点在于现在的输入是逐步处理的。它不可能读入一组起始于某一行但结束于另一行的值,而且,通过尝试转换5个值,无论是输入行的值太多还是太少,都会被检测出来。
/*
**用scanf处理行定向(line-oriented)的输入。
*/
#include<stdio.h>
#define BUFFER_SIZE 100 /*我们将要处理的最长行。*/
void
function( FILE *input ){
int a, b, c, d, e;
char buffer[ BUFFER_SIZE ];
while( fgets( buffer, BUFFER_SIZE, input ) != NULL ){
if( sscanf( buffer, "%d %d %d %d %d", &a, &b, &c, &d, &e) != 4 ){
fprintf( stderr, "Bad input skipped: %s", buffer );
continue;
}
/*
**处理这组输入
*/
}
}
程序15.4 用sscanf处理行定向的输入 scanf1.c
/*
**用scanf处理行定向(line-oriented)的输入。
*/
/*
** 在同一目录下创建的input_file.txt的内容如下:
** 1 1 1 1
** 2 2 2 2 2
** 3 3 3 3
** 4 4 4 4
** 5 5 5 5
*/
#include<stdio.h>
#include<stdlib.h>
#define BUFFER_SIZE 100 /*我们将要处理的最长行。*/
void function( FILE *input );
int main( void ){
FILE *input;
input = fopen( "input_file.txt", "r" );
if( !input ){
printf( "fail to open the input_file.txt.\n" );
perror( "reason:" );
exit( EXIT_FAILURE );
}
function( input );
return EXIT_SUCCESS;
}
void function( FILE *input ){
int a, b, c, d, e;
char buffer[ BUFFER_SIZE ];
while( fgets( buffer, BUFFER_SIZE, input ) != NULL ){
if( sscanf( buffer, "%d %d %d %d %d", &a, &b, &c, &d, &e ) != 4 ){
fprintf( stderr, "Bad input skipped: %s", buffer );
continue;
} else{
printf( "%d %d %d %d\n", a, b, c, d );
}
/*
**处理这组输入。
*/
}
}
/* 输出:

*/
一个相关的技巧可用于读取可能以几种不同的格式出现的行定向输入。每个输入行先用fgets读取,然后用几个sscanf(每个都使用一种不同的格式)进行扫描。输入行由第一个sscanf决定,后者用于转换预期数目的值。例如,程序15.5检查一个以前读取的缓冲区的内容。它从一个输入行中提取或者1个或者2个或者3个值,并对那些没有输入值的变量赋予缺省的值。
/*
**使用sscanf处理可变格式的输入。
*/
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_A 1 /*或其他...*/
#define DEFAULT_B 2 /*或其他...*/
void
function( char *buffer ){
int a, b, c;
/*
**看看3个值是否都已给出。
*/
if( sscanf( buffer, "%d %d %d", &a, &b, &c ) != 3 ){
/*
**否,对a使用缺省值,看看其他两个值是否都已给出。
*/
a = DEFAULT_A;
if( sscanf( buffer, "%d %d", &b, &c ) != 2 ){
/*
**否,对a使用缺省值,看看其他两个值是否都已给出。
*/
b = DEFAULT_B;
if( sscanf( buffer, "%d", &c ) != 1 ){
fprintf( stderr, "Bad input: %s", buffer );
exit( EXIT_FAILURE );
}
}
}
/*
**处理a,b,c。
*/
}
程序15.5 使用sscanf处理可变格式的输入 scanf2.c
/*
**使用sscanf处理可变格式的输入。
*/
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_A 1 /*或其他...*/
#define DEFAULT_B 2 /*或其他...*/
void function( char *buffer );
int main( void ){
char buffer[] = "3";
function( buffer );
char buffer2[] = "3 4";
function( buffer2 );
char buffer3[] = "2 3 4";
function( buffer3 );
char buffer4[] = "c";
function( buffer4 );
return EXIT_SUCCESS;
}
void function( char *buffer ){
int a, b, c;
/*
**看看3个值是否都已给出。
*/
if( sscanf( buffer, "%d %d %d", &a, &b, &c ) != 3 ){
/*
**否,对a使用缺省值,看看其他两个值是否都已给出。
*/
a = DEFAULT_A;
if( sscanf( buffer, "%d %d", &b, &c ) != 2 ){
/*
**否,对a使用缺省值,看看其他两个值是否都已给出。
*/
b = DEFAULT_B;
if( sscanf( buffer, "%d", &c ) != 1 ){
fprintf( stderr, "Bad input: %s", buffer );
exit( EXIT_FAILURE );
} else{
printf( "%d %d %d\n", a, b, c );
}
} else{
printf( "%d %d %d\n", a, b, c );
}
} else{
printf( "%d %d %d\n", a, b, c );
}
/*
**处理a,b,c。
*/
}
/* 输出:

*/
15.10.3 printf家族
printf函数家庭用于创建格式化的输出。这个家族共有3个函数:fprintf、printf和sprintf。它们的原型如下所示:
int fprintf( FILE *stream, char const *format, ... );
int printf( char const *format, ... );
int sprintf( char *buffer, char const *format, ... );
第1章曾见过,printf依据格式代码和format参数中的其他字符对参数列表中的值进行格式化。这个家族的另两个函数的工作过程也类似。使用printf,结果输出送到标准输出。使用fprintf,可以使用任何输出流,而sprintf把它的结果作为一个以NUL结尾的字符串存储到指定的buffer缓冲区而不是写入到流中。这3个函数的返回值是实际打印或存储的字符数。
警告:
sprintf是一个潜在的错误根源。缓冲区的大小并不是sprintf函数的一个参数,所以如果输出结果会溢出缓冲区时,就可能改写缓冲区后面内存位置中的数据。要杜绝这个问题,可以采取两种策略。第1种是声明一个非常巨大的缓冲区,但这个方案很浪费内存,而且尽管大型缓冲区能够减少溢出的可能性,但它并不能根除这种可能性。第2中方法是对格式进行分析,看看最大可能出现的
值被转换后的结果输出将有多大。例如,在4位整型的机器上,最大的整数有11位(包括一个符号位),所以缓冲区至少能容纳12个字符(包括结尾的NUL字节)。字符串的长度并没有限制,但函数所产生的字符串的字符数目可以用格式代码中一个可选的字段来限制。
警告:
printf函数家族的格式代码和scanf函数家族的格式代码用法不同。所以必须小心谨慎,以防止误用。在这两者的格式化代码中,有些可选字段看上去是相同的,这使得问题变得更为困难。不幸的是,许多常见的格式代码,如%d就属于这一类。
警告:
另一个错误来源是函数的参数类型与对应的格式代码不匹配。通常这个错误将导致输出结果或为垃圾,但这种不匹配也可能导致程序失败。和scanf函数家族一样,这些函数无法验证一个值是否具有格式码所表示的正确类型,所以保证它们相互匹配是程序员的责任。
15.10.4 printf格式代码
printf函数原型中的format字符串可能包含格式代码,它使参数列表的下一个值根据指定的方式进行格式化,至于其他的字符则原样逐字打印。格式代码由一个百分号开头,后面可以跟:零个或多个标志字符,用于修改有些转换的执行方式;一个可选的最小字段宽度;一个可选的精度;一个可选的修改符;转换类型。
标志和其他字段的准确含义取决于使用何种转换。表15.6描述了转换类型代码,表15.7描述了标志字符和它们的含义。
表15.6 printf格式代码
代码 参数 含义
c int 参数被裁剪为unsigned char类型并作为字符打印
d,i int 参数作为一个十进制整数打印。如果给出了精度而且值的位数少于精度位 度,前面就用0填充。
u,o,x,X unsigned int 参数作为一个无符号值打印。u使用十进制;o使用八进制;x或X使用十 六进制(两者的区别是x约定使用abcdef,而X约定使用ABCDEF)
e,E double 参数根据指数形式打印。例如,6.023000e23是使用代码e, 6.023000E23是使用代码E。小数点后面的位数由精度字段决定,缺省值 是6。
g,G double 参数以%f或%e(如G则%E)的格式打印,取决于它的值。如果指数大于 等于-4但小于精度字段,就使用%f格式,否则使用指数格式
s char * 打印一个字符串
p void * 指针值被转换为一串因编译器而异的可打印字符。这个代码主要是和 scanf中的%p代码组合使用
n int * 这个代码是独特的,因为它并不产生任何输出。相反,到目前为止函数所 产生的输出字符数目将被保存到对应的参数中。
% (无) 打印一个%字符
表15.7 printf格式标志
标志 含义
- 值在字段中左对齐,缺省情况下是右对齐
0 当数值为右对齐时,缺省情况下是使用空格填充值左边未使用的列。这个标志 表示用零来填充,它可用于d、i、u、o、x、X、e、E、f、g和G代码。使用d、 i、u、o、x和X代码时,如果给出了精度字段,零标志就被忽略。如果格式代码 中出现了负号标志,0标志也没有效果
+ 当用于一个格式化某个有符号值的代码时,如果值非负,正好标志就会给它加 上一个正号。如果值为负,就像往常一样显示一个负号。在缺省情况下,正号 并不会显示
空格 只用于转换有符号值的代码。但值非负时,这个标志把一个空格添加到它的开 始位置。注意,这个标志和正号标志是相互排斥的,如果两个同时给出,空格 标志便被忽略
# 选择某些代码的另一种形式。它们在表15.9中描述
字段宽度是一个十进制整数,用于指定将出现在结果中的最小字符数。如果值的字符数少于字段宽度,就对它进行填充以增加长度。标志决定填充是用空白还是零,以及它出现在值的左边还是右边。
对于d、i、u、o、x和X类型的转换,精度字段指定将出现在结果中的最小的数字个数并覆盖0标志。如果转换后的值的位数小于宽度,就在它的前面插入零。如果值为零且精度也为零,则转换结果就不会产生数字。对于e、E和f类型的转换,精度决定将出现在小数点之后的数字位数。对于g和G类型的转换,精度指定将出现在结果中的最大有效位数。当使用s类型的转换时,精度指定将被转换的最多字符数。精度以一个句点开头,后面跟一个可选的十进制整数。如果未给出整数,精度的缺省值为零。
如果用于表示字段宽度和或精度的十进制整数由一个星号代替,那么printf的下一个参数(必须是个整数)就提供宽度和(或)精度。因此,这些值可以通过计算获得而不必预先指定。
当字符或短整型作为printf函数的参数时,它们在传递给函数之前先转换为整数。有时候转换可以影响函数产生的输出。同样,在一个长整数的长度大于普通整数的环境里,当一个长整数作为参数传递给函数时,printf必须知道这个参数是个长整数。表15.8所示的修改符用于指定整数和浮点数参数的准确长度,从而解决了这个问题。
表15.8 printf格式代码修饰符
修改符 用于...时 表示参数是......
h d、i、u、o、x、X 一个(可能是无符号的)short型整数
h n 一个指向short型整数的指针
l d、i、u、o、x、X 一个(可能是无符号的)long型整数
l n 一个指向long型整数的指针
L e、E、f、g、G 一个long double型值
在有些环境里,int和short int的长度相等,此时h修饰符就没有效果。否则,当short int作为参数传递给函数时,这个被转换的值将升级为(无符号)int类型。这个修饰符在转换发生之前使它被裁剪回原先的short形式。在十进制转换中,一般并不需要进行转换。但在有些八进制或十六进制的转换中,h修饰符可以保证适当位数的数字。
警告:
在int和long int长度相同的机器上,l修改符并无效果。在所有其他机器上,需要使用l修饰符,因为这些机器上的长整型分为两部分传递到运行时堆栈。如果并未给出这个修改符,则就只有第1部分被提取用于转换。这样,不但转换将产生不正确的结果,而且这个值的第2部分被解释为一个单独的参数,这样就破坏了后续参数和它们的格式代码之间的对应关系。
#标志可以用于几种printf格式代码,为转换选择一种替代形式。这些形式的细节列于表15.9中。
表15.9 printf转换的其他形式
用于...... #标志......
o 保证产生的值以一个零开头
x、X 在非零值前面加0x前缀(%X即为OX)
e、E、f 确保结果始终包含一个小数点,即使它后面没有数字
g、G 与上面的e、E和f代码相同。另外,缀尾的0并不从小数点去除
提示:
由于有些机器在打印长整数值时要求l修改符而另外一些机器可能不需要,因此,当打印长整数值时,最好坚持使用l修改符。这样,当把程序移植到任何一台机器上时,就不太需要进行改动。
printf函数可以使用丰富的格式代码、修改符、限定符、替代形式和可选字段,这使得它看上去极为复杂。但是,它们能够在格式化输出时提供极大的灵活性。所以,我们应该耐心一些,花一些时间把它们全部学会!这里有一些例子,可以帮助大家学习它们。
/*
** printf格式代码。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
const char *str = "A";
const char *str2 = "ABC";
const char *str3 = "ABCDEFGH";
printf( "\"A\" = %s, \"ABC\" = %s, \"ABCDEFGH\" = %s\n", str, str2, str3 );
printf( "\"A\" = %5s, \"ABC\" = %5s, \"ABCDEFGH\" = %5s\n", str, str2, str3 );
printf( "\"A\" = %.5s, \"ABC\" = %.5s, \"ABCDEFGH\" = %.5s\n", str, str2, str3 );
printf( "\"A\" = %5.5s, \"ABC\" = %5.5s, \"ABCDEFGH\" = %5.5s\n", str, str2, str3 );
printf( "\"A\" = %-5s, \"ABC\" = %-5s, \"ABCDEFGH\" = %-5s\n", str, str2, str3 );
return EXIT_SUCCESS;
}
/* 输出:

*/
/*
** printf格式代码。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
int i = 1;
int i2 = -12;
int i3 = 12345;
int i4 = 123456789;
printf( "i = %d, i2 = %d, i3 = %d, i4 = %d\n", i, i2, i3, i4 );
printf( "i = %6d, i2 = %6d, i3 = %6d, i4 = %6d\n", i, i2, i3, i4 );
printf( "i = %.4d, i2 = %.4d, i3 = %.4d, i4 = %.4d\n", i, i2, i3, i4 );
printf( "i = %6.4d, i2 = %6.4d, i3 = %6.4d, i4 = %6.4d\n", i, i2, i3, i4 );
printf( "i = %-4d, i2 = %-4d, i3 = %-4d, i4 = %-4d\n", i, i2, i3, i4 );
printf( "i = %04d, i2 = %04d, i3 = %04d, i4 = %04d\n", i, i2, i3, i4 );
printf( "i = %+d, i2 = %+d, i3 = %+d, i4 = %+d\n", i, i2, i3, i4 );
return EXIT_SUCCESS;
}
/* 输出:

*/
/*
** printf格式代码。
*/
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
int main( void ){
float d;
float d2;
float d3;
float d4;
d = 1.f;
d2 = .01f;
d3 = .00012345f;
d4 = 12345.6789f;
printf( "FLT_MIN = %e, FLT_MAX = %e\n", FLT_MIN, FLT_MAX );
printf( "1 = %f, .01 = %f, .00012345 = %f, 12345.6789 = %f\n", d, d2, d3, d4 );
printf( "1 = %10.2f, .01 = %10.2f, .00012345 = %10.2f, 12345.6789 = %10.2f\n", d, d2, d3, d4 );
printf( "1 = %e, .01 = %e, .00012345 = %e, 12345.6789 = %e\n", d, d2, d3, d4 );
printf( "1 = %.4e, .01 = %.4e, .00012345 = %.4e, 12345.6789 = %.4e\n", d, d2, d3, d4 );
printf( "1 = %g, .01 = %g, .00012345 = %g, 12345.6789 = %g\n", d, d2, d3, d4 );
return EXIT_SUCCESS;
}
/* 输出:

*/
/*
** printf格式代码。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
float f = 6.023e23f;
printf( "6.023e23f = %f, %10.2f, %e, %.4e, %g\n", f, f, f, f, f );
return EXIT_SUCCESS;
}
/* 输出:

*/