目录
缓冲区
首先,让我们粗略的了解一下缓冲区的概念。
缓冲区就是在内存空间中预留的一定的存储空间,这部分空间用来缓冲数据,也就是当输入或输出数据时(本章主要介绍输入),它会暂时不传过去,满足一定的条件再传。
这样做的目的是提高计算机的运行速度,因为如果输入一个数据就让CPU处理,可能会打断CPU正在做的事情,使效率减慢。而缓冲区是在的数据取完后再一起到CPU中处理,这样就大大的提高了效率。
缓冲区分为三类:全缓冲、行缓冲和不带缓冲。
这三类与输入函数有关的是行缓存,因此本章仅介绍行缓存。
行缓存:我们输入的字符先存放在缓冲区,等按下回车键换行('\n')时才进行实际后续操作。
粗略了解这些后,我们开始对一些输入函数进行讲解。
scanf函数
首先就是我们最熟悉的scanf函数。
这是cplusplus中的定义
从stdin(这是一个输入流,本文不主要讲解)中读取格式化的数据(大致理解为从键盘读取数据就可以了)。
从图中可以看出键盘的缓冲区就是行缓冲的类型。
当然,scanf有着自己的规则,基础用法(粗略)如下:
int a = 0;
scanf("%d", &a);
其中“%d”是指想要格式化输出的形式,因为我们在键盘上输入的其实是一串字符。
这是指输入了‘1’、‘2’、‘3’、‘4’这四个字符,而我想要得到的是整数,于是“%d”格式化输出就将其转换为了整形也就是整数1234,存储在a所在的内存中,&a指的就是将获得的数据存入相应地址的内存处。
对于scanf的返回值,我们从下面的测试来看。
输入数据
得到
可见,scanf的返回值就是它读取的数据个数。(后面还有相关介绍,需要用getchar(),所以先介绍下getchar)
getchar函数
这是cplusplus中的定义
getchar的用法很简单,依旧是从键盘的缓存区读取数据。
getchar();
至于getchar的返回值,我们依旧来测试一遍。
输入任意字符
得到
可见,getchar得到字符,返回该字符。这样我们可以用 getchar来查看缓存区还剩下什么。
注意:getchar的返回值为int类型,这里我们用char类型接收是为了方便看出输出的字符。而真正getchar返回的是读取字符的ASCII值。
如:
输入字符1
得到49
而字符1的ASCII值正好就是49。(getchar返回值为正好也是它)(至于getcahr的返回值为什么为整形,可能与它遇到文件结尾会返回EOF,也就是-1有关,感兴趣的可以自己去了解)
那么了解了getchar之后,再让我们回到scanf函数。
当我们使用scanf函数时,会发现,scanf函数一次只会读取一个数据。
下面,我们输入 字符‘1’、‘2’、‘3’
可以看到,我们最终只读取了第一个字符‘1’
另外,【1】scanf函数从缓冲区以字符的格式读的时候,输入的都可以读(包括空格(‘ ’)和‘\n’),【2】而当scanf以其他格式读取时,会自动跳过空格和换行符。
以整形的格式读取,输入换行和空格
我们可以发现,程序一直没有往下进行,意思是scanf并没有读取到有效数据。 由此可知【2】
而以字符的格式读取,输入换行和空格
我输入了空格和换行
直接看不出来,所以,我们转到监视去看a数组中存储的是否有空格和换行。
可以明显的看到a数组的前两个元素确实被改成了空格和换行,可得【1】
因此,我们在连续输入整形等数据时,就可以在数据和数据之间加上空格,起到将字符串分割的作用,由此一次输入多组数据。
这样,scanf的读取规则我们大致就了解了。
输入不确定数据
但是,如果我们需要的是输入一串不确定的数据又该怎么办呢?
int main()
{
int arr[100] = { 0 };
int i = 0;
do
{
scanf("%d", &arr[i]);
i++;
} while (1);
return 0;
}
假如我们用这种类似的循环结构,根据之前对scanf的了解,我们可以发现,这种代码会一直让你输入,因为'\n'不是停止的条件,那它也就不会停了。
因此我们必须要加上一个停止的条件在while的判断中,这里我采用的是getchar,因为虽然scanf会跳过'\n'但是getchar不会。
#include<stdio.h>
int main()
{
int arr[100] = { 0 };
int i = 0;
do
{
scanf("%d", &arr[i]);
i++;
} while (getchar()!='\n');
return 0;
}
这样一来,我们就可以输入一行任意个数的数据了,当然有的题目会要求输入几行,那么可以记住输入了几行,一次进行判断结束。(这样输入的话,必须要注意,最后一个数据的后面必须是‘\n’,不然getchar会读取其他字符而不会停止)
#include<stdio.h>
int main()
{
int arr[100] = { 0 };
int i = 0;
int j = 3;
do
{
scanf("%d", &arr[i]);
i++;
if (getchar() == '\n')
{
j--;
}
} while (j);
return 0;
}
这样就可以做到输入三行任意的数据了。
我们都知道一个题目说反话,就是每个测试用例的输出占一行,输出倒序后的句子。
输入
hello world
输出
world hello
这个题有一种解法为先分割字符串,再将串打印。
那么,scanf和getchar组合能不能解决这个问题呢?
答案是肯定的
int main()
{
char arr[10][10] = { 0 };//创建二维数组
int k = 0;//设置变量,数组内容一次向后移
do {
scanf("%s", arr[k++]);//接收字符串,一个字符串存到一个数组中
} while (getchar() != '\n');//判断输入是否结束
while (k--) {
printf("%s", arr[k]);//倒着依次打印字符串
if (k != 0) {
printf(" ");//最后一个不打印空格
}
}
return 0;
}
上面是创建二维数组接收一个个字符串,利用了scanf在以非字符的格式读取时,不读取' '和'\n'。(注意:输入的最后一个数据后面只能是'\n',不然程序不能正常停止)(同时这种写法也有很大的弊端,那就是当我们想要连续输入两个空格时,程序不会读取,而是在打印时在两者之间增加一个空格,同时对于是因为想要输入一个空格但输入了两个空格的情况下和首位不小心输入了空格而言,这又是优势。当然,这段代码还可以改进,以适应各种特殊情况,这就靠大家的想象了,本文章就不过多扩展了)
同时我们通过赋arr[0][4] = '#';arr[0][5] = '#';调试可发现
arr[0][4] = '#'变成了 arr[0][4] = '\0';也就是说,scanf函数接收字符串时,也会像gets(后面有讲解)那样在字符串末尾加上字符串结束标志'\0'。
当我们输入10个数据时,scanf会不会像gets函数那样报错呢?
我们让 arr[1][0] = '#';arr[1][1] = '#';,方便观察
调出监视
可以看到因为是二位数组,即使arr[0]内存不够了,直接将最后的'\0'赋给arr[1][0],因此在用二维数组依次存字符串的时候,一定要开辟足够大的空间,不然会出问题,而且因为这不会报错,也很难找到错误。
那么这还是把'\0'放进去了,我们试一下一维数组满了会怎样
结果超出了我的预料
跟gets函数不一样的是,scanf函数将数据全都放进去了,程序也没有报错,这依旧提醒了我们,用数组存储字符串时一定要开辟足够大的空间。
gets函数
接下来是gets函数,一次读取一行数据。
这是cplusplus中的定义
可以看出,gets函数是从键盘获取一行字符串,正常进行的话会返回此字符串的地址 ,char * str是你想存储的地址,一般是数组。
int main()
{
char arr[100] = { 0 };
gets(arr);
return 0;
}
这样就将一串数据传了过去
查看监视
可以看到字符都传过去了,不仅如此,下面我们将arr[9]和arr[10]赋值为#,再次调监视。
我们可以看到arr[9]变成了'\0',也就是说gets函数获取字符时,自动在最后一位加上'\0',而scanf则没有这种行为。(由此也就让gets函数返回的地址在使用时,是一个标准的(字符结尾是'\0')字符串地址)
另外,结尾加'\0'的特性也就导致了放入数据时的内存问题。
int main()
{
char arr[3] = { 0 };
char*arr1=gets(arr);
return 0;
}
该数组可存三个字符,如果我输入三个字符会怎样呢?它的'\0'会怎样处理呢?
我们可以看到,程序直接报错了,因为数组越界了,也就是说,gets函数即使是数组满了,依然把'\0'加了上去,导致非法访问内存了,这函数还真倔。(另外,gets函数遇到文件结尾会返回NULL,一样,感兴趣的自己了解,本文章不讲这些)
附加一些知识,gets读取了一串字符,从上面我们可以看到它并没有把'\n'读取到数组中,那么'\n'就依然留在缓冲区喽?很显然我这么问就是没有了,gets函数在读取完了之后,把'\n',“扔了”,也可以想成拿了它,知道该结束了,它也没啥用了,就扔了,总而言之,它就是不在了。
可以看到gets函数并没有读取'\n'
当程序再往下进行时,需要我再次输入字符,说明缓存区的'\n'已经没了。
最后就剩下几个加f的函数了
fgetc函数和getc函数
这两个跟getchar很像,只不过getc和fgetc函数都是从stream(流)中获取数据,也就是说getc和fgetc获取数据的方式更多(如从文件中获取数据),不仅仅是从键盘获取数据。而getc和fgetc、getchar又有些小区别,前者是宏,后面两者是函数,getc一般情况下更快,但可能会有些宏具有的副作用。除了这些其他的基本都一样。
以下是这两个函数从键盘上获取数据的方法(从stdin流中获取)
int main()
{
getc(stdin);
fgetc(stdin);
return 0;
}
fscanf函数
去对照scanf函数就可以发现,他们俩不一样在fscanf多了一个参数FILE * stream,看了这么多,你应该也知道了,fscanf也是获取数据的方式更多(如从文件中获取数据)。而FILE * stream就是数据获取的来源。
以下是这该函数从键盘上获取数据(读)的方法(从stdin流中获取)
int main()
{
int m = 0;
fscanf(stdin, "%d", &m);
return 0;
}
最后就是fgets了fgets不仅仅与gets有流的范围的区别,他还能读取'\n'。
int main()
{
char arr[10] = { 0 };
fgets(arr,40,stdin);
return 0;
}
可以明显的看到,gets函数将'\n'也拿了进去。
二进制的fread与文件的关联性强,本文章就不具体介绍了。
以上就是本人目前了解的输入函数。