理解输入输出

什么是交互

程序是为人服务的,程序计算出来的所有数据,最终都以各种不同的方式来间接或直接地提供给我们,也就是输出.

同样,程序在运行时,我们往往也需要为其提供一些必要的数据,作为输入.

程序根据我们输入的信息来进行处理,并将结果显示出来.

最基本的输入输出操作,就是在控制台屏幕使用键盘进行交互,我们将需要输入的数据使用键盘来输入,程序将需要输出的数据显示在屏幕上.这种输入输出叫做标准输入输出.

标准输入输出中,往往使用电脑显示器来作为标准输出的目标,使用键盘来作为标准输入的来源.

本章我们要讲的就是标准输入输出,也就是Standard input/output.即简单地使用键盘输入,从屏幕上看到输出信息.

什么是流

许多资料都会引入这个概念,输入输出也是一种流.

流(Stream)是一个抽象的概念,表示数据从一个地方流向另一个地方,或者数据从一个源生成,并最终被传递到目的地。流可以是字节流(以字节为单位处理数据)或字符流(以字符为单位处理数据).

我们之前存储的数据全部是固定存储在某个地方,我们直接通过地址等方式进行引用即可找到,但是流数据不同.

可以将存储的数据类比为水池中的水,流数据类比为水流.存储的数据是相对固定的,我们可以反复进行存取,例如电脑中存储的一张照片,一段视频;而流数据往往代表的是某个数据源即时生成的数据,直接依次传输到目的地,例如键盘读取的一系列敲击操作.

标准输入输出就是这样的一类流.

在进行标准输入时,也即从键盘进行读取,键盘就是一个标准输入流设备,从键盘读入的数据不需要进行缓冲,直接逐个进行传输即可,就像水流一样.

同理,在进行标准输出是,也即向屏幕显示,也是将其视为数据流来"流"向屏幕(实际上是终端),并逐个打印到屏幕上.

或者,读者可能知道"流媒体"这个概念.与键盘这样的流设备对应的,还有块设备,例如硬盘,它们需要一个成块的空间来进行缓冲,数据的读取是成块成块的.

关于的简单讲解就这么多,我们只需要知道,本章讲解的就是对标准输入输出流进行操作.

C语言对标准输入输出的支持

stdio.h

我们前面看到很多程序的开头都有#include <stdio.h>这行指令,但是并没有深究其作用,实际上,它就是为我们的程序提供了标准输入输出的支持.

#include预处理指令用于引入头文件,这里引入的就是C标准库的stdio.h头文件,这个头文件中定义,声明了有关输入输出的各种内容.想必各位可能已经意识到,stdio,即standard in/out的缩写.

当然,C可以支持的输出输出不仅仅有标准IO(即标准输入输出,以下均简写为标准IO),还有文件IO,网络IO,内存IO等等,它们往往更加复杂,需要更多的处理,但是殊途同归,它们都是数据操作,并且往往都是视为流去处理.

在stdio.h中有许多函数声明,和一些常用的宏,下面进行简单列举:

输入输出函数

函数 功能
scanf()函数 用于格式化输入
printf()函数 用于格式化输出
puts()函数 用于输出字符串,并自动加上换行符
gets()函数 输入一个字符串,同时会接受除了'\n'的其他空白字符.(由于有严重的安全缺陷已弃用)
putchar()函数 输出单个字符
getchar()函数 输入单个字符

三个流

预定义 关联
#define stdin /* implementation-defined */ 标准输入流
#define stdout /* implementation-defined */ 标准输出流
#define stderr /* implementation-defined */ 标准错误流

这些宏展开成 FILE* 类型表达式(即它们是文件指针)

常用函数讲解

C风格字符串

这里简单讲解一下字符串:

C语言中,字符串是由一对""和其包括起来的任意数量的字符组成.

例如,我们想要表示Hello world!这行字符串,就是这么写:

"Hello world!"

任何可以放到char变量中的字符,都可以出现在字符串中.

printf()函数

基本用法

函数原型如下:

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

printf函数名实际就是print formatted的缩写,意即打印格式化的字符串.

可以看到printf函数的第一个参数是一个字符串,后面可以继续接任意个数的其他参数(函数参数的内容将在后面讲解函数时详细展开).

函数的返回值为最终打印的字符个数.

printf()函数用于根据我们的格式要求生成一个最终的字符串,来打印到我们的终端(输出),这个字符串可以由多个字符串,多个整数,多个字符,多个浮点数等等拼接而成.

参数的第一个字符串就是用于描述这些数据如何进行拼装的,也即所谓的格式化字符串,用于描述输出格式.

我们说的各个数据就是后面可以追加的若干任意数量的参数,它们可以是任意数据类型的值.

printf函数的最基本用法就是输出一个字符串常量:

1
2
3
4
5
6
#include <stdio.h>
int main() {
// 此代码中格式化字符串中没有任何格式字符,仅仅是输出了一个普通的字符串
printf("Hello World!");
return 0;
}

但是printf函数的强大是体现在这个格式化字符串的,这个字符串中可以加入一些指定好的格式指示符,来进行额外的格式化操作,举一个简单的例子:

1
2
3
4
5
6
#include <stdio.h>
int main() {
int i = 123;
printf("hello%5d!",i);
return 0;
}

输出结果为:

image-20231018151927825

可以看到123前面有2个空格," 123"一共占用5个字符的宽度,即格式化字符串中的%5d用于将后面的第一个整型参数i输出,并保证至少占用5位的宽度,并插入到其他的"Hello""!"中间,最终生成"Hello 123!"这个字符串打印到终端.

这里%5d就是一种格式指示符,与此类似的格式指示符还有很多,我们列举一些常用的.

格式指示符

这里引用了《C Primer Plus (第6版)》的内容:

image-20231018154428429

image-20231018154451517

image-20231018154512241

读者请自行理解试验,需要用到哪种控制符可以去查找表格学习用法.

注意

1.对于浮点数,为了安全起见,printf函数将所有float类型的参数都提升为double类型(对未使用显式类型原型的所有C函数都有效).

2.如果格式化字符串较长,可以在一行结尾加上\来转义换行符(下一行必须从最开始开始,否则缩进就会变成字符串的一部分),或者使用两个字符串进行拼接:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
int sum = 123;
printf("Hello \
World!\n");
printf("Hello "
"World!");
return 0;
}
image-20231018155655371

3.还有一些表格中可能没有的格式指示符,例如%s,它用于输出一个字符串:

1
2
3
printf("Hello world!");
// 等价于
printf("%s","Hello world!");

scanf()函数

基本用法

函数原型为:

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

scanf()函数的功能和printf相反,它用于输入数据(scan意即"扫描")

同时,它也接受一个格式字符串和若干准备输入的参数,与printf不同的是,scanf后面的若干参数必须是合法的指针.

函数的返回值为成功读取的参数个数.

读者此时不必疑惑指针这个概念,你需要知道相比于printf,scanf后面的参数之前需要加上一个&字符(实际上是取地址运算符),用来获取期待输入值的这个变量的地址(指针).

实际上很简单,我们想要对一个变量进行输入,就需要知道他的存储位置.

同时,scanf的格式化字符串不允许使用像printf那样多样的格式指示符,我们仅仅需要使用例如:

%d来输入int类型的整数;

%s来输入字符串;

%f来输入float类型的浮点数;

%lf来输入double类型的浮点数;

%ld来输入long类型的整数…

等等…

格式指示符

C标准中的描述:

image-20231018160736236

举例

例如我们需要输入两个int类型的值,赋值给i,j,应该这样写:

1
2
3
4
5
6
7
#include <stdio.h>
int main() {
int i,j;
scanf("%d%d",&i,&j);
printf("%d %d", i, j);
return 0;
}
image-20231018160936632

注意到,scanf中的两个%d之间没有空格,我们输入时和printf中却需要空格,这是一个不同,事实上,scanf在输入整数/浮点数时,会不断读取,直到遇到不合法的字符为止—例如空格等空白符,逗号等字符.

%d结束后,再继续读取下一个%d.

另一方面,scanf也允许使用一般字符,scanf会在读取时匹配这些字符(如果格式化字符串中有的话,则必须匹配,否则之后的变量会读取失败),然后简单地丢弃这个字符:

1
2
3
4
5
6
7
#include <stdio.h>
int main() {
int i=0, j=0, k=0;
scanf("%d,%d%d", &i, &j, &k);
printf("%d %d %d", i, j, k);
return 0;
}

正确的输入:

image-20231018161749017

错误的输入:

image-20231018161814337

可以看到后两个变量并没有被正确地赋值,或者我们也可以输出scanf的返回值(成功读取的变量个数)来观察:

1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
int i=0, j=0, k=0,num=0;
num = scanf("%d,%d%d", &i, &j, &k);
printf("%d %d %d\n", i, j, k);
printf("num = %d",num);
return 0;
}
image-20231018161942008

注意

1.不要在scanf中使用'\n',' '等空白字符来结尾,例如:

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

scanf("%d%d%d\n", &i, &j, &k);

因为不同于printf,scanf函数的空白符代表忽略(匹配并丢弃)任意数量的空白字符,如果放在结尾,意味着这个scanf无法使用换行来结束输入!

2.(个人的建议)scanf中尽量只使用格式指示符,不要使用其他普通字符,做到简化输入.

getchar()函数

函数原型为:

int getchar( void );

该函数非常简单,用于读取一个字符:

1
char c = getchar();

上面的代码和下面的代码等价:

1
2
char c
scanf("%c",&c);

putchar()函数

函数原型为:

int putchar( int ch );

很简单,putchar('a');就可以将一个字符a输出.

其他(待扩充)

puts()和gets()将在讲解字符串时展开.


本章讲解了最常用的几个输入输出函数的使用,到现在我们多少掌握了printf和scanf的写法,需要在练习中不断熟悉.由于比较简单,可能例子不多.

自己多查资料.

---WAHAHA



上一篇:c语言教程-3_1-数据类型

下一篇:c语言教程-4-表达式和语句