转自:
虽然说C++ Primer Plus的前7章差不多都是重述C语言的知识,但这并不代表你懂得点C语言就可以忽略这些内容,直接从OOP开始学!和输入和输出有关的缓冲区操作便是一个非常重要但又特别容易被我们这些C++菜鸟所忽视的内容,我在写C++ Primer Plus编程练习时出现的绝大多数bug都和它有关。
对于我这样从BASIC学到VB再学到C的人来说,想弄明白缓冲区是个什么东西还真不是一时半会的事,因为在BASIC和C中根本没有缓冲区的概念。好,现在先看一个小程序。
- #include<iostream>
- using namespace std ;
- int main()
- {
- char ch ;
- cin.get(ch);
- while (ch!='/n')
- {
- cout<<ch ;
- cin.get(ch);
- }
- return 0;
- }
在运行这个程序时,如果你输入123456789,回车后他将回显123456789。一开始我对此很费解,因为这种运行结果好像是输入一个字符串接着再输出它,但事实上并没用到字符串,这里便牵涉到了流的概念:传统的程序是输入设备给一个信息(这里理解为从键盘输入一个字符),程序就接收一个,而C++颠覆了这个概念,它使用了流和缓冲区。打个比方,程序是一个脸盆(囧~~~)用来接水的,水在这里比喻数据,传统的程序是直接往脸盆里倒水。而C++程序则在脸盆上加一个漏斗,漏斗上还有个塞子,水不是直接进脸盆而是先进漏斗,而且在进水时塞子是塞住的,当数据输入完毕时(回车时),塞子打开,数据才开始进入程序。从这个程序看,当运行到第一个cin.get时,因为缓冲区(漏斗)是空的,程序便停下来等待数据,输入一串字符并回车,注意这些字符没进入程序而是进入了缓冲区。然而第一个cin.get 只接收缓冲区前端的第一个字符,进入while循环后一切就变得很神奇。while循环中也有cin.get ,不过程序在这里却不会停了,因为缓冲区在这时候有数据,cin.get 便再从缓冲区读一个字符,这使得while循环在一瞬间就运行完成了,造成好像是在处理字符串的错觉。当然,这个程序演示的是把漏斗的水连续放空的情况,当然你也可以把塞子中途塞住。再看下面一个程序,这个程序更有助于理解缓冲区的工作原理:
- #include<iostream>
- using namespace std ;
- int main ()
- {
- char ch ;
- cin.get (ch);
- while (ch!='5')
- {
- cout<<ch ;
- cin.get(ch);
- }
- cout<<endl<<"下面还有哦"<<endl;
- while(ch!='/n')
- {
- cout<<ch;
- cin.get(ch);
- }
- return 0;
- }
程序设定在输入123456789后回显时接收到5的时候停止从缓冲区接收数据,输出一行中文字再接着回显,相当于在接收到5的时候把塞子塞住,打印一行字后再接着放。注意在中文字之前这个程序只回显1234,5在中文字后出现,各位可以想想为什么。
当然,缓冲区里有时候也会出现垃圾,设想一下下面一种情况。
- #include<iostream>
- using namespace std;
- int main()
- {
- char a[100];
- cin.getline(a,100,'@');
- cout<<a<<endl;
- char b[100];
- cin.getline(b,100,'@');
- cout<<b<<endl;
- return 0;
- }
这个程序先输入一个字符串,以@作结束符,回显这个字符串,再反复一次。这样运行程序显然没有问题:
I'm a C++ beginner.@
I'm a C++ beginner.
You're a VB beginner.@
You're a VB beginner.
可是这并不能说明程序就没有问题,一个完美程序不仅仅要在正确输入时有正确结果,更要在错误输入时不至于崩溃。这就是一个程序的坚固性,个人作为一个初学者认为初学者学习编程不能盲目图快图新,即便是再简单的程序也要具有优秀程序的完美特性。下面想象一下有些人天生比较猥琐故意想找你程序的茬,他可能会这么运行:
I'm a professional C++ coder.@HA HA HA!
I'm a professional C++ coder.
You're a C++ noob.@
HA HA HA!
You're a C++ noob.
这下子出问题了,第一个字符串中@后的内容明明不要了,却怎么还在第二个字符串回显时出现了,给人感觉极其诡异!实际上原因还是出在缓冲区上面,当getline方法扫描到@这个结束符时输入便结束了,剩下的内容(注意还包括你敲的那个回车)便留在了缓冲区中,第二个getline方法再执行时便先把刚才留在缓冲区中的那个HAHAHA一股脑放进了字符串b中,由于最后一个字符是回车不是@,所以输入并没有结束而缓冲区里却没东西了,于是程序出现了光标等待输入,给人一种从零开始的假象,实际上字符串b这时候已经有数据了,最后出现那种结果也不足为奇了。
想解决这个问题只有在输入字符串b之前把缓冲区清空,这里有一种比较容易想到的方式:
- #include<iostream>
- using namespace std;
- int main()
- {
- char a[100];
- cin.getline(a,100,'@');
- cout<<a;
- char waste;
- cin.get(waste);
- while(waste!='/n')
- cin.get(waste);
- char b[100];
- cin.getline(b,100,'@');
- cout<<b;
- return 0;
- }
这种方法就是把缓冲区里面不要的数据挨个扔掉,直到检测到回车符为止。可这种方法第一局限性很大,只能适应这个程序,第二就是时间复杂度高,遇到要抛弃大量数据的情况时耗时巨大。有没有一股脑把这些没用的数据倒掉的方法呢?有!请看下面:
- #include<iostream>
- using namespace std;
- int main()
- {
- char a[100];
- cin.getline(a,100,'@');
- cout<<a;
- cin.sync();
- char b[100];
- cin.getline(b,100,'@');
- cout<<b;
- return 0 ;
- }
这是我在CSDN里学到的方法,用cin的sync方法可以一下子把缓冲区清空,以上两个程序无论怎么输入便都可以正常执行了。
看来就算是C++里面向过程的东西都有这么多玄机在里面,所以说初学C++真的不要急于去研究OOP,基础打牢同样重要。鄙人也是个菜鸟,若有什么不对的地方请高手们指正啊!
///
我的实践经验:在下面一段代码中cin.sync()要和cin.clear()配合使用,否则并不能达到清空缓冲区的目的,依然会出现怪异的行为。
#includeusing namespace std;int main(){ char ch; int ival=0; int aCnt=0,eCnt=0,iCnt=0,oCnt=0,uCnt=0; int spaceCnt=0,tabCnt=0,newlineCnt=0; while(cin.get(ch)){ switch(ch){ case 'a': case 'A': ++aCnt; break; case 'e': case 'E': ++eCnt; break; case 'i': case 'I': ++iCnt; break; case 'o': case 'O': ++oCnt; break; case 'u': case 'U': ++uCnt; break; case ' ': ++spaceCnt; break; case '\t': ++tabCnt; break; case '\n': ++newlineCnt; break; } } cout<<"Number of vowel a: \t"< <<'\n' <<"Number of vowel e: \t"< <<'\n' <<"Number of vowel i: \t"< <<'\n' <<"Number of vowel o: \t"< <<'\n' <<"Number of vowel u: \t"< <<'\n' <<"Number of vowel space character: \t"< <<'\n' <<"Number of vowel tab character: \t"< <<'\n' <<"Number of vowel newline character: \t"< <
疑惑解答:
“cin方法检测到EOF时,将设置cin对象中一个指示EOF条件的标记。设置这个标记后,cin将不读取输入再次调用cin也不管用.....cin.clear()方法可能清除EOF标记,使输入继续进行。不过要记住的是,在有些系统中,按crl+z实际上将结束输入和输出,而cin.clear()将无法恢复输入和输出”——摘自《c++ primer plus 第五版 中文版》page154.