Skip to content

C语言标准输入与输出

C语言输入输出函数家族

家族名目的可用于所有流只用于stdin和stdout
getchar字符输⼊fgetc、getcgetchar
putchar字符输出fputc、putcputchar
gets文本行输⼊fgetsgets
puts文本行输出fputsputs
scanf格式化输⼊fscanfscanf
printf格式化输出fprintfprintf

getchar函数

getchar 函数从标准输入(通常是键盘)读取一个字符并返回该字符。

语法

c
int getchar(void);

返回值

  • 如果成功读取一个字符,则返回该字符的 ASCII 码。
  • 如果遇到文件尾(EOF),则返回 EOF(通常定义为 -1)。

示例

c
char ch;

ch = getchar();

这将从标准输入读取一个字符并将其存储在变量 ch 中。

putchar函数

putchar 函数将一个字符写入标准输出(通常是控制台)。

语法

c
int putchar(int ch);

参数

  • ch:要写入的字符的 ASCII 码。

返回值

  • 如果成功写入字符,则返回该字符。
  • 如果写入失败,则返回 EOF(通常定义为 -1)。

示例

c
char ch = 'A';

putchar(ch);

这将向控制台打印字符 'A'

printf函数

printf函数的核心作用是将各种类型的数据转换为字符形式并输出到中。

从实际效果看,printf函数会展示的内容,并在指定的位置插入对应的值

调用printf函数时,首要参数是格式字符串。紧随其后的参数表达式则表示要插入到该字符串中的值。调用形式如下(语法格式):

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

printf 函数被设计用来显示格式串中的内容,并且在该字符串的指定位置插入要显示的值。调用printf 函数时必须提供格式串,后面的参数是需要插入到格式串中指定位置的值:

函数原型:

c
#include <stdio.h>

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

其中:

  • format:一个格式化字符串,指定要打印的数据类型和格式。
  • ...:要打印的数据变量。

格式串包含普通字符和转换说明(conversion specification),其中转换说明以字符 %开头。转换说明是一个占位符,它表示待插入的值。跟随在字符 % 后边的信息指定了如何把数值从二进制形式转换成可打印的字符形式,这也是"转换说明"这一术语的由来。例如,%d 指定printf 函数把 int 类型值从二进制形式转换成由十进制数字组成的字符串,%f 对 float 类型值也进行类似的转换。

格式串中的普通字符会原样显示,转换说明则会替换为后面表达式的值。

代码示例如下:

cpp
int i, j;
float x, y;

i = 10;
j = 20;
x = 43.2892f;
y = 5527.0f;

printf("i = %d, j = %d, x = %f, y = %f\n", i, j, x, y);

格式字符串包含两个主要部分:

  1. 普通字符,printf函数会将普通字符原封不动的进行显示。比如上面代码中的"i = , j = "。
  2. ,以字符% 开头,它为后续对应位置的表达式提供了一个占位符。在上述示例中,"%d"和"%f"就是转换说明。

理解转换说明的含义和用法是掌握printf函数的关键。

转换说明(重点)

转换说明在printf函数中起到了关键的角色,允许开发者对输出格式进行精细的控制。它主要有以下几个作用:

  1. 占位符的作用。
  2. 控制输出的格式,比如宽度,精度等。

系统的讲,转换说明的组成公式如下:

%[标志][字段宽度][.精度][长度]说明符

