C Primer Plus(6) 中文版 第4章 字符串和格式化输入/输出 4.4 printf()和scanf()

4.4 printf()和scanf()
这两个函数和C库的一些其它函数一样,并不是C语言定义的一部分。C90和C99标准规定了这些函数的标准版本。
这两个函数都使用格式字符串和参数列表。
4.4.1 printf()函数
                表4.3 转换说明及其打印的输出结果
转换说明    输出 
%a            浮点数、十六进制数和p计数法(C99/C11) 
%A            浮点数、十六进制数和p计数法(C99/C11) 
%g            根据值的不同,自动选择%f或%e。%e格式用于指数小于-4或者大于或等于精度时。
%G           根据值的不同,自动选择%f或%E。%E格式用于指数小于-4或者大于或等于精度时。
%i             有符号十进制整数(与%d相同)
%o            无符号八进制数
%p            指针
%%           打印一个百分号 
4.4.2 使用printf()
/* printout.c -- uses conversion specifiers */
#include <stdio.h>
#define PI 3.141593
int main(void)
{
    int number = 7;
    float pies = 12.75;
    int cost = 7800;
    printf("The %d contestants ate %f berry pies.\n", number,
           pies);
    printf("The value of pi is %f.\n", PI);
    printf("Farewell! thou art too dear for my possessing,\n");
    printf("%c%d\n", '$', 2 * cost);
    
    return 0;

/* 输出:

*/  

printf()函数的格式:
printf( 格式字符串, 待打印项1, 待打印项2, ... );
待打印项都是要打印的项。它们可以是变量、常量,甚至是在打印之前先要计算的表达式。第3章提到过,格式字符串应包含每个待打印项对应的转换说明。
格式字符串是双引号括起来的内容。
格式字符串包含两种不同的信息:
*实际要打印的字符;
*转换说明。
如果只打印短语或句子,就不需要使用任何转换说明。如果只打印数据,也不用加入说明文字。
4.4.3 printf()的转换说明修饰符
在%和转换字符之间插入修饰符可修饰基本的转换说明。
            表4.4 printf()的修饰符
修饰符        含义
标记     5种标记(-、+、空格、#和0),可以不使用标记或使用多个标记
数字     最小字段宽度
            如果该字段不能容纳待打印的数字或字符串,系统会使用更宽的字段
.数字    精度
            对于%e、%E和%f转换,表示小数点右边数字的位数
            对于%g、%G转换,表示有效数字最大位数
            对于%s转换,表示待打印字符的最大数量
            对于整数转换,表示待打印数字的最小位数
            如果必要,使用前导0来达到这个位数
            只使用.表示其后跟随一个0,所以%.f和%.0f相同
h           和整型转换说明一起使用,表示short int或unsigned short int类型的值。
             示例:"%hu"、"hx"、"%6.4hd"
hh         和整型转换说明一起使用,表示signed char或unsigned char类型的值
j            和整型转换说明一起使用,表示intmax_t或uintmax_t类型的值。这些类型定义在stdint.h中
t            和整型转换说明一起使用,表示ptrdiff_t类型的值。ptrdiff_t是两个指针差值的类型(C99)
z           和整型转换说明一起使用,表示size_t类型的值。size_t是sizeof返回的类型(C99)
注意 类型可移植性
标准只规定了size_t是无符号类型。
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。对未使用显式原型的所有C函数float仍自动转换成double类型。
                 表4.5 printf()中的标记
标记        含义
-           待打印项左对齐。
+          有符号值若为正,则在值前面显示加号;若为负,则在值前面显示减号
空格     有符号值若为正,则在值前面显示前导空格(不显示任何符号);若为负,则在值前面                  显示减号
            +标记覆盖空格
#          把结果转换为另一种形式。
            对于所有的浮点格式,#保证了即使后面没有任何数字,也打印一个小数点字符。对于%g              和%G格式,#防止后面的0被删除
0          对于数值格式,用前导0代替空格填充字段宽度。对于整数格式,如果出现-标记或指定精                度,则忽略该标记
1.使用修饰符和标记的示例
/* width.c -- field widths */
#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;
}    

/* 输出:

     

*/

// floats.c -- some floating-point combinations
#include <stdio.h>

int main(void)
{
    const double RENT = 3852.99;  // const-style constant
    
    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;
}

/* 输出:

*/

/* flags.c -- illustrates some formatting flags */
#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;
}

/* 输出:

 */ 

/* stringf.c -- string formatting */
#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;
}

/* 输出:

*/ 

