不同版本的抽取运算符查看输入流的方法是相同的. 他们跳过空白(空格, 换行符, 制表符), 直到遇到非空白字符. 但对于c语言的字符输入函数, 并不是这样的, 在单字符模式下(char, unsigned char或signed char), >> 运算符将读取该字符, 将它放置到指定的位置. 在其他模式下, >> 运算符将读取一个指定类型的数据. 也就是说, 它读取从非空白字符开始, 到与目标类型不匹配的第一个字符之间的全部内容. 来看一个例子:
char philosophy[20];
int distance;
char initial;
cin >> philosophy >> distance >> initial;
对于上面的代码, 我们输入一下内容:
stoic 100 Blaise
实际上对应的结果为:
philosophy: stoic
distance: 100
initial: B
剩下的laise将留在输入流中, 下一个cin语句将从这里开始读取.
输入有时可能没有满足程序的期望, 例如:
int ele;
cin >> ele;
如果我们输入的是asdf, 在这种情况下, 抽取运算符将不会修改ele的值, 并返回0(如果istream对象的错误状态被设置, if或while语句将判定该对象为false). 返回值false让程序能够检查输入是否满足要求, 来看一个例子:
// check_input.cpp
#include
int main()
{
using namespace std;
cout << "Enter numbers: ";
int sum = 0;
int input;
while(cin >> input)
{
sum += input;
cout << "input 1 = " << input << endl;
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
return 0;
}
来看一下运行结果:
由于输入被缓存, 因此第二行输入在用户按下回车键之前, 是不会发送给程序的, 然而由于有个非法字符z不与任何一种格式匹配上, 所以输入与预期格式不匹配, 因此表达式cin >> input的结果为false, 因此while循环终止.
cin, cout对象包含一个描述流状态(stream state)的数据成员(从ios_base类那里继承的). 流状态(被定义为iostate类型, 而iostate是一种bitmask类型)由三个ios_base元素组成: eofbit, badbit, failbit, 其中每个元素都是1位, 可以是1(设置)或0(清除). 当cin操作到达文件末尾时, 它将设置eofbit; 当cin操作未能读取到预期的字符时, 它将设置failbit; I/O失败(试图读取不可访问的文件或试图写入写保护的磁盘), 也可能将failbit设置为1. 在一些无法诊断的失败破坏流时, badbit元素将被设置. 当全部三个状态位都设置为0时, 说明一切顺利, 程序可以检查流状态, 并使用这些信息来决定下一步做什么.
下面是一些流状态的方法和位信息:
eofbit: 如果达到文件末尾, 则设置为1
badbit: 如果流被破坏, 则设置为1, 例如: 文件读取错误
failbit: 如果输入操作需哦未能读取预期的字符或输出操作没有写入预期的字符, 则设置为1
goodbit: 另一种表示0的方法.
good(): 如果流可以使用(所有的为都被清除), 则返回true
eof(): 如果eofbit被设置, 则返回true
bad(): 如果badbit被设置, 则返回true
fail(): 如果badbit或failbit被设置, 则返回true
rdstate(): 返回流状态.
exceptions(): 返回一个位掩码, 指出哪些标记导致异常被引发.
exceptions(iosstate ex): 设置哪些状态将导致clear()引发异常, 例如: 如果ex是eofbit, 则如果eofbit被设置, clear()将引发异常
clear(iostate s): 流状态设置为s: s默认值为0(goodbit), 如果(restate() & exceptions()) != 0 , 则引发异常basic_ios::failure
setstate(iostate s): 调用clear(rdstate() | s). 这将设置与s中设置的位对应的流状态位, 其他流状态位保持不变.
clear()和setstate()很相似, 他们都重置状态, 但采取的方式不同, clear()方法将状态设置为它的参数. 因此, 下面的调用将使用默认参数0, 这将清除全部三个状态位(eofbit, badbit, failbit):
clear();
下面的调用将状态设置为eofbit; 也就是说eofbit将被设置, 另外两个状态位被清除:
clear(eofbit);
而setstate()方法只影响其参数中已设置的位, 例如:
setstate(eofbit);
这行代码将设置eofbit, 而不会影响其他位;
I/O和异常
exceptions()方法返回一个位字段, 它包含3位, 分别对应于eofbit, failbit, badbit. 修改流状态涉及clear()或setstate(), 这都将使用clear(). 修改流状态后, clear()方法将当前的流状态与exceptions()返回的值进行比较. 如果在返回值中某一位被设置, 而当前状态中的对应为也被设置, 则clear()将引发ios_base::failure异常. 如果exceptions()返回goodbit, 则不会引发任何异常. ios_base::failure异常类是从std::exception类派生而来的, 因此包含一个what()方法.
exceptions()的默认设置为goodbit, 也就是说没有引发异常. 但重载的exceptions(iostate)函数使得能够控制其行为:
cin.exceptions(badbit);
来看一个完整的例子:
// cinexce.cpp
#include
#include
int main()
{
using namespace std;
// 这里设置如果输入产生failbit, 则引发异常
cin.exceptions(ios_base::failbit);
cout << "Enter numbers: ";
int sum = 0;
int input;
try {
while(cin >> input)
{
sum += input;
}
} catch(ios_base::failure & bf) {
cout << bf.what() << endl;
cout << "O! the horror!\n";
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
return 0;
}
程序运行结果为:
只有在流状态正常(所有的位都被清除)的情况下, 下面的语句才会返回true:
while(cin >> input)
如果失败, 可以使用成员函数来判断可能的原因, 例如:
while(cin >> input)
{
sum += input;
}
if(cin.eof())
cout << "Loop terminated because EOF encountered\n";
设置流状态有一个非常重要的后果: 流将对后面的输入或输出关闭, 直到位被清除. 例如:
while(cin >> input)
{
sum += input;
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
cout << "Now enter a new number: ";
// 这行代码是不会工作的
cin >> input;
如果希望程序在流状态位被设置后能够读取后面的输入, 就必须将流状态重置为正常:
while(cin >> input)
{
sum += input;
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
cout << "Now enter a new number: ";
// 重置流状态
cin.clear();
// 由于导致输入循环终止的不匹配输入仍留在输入队列中, 程序必须跳过他
// 一种方法就是一直读取字符, 直到达到空白为止.
while(!isspace(cin.get()))
continue;
cin >> input;
还有一种丢掉不匹配输入的方法是丢弃行中的剩余部分:
while(cin.get() != '\n')
continue;
这个例子假设循环由于不恰当的输入而终止. 现在, 假设循环是由于到达文件尾或者由于硬件故障而终止的. 则处理错误输入的新代码就不起作用了, 我们可以使用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())
{
// 不是到达文件结尾的异常
cin.clear();
while(!isspace(cin.get()))
continue;
} else {
cout << "I can't go on !\n";
exit(1);
}
cout << "Now enter a new number: ";
cin >> input;
在使用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.
按下回车后, 这行输入将发送给程序, 并逐个显示(包括空格), 然后直到将回车键作为换行符处理, 并终止循环.
假设我们使用:
int ct = 0;
char ch;
cin >> ch;
while(ch != '\n')
{
cout << ch;
ct++;
cin.get(ch);
}
cout << ct << endl;
上述代码: 第一, 会跳过空格, 这样将不考虑空格, 因此相应的输出如下:
IC++clearly.
更重要的死程序将不会停止, 因为跳过了换行符, 因此代码不会将换行符赋值给ch, 所以while循环不会终止循环.
get(char &)成员函数返回一个指向用于调用它的istream对象的引用, 这意味着可以拼接get(char &)后面的其他输入:
char c1, c2, c3;
cin.get(c1).get(c2) >> c3;
get(void)成员函数还读取空白, 但使用返回值来将输入传递给程序.
int ct = 0;
char ch;
ch = cin.get();
while(ch != '\n')
{
cout << ch;
ct++;
ch = cin.get();
}
cin.get()将返回一个int值. 因此不能连续输入
看下面这个代码:
char c1;
cin.get(c1).get();
这个代码将第一个输入字符给c1, 然后读取并丢弃第二个输入字符
到达文件尾后, cin.get(void)都将返回值EOF(头文件iostream提供的一个符号常量). 这种特性可以用来这样读取输入:
int ch;
while((ch = cin.get()) != EOF)
{
...
}
采用哪种单字符输入形式:
1. 如果希望跳过空白:
选择使用>>
2. 如果希望程序检查每个字符, 则应使用get()方法.
getline()成员函数和get()的字符串读取版本都读取字符串, 他们的函数原型如下:
istream & get(char *, int, char);
istream & get(char *, int);
istream & getline(char *, int, char);
istream & getline(char *, int);
第一个参数是用于存放字符串的内存单元的地址. 第二个参数是读取的字符串的字符数+1, 第三个参数指定用作分解符的字符. 例如:
char line[50];
cin.get(line, 50);
cin.get()函数将在到达第49个字符或遇到换行符后停止将输入读取到数组中. get()和getline()之间的主要区别在于, get()将换行符留在输入流中. 这样接下来的输入操作首先看到的将是换行符, 而getline()读取并丢弃输入流中的换行符.
下面来看一下接收三个参数的版本, 第三个参数用于指定分界符, 遇到分界符后, 输入将停止, 即使还未读取最大数目的字符. 和默认情况一下, get()将分界符字符留在输入队列中, 而getline()不保留.
下面来看igone()成员函数. 该函数接收两个参数: 第一个是数字, 指定要读取的最大字符数; 另一个是字符, 用作输入分界符. 例如:
cin.ignore(255, '\n');
上述代码, 将读取并丢弃接下来的255个字符或直到到达第一个换行符.
原型为两个参数提供的默认值为1和EOF, 该函数的返回类型为istream &:
istream & ignore(int = 1, int = EOF);
默认值EOF导致ignore()读取指定数目的字符或读取到文件尾.
来看一个完整的例子:
// get_fun.cpp
#include
const int Limit = 255;
int main()
{
using std::cout;
using std::cin;
using std::endl;
char input[Limit];
cout << "Enter a string for getline() processing: \n";
cin.getline(input, Limit, '#');
cout << "Here is your input:\n";
cout << input << "\nDone with phase 1\n";
char ch;
cin.get(ch);
cout << "The next input character is " << ch << endl;
if(ch != '\n')
cin.ignore(Limit, '\n');
cout << "Enter a string for get() processing:\n";
cin.get(input, Limit, '#');
cout << "Here is your input:\n";
cout << input << "\nDone with phase 2\n";
cin.get(ch);
cout << "The next input character is " << ch << endl;
return 0;
}
程序运行结果为:
意外字符串输入
get(char *, int)和getline(), 对于这两个方法, 如果不能接收字符, 他们将把空值字符放置到输入字符串中, 并使用setstate()设置failbit, 方法在输入方法法立刻达到了文件尾的时候会出现不能接收字符. 对于get(char *, int)来说, 还有一种可能是输入了一个空行.
char temp[80];
while(cin.get(temp, 80))
...
空行不会导致getline()设置failbit. 这是因为getline()仍将抽取换行符. 虽然不会存储它. 如果希望getline()在遇到空行时终止循环, 则可以这么编写:
char temp[80];
while(cin.getline(temp, 80) && temp[0] != '\0')
假设输入队列中的字符数等于或超过了输入方法指定的最大字符数, 首先看getline():
char temp[30];
while(cin.getline(temp, 30))
getline()方法将从输入队列中读取字符, 并将它们放到temp数组的元素中, 直到到达文件末尾, 将要读取的字是换行符或存储了29个字符为止. 如果遇到文件尾, 则设置eofbit; 如果将要读取的字符是换行符, 则该字符将被读取并丢弃; 如果读取了29个字符, 并且下一个字符不是换行符, 则设置failbit. 因此包含30个或更多字符的输入行将终止输入.
再来看get(char *, int)方法, 它首先测试字符数, 然后测试是否为文件尾以及下一个字符是否是换行符. 如果它读取了最大数目的字符, 则不设置failbit标记. 由此可以知道终止读取是否是由于输入字符过多引起的. 可以用peek()来查看下一个输入字符. 如果它是换行符, 则说明get()已读取了整行; 如果不是换行符, 则说明get()是在到达行尾前停止的.
总结他们的行为如下:
getline(char *, int): 如果没有读取任何字符(但换行符被视为读取了一个字符), 则设置failbit, 如果读取了最大数目的字符, 且行中还有其他字符, 则设置failbit.
get(char *, int): 如果没有读取任何字符, 则设置failbit
同样也是读取指定数目的字节, 并将其存储在指定的位置中. 与getline()和get()不同的是, read()不会在输入后加上空值字符, 因此不能将输入转换为字符串. 例如:
char gross[150];
cin.read(gross, 150);
上述代码读取150个字符, 并存储在gross数组中. read()方法返回类型为istream &, 因此可以连续使用:
char gross[150];
char score[20];
cin.read(gross, 150).read(score, 20);
peek()函数返回输入中的下一个字符, 但不抽取输入流中的字符, 也就是说, 它能查看下一个字符. 假设要读取输入, 直到遇到换行符或句点, 则可以用peek()查看输入流中的下一个字符, 由此来判断是否读取:
char gread_input[100];
char ch;
int i = 0;
while((ch = cin.peek()) != '.' && ch != '\n')
cin.get(gread_input[i++]);
gread_input[i] = '\0';
gcount()方法返回最后一个非格式化抽取方法读取的字符数. 这意味着是由get(), getline(), ignore()或read()方法读取的, 不是由抽取运算符>>读取的. 抽取运算符对输入进行格式化, 使之与特定的数据类型匹配. 例如: 假设使用cin.get(myarray, 80)将一行读入myarray数组中, 并想知道读取了多少个字符, 则可以使用strlen()函数来计算数组中的字符数, 这种方法比使用cin.gcount()计算从输入流中读取了多少字符的速度要快.
putback()函数将一个字符插入到输入字符串中, 被插入的字符将是下一条输入语句读取的第一个字符. putback()方法接收一个char参数(要插入的字符), 其返回类型为istream &. 使用peek()的效果相当于先使用get()读取一个字符, 然后使用putback()将该字符放回到输入流中. 然而, putback()允许将字符放到不是刚才读取的位置.
来看一个完整的例子:
// peeker.cpp
#include
int main()
{
using std::cout;
using std::cin;
using std::endl;
char ch;
while(cin.get(ch))
{
if(ch != '#')
cout << ch;
else {
cin.putback(ch);
break;
}
}
if(!cin.eof())
{
cin.get(ch);
cout << endl << ch << " is next input character.\n";
} else {
cout << "End of file reached.\n";
std::exit(0);
}
while(cin.peek() != '#')
{
cin.get(ch);
cout << ch;
}
if(!cin.eof())
{
cin.get(ch);
cout << endl << ch << " is next input character.\n";
} else {
cout << "End of file reached.\n";
}
return 0;
}
程序运行结果为:
再来看一个例子:
// truncate.cpp
#include
const int SLEN = 10;
inline void eatline()
{
while(std::cin.get() != '\n')
continue;
}
int main()
{
using std::cin;
using std::cout;
using std::endl;
char name[SLEN];
char title[SLEN];
cout << "Enter your name: ";
cin.get(name, SLEN);
if(cin.peek() != '\n')
cout << "Sorry, we only have enough room for " << name << endl;
eatline();
cout << "Dear " << name << ", enter your title: \n";
cin.get(title, SLEN);
if(cin.peek() != '\n')
cout << "We were forced to truncate your title.\n";
eatline();
cout << "Name : " <
程序运行结果为: