C语言-第3章-格式化输出\输入

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/cb_east/article/details/97616024


本章介绍格式化输出\输入相关知识,难点是格式化输入的理解。

3.1 输出函数printf

printf函数用来输出字符串,包含了将数值类型转换为字符串的功能。printf中的f是单词format的首字母。其使用形式为

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

格式串,作为函数的第一个参数,对应了将要输出的内容。如果在其中的是普通的字符,则直接输出。如果是类似%d、%f的转换说明符,说明要显示的是值,%d对应整数类型的值,%f对应浮点类型的值,而具体要输出的值放在了printf函数之后的参数中。比如,整型变量age值为10,需要输出age中的值,并且输出全文是"年龄是*岁",printf函数调用如下:

printf("年龄是%d岁\n", age);

如果要显示的是float类型变量height,则printf函数调用:

printf(“身高是%f\n”, height);

也可以显示带有运算符的表达式

printf("年龄是%d岁,下一年的年龄是%d\n", age, age + 1);

也可以在一个printf语句中显示不同类型的表达式

printf("年龄是%d岁,身高是%f\n", age, height);

printf函数可以显示的表达式的个数是任意的,也可以没有表达式,比如我们见过的最早的程序,只显示字符串"hello, world!"

printf("hello, world!\n");

这里,要注意的问题是转换说明符和表达式的对应关系。实际上,转换说明符帮助printf函数解释二进制数值(值在内存中都是以二进制存储的),不同类型的值,其二进制数值的解释是不同的,因此转换说明符必须和表达式的数值类型相一致。

假如有如下类型变量

int age = 20;
float height = 1.7;

下面的列表给出了部分类型值的转换说明符。

表达式类型 输出 转换说明符 例子
整数 整数 d printf("%d", age); // 输出10
浮点数 浮点数(小数形式) f printf("%f", height); // 输出1.700000
浮点数 浮点数 (指数形式) e printf("%e", height); // 输出1.700000e+000
浮点数 浮点数 (小数形式和指数形式短的那个,不显示尾随的零) g printf("%g", height); // 输出1.7

C99之前,用printf输出double类型的值时与float类型的值一样,不用要加l,直接用e、f或g,C99允许printf函数使用%le、%lf和%lg来输出double类型的值。

下图是一个printf函数的例子,printf函数的格式串指明了输出的内容:
在这里插入图片描述

格式串中的转换说明符用来指明输出值的类型,值的类型不同,转换说明符不同:

在这里插入图片描述

3.1.1 细化的转换说明

转换说明还可以对显示的字符做更精确的控制。比如,我们可以用%.1f来显示浮点数,显示的效果是小数点后面只带一位小数。更一般地,转换说明可以用%m.pX格式或者%-m.pX格式。这里的m和p都是整数常量,而X是字母。m和p都是可选的。如果省略p,m和p之间的小数点也要去掉。在转换说明%10.2f中,m是10, p是2,而X是f。在转换说明%10f中,m是10,p(连同小数点一起)省去了;而在转换说明%.2f中,p是2,m省去了。

m用来指定要显示的表达式占的最小字段宽度。 如果待显示的字符数量小于m,那么整个表达式占m个字段宽度,实际显示的字符右对齐显示。比如,转换说明%4d将以" 123"显示整数123。如果在m之前放负号,则左对齐。比如,转换说明%-4d将以"123 "显示整数123。如果待显示的字符数量大于等于m,那么忽略m值,以待显示的字符数量决定整个表达式占的字段宽度。比如,转换说明%4d将以12345的形式显示12345,不会丢失数字。