下面,我们一步步详细地讲解每个部分:

  1. "%"是转换说明的开始,必不可省略。

  2. 用于决定一些特殊的格式,常见的标志有:

    • -:左对齐输出。如果没有该标志,输出默认是右对齐的。
    • +:输出正负号。对于正数,会输出+,对于负数,会输出-。
    • 0:当输出宽度大于实际数字的字符数量时,使用0而不是空格来填充。
    • 空格:当数值为正时,在数值前面添加一个空格,而负数则添加-。如果同时使用了+标志,+标志会覆盖空格标志。
  3. 用于指定输出的最小字符宽度,但不会导致截断数据:

    • 如果输出的字符,宽度小于指定的宽度,那么输出的值将会按照指定的**[标志]**来进行填充。若标志位没有0,则会填充空格。
    • 如果输出的字符,宽度大于指定的宽度,那么printf函数并不会截断,而是完全输出所有字符。
  4. 定义打印的精度:

    • 对于整数,表示要输出的最小位数,若位数不足则左侧填充0。

    • 对于浮点数,表示要在小数点后面打印的位数。

      • 当有效数字不足时,会自行在后面补0

      • 当有效位数超出时,会截断保留指定的有效位数。这个过程一般会遵守"四舍五入"的原则。

  5. 主要描述参数的数据类型或大小。常见的长度修饰符有:

    • h : 与整数说明符一起使用,表示short类型。
    • l (小写的L): 通常与整数或浮点数说明符一起使用,表示long(对于整数)或double(对于浮点数)。
    • ll (两个小写的L): 与整数说明符一起使用,表示long long类型的整数。
    • L (大写的L): 与浮点数说明符一起使用,表示long double。
  6. 描述如何格式化和显示该参数。常见的说明符有:

    • di : 表示有符号的十进制整数。

    • u:表示无符号的十进制整数。

    • o:表示无符号的八进制整数。

    • x:表示无符号的十六进制整数,使用小写字母(例如:a-f)。

    • X:表示无符号的十六进制整数,使用大写字母(例如:A-F)。

    • f, eE : 浮点数。

      • e:强制用科学计数法显示此浮点数,使用小写的“e”表示10的幂次。

      • E: 强制用科学计数法显示此浮点数,使用大写的“E”表示10的幂次。

    • gG : 选择最合适的表示方式,浮点数或科学记数法。

      • g,当选择使用科学计数法显示此浮点数时,使用小写的“e”表示10的幂次。

      • G,当选择使用科学计数法显示此浮点数时,使用大写的“E”表示10的幂次。

    • c : 字符。

    • s : 字符串。纯粹打印字符串一般不需要用转换说明,直接使用普通字符输出即可。

    • p : 指针。

通过结合这些组件,你可以精确地控制printf的输出格式。但是请不要尝试死记硬背,要在不断使用的过程中,逐渐理解记忆。当遇到不会写的格式或者忘记时,再及时查表即可。

特殊符号"%"

在转换说明中,有一个非常特殊的字符——"%"。百分号用于转换说明的开始,那么如果我就希望打印一个百分号咋办?

例如:

cpp
printf("Download progress: %d%%\n", progress);

在这个例子中,printf函数用于打印一条消息,表明了某项下载的进度,若progress等于80,则打印效果是:

Download progress: 80%

特殊符号"*"

假如给定一个浮点数:

cpp
float a = 3.1415926f;

现在我的需求是以下列格式打印它:

  1. 小数点后面保留2位
  2. 打印结果字符串的总宽度是5

这个需求是很容易实现的,参考代码如下:

cpp
printf("|%5.2f|", a);

打印结果是:

| 3.14|

现在我改变需求,希望能够用变量在程序运行时期确定打印小数点后的位数以及打印结果的总宽度,比如给定两个变量:

代码块 2.

cpp
int width = 5;
int point = 2;

那么该如何实现呢?

此时就需要用到转换说明中的特殊字符"*",它的使用语法如下:

cpp
printf("|%*.*f|", width, point, a);

该语句中的第一个"*"就表示将从printf函数的参数列表中动态获取宽度,也就是变量width作为宽度。

该语句中的第二个"*"就表示将从printf函数的参数列表中动态获取保留小数点位数,也就是变量point作为保留小数点位数。

*字符的存在,使得转换说明中的具体值可以动态的从函数的参数中获取,给格式字符串的使用带来了极大的灵活性,是一个非常重要的语法。

但是要注意,在scanf中*星号具有特殊的含义,不要和printf函数混淆了。

%f和%lf转换说明

对于printf函数来说,固有印象里%f表示输出float,%lf表示输出double,但实际上转换说明%f%lf是完全等价的。

这是因为在C标准中,当使用printf函数时,float类型的值会被自动转换为double类型。因此,不管是使用%f还是%lf,传递给printffloat值都会被转换成double,它们是等价的。

scanf函数的%f和%lf转换说明,是完全不同的!

注意行缓冲区(重要)

printf函数将数据输出到stdout的行缓冲区,但要将这些数据真正展示到外部设备(如屏幕),则需依靠stdout的自动刷新机制。

为了增加输出的实时性和可预测性,,这样可以立即触发缓冲区的刷新。这确保了待显示的信息能够迅速呈现,不会因其他因素延迟。

