目录
[cin.get(char ch)](#cin.get(char ch))
[istream &get(char*,int)](#istream &get(char*,int))
[istream &get(char*,int,char)](#istream &get(char*,int,char))
[istream &getline(char*,int);](#istream &getline(char*,int);)
[丢弃后续字符:istream &ignore(int =1,int =EOF)](#丢弃后续字符:istream &ignore(int =1,int =EOF))
cin.get(char)什么时候会被设置eofbit和failbit
cin.get(void)什么时候会被设置eofbit和failbit
cin.get(char*,int)什么时候会被设置eofbit和failbit
cin.getline(char*,int)什么时候会被设置eofbit和failbit
[cin >> str 和 cin.get(char*,int)有什么区别](#cin >> str 和 cin.get(char*,int)有什么区别)
cin.get(char*,int)与cin.getline(char*,int)有什么区别
cin.get(char*,int)与cin.getline(char*,int)怎么开始的
[char cin.peek()](#char cin.peek())
[istream& cin.putback( char ch)](#istream& cin.putback( char ch))
详细了解cin,了解c++的IO逻辑,对我们的工作以及字符串leetcode题目都大有好处。文章中尽量用更加通俗的语言,去给大家描述。举了大量的例子,去给大家演示。只要大家能够跟着文章节奏走,一定会有很大收获。对于日后的c++文件操作,大有裨益。对于文章中的笔误之处,欢迎大家批评指正。
cin原理介绍
大家感兴趣的可以先看一下这篇博文:cout格式控制 ,了解cout和cin有助于我们深切理解c++处理IO的逻辑。
c++把输入输出都看做字节流。通过键盘所生成的屏幕上的字符,就是字符字节流或字符序列。
cpp
int a;
cin >> a;
可是变量a底层存储的是32个二进制位,4个字节。cin不可能每次严格读取四个字符输入给a。这就是大家要明白的一件事:抽取时进行了类型转换--->将字符字节流转换为相应变量类型的二进制位
- 输入123
- cin >> a;a是int;此时cin将字符123转化为4字节二进制,存储到a。
- cin >> a;a是double;此时cin将字符123转化为8字节二进制,存储到a。
控制符(hex、oct、dec)
- cin >> hex;后续cin会持续将输入解释为16进制;
- cin >> oct;后续cin会持续将输入解释为8进制;
- cin >> dec;后续cin会持续将输入解释为10进制
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
int a;
cin >> hex;
cin >> a;// 此时键入12
cout << a << endl;
cout << hex ;
cout << a << endl;
return 0;
}

cin如何检查输入
- cin会跳过开头所有空白(空白:空格、制表符、换行符);
- cin在遇到第一个类型不匹配的字符时会停止。例如,cin >> a(int);输入" 123S",cin遇到S时会停止
- 注意:cin正在继续匹配的过程中遇到空白,认为空白是类型不匹配的字符
- 注意:即使是:cin >> ch(char);大家知道char类型当然是可以包括空格字符的,但是即使是这样cin依然会跳过所有的空白,无法把空格赋给ch。
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
int a;
char ch;
cin >> a;// 此时键入123S--->123会被输入给a,字符'S'会留在输入队列里
cout << a << endl;
cin >> ch;// 将输入队列里的'S'取出,赋给ch
cout << ch << endl;
return 0;
}

cin与字符串
cin将输入流中的数据赋给字符串指针时,会自动在后面添加'\0'。正常的字符串中当然可以有空格、制表符、换行符,但是cin同样会跳过开头空白,并且认为中间的空白是类型不匹配的字符。
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
char ch[20];
cin >> ch;// 输入" 123 456"
cout << ch << endl;
return 0;
}

cin.get(char ch)
cin.get(char ch)将下一字符赋给ch,即使下一字符是空格、制表符、换行符。cin.get(char ch)返回指向该cin的引用,因此可以拼接使用:cin.get(char ch1).get(char ch2).get(char ch3)。
cin.get(char ch)与cin >> ch的比较:
- 两者都是从输入队列里获取下一个字符
- cin.get(char ch)的的确确是获取下一个字符,cin >> ch会跳过空白去寻找下一个字符
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
char ch;
cin.get(ch);// 输入" 1"
cout << ch << endl;
return 0;
}

cin.get(void)
- 返回类型为 int
- 取出下一个字符,将这个字符的ascII码返回
- 由于返回类型为int,所以不能拼接使用。例如:cin.get().get(),这样写是错误的。
接下来的例子中,大家运行一下,想想其中的逻辑。
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
int a;
a = cin.get();// 输入1
cout << a << endl;
a = cin.get();
cout << a << endl;
a = cin.get();// 输入2
cout << a << endl;
return 0;
}

cin.get(char)与cin.get(void)都是用于处理单字符的。接下来我们会讲解四个处理字符串的成员函数。
istream &get(char*,int)
假设有:
cpp
char line[5];
cin.get(line,5);
cin.get(char*,int)会读取4个字符,然后将数组末尾添加'\0';如果cin.get(char*,int)还未读取4个字符就遇到了换行符,那么cin.get(char*,int)将停止读取,补上'\0',并且把换行符留在输入队列中。
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
char line[5];
cin.get(line,5); // 输入1234\n
cout << line << endl;
char ch;
cin.get(ch); // 此时还残留在输入队列里的'\n'将会被赋给ch,也就是说此处控制台不会提示让我们继续输入
cout << ch;
return 0;
}

istream &get(char*,int,char)
与istream &get(char*,int)逻辑完全一样,只不过把默认的遇到换行符停止变为自定义的char而已。
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
char line[5];
cin.get(line,5,'$'); // 输入1234$
cout << line << endl;
char ch;
cin.get(ch); // 此时还残留在输入队列里的'$'将会被赋给ch,也就是说此处控制台不会提示让我们继续输入
cout << ch;
return 0;
}

istream &getline(char*,int);
同istream &get(char*,int)逻辑一样,主要区别是:istream &getline(char*,int)会把换行符从输入队列中取出,然后丢弃。
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
char line[5];
cin.getline(line,5); // 输入1234\n
cout << line << endl;
char ch;
cin.get(ch); // 此时输入队列里为空,也就是说此处控制台会提示让我们继续输入
cout << ch;
return 0;
}

