输入和输出
说一下题外话,最近在出差,所以没什么精力,导致更新进度滞后。也是兴致来朝,索性更新一篇。
那本节内容主要讲解,C语言的标准输入与输出的定义、使用以及内容缓冲区的原理,让你能够对C语言的printf和scanf有更加深刻地理解。
1 标准输出
C语言标准输出是一个接口库函数,已经写好了实现,我们只需要去调用它,就可以实现从终端中打印出信息。标准输出函数格式:
// [返回值] [函数名]([格式化字符串], [可变参数列表]);
int printf(const char* fromat, ...);
- 参数:
fromat
为格式化字符串,包含两部分。第一部分是正常字符,原样输出。第二部分是格式占位符。...
为可变参数列表,可变参数个数与格式占位符数量一致。
- 返回值
- 作用是打印出字符的个数。(包括空白字符)
1.1 格式占位符
含义 | 格式占位符 |
---|---|
用于有符号整型 | %d or %i |
用于字符 | %c |
用于无符号整型 | %u |
用于单浮点数 | %f or %F |
用于双精度浮点数 | %lf |
用于科学计算法表示浮点数 | %e or %E |
用于实数(不显示无意义0) | %g or %G |
用于内存地址 | %p |
用于字符串 | %s |
1.2 输出方式
第一种,无格式占位符。
注:这里的字符 \n
是一个字符,代表着换行,ASCI码为13。
printf("Hello Home!\n");
第二种,一个格式占位符。
int param = 10;
printf("param = %d", param);
第三种,多个占位符。
int param_a = 100;
int param_b = 200;
printf("param_a = %d, param_b = %d\n", param_a, param_b);
1.3 头文件
在真正的使用过程中,如果我们只调用 printf
或 scanf
往往还是不够的,还需要先调用系统的库文件。
#include <stdio.h>
因为在 stdio.h
文件当中包含 printf
和 scanf
两个函数的声明,Linux
系统提供给外部的接口函数,用户可以直接拿来使用。
1.4 扩展
第一种,有 一
个占位符,无可变参数。
printf("hello %s");
我们会发现在Linux系统下编译,并没有报 error, 而是 warning ,且最后输出结果除了 Hello boys
以外,后面都是乱码, 原因是我们并没有相应加上需要输出的参数。
第二种,无占位符,由于输出格式字符串较长,有些人习惯把字符串分为多行。
printf("Hello boys
and
girls!");
这种方式是错误的,固定字符串不能够直接进行换行,否则在编译期间将会报错。那我们怎么解决这个问题呢?是的,要引入换行符(\
)。
printf("Hello boys \
and \
girls!");
2. 标准输入
printf
这个函数大多数人都可以使用好,虽然 printf
和 scanf
两个函数原型相同,但是 scanf
在用起来,还是有些让人抓狂的错误,那我们先来看下,C语言标准输入函数格式:
// [返回值] [函数名]([格式化字符串], [可变参数列表]);
int scanf(const char *format, ...);
- 参数:
fromat
为格式化字符串,包含两部分。第一部分是正常字符,原样输出。第二部分是格式占位符。...
为可变参数列表,可变参数个数与格式占位符数量一致。
- 返回值
- 成功返回读入的数据项数。
- 遇到错误或遇到
end of line
,返回EOF
(按Ctrl+Z
)。
int n;
scanf("%d", &n);
变量有它的基础数据特性,还有一个是地址(address
),怎么理解地址?从生活上,可以理解为是”商品盒子“上的数字标签,如果把这个地址给到你的朋友,他就能通过这个地址来找到对应数字标签的盒子。”数字标签“就是地址,”盒子“就是变量,盒子里”物品“是变量存储的值。
&
就是取 n
变量的地址,那我们为什么要取变量地址呢?因为用户从终端输入信息后存放到输入缓冲区,scanf
函数 要通过地址找到变量存储区域,然后缓冲区里的信息传送给变量这块地址。
2.1 扩展
2.1.1 %d%d
scanf函数的使用要严格按照规定的格式输入。例如格式化字符串为 %d+%d
,那么在终端输入就要加上中间的 +
,否则可能得不到你想要的结果。
int n;
int m;
scanf("%d+%d", &n, &m);
如果想要在终端连接输入两个元素。
#include<stdio.h>
int main()
{
int n;
int m;
scanf("%d%d", &n, &m);
printf("n = %d\n", n);
printf("m = %d\n", m);
return 0;
}
大家仔细可以看到,输入的是 20 30
,中间是有一个空格把两个元素分开,而 scanf
里是 %d%d
并没有空格把两者分隔开,为什么我们可以这么做?这是因为 scanf
在处理输入时,如果格式占位符不是用于读入单个字符的 %c
, 它就会将空白字符(空格符、制表符和换行符)都视为一次输入的终止标记。
2.1.2 %c%c
#include<stdio.h>
int main()
{
char n;
char m;
scanf("%c%c", &n, &m);
printf("n = %c\n", n);
printf("m = %c\n", m);
return 0;
}
输入 a
和 b
之间如果加了空格,空白字符也会被读入,所以大家一定要注意,不能够加入空格,如果要加入空格的话,应这么写输入:
scanf("%c %c", &n, &m);
3. 缓冲区
我们来看下曾经困扰很多人的问题,先看下代码。
#include <stdio.h>
int main()
{
int n;
scanf("%d\n", &n);
printf("n = %d\n", n);
}
如果我们正常输入一个整型,然后按下回车(\n),输出结果是:
2\n
无论输入了多少个回车(\n),运行处于阻塞状态。然后我们再输入任意的数,看看结果。
发现当我们输入任意的数时候,再去敲回车(\n),程序继续执行打印,运行结果结束且最终打印结果是我们第一次输入的结果,也就是 2
。
这样的现象,其实是和缓冲区是有关的,怎么去解释呢?
首先,输入的是 2\n\n\n\n
,这一串数据是存放在缓冲区内。
然后,我们再输入 3\n
,这个时候,退出阻塞,输出 2
,程序结束,那么缓冲区数据为:
根本上造成这个现象是因为,缓冲区里最先输入的 2\n
是满足 scanf
输入的格式,但是接下来的三个 \n
在它相邻左边并没检测到有效的字符(它们的左边一个都是空白字符),导致了程序阻塞,直到 3\n
的出现,最后一个 \n
检测到它相邻左边有一个非空白字符,阻塞就退出了。
那输出为 2
而不是 3
的原因是,缓冲区是单向的,有点像队列(先进先出),所以我们会输出第一个非空白字符,也就是 2
。