4.4.4 转换说明的意义
转换说明把以二进制格式存储在计算机中的值转换成一系列字符(字符串)以便于显示。
实际上,转换说明是翻译说明,并不会把原始值替换成转换后的值。
1.转换不匹配
打印一个int类型的值,可以使用%d、%x、%o。
打印一个double类型的值,可以使用%f、%e、%g。
/* intconv.c -- some mismatched integer conversions */
#include <stdio.h>
#define PAGES 336
#define WORDS 65618
int main(void)
{
    short num = PAGES;
    short mnum = -PAGES;
    
    printf("num as short and unsigned short:  %hd %hu\n", num,
           num);
    printf("-num as short and unsigned short: %hd %hu\n", mnum,
           mnum);
    printf("num as int and char: %d %c\n", num, num);
    printf("WORDS as int, short, and char: %d %hd %c\n",
           WORDS, WORDS, WORDS);
    return 0;

/* 输出:

*/

第2行mnum变量对应的转换说明%u(无符号)输出的结果却为65200,并非期望的336。这是由于有符号short int类型的值在我们的参考系统中的表示方式所致。首先,short int的大小是2字节,其次,系统使用二进制补码来表示有符号整数。这种方法,数字0~32767代表它们本身,而数字32768~65535则表示负数。其中65535表示-1,65534表示-2,以此类推。一个数字可以被解释成两个不同的值。尽管并非所有的系统都使用这种方法来表示负整数,但要注意一点:别期望用%u转换说明能把数字和符号分开。
一个大于255的值转换成字符会导致该值对256求模(char是1字节,模为256),余数为对应于ASCII码中的字符中的ASCII码。
一个大于SHRT_MAX的值转换成short会导致该值对USHRT_MAX求模,余数为对应于short类型范围中的值。 
/* floatcnv.c -- mismatched floating-point conversions */
#include <stdio.h>
int main(void)
{
    float n1 = 3.0;
    double n2 = 3.0;
    long n3 = 2000000000;
    long n4 = 1234567890;
    
    printf("%.1e %.1e %.1e %.1e\n", n1, n2, n3, n4);
    printf("%ld %ld\n", n3, n4);
    printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
    
    return 0;
}

/* 输出:

*/ 

第一行输出显示%e转换说明没有把整数转换成浮点数。%e转换说明让printf()函数认为待打印的值是double类型(本系统double为8字节)。当printf()查看n3(本系统中是4字节的值)时,除了查看n3的4字节,还会查看n3相邻的4字节,共8字节单元。接着,它将8字节单元中的位组合解释成浮点数。因此,即使n3的位数正确,根据%e转换说明和%ld转换说明解释出来的值也不同。最终
得到的结果是无意义的值。
float类型的值作为printf()参数时会被转换成double类型。在本系统中,float是4字节,但是为了printf()能正确显示该值,n1被扩充为8字节。
第三行输出显示的结果也不正确。
参数传递
程序把传入的值放入被称为栈(stack)的内存区域。计算机根据变量类型(不是根据转换说明)把这些值放入栈中。因此,n1占8字节(float类型转换为double类型),n2也在栈中占8字节,而n3和n4在栈中分别占4字节。然后,控制转换printf()函数。该函数根据转换说明(不是根据变量类型)从栈中读取值。%ld转换表明printf()应该读取4字节,所以printf()读取栈中的前4字节作为第1个值。这是n1的前半部分,后面的读取也导致了错误。
float n1; /* 作为double类型传递 */ 
double n2;
long n3, n4;
printf( "%ld %ld %ld %ld\n", n1, n2, n3, n4 );
2.printf()的返回值
大部分函数都有一个返回值,这是函数计算并返回给主调函数(calling program)的值。可以把返回值赋给变量,也可以用于计算,还可以作为参数传递。总之,可以把返回值像其他值一样使用。printf()函数也有一个返回值,它返回打印字符的个数。如果输出错误,printf()则返回一个负值(printf()函数的旧版本会返回不同的值)。 
printf()的返回值在检查输出错误时可能会用到(如,在写入文件时很常用)。
/* prntval.c -- finding printf()'s return value */
#include <stdio.h>
int main(void)
{
    int bph2o = 212;
    int rv;
    
    rv = printf("%d F is water's boiling point.\n", bph2o);
    printf("The printf() function printed %d characters.\n",
           rv);
    return 0;

/* 输出:

*/ 

注意,计算针对所有字符数,包括空格和不可见的换行符(\n)。
3.打印较长的字符串
如果空白(空格、制表符、换行符)仅用于分隔不同的部分,C编译器会忽略它们。因此,一条语句可以写成多行,只需在不同部分之间输入空白即可。
但是,不能在双引号括起来的字符串中间断行。
在字符串中,可以使用\n来表示换行符,但是不能通过按下Enter(或Return键)产生实际的换行符。
/* longstrg.c --printing long strings */
#include <stdio.h>
int main(void)
{
    printf("Here's one way to print a ");
    printf("long string.\n");
    printf("Here's another way to print a \
long string.\n");
    printf("Here's the newest way to print a "
           "long string.\n");      /* ANSI C */
    
    return 0;

/* 输出:

*/

给字符串断行的方法:
方法1:使用多个printf()语句。
方法2:用反斜杠(\)和Enter(或Return)键组合来断行。这使得光标移至下一行,而且字符串中不会包含换行符。其效果是在下一行继续输出。但是,下一行代码必须和程序清单中的代码一样从最左边开始。如果缩进该行,那么缩进的部分也会成为字符串的一部分。
方法3:ANSI C引入的字符串连接。在两个用双引号括起来的字符串之间用空白隔开,C编译器会把多个字符串看作是一个字符串。
4.4.5 使用scanf()
scanf()可以读取不同格式的数据。当然,从键盘输入的都是文本,因为键盘只能生成文本字符:字母、数字和标点符号。scanf()把输入的字符串转换成整数、浮点数、字符和字符串,而printf()正好与它相反,把整数、浮点数、字符和字符串转换成显示在屏幕上的文本。
scanf()中的格式字符串表明字符输入流的目标数据类型。两个函数主要的区别在参数列表中。printf()函数使用变量、常量和表达式,而scanf()函数使用指向变量的指针。这里,我们只需记住以下两条简单的规则:
*如果用scanf()读取基本变量类型的值,在变量名前加上一个&;
*如果用scanf()把字符串读入字符数组中,不要使用&。
// input.c -- when to use &
#include <stdio.h>
int main(void)
{
    int age;             // variable
    float assets;        // variable
    char pet[30];        // string
    
    printf("Enter your age, assets, and favorite pet.\n");
    scanf("%d %f", &age, &assets); // use the & here
    scanf("%s", pet);              // no & for char array
    printf("%d $%.2f %s\n", age, assets, pet);
    
    return 0;

/* 输出:

*/ 

scanf()使用空白(换行符和空格)把输入分成多个字段。在依次把转换说明和字段匹配时跳过空白字符。只要在每个输入项之间输入至少一个换行符、空格或制表符即可,可以在一行或多行输入。
唯一例外的是%c转换说明。根据%c,scanf()会读取每个字符,包括空白。
printf()与scanf()主要的区别是,对于float类型和double类型,printf()都使用%f、%e、%E、%g和%G转换说明。而scanf()只是把它们用于float类型,都double类型要使用l修饰符。
        表4.6 ANSI C中的scanf()的转换说明
转换说明        含义
%e、%f、%g、%a        把输入解释为浮点数(C99标准新增了%a)
%E、%F、%G、%A     把输入解释为浮点数(C99标准新增了%A)
%i                                  把输入解释为有符号十进制整数
%o                                 把输入解释为有符号八进制整数
%p                                 把输入解释为指针(地址)
%u                                 把输入解释为无符号十进制整数
%x、%X                        把输入解释为有符号十六进制整数
        表4.7 scanf()转换说明中的修饰符
*              抑制赋值
数字        最大字段宽度。输入达到最大字段宽度处,或第1次遇到空白字符时停止
hh            把整数作为signed char或unsigned char类型读取
ll              把整数作为long long或unsigned long long类型读取(C99)
h、l或L    "%hd"和"%hi"表明把对应的值存储为short int类型
                "%ho"、"hx"和"hu"表明把对应的值存储为unsigned short int类型
                "%ld"和"%li"表明把对应的值存储为long类型
                "%lo"、"%lx"和"%lu"表明把对应的值存储为unsigned long类型
                "%le"、"%lf"和"%lg"表明把对应的值存储为double类型
                在e、f和g前面使用L而不是l,表明把对应的值被存储为long double类型。
                如果没有修饰符,d、i、o和x表明对应的值被存储为int类型,f和g表明把对应的值
                存储为float类型
j               在整数转换说明后面,表明使用intmax_t或uintmax_t类型(C99)
z              在整数转换说明后面,表明使用sizeof的返回类型(C99)
t               在整数转换说明后面,表明使用两个指针差值的类型(C99)
1.从scanf()角度看输入
假设scanf()根据一个%d转换说明读取一个整数。
scanf()函数每次读取一个字符,跳过所有的空白字符,直到遇到第1个非空白字符才开始读取。因为要读取整数,所以scanf()希望发现一个数字字符或者一个符号(+或-)。如果找到一个数字或符号,它便保存该字符,并读取下一个字符。如果下一个字符是数字,它便保存该数字并读取下一个字符。scanf()不断地读取和保存字符,直至遇到非数字字符。如果遇到一个非数字字符,它便认为读到了整数的末尾。然后,scanf()把非数字字符放回输入。这意味着程序在下一次读取输入时,首先读到的是上一次读取丢弃的非数字字符。最后,scanf()计算已读取数字(可能还有符号)相应的数值,并将计算后的值放入指定的变量中。
如果使用字段宽度,scanf()会在字段结尾会第1个空白字符处停止读取(满足两个条件之一便停止)。不会把值赋给变量。另外,C规定在第1个出错处停止读取输入。
用其他数值匹配的转换说明读取输入和%d的情况相同。区别在于scanf()会把更多字符识别成数字的一部分。例如%x。
浮点转换说明要求scanf()识别小数点、e记数法(指数记数法)和新增的p记数法(十六进制记数法)。
%s转换说明,scanf()会读取出空白以外的所有字符。scanf()跳过空白开始读取第1个非空白字符,并保存非空白字符直到再次遇到空白。这意味着scanf()根据%s转换说明读取一个单词,即不包含空白字符的字符串。如果使用字段宽度,scanf()在字段末尾或第1个空白字符处停止读取。无法利用字段宽度让只有一个%s的scanf()读取多个单词。最后要注意一点:当scanf()把字符串放进指定数组时,它会在字符序列的末尾加上'\0',让数组中的内容成为一个C字符串。
scanf()能读取不同类型的数据。
getchar()和fgets()适合读取单个字符或包含空格的字符串。
2.格式字符串中的普通字符
scanf()允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。
格式字符串的空白意味着跳过下一个输入项前面的所有空白。
请注意,“所有空白”的概念包括没有空格的特殊情况。
除了%c,其他转换说明都会自动跳过待输入值前面所有的空白。
scanf( "%c", &c );从输入的第一个字符开始读取。scanf( " %c", &c );则从第1个非空白字符开始读取。
3.scanf()的返回值
scanf()函数返回成都读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf()便返回0。 当scanf()检测到“文件结尾”时,会返回EOF。(EOF是stdio.h中定义的特殊值,通常用#define指令把EOF定义为-1。
4.4.6 printf()和scanf()的*修饰符
可以用*修饰符代替字段宽度。但还是需要一个参数告诉函数,字段宽度应该是多少。也就是说,如果转换说明是%*d,那么参数列表中应包含*和d对应的值。这个技巧也可用于浮点值指定精度和字段宽度。
/* varwid.c -- uses variable-width output field */
#include <stdio.h>
int main(void)
{
    unsigned width, precision;
    int number = 256;
    double weight = 242.5;
    
    printf("Enter a field width:\n");
    scanf("%d", &width);
    printf("The number is :%*d:\n", width, number);
    printf("Now enter a width and a precision:\n");
    scanf("%d %d", &width, &precision);
    printf("Weight = %*.*f\n", width, precision, weight);
    printf("Done!\n");
    
    return 0;

/* 输出:

*/

scanf()中*的用法与此不同。把*放在%和转换字符之间时,会使得scanf()跳过相应的输入项。
/* skiptwo.c -- skips over first two integers of input */
#include <stdio.h>
int main(void)
{
    int n;
    
    printf("Please enter three integers:\n");
    scanf("%*d %*d %d", &n);
    printf("The last integer was %d\n", n);
    
    return 0;

/* 输出:

 */

printf()的用法提示
想把数据打印成列,指定固定长度很有用。因为默认的字段宽度是待打印数字的宽度。如果同一行打印的数字位数不同,那么打印出来的数字可能参差不齐。
使用足够大的固定字段宽度可以让输出整齐美观。
在两个转换说明之间插入一个空白字符,可以确保即使一个数字溢出了自己的字段,下一个数字也不会紧跟该数字一起输出。这是因为格式字符串中的普通字符(包括空白)会被打印出来。
另一方面,如果要在文字里嵌入一个数字,通常指定一个小于或等于该数字宽度的字段会比较方便。这样,输出数字的宽度正合适,没有不必要的空白。
本地化设置
美国和世界上的许多地区都使用一个点来分隔十进制值的整数部分和小数部分,然而,许多其他地区用逗号来分隔。C语言考虑到了这种情况。C支持本地化概念,因此C程序可以选择特定的本地化设置。另外,一旦指定了环境,便可在代码的数字中使用逗号。
C标准由两个本地化设置:"C"和""(空字符串)。默认情况下,程序使用"C"本地化设置,基本上符合美国的用法习惯。而""本地化设置可以替换当前系统中使用的本地语言环境。原则上,这与"C"本地化设置相同。事实上,大部分操作系统都提供本地化设置选项列表,只不过它们提供的列表可能不同。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_40186813

你的能量无可限量。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值