C++_IO与文件3-用cin进行输入
接下来讨论的是如何给程序提供数据?
cin对象将标准输入表示为字节流。
通常情况下是通过键盘来生成这种字节流
cin对象根据接收值得变量类型,使用其方法将字符序列转换为所需的类型。
cin>>value_holder;
cin解释输入的方式取决于value_holder的数据类型;value_holder为存储输入的内存单元;
istream类重载了抽取运算符>>,使之能够识别下面这些基本类型;
signed char &; unsigned char &;
char &; short &; ...
典型的运算符函数的原型如下:
istream & operator>>(int &);
参数和返回值都是引用。引用参数意味着下面这样的语句将导致operator>>()函数处理变量staff_size本身,而不是像常规参数那样处理它的副本;
cin >> staff_size;
每个抽取运算符都返回调用对象的引用,这使得能够将输入拼接起来,就像拼接输出那样:
char name[20];
float fee;
int group;
cin>>name>>fee>>group;
=====================================================
一、cin>>如何检查输入
不同版本的抽取运算符查看输入流的方法是相同的。
它们跳过空白,直到遇到非空白字符。
空白字符(空格、换行符、制表符);
例如对于下面的代码:
int elevation;
cin >>elevation;
假设输入下面的字符:
-123Z
运算符将读取字符 - 、1、2和3;因为它们都是整数的有效部分;但Z不是有效字符。
因此输入中最后一个可接受的字符是3。Z将留在输入流中。下一个cin语句将从这里开始读取。
与此同时,运算符将字符序列-123转换为一个整数值,并将它赋给elevation。
输入有时候并不能满足要求,例如输入的是Zcar,而不是-123Z。在这种情况下,抽取运算符将不会修改elevation的值,并返回0;
返回值false让程序能够检查输入是否满足要求。
//check_it.cpp -- checking for valid input #include<iostream> int main() { using namespace std; cout<<"Enter numbers: "; int sum = 0; int input; while(cin>>input) { sum += input; } cout<<"Last value entered = "<<input<<endl; cout<<"Sum = "<< sum <<endl; return 0; }
运行结果:
Enter numbers: 200
10 -50 -123Z 60
Last value entered = -123
Sum = 37
分析:
由于输入是被缓冲的,因此通过键盘输入的第二行在用户按下回车键之前,是不会被发送给程序的。
然而,循环在字符Z处停止了对输入的处理,因此它不与任何一种浮点格式匹配。
输入与预期不匹配反过来讲导致表达式cin>>input的结果为false,因此while循环被终止。
=====================================================
二、流状态
接下来进一步讨论不适当的输入会带来什么后果;
cin或cout对象包含一个描述流状态(stream state)的数据成员。
流状态有3个ios_base元素组成:eofbit、badbit、failbit;
流状态被定义为iostate类型,是一种bitmask类型;
当全部3种状态被设置为0时,说明一切顺利;
当cin操作到达文件末尾时,它将设置eofbit;
当cin操作未能读到预期的字符时,它将设置为failbit;
在一些无法诊断的失败破坏流时,badbit元素将被设置failbit;
1、设置状态
下面介绍两种方法——clear()和setstate()很相似;
clear(); //这样的调用将使用默认参数0,这将清除全部3个状态位(eofbit、badbit、failbit);
clear(eofbit); //这将会设置eofbit位,同时另外两个状态位被清除;
setstate()方法只影响其参数中已被设置的位。
setstate(eofbit) //只会影响eofbit,而不会影响其他位;
为什么需要重新设置位状态?
这取决于程序需要执行的任务;
2、I/O和异常
cin.exceptions(badbit); //如果badbit位被设置,将引发异常;
cin.exceptions(badbit|eofbit); //如果badbit或eofbit被设置,将引发异常;
下面的程序,能够在failbit被设置时引发并捕获异常;
1 //cinexcp.cpp -- having cin throw an exception 2 #include<iostream> 3 #include<exception> 4 5 int main() 6 { 7 using namespace std; 8 cin.exceptions(ios_base::failbit); 9 cout<<"Enter numbers: "; 10 11 int sum = 0; 12 int input; 13 try{ 14 while(cin>>input) 15 { 16 sum += inputs; 17 } 18 }catch(ios_base::failure & bf) 19 { 20 cout<<bf.what()<<endl; 21 cout<<"O! the error!\n": 22 } 23 24 cout<<"Last value entered = "<<input<<endl; 25 cout<<"Sum = "<< sum <<endl; 26 return 0; 27 }
运行结果:
Enter numbers: 20 30 40 pi 6
ios_base failure in clear
O! the horror!
Last value entered = 40.00
Sum = 90.00
3、流状态的影响
设置流状态可以有非常重要的后果:流将对后面的输入或输出关闭,直到位被清除;
如果希望程序在流状态位被设置后能够读取后面的输入,就必须将流状态重置为良好。
这可以通过调用clear()方法来实现;
while(cin>>input)
{
sum += input;
}
cout<<"Last value entered = "<<input<<endl;
cout<<" Sum = "<<sum<<endl;
cout<<"Now enter a new number: ";
cin.clear(); //reset stream state
while(!isspace(cin.get()))
continue;
cin>>input; //will work now
注意这还不足以重新设置流状态。导致输入循环终止的不匹配输入仍留在输入队列中,程序必须跳过它。
一种方法是一直读取字符,直到到达空白为止;
isspace()函数是一个cctype函数,它在参数是空白字符时返回true。
另一种方法是丢弃行中的剩余部分,
while(cin.get() != '\n')
continue; //get rid rest of line
现在假设循环是由于到达文件尾或者由于硬件故障而终止的。
可以使用fail()方法检测假设是否正确,来修复问题。
fail()在failbit或eofbit被设置时返回true。因此代码必须排除这种情况;
while(cin>>input)
{
sum += input;
}
cout<<"Last value entered = "<<input<<endl;
cout<<" Sum = "<<sum<<endl;
if(cin.fail() && !cin.eof())
{
}
else //else bail out
{
cout<<"I cannot go on!\n";
exit(1);
}
cout<<"Now enter a new number:";
cin>>input; //will work now
=====================================================
三、其他istream类方法
非格式化输入函数,因为它们只是读取字符输入,而不会跳过空白,也不进行数据转换;
函数get(char*, int, char)和getline(char*, int, char)在默认情况下读取整行而不是一个单词;
方法get(char &)和get(void)提供不跳过空白的单字符输入功能;
1、单字输入
在使用char参数或没有参数的情况下,get()方法读取下一个输入字符,即使该字符是空格、制表符或换行符;
get(char & ch)版本将输入字符赋给其参数。
而get(void)版本将输入字符转换为整型(通常是int),并将其返回。
1)成员函数get(char &)
int ct = 0;
char ch;
cin.get(ch);
while(ch != '\n')
{
cout<<ch;
ct++;
cin.get(ch);
}
cout<<ct<<endl;
假设输入了 I C++ clearly.<Enter>
输出的结果是 I C++ clearly.
假设程序试图使用>>
int ct = 0;
char ch;
cin>>ch;
while(ch != '\n')
{
cout<<ch;
ct++;
cin>>ch;
}
cout<<ct<<endl;
则代码首先跳过空格,这样做将不考虑空格。因此相应的输出压缩为:
IC++clearly.
这里有个很糟糕的情况,抽取运算符跳过了换行符,因此代码不会将换行符赋给ch,所以while循环测试将不会终止;
get(char &)成员函数返回一个指向用于调用它的istream对象的引用,这意味着可以拼接get(char &)后面的其他抽取:
char c1,c2,c3;
cin.get(c1).get(c2)>>c3;
首先cin.get(c1)将第一个输入字符赋给c1,并返回调用对象——cin.
这样代码缩为cin.get(c2)>>c3,它将第二个输入字符赋给c2.该函数调用返回cin,将代码缩为cin>>c3。
这将把下一个非空白字符赋给c3。因此c1和c2的值最后为空格,但c3不是。
如果cin.get(char &)到达文件尾——无论是真正的文件尾还是模拟尾。它都不会给参数赋值了。也就是说到达文件尾时,没有值可以赋给参数了。
char ch;
while(cin.get(ch))
{
//process input
}
只要存在有效输入,cin.get(ch)的返回值都是cin,此时判定结果为true,因此循环将继续。
到达文件尾时,返回值判定为true,循环终止。
2) 成员函数get(void)
get(void)使用返回值的形式来将输入传递给程序。
可以这样使用它:
int ct = 0;
char ch;
ch = cin.get();
while(ch != '\n')
{
cout<<ch;
ct++;
ch = cin.get();
}
cout<<ct<<endl;
get(void)成员函数的返回类型为int,这使得下面的代码是非法的:
char c1, c2, c3;
cin.get().get()>>c3; //not valid
到达文件尾后,cin.get(void)都将返回值EOF——头文件提供的iostream提供的一个符号常量。
int ch;
while((ch = cin.get()) != EOF)
{
//process input
}
2、采用哪种单字输入形式
假设可以选择>>、get(char &)、get(void),应该使用哪一种呢?
首先应该确定是否希望跳过空白。
如果跳过空白更加方便,则使用抽取运算符>>;
如果希望程序检查每个字符,请使用get()方法。
3、字符串输入:getline()、get()和ignore()
istream & get(char *, int, char);
istream & get(char *, int);
istream & getline(char *, int, char);
istream & getline(char *, int);
第一个参数哟用于防止输入字符串的内存单元的地址;第二个参数比要读取的最大字符数大1;第三个参数指定用作分界符的字符;
例如,下面的代码将字符输入读取到字符数组line中:
char line[50];
cin.get(line,50);
cin.get()函数在到达第49个字符或遇到换行符后停止将输入读取到数组中。
get()和getlne()的区别在于;get()将换行符留在输入流中,那么接下来的输入操作首先看到的将是换行字符。而getline()抽取并丢弃输入流中的换行符。
第三个参数的用法,遇到分界符后,输入将停止,即使还未读取到最大数目的字符。
默认情况下,get()将分界字符留在输入队列中,getline()不保留。
1 //get_fun.cpp -- using get() and getline() 2 #include<iostream> 3 const int Limit = 255; 4 5 int main() 6 { 7 using std::cout; 8 using std::cin; 9 using std::endl; 10 11 char input[Limit]; 12 cout<<"Enter a string for getline() processing:\n"; 13 cin.getline(input, Limit, '#'); 14 cout<<"Here is your input: "; 15 cout<<input<<"\nDone with phase 1\n"; 16 17 char ch; 18 cin.get(ch); 19 cout<<"The next input character is "<<ch<<endl; 20 21 if(ch != '\n') 22 cin.ignore(Limit, '\n'); 23 24 cout<<"Enter a string for get() processing:\n"; 25 cin.get(input, Limit, '#'); 26 cout<<"Here is your input:\n"; 27 cout<<input<<"\nDone whti phase 2\n"; 28 29 return 0; 30 }
运行结果:
Enter a string for getline() processing:
Please pass
me a #3 melon!
Here is your input:
Please pass
me a
Done with phase 1
The next input character is 3
Enter a string for get() processing:
I still
want my #3 melon!
Here is your input:
I still
want my
Done with phase 2
The next input character is #
其中的cin.ignore(Limit, '\n'); 这个函数调用将读取并丢弃接下来的255个字符直到到达第一个换行符;
看下原型:
istream & ignore(int = 1, int = EOF);
默认参数值EOF导致igonre()读取指定数目的字符或读取到文件尾。
该函数返回调用对象,这使得能够拼接函数调用;
cin.ignore(255, ‘\n’).ignore(255, '\n');
其中,第一个调用读取并丢弃一行,第二个调用读取并丢弃一行。因此一共读取了两行;
4、意外字符串输入
get(char* , int)和getline()的某些输入形式将影响流状态。
这两个函数在遇到文件尾时,将设置eofbit;
遇到流破坏时,将设置badbit;
另外两种情况是,无输入以及输入到达或超过函数调用指定的最大字符数。
如果不能抽取字符,它将把空值字符放置到输入字符串中,并使用setstate()设置failbit。
什么时候不能抽取字符:1)输入方法到达了文件尾;2)输入了空行
输入空行并不会导致getline()设置failbit。这是因为getline()仍将抽取换行符,虽然不会存储它。
如果希望getline()在遇到空行时终止循环,可以这样编写:
char temp[30];
while(cin.getline(temp,80) && temp[0] != '\0') //terminate on empty line
如果输入队列中的字符数超过了输入方法中指定的最大字符数;
如果读取了最大字符数,并且下一个字符不是换行符,则设置failbit;
接下来看一下get(char * int)方法:
它首先读取字符数,然后测试是否为文件尾以及下一个字符是否是换行符。
提供它读取了最大数目的字符,则不设置failbit标记;
可以用peek()查看下一个输入字符,如果是换行符,则说明已经读取了整行;如果不是换行符,则说明get()在到达行尾前停止;
getline()不适合使用peek()方法,是因为getline()读取并丢弃了换行符;
如果使用get(),则可以知道是否读取了一整行;
=====================================================
四、其他istream方法
其他istream方法还包括read()、peek()、gcount()和putback()。
read()函数读取指定数目的字节,并将它们存储在指定的位置中。
char gross[144];
cin.read(gross, 144);
这段代码从标准输入中读取144个字节,并将它们存储在gross数组中;
注意read()不会在输入后面加上空值字符,因此不能将输入转换为字符串;
read()方法不是为键盘而设计的,它与ostream write()函数配合使用,来完成文件输入和输出;
该方法的返回类型为istream &,因此可以像下面这样将它拼接起来:
char gross[144];
char score[20];
cin.read(gross, 144).read(score, 20);
peek()函数返回输入中的下一个字符,但不抽取输入流中的字符。
可以用peek()查看输入流中的下一个字符,以此来判断是否继续读取:
char great_input[80];
char ch;
int i = 0;
while(ch = cin.peek() != ' . ' && ch != '\n')
cin.get(great_input[i++]);
great_input [i] = '\0';
gcount()方法返回最后一个非格式化抽取方法读取的字符数。
这指的是有get()、getline()、ignore()、read()方法读取的;
putback()函数将一个字符插入到输入字符串中
putback()方法接受一个char参数——要插入的字符,其返回类型是istream &;这使得可以将该函数调用与其他istream方法拼接起来;
被插入的字符是下一条输入语句读取的第一个字符。
1 //peeker.cpp -- some istream methods 2 #include<iostream> 3 4 int main() 5 { 6 using std::cout; 7 using std::cin; 8 using std::endl; 9 10 //read and echo input up to a # character 11 char ch; 12 13 while(cin.get(ch)) //terminate on EOF 14 { 15 if(ch != '#') 16 cout<<ch; 17 else 18 { 19 cin.putback(ch); //reinsert character 20 break; 21 } 22 } 23 24 if(!cin.eof()) 25 { 26 cin.get(ch); 27 cout<<endl<<ch<<" is next input character.\n"; 28 } 29 else 30 { 31 cout<<"End of file reached.\n"; 32 std::exit(0); 33 } 34 35 while(cin.peek() != '#') // look ahead 36 { 37 cin.get(ch); 38 cout<<ch; 39 } 40 41 if(!cin.eof()) 42 { 43 cin.get(ch); 44 cout<<endl<<ch<<" is next input character.\n"; 45 } 46 else 47 cout<<"End of file reached.\n"; 48 return 0; 49 }
运行结果:
I used a #3 pencil when I should have used a #2.
I used a
# is next input character.
3 pencil when I should have used a
# is next input character.
1 //truncate.cpp -- using get() to truncate input line, if necessary 2 #include<iostream> 3 const int SLEN = 10; 4 inline void eatline() {while (std::cin.get() != '\n') continue;} 5 6 int main() 7 { 8 using std::cout; 9 using std::cin; 10 using std::endl; 11 12 char name[SLEN]; 13 char title[SLEN]; 14 cout<<"Enter your name: "; 15 cin.get(name,SLEN); 16 17 if(cin.peek() != '\n') 18 cout<<"Sorry, we only have enough room for "<<name<<endl; 19 eatline(); 20 21 cout<<"Dear"<<name<<", enter your title: \n"; 22 cin.get(title, SLEN); 23 if(cin.peek() != '\n') 24 cout<<"We were forced to truncate your title.\n"; 25 eatline(); 26 cout<<"Name: "<<name<<"\nTitle: "<<title<<endl; 27 28 return 0; 29 }
运行结果:
Enter your name: Ella Fishsniffer
Sorry, we only have enough room for Ella Fish
Dear Ella Fish, enter your title:
Executive Adjunct
We were forced to truncate your title.
Name: Ella Fish
Title: Executive
注意,下面的代码确定第一条输入语句是否读取了整行:
while(com.get() != '\n') continue;