istream &getline(char*,int,char);
与istream &getline(char*,int)逻辑完全一样,只不过把默认的遇到换行符停止变为自定义的char而已。
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
char line[5];
cin.getline(line,5,'$'); // 输入1234$\n
cout << line << endl;
char ch;
cin.get(ch); // 此时输入队列里还有一个'\n',也就是说此处控制台不会提示让我们继续输入
cout << ch;
return 0;
}

遇到文件结尾EOF
EOF是个宏,值是-1,不同系统中可能数值不一样;什么叫做文件结尾,换行符首先不是文件结尾,换行符是一个字符,这一点要先搞清楚。当我们想要读取文件时,文件最后一个字符的后面会被插入一个EOF,这个EOF就是文件结尾。在控制台上我们可以通过快捷键模拟出文件结尾:window--->ctrl+z;linux/macos--->ctrl+d。
回顾一下我们都讲了几种输入方式:
- cin
- cin.get(char)
- cin.get(void)
- cin.get(char*,int)
- cin.getline(char*,int)
这几种输入,一旦遇到文件结尾。都会使对象cin变为错误状态。
cin对象内部有一个比特位,这个比特位对应的变量(这里我们权且把它叫做比特位变量吧,大家都能理解),叫做eofbit。很显然eofbit取值只能是0和1。默认情况下,eofbit = 0,代表cin状态正常。上文讲的所有输入方式一旦遇到文件结尾,就会把cin对象内部的eofbit比特级变量设置为1,此时cin处于错误状态。这意味着:cin >> ch;cin.get(char);cin.get(char*,int);cin.getline(char*,int)全部都会将eofbit设置为1。这还意味着接下来的cin不能正常使用。
cin.eof()用于判断eofbit是否被设置为1,被设为1返回true,否则返回false;
在说明这件事之前,有一个小细节需要表明。cin >> ch等,平时返回cin对象。如果放在if中,即if(cin >> ch)将会返回布尔值。如果完成一次完整输入,则返回false,否则将返回false。
何时无法完成一次完整输入呢
- cin状态一开始就处在错误状态
- cin处在良好状态,读到的第一个非空字符就不符合要求
何时完成一次完整输入呢
- cin读取到正确字符并正常返回,此时cin状态良好
- cin读取到了正确字符,继续读时遇到eof,此时cin处在错误状态。但是完成了一次完整输入,if(cin >> a)成立。
关于if(cin >> a)何时成立,大家按照上述逻辑自行测试。下面是测试eofbit的代码。
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
char line[5];
cin >> line; //输入12ctrl+d 我在vscode下需要两次ctrl+d
if(cin.eof())
{
cout << "eofbit == 1"<< endl;//这意味着确实读到了文件结尾EOF
}
else
{
cout << "eofbit == 0"<< endl;
}
if(cin >> line)
{
cout << "cin is true" << endl;
}
else
{
cout << "cin is false" << endl;
}
return 0;
}

无法完成一次完整输入:设置failbit为1
failbit与上述eofbit逻辑一样,区别是eofbit在遇到文件结尾EOF时被设置为1,而failbit在无法完成一次完整输入被设置为1。cin.eof()用于检测eofbit是否被设置为1,而cin.fail()用于检测failbit是否被设置为1。failbit被设置为1时,cin同样处于错误状态
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
int a;
cin >> a; //输入S
if(cin.fail())
{
cout << "failbit == 1"<< endl;//这意味着读取失败,failbit被设置
}
else
{
cout << "failbit == 0"<< endl;
}
if(cin >> a)
{
cout << "cin is true" << endl;
}
else
{
cout << "cin is false" << endl;
}
return 0;
}

发生硬件错误时:badbit被设置为1
发生硬件错误时,badbit被设置为1,此时cin处在错误状态。cin.bad()用于检测badbit是否为1。不再赘述。
设置cin状态:clear()和setstat()
eofbit、failbit、badbit只要有一个被设为1,cin就会处于错误状态。处于错误状态的cin将无法使用。
- cin.clear():清除全部3个状态位
- cin.clear(eofbit):设置eofbit为1,其余两个状态位被清除
- setstat(eofbit):设置eofbit为1,不对另外两个状态做任何操作
别着急测试,等讲完下一个再测试,后续会说明原因。
丢弃后续字符:istream &ignore(int =1,int =EOF)
cin.ignore(255,'\n'):丢弃后续255个字符或者遇到换行符。
默认cin.ignore(int =1,int =EOF)丢弃指定数目的字符或者遇到文件结尾。
大家多加体会一下下列代码
cpp
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main()
{
int a;
cin >> a; //输入S
if(cin.fail())
{
cout << "failbit == 1"<< endl;//failbit被设置,cin进入错误状态
}
else
{
cout << "failbit == 0"<< endl;
}
cin.clear(); // 设置cin为正常状态,但是不匹配字符还在输入队列里
cin.ignore(1,'\n');
if(cin >> a) // 此时cin状态恢复正常,输入队列里已经没有数据,此时控制台会要求再次输入数据
{
cout << "cin is true" << endl;
}
else
{
cout << "cin is false" << endl;
}
return 0;
}

