引子
在《实践教程》P10的程序代码如下:
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<cstdlib> #include<algorithm> using namespace std; int main() { char a[20]; int i; cout<<"请输入以下包含空格的字符串:It's a book."<<endl; cin>>a[0]>>a[1]>>a[2]>>a[3]>>a[4]>>a[5]>>a[6]>>a[7]>>a[8]>>a[9]; a[10]='\0'; cout<<"保存的是:"<<a<<endl; cout<<"请重新输入一遍"<<endl; cin.get(a[0]); cin.get(a[1]); cin.get(a[2]); cin.get(a[3]); cin.get(a[4]); cin.get(a[5]); cin.get(a[6]); cin.get(a[7]); cin.get(a[8]); cin.get(a[9]); cin.get(a[10]); cin.get(a[11]); cin.get(a[12]); cin.get(a[13]); a[14]='\0'; cout<<"保存的是:"<<endl; cout<<a<<endl; cout<<"注意:a[0]存放上次输入的回车符,a[14]存放本次输入结束的回车符,各自产生一个换行。"<<endl; cout<<"请输入一个八进制整数:0750"<<endl; cin>>i; cout<<"输出的数用十进制表示是:"<<endl; cout<<dec<<i<<endl; cout<<"请输入十六进制整数:0xff"<<endl; cin>>i; cout<<"输入的数用十进制表示是:"<<endl; cout<<dec<<i<<endl; cout<<"使用get.line()吸收0后面的字符:"<<endl; cin.getline(a,18); cout<<a<<endl; cout<<"改用显式指定类型,输入八进制数750:"<<endl; cin>>oct>>i; cout<<"输入的数用十进制表示是:"<<endl; cout<<i<<endl; cout<<"输入的数用八进制表示是:"<<endl; cout<<oct<<i<<endl;//指明用八进制输出以后一直有效 cout<<"请输入十六进制整数:ff"<<endl; cin>>hex>>i; cout<<"输入的数用十进制表示是:"<<endl; cout<<dec<<i<<endl;//必须显式地改为十进制 cout<<"输入的数用十六进制表示是:"<<endl; cout<<hex<<i<<endl; cout<<"请输入字符串:This is a cat."<<endl; cin.getline(a,18); cout<<a<<endl; cout<<"没有机会输入字符串:This is a cat。程序读了上次输入数字后的回车,输出了一个空串。现在可以重新输入一遍字符串:This is a cat。"<<endl; cin.getline(a,18); cout<<a<<endl; cout<<"成功!"<<endl; return 0; }
依照指示运行的输入输出如下:
请输入以下包含空格的字符串:It's a book. It's a book. 保存的是:It'sabook. 请重新输入一遍 It's a book. 保存的是: It's a book. 注意:a[0]存放上次输入的回车符,a[14]存放本次输入结束的回车符,各自产生一个换行。 请输入一个八进制整数:0750 0750 输出的数用十进制表示是: 750 请输入十六进制整数:0xff 0xff 输入的数用十进制表示是: 0 使用get.line()吸收0后面的字符: xff 改用显式指定类型,输入八进制数750: 750 输入的数用十进制表示是: 488 输入的数用八进制表示是: 750 请输入十六进制整数:ff ff 输入的数用十进制表示是: 255 输入的数用十六进制表示是: ff 请输入字符串:This is a cat. 没有机会输入字符串:This is a cat。程序读了上次输入数字后的回车,输出了一个仅包含一个回车的空串。现在可以重新输入一遍字符串:This is a cat。 This is a cat. This is a cat. 成功!
教材上的这段代码,实际上包含了两个类型的知识点:字符串读入 与 进制控制
进制控制
在代码中,我见到了此前NOIP竞赛中没有见过的表达,即 oct,dec,hex。(可能是我太菜了。。。qwq
根据代码及其输出结果,我们可以猜测,它们都与进制转换有关——其实,看看下面这几个英文单词你就明白了:
octal 八进制的
hexadecimal 十六进制的
decimal 十进制的
在cin、cout语句中,我们通常用oct、hex、dec来声明输入输出数值的进制类型(若在程序中始终未声明,则默认为十进制读入输出):
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; int main() { int i, j, k, l; cout<<"Input i(oct), j(hex), k(hex), l(dec):"<<endl; cin>>oct>>i; //输入为八进制数 cin>>hex>>j; //输入为十六进制数 cin>>k; //输入仍为十六进制数 cin>>dec>>l; //输入为十进制数 cout<<"hex:"<<"i="<<hex<<i<<endl; cout<<"dec:"<<"j="<<dec<<j<<'\t'<<"k="<<k<<endl; cout<<"oct:"<<"l="<<oct<<l; cout<<dec<<endl; } /* Input i(oct), j(hex), k(hex), l(dec): 032 0x3f 0xa0 17 hex:i=1a dec:j=63 k=160 oct:l=21 */
值得注意的是,在cin、cout中指明进制后,该进制将会一直被默认为读入与输出的进制格式,直至下一次再次声明进制。
此外,这种进制控制方式只适用于整型,而对于实型、字符并不适用。所以想将小数转化为其它进制的同学还是老老实实码代码吧~
还有一个小细节:使用不带.h的头文件<iostream>时,必须在cin中指明数制,否则从键盘输入时,无法识别八进制和十六进制数开头的0和0x标志。指明后可省略0和0x标志。
字符串读入
比起上面这些比较陌生的进制控制,字符串读入对我而言显然要熟悉多了~(不由自主地想起了曾经被字符串各种支配的恐惧 T_T
在文章开头的程序中,用到了这么一些字符串的读入方式:
//第一种:逐个读入单个字符 cin>>a[0]>>a[1]>>a[2]; //第二种:逐个读入单个字符串(它与前一种的区别我们之后会提到) cin.get(a[0]); cin.get(a[1]); cin.get(a[2]); //第三种:一次性读入整个字符串 cin.getline(a,4);
下面,我们来逐个认识这三种读入方式:
第一种:cin
在程序中,是用cin逐个读入单个字符,但cin不能读取空格、回车、tab键,并且将其作为单次输入的结束。因此,程序使用这种方式读取时,字符串中的空格不能被存储,而是被当作了输入的字符间的间隔。
<一点点补充> cin除了可以逐个读入单个字符,还可以读入字符串——类似的,这个字符串不能包含空格、tab、回车。例如下面这段代码:
char s[30]; cin>>s; cout<<s<<endl;
倘若输入"I love you.",则输出结果为"I",因为cin读入字符串遇到字符‘I’后的空格时自动结束,s中只存储了空格前的字符。
综上所述,cin只能被用来读入连续的纯字符串,倘若字符串中可能出现空格、回车、tab,还是换一种读入方式吧!
第二种:cin.get()
cin不能读取空格,那么若我们想要读入一个包含空格的字符串怎么办呢?这个时候我们就可以用cin.get()啦!cin.get()不仅可以存储普通字符,还能够存储空格、回车、tab!但在程序中,我们会发现使用cin.get()存储的字符串前后分别含有一个换行符。那么,它们是怎么产生的呢?
下面,我们来简单引入一下“键盘缓冲区”这个概念:对于cin、cin.get()、cin.getline()来说,它们总是首先从键盘缓冲区读入数据,若键盘缓冲区有数据,就直接读取,若无数据,就等待用户输入。
我们之前使用cin.get()存储字符串,其中代码cin.get(a[0])存储的实际上就是上一次输入字符串后我们键入的回车符,这个回车符本来被存放于键盘缓冲区,在执行cin.get(a[0])时被存储在了a[0]中。而在字符串后的回车则是我们本次输入结束时的回车。
第三种:cin.getline()
与cin.get()不大相同,cin.getline()是一次性读入整行字符串。cin.getline()可以完美地读入空格与制表符,但当其读入换行符时,它会结束字符串的读入。对于文章开头的程序,第一个cin.getline()仅仅读入了一个换行符,因此在输出时只输出了一个空串。
cin.getline()的格式为:cin.getline(字符串名,字符串长度)。值得注意的是,cin.getline()读入字符串后会在末尾自动添加一个空字符‘\0’,作为字符串结尾的标志。同时需要注意的是,我们在cin.getline中输入字符串长度时,也需要考虑'\0'的存在!
你有可能已经发现了,在程序使用cin.get()读入字符串时,也在最后手动将字符串的后一个位置赋值为'\0'。我们不妨将这行语句删除,看看会发生什么——
//本次字符串读入的输出结果: It's a book. ?u發A€3u
我们发现,若删除此语句,则输出的字符串a后出现乱码。原因不难理解,cout输出字符串a时,将'\0'作为字符串结束的标志,当输出'\0'时停止,若无'\0'则继续输出后面的元素,而在例子中后面的元素未被赋值,其值是不确定的,导致出现乱码。
<一点点补充> 在例子中,我们多次遇到了读入字符串时“意外”读入换行符导致输入提前结束的情况。那么如何避免呢?通常情况下有两种方法:
1> 第一种,在字符串读入前加上cin.get(),也就是说,我们可以通过在字符串读入前先单独读入留在键盘缓冲区的换行符,来避免在读入字符串时读入它。
2> 另一种方法是使用cin.ignore(),cin.ignore()函数中有两个参数,分别为数值型的a和字符型的ch ,即cin.ignore( a, ch )。它表示从输入流 cin 中提取字符,提取的字符被忽略,不被使用。而每抛弃一个字符,它都要进行计数和比较字符:如果计数值达到 a 或者被抛弃的字符是 ch ,则cin.ignore() 函数执行终止;否则,它继续等待。它的一个常用功能就是用来清除以回车结束的键盘缓冲区的内容,消除上一次输入对下一次输入的影响。例如可以这么用,cin.ignore(1024, '\n')。我们通常把第一个参数设置得足够大,这样是为了只让第二个参数 '\n' 起作用,所以这一个语句的目的就是把回车符(包括回车符本身)之前的所有字符从输入缓冲流中清除出去。
3> 其实还有一些简单粗暴地清除键盘缓冲区的办法,例如Windows系统下的fflush(stdin)与rewind(stdin)(它们都被包含在头文件<cstdio>中),在Linux系统下的setbuf(stdin,NULL)。