C++中用于输入和输出的函数很多,不同的输入输出函数之间功能上的细微区别有时会让初学者摸不着头脑,本文主要是对这些输入输出函数的小结,同时也是防止自己忘记.首先我们先明确缓冲区的概念.
一. 缓冲区
1. 什么是缓冲区
缓冲区(缓存)是内存中预留的一部分存储空间,这些空间用来缓存输入或者输出的数据.根据其对应的输入还是输出设备,可以分为输入缓冲区和输出缓冲区.
2. 为什么要引入缓冲区
设置缓冲区的目的是提高计算机I/O速度,例如计算机从磁盘里读取信息,我们可以先把读出的数据放在缓冲区,再直接从缓冲区读取数据.这种做法大大减少了磁盘的读写次数,且计算机对缓冲区(内存)的操作速度远高于对磁盘的操作,因此I/O速度也得到了极大的提高;又例如打印机打印文档,因为打印机打印的速度较慢,因此可以先将文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这样CPU的工作就不会被频繁的I/O中断所打断了.
总的来说,缓冲区就是输入输出设备与CPU之间用来缓存数据的一款内存区域,它使得低速的输入输出设备与高速的CPU能够协调工作,从而整体提高计算机的工作效率.
3. 缓冲区的类型
缓冲区分为三种类型:全缓冲 行缓冲 不带缓冲
(1) 全缓冲
全缓冲的典型代表是对磁盘的读写,在这种情况下,当填满标准I/O缓存后才进行实际I/O操作.
(2) 行缓冲
行缓冲的典型代表是标准输入(stdin)和标准输出(stdout),也就是本文的主题.在这种情况下,我们先把输入或输出的字符存放在缓 冲区,当输入输出遇到换行符时才进行实际I\O操作.
(3)不带缓冲
也就是不进行缓冲,典型代表是标准出错情况stderr,这是为了让出错信息可以尽快地显示出来.
4. 缓冲区的大小
如果我们没有自己设置缓冲区的话,系统会默认为标准输入输出设置一个缓冲区,这个缓冲区的大小通常是8192Bytes,缓冲区大小由头文件stdio.h中的宏BUFSIZ定义,如果希望查看它的大小,可直接输入
printf("%d", BUFSIZ);
5. 缓冲区的刷新
缓冲区在以下集中情况会刷新:
(1) 缓冲区满时; (2) 行缓冲区遇到回车时; (3) 关闭文件时; (4) 使用特定函数刷新缓冲区时
二. C++ 常用输入函数
接下来切入正题,在缓冲区概念的基础上对C++中常见的输入函数进行总结:
1.scanf()
scanf()函数是我们在ACM竞赛中使用的最多的输入函数,因为它效率远高于cin且可以输入多种类型的数据.值得注意的是,除%c 之外,scanf()对其他格式符(如%d)的输入是以空白符(空格、回车)为结束的判断标志的.也就是说除非使用%c将空格或换行按字符读入,其他情况都会自动跳过空格(这也说明当使用%s输入字符串时,无法输入空字符串"\0").如下例:
#include<cstdio>
int main() {
char str1[10], str2[10];
char c, d;
scanf("%s", str1);
scanf("%c", &c);
scanf("%s", str2);
scanf("%c", &d);
printf("%s\n%s\n", str1, str2);
printf("%d\n%d\n", c, d);
return 0;
}
输入:
abcd[空格][空格]efgh[换行]
输出:
abcd
efgh
32
我们会发现虽然输入使用了四个scanf()函数,但只输入了一行便输出了结果.这是因为当我们按下回车键时实际I\O开始,str1接收字符串"abcd",并在遇到遇到第一个空格时判断输入结束.此时缓冲区内还剩下: [空格][空格]efgh[换行];
然后第二个scanf()由于使用的是%c读入字符,因此它可以读入空格字符,故字符变量c为空格字符(ASCII码为32),此时缓冲区内还剩下: [空格]efgh[换行];
第三个scanf()使用%s读入字符串,遇到第一个字符是空格时直接跳过,然后字符串str2接收字符串"efgh",再遇到换行符时判断输入结束,此时缓冲区内还剩下: [换行];
最后一个scanf()同样使用%c读入字符,因此它可以读入换行符,故字符变量d为换行符(ASCII码为10),至此缓冲区为空,I\O结束.
例子2:
#include<cstdio>
int main() {
int hh, mm, ss;
scanf("%d:%d:%d", &hh, &mm, &ss);
printf("%d\n%d\n%d", hh, mm, ss);
return 0;
}
输入:
11:12:13
输出:
11
12
13
这个例子告诉我们scanf()的双引号内的内容其实就是整个输入,只不过把数据换成它们对应的格式符并把变量的地址按次序写在后面而已.
最后值得注意的是,scanf()函数是一个有返回值的函数,函数原型为:
int scanf(const char* restrict format, ...);
可见scanf()的返回值为一个int整型,当成功读入数据时,scanf()函数返回成功读入数据的项数;当读入数据遇到文件结束时返回EOF,因此在ACM竞赛中如果我们遇到多点测试的情况时可以使用:
while(scanf("%d", &a) != EOF) {
//所要进行的操作
}
2.cin
cin是C++标准输入流,需要包含头文件<iostream>及命名空间using namespace std;才能使用.使用cin的好处在于它不需要像scanf()那样指定输入格式,坏处在于它在输入大量数据时表现非常糟糕,因此在ACM竞赛中少用cin以免造成超时.
#include<iostream>
using namespace std;
int main() {
char str1[10], str2[10], c;
cin >> str1 >> c >> str2;
cout << str1 << endl << c << endl << str2;
return 0;
}
输入:
abcd[空格][空格]efgh[换行]
输出:
abcd
e
fgh
由这个例子我们可以发现,cin也是以空格和换行作为结束输入的标志.除了效率问题之外,cin和scanf()的最大不同在于使用cin输入字符变量时也不能也不能接收空格和换行.
3.getchar()
getchar()函数同样来源于头文件<stdio.h>,它被用来输入单个字符.由于它能接收空格字符和换行符,因此也常常被用作吸收缓冲区内多余空格和换行符.例如:
#include<cstdio>
int main() {
char a, b;
scanf("%c", &a);
getchar();
scanf("%c", &b);
printf("%c\n%c", a, b);
return 0;
}
输入:
a[空格]b[换行]
输出:
a
b
如果将上述代码中的getchar()注释掉,那么字符变量b就将会是空格符了.
4.gets() cin.getline()
这两个函数一个来自<stdio.h>一个来自<iostream>,但作用几乎完全相同,都是用于直接输入一整行字符,因此这两个都仅以换行符作为输入结束的标志(这导致了使用这两个函数输入的字符串可以包含空格字符,而前面讲到的scanf()和cin都不行).还有一点需要注意的是,gets()和cin.getline()均能接收空字符串,因此在使用这两个函数之前必须先用getchar()将前面换行符接收掉.最后,这两个函数都会直接清空行缓冲,不会留下换行符.下面我们以gets()为例:
#include<cstdio>
int main() {
char str1[10], str2[10], str3[10];
int a;
scanf("%d", &a);
//getchar();
gets(str1);
gets(str2);
gets(str3);
puts(str1);
puts(str2);
puts(str3);
return 0;
}
输入:
1[换行]
abcd[换行]
efgh[换行]
输出:
[空字符串]
abcd
efgh
这个例子我们可以看到,第一次按下换行时执行第一次I\O,在scanf()接收了数字1后,缓冲区还剩下: [换行];
然后第一个执行第一个gets()函数,str1接收这个换行符变为空字符串"\0",然后刷新行缓冲区;
然后第二次按下换行后执行第二次I\O,第二个gets()函数接收这一整行字符数据并清空行缓冲区,因此str2为字符串"abcd";
由于上一个gets()函数没有留下换行符,因此需要进行第三次输入,str3为字符串"efgh",gets()函数再次清空行缓冲区后结束.
另外如果读入字符串,在多点测试时也可以使用如下方法:
while(gets(str) != NULL) {
//要执行的操作
}
5.sscanf()和sprintf()
sscanf()和ssprinf()函数是处理字符串问题的利器,它们均来自头文件<stdio.h>,我们可以通过scanf()和printf()函数来对它们进行类比,例如:
scanf("%d", &n);
printf("%d", n);
这两个函数其实相当于:
scanf(screen, "%d", &n);
printf(screen, "%d", n);
即将屏幕上的字符串以"%d"的格式输入到n中和将n以"%d"的格式写入到屏幕的字符串中.sscanf()与sprintf()其实就是将上述的screen换成了字符数组而已,例如:
char str1[10] = "12345";
char str2[10];
int n;
sscanf(str1, "%d", &n);
sprintf(str2, "%d", n);
则运行之后str2也会变为"12345" .由此可见,sscanf()和sprintf()可以帮助我们省去许多字符串与其他数据类型相互转换的麻烦,且它们也可以像scanf()和printf()一样进行复杂的格式输入输出,例如:
#include<cstdio>
int main() {
int year, month, day;
double db;
char str1[100] = "2019.2.21,hello!", str2[100];
sscanf(str1, "%d.%d.%d,%s", &year, &month, &day, str2);
printf("year = %d\nmonth = %d\nday = %d\nstr2 = %s", year, month, day, str2);
return 0;
}
输出:
year = 2019
month = 2
day = 21
str2 = hello!
最后我们需要指出,sscanf()还支持正则表达式,配合正则表达式来进行字符串的处理,那么很多字符串问题将迎刃而解,关于这个问题我们不再本文进行讨论,以后专门再写另一篇文章专门进行讨论.