建议:

scanf函数

scanf函数的核心作用是从中读取字符形式的数据,并将其转换为特定类型的数据。

从实际效果看,scanf函数会根据读取输入的内容,并将这些内容赋值给指定的变量。

调用scanf函数时,首要参数也是格式字符串,,表示将读取到的值存放在哪个地址。调用形式如下(语法格式):

scanf(格式字符串, &变量1, &变量2, ...);

scanf 函数可以根据格式串中指定的格式读取输入。scanf 函数的格式串也包含普通字符和转换说明两部分,其中scanf 函数的转换说明和printf 函数的转换说明的含义基本上是一样。

函数原型:

c
int scanf(const char *format, ...);

其中:

  • format:一个格式化字符串,指定要读取的数据类型和格式。
  • ...:要读取的数据变量的地址。

在许多情况下, scanf 函数的格式串只包含转换说明:

cpp
int i, j;
float x, y;

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

值得注意的是,通常我们会在后面变量的前面加 & 符号(取地址运算符),但这并不是 必须的。何时使用 & 符号是程序员的责任。

示例代码如下:

cpp
int i;
float x;


printf("输入整数: ");
scanf("%d", &i);


printf("输入浮点数:");
scanf("%f", &x);


printf("您输入的数据是: i = %d, x = %f", i, x);

scanf函数的格式字符串中可能包含:

  1. 普通字符,比如空格和其他字符,scanf函数会期望输入中有与之匹配的字符。
  2. 转换说明,以字符"%" 开头,它告诉scanf函数应该如何解释输入中的数据并如何存储它。在上述示例中,"%d"和"%f"就是转换说明。

值得注意的是,scanf函数在调用时填入的变量前面要加符号"&",它是取地址运算符,意思是告诉scanf函数将数据存储到某个地址。

转换说明

scanf函数使用转换说明来解析和读取输入,这为开发者提供了对输入数据格式的精细控制。

系统地讲,scanf函数的转换说明的组成公式如下:

cpp
%[*][宽度][长度]说明符