p的含义很难描述,因为它依赖转换说明符X的选择。

  • f - 表示显示“定点十进制”形式的浮点数。p指明了待显示的浮点数的小数点后显示的数字的个数(默认是6,不足补零)。如果p为0,则不显示小数点和其后的小数。

  • e - 表示显示指数形式的浮点数。p的含义与在f说明符中的一样。

  • g - 表示以“定点十进制”或者指数形式来显示浮点数。选择它们中最短的那个。p的含义是显示的有效数字的最大数量。比如,%.3g将以"1.23"显示1.2345。与f不同,它不显示尾随的零。比如,%.5f将以"1.23000"来显示1.23,而%.5g将以"1.23"来显示1.23。

  • d - 表示显示整数值。p指明了待显示的数字的最少个数(必要时在前面加上额外的零);如果省略p,则默认它的值是1。如果待显示的数字个数大于p,则按照实际数字个数来显示。比如,转换说明%.4d将以"0020"显示整数20,转换说明%.2d将以“2000”显示整数2000。(这个用法用的较少)

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

/*用各种格式输出整数和浮点数*/

#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函数格式串中的字符 |只是y用来帮助显示每个数所占用的空格数量;不同于%或\,字符|对printf函数而言没有任何特殊意义。此程序的输出如下:

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

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

3.1.2 转义序列

