IO之详解cin(c++IO关键理解)

目录

cin原理介绍

控制符(hex、oct、dec)

cin如何检查输入

cin与字符串

[cin.get(char ch)](#cin.get(char ch))

cin.get(void)

[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);)

遇到文件结尾EOF

无法完成一次完整输入:设置failbit为1

发生硬件错误时:badbit被设置为1

设置cin状态:clear()和setstat()

[丢弃后续字符:istream &ignore(int =1,int =EOF)](#丢弃后续字符:istream &ignore(int =1,int =EOF))

关键总结

cin什么时候会被设置eofbit和failbit

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怎么停的

cin怎么开始的

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; 
}

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

相关推荐
呃m36 分钟前
双重特征c++
c++
景彡先生1 小时前
C++ 中文件 IO 操作详解
开发语言·c++
无影无踪的青蛙1 小时前
[C++] STL大家族之<map>(字典)容器(附洛谷)
开发语言·c++
二进制人工智能2 小时前
【OpenGL学习】(四)统一着色和插值着色
c++·opengl
ScilogyHunter2 小时前
vscode的c工程配置文件详解
c语言·ide·vscode
love530love3 小时前
是否需要预先安装 CUDA Toolkit?——按使用场景分级推荐及进阶说明
linux·运维·前端·人工智能·windows·后端·nlp
m0_694845573 小时前
日本云服务器租用多少钱合适
linux·运维·服务器·安全·云计算
红石程序员3 小时前
VSCode配置C++项目全攻略
开发语言·c++·visual studio
徐新帅3 小时前
基于 C 语言的图书管理系统开发详解
c语言·开发语言·数据结构
一心0923 小时前
Linux部署bmc TrueSight 监控agent步骤
linux·运维·服务器·监控·bmc truesight