接下来,我们逐一解释每个组成部分:

  1. 是转换说明的开始,并且是必不可省略的。
  2. 当使用该符号时,对应的输入会被读取,但不会存储到任何变量中。例如,使用"%*d"意味着会读取一次输入,但此输入完全无效不会赋值到对应变量。
  3. 表示要读取的最大字符数量。例如,"%5d"意味着读取最多5个字符来解析为一个整数。
  4. 描述参数的数据类型或大小。常见的长度修饰符有
    • h : 与整数说明符一起使用,表示short类型。
    • l (小写的L): 通常与整数或浮点数说明符一起使用,表示long(对于整数)或double(对于浮点数)。
    • ll (两个小写的L): 与整数说明符一起使用,表示long long类型的整数。
    • L (大写的L): 与浮点数说明符一起使用,表示long double。
  5. 说明符:这是必不可少的部分,描述如何解析输入数据。常见的说明符有

    • d: 表示有符号的十进制整数。

    • i:注意scanf函数的转换说明i和printf的是不同的。

      • printf函数的i和d都是等价的,都表示输出有符号的十进制整数。

      • scanf的i会自动判断输入的整数的进制,从而进行不同的录入。支持十进制、八进制、十六进制整数。

    • u:表示无符号的十进制整数。

    • o:表示无符号的八进制整数。

    • x/X: 表示无符号十六进制整数。

    • f, e, E, g, G: 表示浮点数。

    • c: 表示单字符。

    • **s:**字符串。会读取连续的字符,直到遇到空白字符(如空格、制表符或换行符)为止。

    • p: 指针。

    • %[字符集]: 这告诉scanf只接受和存储来自指定字符集的字符。字符集是直接写在[]之间的。例如,%[abc]将只读取'a', 'b', 或 'c'字符,其他的字符将导致读取停止。

    • %[^字符集]: 这是扫描集的否定形式,告诉scanf接受和存储除了指定字符集之外的所有字符。^字符放在[之后立即表示否定。例如,%[^abc]将读取除了'a', 'b', 和 'c'之外的所有字符,直到遇到这三个字符中的任何一个为止。

和printf函数一样,当你在使用scanf函数时,建议不要死记硬背转换说明,而是在实践中逐渐熟悉并查阅文档或其他资料进行验证和参考。

工作原理

scanf 函数本质上是一个"模式匹配"函数,试图把输入的字符与转换说明匹配。

scanf 函数是从左到右处理格式串中的信息。对于格式串中的每一个转换说明,scanf 函数试图从输入中读取对应的数据项。如果读入数据项成功,那么scanf 函数会继续处理格式串的剩余部分;如果读入不成功,那么scanf 函数将不再处理格式串的剩余部分,而会立刻返回。

那么scanf 函数是如何识别整数和浮点数的呢?

scanf 函数在寻找数值时,会忽略前面的空白字符 (white-space character: 包括空格符、水平和垂直制表符、换页符和换行符)。

在读入整数时, scanf 函数首先会寻找数字、正号或者负号,然后读取数字直到读到一个非数字时才停止。

在读入浮点数时, scanf 函数会首先读取正号或者负号 (可选),随后是一串数字(可能包含小数点),再然后是指数 (可选)。指数由字符 e (或 E),正负号 (可选) 和一串数字组成。

录入字符数据的特殊性

所以在录入字符时,尤其是一行录入多个数据且包含输入字符时,一定要在转换说明前面留出一个空格,以匹配可能的空格:

cpp
char ch;
int num;
printf("请输入一个数字以及一个字符: ");
scanf("%d %c", &num, &ch); // 注意 %c 前的空格
printf("你输入的数字是: %d\n", num);
printf("你输入的字符是: %c\n", ch);

上述代码运行,键盘录入:

100 a

程序打印结果:

你输入的数字是: 100

你输入的字符是: a

格式串中的普通字符

处理格式串中的普通字符时, scanf 的行为依赖于这个字符是否为空白字符。

  • 空白字符。当在格式串中遇到一个或多个连续的空白字符时, scanf 会读取所有的空白字符直到遇到一个非空白字符。格式串中的一个空白字符可以与输入中任意数量的空白字符匹配,所以格式串中空白字符的数量是无关紧要的,一个空白字符的效果与多个空白字符的效果是一样的。

    附带提一下,在格式串中包含空白字符并不意味着输入中必须包含空白字符。格式串中的一个空白字符可以与输入中任意数量的空白字符相匹配,包括零个.

  • 其他字符。当在格式串中遇到非空白字符时, scanf 会把它与下一个输入字符进行比较。如果两个字符相匹配,那么scanf 会丢弃输入字符而继续处理格式串。如果两个字符不匹配,那么scanf 不会读取该字符。之后会异常退出,不会进一步处理格式串或者从输入中读取字符。

缓冲区对scanf函数的影响

scanf函数是从标准输入缓冲区stdio从读取数据,而不是直接从键盘读取数据。

这句话很多同学是知道的,但没有完全理解。

我们来看一段普通的scanf代码:

cpp
int num;
char ch;
scanf("%d", &num);
scanf(" %c", &ch);

该行代码执行如果正常进行键盘录入:

100

A

最终num和ch的结果分别是100和A,但如果键盘录入:

100A

最终结果是什么呢?

实际上结果仍然是100和A,那么为什么呢?

这是因为当键盘录入100A时,这一段数据会被放入stdin缓冲区,缓冲区中已有数据100A,然后scanf开始从缓冲区中读数据。

第一个scanf以转换说明%d读取整数,所以num变量的最终取值是100,而缓冲区中的A字符,第一个scanf是不会管它的。因为当 scanf 函数遇到一个不属于当前项的字符时,就返回了。

第二个scanf以转换说明%c读取字符,也是从缓冲区中读,缓冲区不为空,且恰好存在一个字符A,于是ch的最终取值就是A

所以,在需要连续录入的场景中,我们要特别注意缓冲区中数据的变化,避免因此引发的一些问题。

不要混淆printf函数和scanf函数

虽然scanf 函数调用和printf 函数调用看起来很相似,但这两个函数之间有很大的差异!

一个常见的错误是:调用printf 函数时,在变量的前面加 &。

cpp
printf("%d, %d\n", &i, &j); /*** WRONG ***/

scanf 函数在寻找数据项时,通常会跳过前面的空白字符。所以除了转换说明,格式串通常不包含其他字符。

另一个常见的错误就是:认为scanf 函数的格式串应该类似于printf 函数的格式串。

cpp
scanf("%d, %d", &i, &j);
输入:10 20