关键总结
我们已经几乎全部讲完cin相关知识,为了能够更加牢靠的记住这些知识。我们做一些大致的总结和比较,也就是说我们简单想一些场景,进行总结比较一下。如果大家觉得有什么问题或者错误,感谢大家评论区批评指正。对于初学者别怕麻烦,最好一个个测试一下。
cin什么时候会被设置eofbit和failbit
- cin >> ch(char);除非第一个非空字符就是EOF文件结尾,此时会同时设置eofbit和failbit;其余情况,不可能设置eofbit或者failbit(我说的不可能,是因为我确实没有想到相关场景。如果说成大概率不会设置...,这样我说的话更不容易出错。可是这种说辞很不利于我们学习成长,这里不是在考试。我们应当渴望自己发现问题,并改正它)。
- cin >> str(char*);第一个字符就遇到EOF,会同时设置eofbit和failbit;已经读取了一些字符,继续读取时遇到EOF,此时会设置eofbit;
- cin >> a(int):第一个字符就遇到EOF,第一个非空字符不是数字,都会同时设置eofbit和failbit;已经读取了相关数字,继续读取时遇到EOF,此时会设置eofbit;
cin.get(char)什么时候会被设置eofbit和failbit
- 除非第一个非空字符就是EOF文件结尾,此时会同时设置eofbit和failbit;其余情况,不可能设置eofbit或者failbit。
cin.get(void)什么时候会被设置eofbit和failbit
- 遇到文件结尾时cin.get()返回EOF;除非第一个非空字符就是EOF文件结尾,此时会同时设置eofbit和failbit。
cin.get(char*,int)什么时候会被设置eofbit和failbit
- 第一个非空字符就是EOF文件结尾,此时会同时设置eofbit和failbit;此时会把空值'\0'放入数组中
- 已经读取了一些字符,继续读取时遇到EOF,此时会设置eofbit;
- 直接读取到换行符,此时会设置failbit,因为没有读取成功啊。
cin.getline(char*,int)什么时候会被设置eofbit和failbit
- 第一个非空字符就是EOF文件结尾,此时会同时设置eofbit和failbit;此时会把空值'\0'放入数组中
- 已经读取了一些字符,继续读取时遇到EOF,此时会设置eofbit;
- 直接读取到换行符,不会设置failbit,因为getline从输入队列中取走了换行符只不过丢弃了它
- cin.getline(str,30):如果已经读取了29个字符,下一个字符不是换行符,将设置failbit。因此包含30个或更多字符的输入行将终止输入。
cin >> str 和 cin.get(char*,int)有什么区别
这个问题留给大家,主要是相互类比加深印象,已经进一步理解其特性。
cin.get(char*,int)与cin.getline(char*,int)有什么区别
- 前者遇到换行符停止,换行符留在输入队列;后者遇到换行符终止,换行符被取走丢弃
- 当第一个字符就是换行符时,前者属于无法完成一次完整输入,设置failbit;后者读取丢弃这个换行符,不设置failbit;两者都把空白加入str
- 后者读取字符数到达num-1后,如果下一个字符不是换行符,将设置failbit;前者则不会
cin怎么停的
- 遇到第一个不匹配的字符停止
- 注意:cin正在继续匹配的过程中遇到空白,认为空白是类型不匹配的字符。即使是cin >> str(char*),它依然视空白为不匹配的字符。
cin怎么开始的
这个问题比较微妙,大家后续学习中大概率会有些疑问,到时候大家读一下下面这一句话。看看我总结的对不对,欢迎批评指正。
- cin在输入队列里找数据,它丢弃一个个空白,只要输入队列里没有要匹配的数据,控制台就打开输入,让你输入数据。
cin.get(char*,int)与cin.getline(char*,int)怎么开始的
- 如果输入队列没有数据,则从调用处开始,从控制台获得数据
- 如果输入队列里有数据,则读取这个输入队列里的数据。对于控制台IO来讲,它不可能在从控制台获得数据。
++至此文章主要逻辑已经全部阐述完毕,剩下的篇幅,用来给大家讲两个好用的cin成员方法。++
char cin.peek()
- 返回输入队列中的下一个字符,但是不抽取这个字符,只是看看而已
- 大家自行测试即可
istream& cin.putback( char ch)
- 将一个字符插入到输入字符串中,被插入的字符将是下一条输入语句读取的第一个字符
- 相当于cin.peek()与cin.get(char)组合使用
cpp
#include <iostream>
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;
}

有什么问题大家可以在评论区说明,欢迎大家批评指正。