今天偶然间碰到这么一个程序,小样,还挺有意思,废话不多说了,直接看吧!
代码1:
#include <stdio.h> #include <stdlib.h> #include <conio.h> int main(){ struct node{ char name[10]; int score; }; struct node student; FILE *fptr; if((fptr = fopen("student.txt", "wb")) == NULL){ printf("无法打开student.txt"); exit(1); } do{ printf("\n\nEnter name:"); gets(student.name); printf("Enter score:"); scanf("%d", &student.score); fwrite(&student, sizeof(student), 1, fptr); printf("One more(Y/n)?"); }while(getche() == 'y'); fclose(fptr); getch(); }
恩恩,大家可以先过一遍,然后自己敲一下代码,(哈哈,不错,全出现了,gets,fwrite,getche,scanf),
问题::结果有点诡异
恩恩,大家可以看到了,第二次name还没有来得及输入就直接跳到了score,啊,啊,为什么,别急啊!
代码2:
#include <stdio.h> #include <stdlib.h> #include <conio.h> int main(){ struct node{ char name[10]; int score; }; struct node student; FILE *fptr; char scorebuf[81]; if((fptr = fopen("student.txt", "wb")) == NULL){ printf("无法打开student.txt"); exit(1); } do{ printf("\n\nEnter name:"); gets(student.name); printf("Enter score:"); student.score = atoi(gets(scorebuf)); fwrite(&student, sizeof(student), 1, fptr); printf("One more(Y/n)?"); }while(getche() == 'y'); fclose(fptr); return 0; }
好:继续看:
对,这回好了,但是,这是为什么呢?
当时刚看到这个程序,想不通,然后疑问还比较大,问了韩飞同学,他说:光是上面前面那几句,一个gets函数处理换行符,scanf不处理换行符,你明白吗?
哈哈, 谢谢他,给我指出了重要的一点,想着这些东西虽然小,但是还比较重要,就想着写下来,跟大家讨论讨论
我们来看一看:
1.函数gets(字符指针)
头文件 stdio.h (c) cstdio(c++ )
原型:char *gets(char*)
读入成功,返回与参数相同的指针,读入过程中遇到EOF或发生错误,返回NULL指针。
但是,读取的结果是存放在参数指针所指向的那个数组里,也称buffer数组,但是在写入的时候,应该保证数组空间足够大,一边读写的时候不发生溢出。(如果溢出,多出来的字符将被写到堆栈中,会间接的覆盖堆栈原来的内容,破坏了其他的空间的值),假如空间足够大,那么函数会无限读取,相当于无限缓存,直至接受到换行符或EOF或发生错误
换行符不作为读取数组的内容,读取的换行符被转化成NULL值,并由此来结束字符串,(也就是说gets这个函数对这个换行符已经进行过处理了)
2.函数scanf
同printf函数一样,都被定义在stdio.h中,因此使用之前要先加上#include,格式输入函数
原型:int scanf(const char*format[,argument]);
scanf是有返回值的,有返回值的!!!!,它的返回值表示读入数据的个数 ,如scanf("%d %d", &a,&b);若成功读入了a,b则返回2,,若只成功的读入了a但是错误的读入b,(再输入的时候给a输入整数,给b输入字符),返回值是1,但是反过来(若让a为字符,b为整数),则表示当前读入已经错误,表示,都读入失败则返回0,
同gets一样都是获取字符串用的,但是,但是,scanf对于输入的字符串是有要求的,当输入时,输入的字符中若出现空格, tab键, 回车键,即表示当前的输入结束
对于字符数组的话,scanf处理换行符的话,他是不将换行符加入到数组中去的,也不会将换行符转化成NULL,而是对这个换行符不做任何操作,也就是说他是仍然留在缓冲区,等待其他函数来提取
printf函数:
int printf(const char*format,....);
printf函数的返回值是打印的字符的个数(就是将%d,%s替换之后的字符上面,如果存在\n的话,也会算作一个待打印的字符的)
#include <stdio.h> int main() { int a = 1, b = 200; int x,y; x = printf("%d\n",a); printf("%d", x); return 0; }
y = printf("%d\n",b);这里的y也就是,替换后的待打印字符,即就是:“200\n”,所以也就是4
z = printf("%d",b);这里的z同样是:“200”,所以是3
代码3:
#include <stdio.h> int main(){ char a[50],s1[62]; scanf("%s", a); //输入 I love you scanf("%s", s1); //输出 I love printf("%s",a); printf("%s", s1); return 0; }
这时我们来看下,在编辑面输入 I love you时,按下回车,程序结果直接输出:
I love
运行结果如下:
代码4:
#include <stdio.h> int main(){ char a[50],s1[62]; gets(a); gets(s1); printf("%s\n",a); printf("%s", s1); return 0; }
I love you
hello world
运行结果如下:
分析:在程序接收gets时,一般情况下(内存空间足够,读正确)会在按下回车键后,停止接收
故代码4,会等待,知道用户按下两次回车,这时才会进行输出
而代码3,在用户输入I love you 并且按下回车之后,相当于里面已经实现了3次的scanf(两次的回车,一次回车)
哈哈,原来这样啊
3.getchar()
C中的函数,从stdio流中读字符,相当于getc(stdin),他是从标准输入里读取下一个字符
(这里stdio是标准输入输出,stdin指的是从标准输入里读取下一个字符)
头文件 stdio.h
原型 int getchar(void);
当程序调用getchar时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区(个人理解可以抽象的当做标准输入流,后都是从其中读取下一个字符的),直到用户按回车为止(回车字符也放在缓冲区),当用户按回车之后,getchar才开始从stdio流中每次读入一个字符,getchar函数返回值是用户输入的第一个字符的AscII码,如出错则返回-1,且将用户输入的字符回显在屏幕上,如用户在按回车之前输入了不止一个字符,其他字符保留在键盘缓冲区,等待后续getchar调用读取,也就是说,后续的getchar调用不会等待用户按键,而是直接读取缓冲区的字符,知道读完缓冲区的字符,才会等待用户按键
代码5:
#include <stdio.h> #include <iostream> using namespace std; int main(){ char c,a,b; c = getchar(); b = getchar(); a = getchar(); cout<<c<<endl<<b<<endl<<a<<endl; return 0; }
同样的,我们来看下程序的运行,
在程序的输入窗口中,我们输入:a bc d回车(中间是空格键),即输入回车之后,程序直接输出:
a
b
接下来大家看看运行结果:
分析:
对于getchar,还是上面提到的,一个getchar,当输入多个字符时,其他字符保留在键盘缓冲区,等待后续getchar调用,(呵呵,至于当前程序结束之前,缓冲区里还有好多的字符的话,等程序结束之后这些字符也会被系统或者编译器或者其他东西清理掉,绝不会留着,等其他程序中的getchar来继续取的)可以很直观的看到,我们的程序在输入了一次回车之后,后面就没有了输入,这也是因为当前键盘缓冲区中的字符,足够了后面getchar的需求,所以不用了啊,
4.getch()
头文件: #include <conio.h>
作用:gets函数是在windows平台下从控制台无回显的取一个字符,在linux下是有回显得
原函数:int getch(void);
对于这个getch函数,他是从控制台无回显的取一个字符,但是要注意它与getchar的区别,getchar有他自己的缓冲区,而getch没有,并且getchar是从键盘缓冲区中取字符的,而getch并不是这样的,他是从控制台取一个字符,(getch取字符,并不会影响getchar中的缓冲区,两者实实在在的么有关系,对于取字符)
代码6:
#include <iostream> #include <stdio.h> #include <conio.h> using namespace std; int main(){ char r,b,c,d; r = getchar(); b = getchar(); c = getch(); d = getchar(); cout<<r<<endl<<b<<endl<<c<<endl<<d<<endl; return 0; }恩恩,接下来继续我们的探索之旅,哈哈,
还是一样:在输出界面中,我们输入:
123456
d
它的输出是:
1
2
d
3
运行结果:
哈哈,大家有点懵吗?么关系,我第一次,也是这样,一时间也不理解,不过后来也就好了,自己理解还是可以的,如果,还有不懂的话,可以在返回上面去看看getchar,getch,应该就好理解了,
分析:
根据程序内容,第一次执行getchar时,我们输入了123456,然后按下了回车输入d,然后结果直接输出,因为第一个getchar读取了缓冲区的1,第二个getchar读取了缓冲区的2,然后执行到了getch,它与getchar读取的位置不影响,故不影响getchar缓冲区中现有字符,他只是让程序运行暂时停止而已,等待从控制台获取一个任意字符而已并且不回显。然后依次输出。
5.getche()
getche的话,跟getch基本差不多,完全是一个回显,一个不回显的问题
恩恩,好了,现在解决最开始的问题:首先gets获取字符串,同时也对了回车键进行了处理,下面继续执行scanf,这个对回车键并没有进行处理,所有回车键目前还在缓冲区中,printf时直接打印东西,并不对缓冲区进行影响,接下来就到了getche,由于是回显的,所以打印出了字符,进入循环,再次gets获取字符串,还有回车键在缓冲区,gets直接获取,故看到的效果是么有输出student.name这个过程
第二个:恩恩,下面这个程序还是可以解决上面的问题,不一样的是atoi(gets(scorebuf)),先获取字符串,然后通过函数转化,将其转化成相应的整数,即这样就可以避免了那个回车键,也就直接的避免了二次循环的student.name的跳过,,,,
恩恩,上面内容,如有问题或错误,欢迎指出
6.printf()
关于printf个人么有太多要说的,就是关于
printf的行缓冲函数,先写到缓冲区,满足条件后,才将缓冲区刷到对应文件中,
自己对于这句话不是很理解,
在linux下,确实是这样(刷新缓冲)
1,缓冲区填满
2,写入的字符中有‘\n’, '\r', '\t'
3,调用fflush手动刷新缓冲区
4,调用scanf要从缓冲区中读取数据时,也会刷新
但是,但是,但是,还是不太明白在windows中,printf的缓冲机制,如下:
#include <iostream> using namespace std; int main(){ printf("nihao"); return 0; }实在不明白,在这个程序中,完全没有刷新缓冲的条件存在,那个printf中的东西为什么会打印啊,,,
求教啊,热烈欢迎大家来讨论,,,