格式串中常用的\n表示转义序列。转义序列是字符串包含一些特殊的字符而不会使编译器引发问题,这些字符包括非打印的字符(控制字符)以及一些对编译器有特殊含义的字符(如")。

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

字符\a会产生一个鸣响,\b会使光标从当前位置回退一个位置。\n输出换行符,使光标跳到下一行的起始位置。\t使输出从下一个制表位开始。

其它一些常见的转义序列有:

  • 双引号 " : 由于双引号用来表示字符串的结束,故字符串要含有",需要用\对它进行转义。
  • 反斜杠\ : 反斜杠用来表示对字符进行转义,因此要表示具体字符反斜杠\,需要用\对反斜杠进行转义。

3.1.3 printf常见问题

参考第2章-printf常见问题

3.2 输入函数scanf

printf函数将字符或者数值输出成字符串显示到计算机外部,而scanf函数恰好相反,从计算机外部将字符串读入到计算机内部。scanf中的f也是format的首字母。其一般格式如下:

scanf(格式串, 变量地址, 变量地址, ...);

在许多情况下,scanf函数的格式串中只需要包含转换说明,比如

int i, j;
float x, y;

scanf("%d%d%f%f", &i, &j, &x, &y);

假设用户输入了下列内容并按回车:

1 -20 .3 -4.0e3

scanf函数将读入上述字符序列,并转换为“%d%d%f%f”标识的int整数、int整数、float浮点数、float浮点数,并依次保存到变量i,j,x,y中。

在这里插入图片描述
输入以上字符序列,并按下换行符
在这里插入图片描述
不同于printf函数,scanf基本不用出现除转换说明外的其它字符。scanf函数中像"%d%d%f%f"这样的“紧密压缩”的格式说明符很普遍,而printf函数的格式串中很少有这样紧挨着的转换说明,否则输出的数值之间就没有间隙,用户无法分辨出各个数值。

类似于printf函数,使用scanf函数时也要注意转换说明和变量的个数的匹配,类型的匹配。变量之前要加&符号来取得变量的在内存中的地址(尽管不是所有的情况都要加&)。

变量类型 输入 转换说明符 例子
int 十进制整数 d scanf("%d", &age); // 输入10
float “定点十进制”或者指数形式的浮点数 f或者e或者g scanf("%f", height); // 输入 1.7或者0.17e1
double “定点十进制”或者指数形式的浮点数 lf或者le或者lg scanf("%lf", height); // 输入 1.7或者0.17e1

注意:

  • 输入浮点数时,不论采用的是“定点十进制”还是指数形式,转换说明f、e或者g是通用的。
  • 向double类型变量输入时,在f、e或者g前面加字母l。

3.2.1 scanf函数的工作方法

scan单词的意思是扫描,scanf函数本质上是一种“模式匹配”函数,它会把输入的内容看成字符序列,然后把这个字符序列与格式串相匹配,直至格式串中的全部内容得到处理。值得注意的是,scanf函数的扫描过程是在按下回车键才真正执行的,而不是输入一个字符就处理一下,在这之前用户输入的内容是保存在输入缓冲区里面的,并不会丢失。

按下回车键scanf函数真正开始执行后,它会以格式串中的转换说明来匹配/解读输入缓冲区中的字符序列,匹配成功会将其表示的值存储到相应的变量中,然后继续以格式串中剩余的转换说明来匹配输入缓冲区剩余的字符序列。直到格式串中的转换说明全部被匹配,scanf函数执行结束。输入缓冲区中剩余未被处理的字符序列会被后续的输入函数来处理。

在匹配数值类型输入的时候,scanf函数会忽略输入的数值序列之前的空白字符。包括空格符、水平制表符、换行符在内的字符称为空白字符。读取到数值之后的空白字符意味着当前的数值的字符序列结束了。数值字符序列被解读为数值并保存到变量中,而输入缓冲区中数值字符序列之后的空白字符会留下来待后续的输入函数来读取。

比如,考虑下面的scanf函数调用:

scanf("%d%d", &i, &j);

如果输入了:12 345回车
即字符1、2、空格、3、4、5、换行符
那么scanf函数会以第一个%d意味着匹配整数并放到变量i中。它先后读取字符1、2、空格,读取到空格后知道整数字符序列结束了,字符1、2会被转换为数值12并保存到变量i中。而空格会被放回输入缓冲区。接下来,scanf函数会匹配第二个%d。此次依然是匹配输入的整数。在读取并跳过空格后,字符3、4、5会被读取,直到读取到换行符,表明整数序列的结束,字符3、4、5会被转换为数值345并保存到变量j中。而换行符会被放回输入缓冲区,会被后续输入函数读取到。

需要注意的是,如果在匹配转换说明和输入字符序列的过程中失败,scanf会停止后续的匹配过程。比如

scanf("%d%d", &i, &j);

如果输入了:1 .5回车
即字符1、空格、小数点、字符5、换行符
第1个%d意味着匹配整数放到变量i中,1和空格被依次读取并扫描,由于空格不属于整数,故字符1被转换为整数1并赋值到i中,空格被放回输入缓冲区。接下来,第2个%d意味着继续匹配整数并放到变量j中,那么在匹配过程中,首先忽略空格,然后扫描到小数点,由于整数不会包含小数点,故这意味着匹配的结束,而此时并没有读到任何整数字符,这意味着输入内容没有遵循转换说明符的要求输入一个整数,是表示输入出错了,故在小数点被放回输入缓冲区后,scanf函数运行结束。i被赋值为1,而j变量未被改变。此时,输入缓冲区还有小数点、字符5和换行符共3个字符。

如果输入的是:1 1.5回车
即字符1、空格、字符1、小数点、字符5、换行符
那么如同上述步骤那样,scanf会把第1个1读取到i中,把第2个1读取到j中,而输入缓冲区中剩余包含小数点和其后的字符串(字符5和换行符)。

在匹配数值的过程中,scanf函数会忽略数值之前的任意多个空白字符。

scanf("%d%d%f%f", &i, &j, &x, &y);

用户可以输入:
1 -20 .3 -4.0e3
注意,各个数之间有一个空格。
或者用户可以输入:
1
-20
.3
-4.0e3
注意,此时各个数之间有一个换行符。
或者,用户可以输入:
  1
-20 .3
    -4.0e3
即各个数之间既有换行符又有空格。

3.2.2 格式串中的普通字符

上面介绍了格式串中最常见的情况,即只包含转换说明。如果格式串中还包括普通字符,那么scanf要求在匹配到格式串中这个普通字符时,输入的字符序列中也是这个字符。如果匹配,scanf会放弃这个字符,继续处理格式串中的后续内容。如果不匹配,那么scanf会把不匹配的字符放回输入缓冲区,然后scanf函数不再向后处理。

比如

scanf("%d,%d", &x, &y);

如果输入的是:5,10回车
即字符5,逗号,字符1、字符0、回车符
那么可以顺利的读入。读入过程是先匹配格式串"%d,%d"和输入字符串"5,10"。格式串中的转换说明符%d被先匹配,5和逗号会依次扫描到,扫描到逗号停止扫描,因为逗号不是数值,5和%d是匹配的,整数5被读入到x中,逗号被放回待扫描的输入缓冲区中。接下来,scanf会匹配格式串中普通字符逗号。恰好输入缓冲区中是逗号,它们相匹配。scanf会放弃它,继续匹配格式串"%d",于是10被匹配。scanf完成整个格式串的处理。

输入5,逗号,10
在这里插入图片描述
输入回车键
在这里插入图片描述
如果输入的是5 10(5和10之间只有一个空格,没有逗号)

那么读入过程中会出错。格式串中的转换说明符%d被先匹配,5和空格会依次扫描到,扫描到空格停止扫描,因为空格不是数值,5和%d是匹配的,数值5被读入到x中,空格被放回待扫描的输入缓冲区中。接下来,scanf会匹配格式串中普通字符逗号。格式串中的普通字符逗号和输入缓冲区的空格不匹配,故scanf函数出错,scanf停止执行。

上述过程,进一步说明了printf的函数中的格式串指出了输出内容,而scanf函数的格式串指出了输入的内容。

3.2.3 格式串中的空白字符

一种特殊的情况,如果格式串中出现的普通字符是空白字符。格式串中的一个或者多个连续空白字符与输入字符串中的任意个(包括零个)空白字符匹配。但是,这个匹配过程会一直持续下去,直到遇到输入字符串中的某个非空白字符才会停止,该非空白字符会放回输入缓冲区待用。

这解释了如果读入一个整数的scanf函数写成了scanf("%d ");(%d后面有一个空格)或者scanf("%d\n");,那么即使用户输入了一个整数并按下了回车,scanf函数还在运行,并没有结束,造成了程序的“挂起”现象。直到用户输入了任意一个非空白字符并再次按下回车键scanf函数才能结束运行。

在这里插入图片描述
例子中,在输入123后连续输入了多个换行符,没有用,程序没有结束,直到输入了某个非空白字符,这里输入了字符a,再按下换行符,程序输出printf运行结果,再按下换行符,运行结束。

3.2.4 scanf函数的常见问题

参考第2章-scanf函数的常见问题

3.2.5 使用getchar函数暂停程序

VC开发环境下,在debug模式下运行程序,程序运行结束会自动销毁窗口,如果程序中没有输入函数的调用,程序会一闪而过。
为了解决这个问题, 我们在程序的最后(return 0;语句之前)放置一个scanf函数来尝试读取一个字符,这样程序运行到那里就会暂停下来等待用户输入字符,故之前的运行结果即可被显示出来。

在这里插入图片描述
如上调用scanf函数会将输入的字符保存到字符变量ch中。其实输入数值本质上也是将输入的内容看成字符序列,只是scanf函数会将它们转换为相应的整数。除了scanf可以输入字符,getchar函数也具有读取输入的一个字符的功能,我们可以用它来替代scanf函数。
ch = getchar();

getchar函数会返回读取到的字符值,ch = getchar();会将这个字符值赋值给字符变量ch,所以效果上和用scanf是一致的。由于这里读取字符的目的只是暂停程序,并不对输入的字符值本身感兴趣,所以可以像如下那样调用

在这里插入图片描述

这样getchar函数依然会读取一个字符,只是没有把它保存到字符变量中,所以在这条语句之后这个字符的值就无法访问了。

那么为什么在某些情况下,只调用一次getchar函数还是无法让程序暂停下来呢?

首先,从原理上讲,一个程序中有唯一一个输入缓冲区,用户的输入都会暂存到那里,供程序中的scanf、getchar等输入函数来读取。而只有在输入缓冲区为空的情况下getchar才会让程序停下来。因为此时,getchar函数会阻塞住,等用户输入字符后存入输入缓冲区后,它才能读到字符并结束运行。否则如果输入缓冲区中原先就有字符,getchar将会直接从中读取字符并结束运行,所以用户没有机会输入,窗口也会一闪而过。

什么时候输入缓冲区会有遗留的尚未被读取的字符呢,很可能是之前运行的输入函数没有将输入缓冲区中用户输入的字符读取完,还剩有一些。比如,

  1. scanf函数正常运行,输入缓冲区中留下的字符是换行符。
    在这里插入图片描述
    上述程序在输入123后再输入换行符,程序会一闪而过。这是由于scanf函数运行时需要按下换行符触发其真正执行。故scanf函数输入了字符串123和换行符后,scanf函数只读取了其中的字符串123(并将其转换为整数123并保存到变量x中),可是换行符仍旧在输入缓冲区中。故getchar函数运行时输入缓冲区并不空,在getchar读取了换行符后getchar函数结束运行,程序也即将结束,会有一闪而过的现象。

  2. scanf函数运行中用户遇到了不匹配的内容,输入缓冲区中留下了scanf函数尚未读取的若干不匹配字符。
    在这里插入图片描述
    上述程序运行过程中,用户输入了3个字符,小数点和4以及换行符。由于scanf函数中的转换说明符是%d,故scanf尝试从输入字符串中读取整数,但是第一个字符小数点就不是整数的一部分,故scanf函数并没有读取任何整数就结束了,而用户输入的3个字符仍然在输入缓冲区中。故getchar函数运行时直接从输入缓冲区中读取了第1个字符,小数点。随即getchar函数运行结束,程序运行结束,窗口会一闪而过。

在如上述两种情况下,如何让程序暂停下来?办法很简单,getchar函数的调用次数至少超过输入缓冲区尚未被读取的字符数量即可。第1个例子中,getchar运行时输入缓冲区有1个字符,换行符,故共放置2条getchar函数调用即可。第2个例子中,getchar运行时输入缓冲区中有3个字符,小数点、字符4、换行符。故共放置4条getchar函数即可。

在这里插入图片描述
输入123和换行符,程序可以暂停下来,直到用户输入换行符。

在这里插入图片描述
输入小数点、4、换行符后程序可以暂停下来,直到用户输入换行符。

以下内容不做要求:

尽管我们用不同数量的getchar函数可以让程序暂停下来,但是需要的getchar的数量是与运行时的输入内容是有关的,而getchar本身又是编译期写到程序中的代码,可是写代码时是无法预知程序运行时遗留在输入缓冲区的字符数的。故需要getchar的数量是无法预知的。同时,我们已经说过,一个程序中输入缓冲区是共用的,之前scanf语句运行时遗留下的字符串会被后续的scanf函数接着读取,这也是编程人员不愿看到的情况,程序员一般希望各个scanf能让用户重新开始输入。所以,在下一个scanf函数运行之前清空输入缓冲区是非常有必要的。用以下代码可以清空输入缓冲区

while((ch = getchar()) != EOF && ch != '\n');

上述语句的功能是用getchar不断读取输入缓冲区的字符,直到遇到文件结束符EOF或者换行符。(输入缓冲区最后一个字符不是文件结束符EOF,就是换行符’\n’。)

为了让程序暂停,只要在一条getchar函数之前添加上述清空输入缓冲区的代码即可。这样,无论输入缓冲区中遗留多少字符,都可以被清空。

在这里插入图片描述
输入123和换行符,程序不会结束,直到再次输入一个换行符。

在这里插入图片描述
输入小数点、4、换行符后,程序不会结束,直到再次输入一个换行符。

猜你喜欢

转载自blog.csdn.net/cb_east/article/details/97616024