C/C++的一众输入输出函数的区别常常搞得人晕头转向,二者之中又以输入函数更加令人头疼。本文尝试整理C/C++的各种输入输出函数。
由于输入涉及空格、换行符的读取、忽略等问题,因此输入比输出更麻烦。所以本文将以输入为主线,对应的输出用法是类似的。
水平有限,如有疏漏,欢迎提出。
标准输入流
C 标准输入
C语言使用标准输入输出函数,需要包含头文件<stdio.h>
。而在 C++ 中,只要包含头文件<iostream>
,就完全可以使用这些 C 中的输入输出函数。
标准输入流及对缓冲区的理解
stdin
是一个文件描述符(Linux)或句柄(Windows),它在 C 程序启动时就被默认分配好。在 Linux 中一切皆文件,stdin
也相当于一个可读文件,它对应着键盘设备的输入。因为它不断地被输入,又不断地被读取,像流水一样,因此通常称作输入流。
stdin
是一种行缓冲I/O。当在键盘上键入字符时,它们首先被存放在键盘设备自身的缓存中(属于键盘硬件设备的一部分)。只有输入换行符时,操作系统才会进行同步,将键盘缓存中的数据读入到stdin
的输入缓冲区(存在于内存中)。所有从stdin
读取数据的输入流,都是从内存中的输入缓冲区读入数据。当输入缓冲区为空时,函数将被阻塞。
若无特殊说明,以下所有的“缓冲区”均是指内存中的stdin
输入缓冲区。用户程序中自定义的buffer
数组、str
数组等,将称作“数组”、“变量”,以免产生混淆。
scanf()
按照特定格式从stdin
读取输入。
用法示例:
char str[100];
int a;
scanf("%s %d", str, &a); // 注意,传入的一定是变量的地址
对空白字符的处理:
- 缓冲区开头:丢弃空白字符(包括空格、Tab、换行符),直到第一个非空白字符才认为是第一个数据的开始。
- 缓冲区中间:开始读取第一个数据后,一旦遇到空白字符(非换行符), 就认为读取完毕一次。遇到的空白字符残留在缓冲区,直到下一次被读取或刷新。例如输入字符串
this is test
,则会被认为是3
个字符串。 - 缓冲区末尾:按下回车键时,换行符
\n
残留在缓冲区。换行符之前的空格可以认为是中间的空白字符,处理同上。
注意,格式控制符只会读取正确类型的变量。如果输入格式不正确,比如在%d
处输入了一个字符a
,则会使读取中断,即后续不读取任何变量。
格式控制符说明:
类型 | 类型输入 | 参数的类型 |
---|---|---|
%d | 十进制整数 | int * |
%u | 无符号十进制整数 | unsigned int * |
%o | 八进制整数 | int * |
%x | 十六进制整数 | int * |
%f、%e、%g | 浮点数 | float * |
%lf、%le、%lg | 双精度浮点数 | double * |
%c | 单个字符(含空白字符) | char * |
%s | 字符串 | char * |
%% | 读 % 符号 |
注意,%c
是一个比较特殊的格式符号,它将会读取所有空白字符,包括缓冲区开头的空格、Tab、换行符,使用时要特别注意。
scanf()
的读取也没有边界,所以并不安全。C11 标准提供了安全输入scanf_s()
。
scanf()
对应的输出函数是printf()
。
gets() - 不建议
按下回车键时,从stdin
读取一行。
用法示例:
char str[100];
gets(str);
对空白字符的处理:
- 所有空格、Tab等空白字符均被读取,不忽略。
- 按下回车键时,缓冲区末尾的换行符被丢弃,字符串末尾没有换行符
\n
,缓冲区也没有残留的换行符\n
。
注意,gets()
不能指定读取上限,因此容易发生数组边界溢出,造成内存不安全。C11 使用了gets_s()
代替gets()
,但有时编译器未必支持,因此总体来说不建议使用gets()
函数来读取输入。
gets()
对应的输出函数是puts()
。
fgets()
从指定输入流读取一行,输入可以是stdin
,也可以是文件流,使用时需要显式指定。
读取文件流示例:
char str[100];
memset(str, 0, sizeof(str));
int i = 1;
FILE *fp = fopen("...test.txt", "r");
if (fp == NULL) {
printf("File open Error!\n");
exit(1);
}
while (fgets(str, sizeof(str), fp) != NULL)
printf("line%d [len %d]: %s", i++, strlen(str), str);
fclose(fp);
读取stdin示例:
char str[100];
memset(str, 0, sizeof(str));
int i = 1;
while (fgets(str, sizeof(str), stdin) != NULL)
printf("line%d [len %d]: %s", i++, strlen(str), str);
对空白字符的处理:
- 所有空格、Tab等空白字符均被读取,不忽略。
- 按下回车键时,缓冲区末尾的换行符也被读取,字符串末尾将有一个换行符
\n
。例如,输入字符串hello
,再按下回车,则读到的字符串长度为6
。
fgets()
函数会自动在字符串末尾加上\0
结束符。
第 2 个参数n
指定了读取的最大长度。函数读到n-1
个字符(包括换行符\n
)就会停止,并在末尾加上\0
结束符。剩余字符将残留在缓冲区。
建议使用fgets()
完全替代gets()
。
fgets()
对应的输出函数是fputs()
。
fgetc() & getc()
从指定输入流读取一个字符,输入可以是stdin
,也可以是文件流,使用时需要显式指定。
这两个函数完全等效,getc()
由fgetc()
宏定义而来。不同的是,前述的gets()
和fgets()
相互之间没有关系。
用法示例:
char a, b;
a = fgetc(stdin);
b = getc(stdin);
对空白字符的处理:
- 所有空格、Tab、换行等空白字符,无论在缓冲区开头、中间还是结尾,均会被读取,不忽略。
- 因为只读取一个字符,所以如果输入多于
1
个字符(包括换行符),则它们均会残留在缓冲区。具体地说,如果什么字符都不输入,直接按下回车键,则读取到的是换行符\n
,缓冲区无任何残留;如果输入一个字符如a
,然后按下回车键,则读取到的是字符a
,同时换行符\n
残留在缓冲区。
fgetc()
和getc()
对应的输出函数是fputc()
和putc()
。
getchar()
从stdin
读取一个字符。
getchar()
实际上也由fgetc()
宏定义而来,只是默认输入流为stdin
。
用法示例:
char a;
a = getchar();
getchar()
常常用于清理缓冲区开头残留的换行符。当知道缓冲区开头有\n
残留时,可以调用getchar()
但不赋值给任何变量,即可实现冲刷掉\n
的效果。
getchar()
对应的输出函数是putchar()
。
C++ 标准输入
C++中使用标准输入输出需要包含头文件<iostream>
。一般使用io