Ocxs/Cpp-Primer

chapter8_question2_book(what does rdstate() return value means?)

Closed this issue · 51 comments

Ocxs commented

@pezy
I asked this queation in SO.
尽管大牛回答了,依然不是太明白,再此以中文重新描述一下。

istream& Read(istream &is)
{
    std::string buf;
    while (is >> buf)       
    {   
        cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
        cout << is.rdstate() << endl;
        cout << buf << endl;
    }
    cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
    cout << is.rdstate() << endl;
    is.clear();
    cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
    cout << is.rdstate() << endl;
    return is;
}

我如果输入普通的字符串,输出是1 2 4 0,对应eofbit ailbit badbit rdstate()
如果我键入 CTRL+Z (windows),输出1 2 4 3,说明当前状态有变化,那么这个返回的3代表什么意思呢?是eofbit ailbit badbit 变化的个数?比如变化两个,就返回2?

2.我输入CTRL+Z,按说eofbit和failbit 会变化吧?书上都说了

Reaching end-of-file sets both eofbit and failbit

3.又例如我输入

        std::istringstream in2("test test");
        Read(in2 );

output is 1 2 4 0 test 1 2 4 1 test 1 2 4 3 1 2 4 0
中间输出个1我很诧异,好像没改变什么值,怎么返回的状态就改变了呢?

4.又例如我输入

        std::istringstream in("test test");
        Read(in >> std::noskipws);

output is 1 2 4 0 test 1 2 4 2 1 2 4 0
我又疑惑了: a.为何只输出一个test呢?加了noskipws,我觉得应该是直接将test test当作一个单词一次性输出的。 b.为什么这里返回的状态又变成2了?

上面的1 2 4我认为的意思是eofbit ailbit badbit在计算机中是连续排列的。
111 高位1代表badbit,低位1代表eofbit,所以在取它们值的时候,自动忽略其他位的值,所以取badbit就相当于100(4),eofbit相当于001(1)

以上

(最近忙于老师布置的作业,一个星期没看C++primer了,感觉拉下好多啊)
顺便@Mooophy 提高一下答题效率,再此先行谢过2位!

先赞下 @Ocxs 的钻研精神,加以时日,必有大成!

pezy commented

恭喜 @Ocxs , @Mooophy 被你感动地,献出了自己的处女中文 show。

我们可以从多个角度来想办法,先看看源码有什么线索。
下面贴自 VS2013:

    enum _Iostate
        {   // constants for stream states
        _Statmask = 0x17};

    static const _Iostate goodbit = (_Iostate)0x0;
    static const _Iostate eofbit = (_Iostate)0x1;
    static const _Iostate failbit = (_Iostate)0x2;
    static const _Iostate badbit = (_Iostate)0x4;
    static const _Iostate _Hardfail = (_Iostate)0x10;

由于到2013为止,vs仍没有实现constexpr,所以这里我们看到的是static const。但我们仍能看出:
eofbitfailbitbadbit都是静态的且只可读。换句话说,这些变量在程序编译之前已经定死,是不变的。

于是,思路和后续的实验代码都可以得到一定的简化。

@pezy 😓

另外,还可从上述看出:eofbit等虽然以bit命名,但事实上并非以bit存储。
事实上在PC、服务器等机器上很少见直接进行bit存储和操作的。(个别单片机是可以的,此处不展开说了)

另外,观察 1,2,4这几个值。
特点是正好是2的 0,1, 2次方。这种数字有个特点,就是可以保证相加之后结果的唯一性。比如
3 = 1 + 2
于是3 就代表 1 和 2 两个状态同时存在,回到 @Ocxs 第一个疑问:

我如果输入普通的字符串,输出是1 2 4 0,对应eofbit ailbit badbit rdstate()
如果我键入 CTRL+Z (windows),输出1 2 4 3,说明当前状态有变化,那么这个返回的3代表什么意思呢?是eofbit ailbit badbit 变化的个数?比如变化两个,就返回2?

此处的3应该代表 1 + 2,即eofbitfailbit对应值的相加,代表二者同时存在。也就是你在疑问2里所提到的:

Reaching end-of-file sets both eofbit and failbit

关于疑问3。可以从另外一个角度入手,推敲一下实验代码,有没有漏掉重要信息。个人觉得,目前的实验代码不够细,关键细节从眼皮底下溜走了。
现修改如下:

#include <iostream>
#include <istream>
#include <string>
#include <sstream>

using namespace std;

istream& Read(istream &is)
{
    int i;
    std::string buf;
    int count = 1;
    while (std::cout << "\nwhile checking " << count++ << " --> ", is >> buf)
    {
        cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
        cout << is.rdstate() << endl;
        cout << buf << endl;
    }

    cout << "\n\nout of while loop before clearing\n\n";
    cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
    cout << is.rdstate() << endl;

    cout << "\n\nout of while loop after clearing\n\n";
    is.clear();
    cout << is.eofbit << " " << is.failbit << " " << is.badbit << endl;
    cout << is.rdstate() << endl;
    return is;
}

int main()
{
    std::istringstream in2("test test");
    Read(in2);
    system("pause");
    return 0;
}

运行结果:

while checking 1 --> 1 2 4
0
test

while checking 2 --> 1 2 4
1
test

while checking 3 --> 

out of while loop before clearing

1 2 4
3


out of while loop after clearing

1 2 4
0
Press any key to continue . . . 

回到你的疑问:

...
中间输出个1我很诧异,好像没改变什么值,怎么返回的状态就改变了呢?

从上述代码和运行结果可知:
1、中间这个1发生在第二次while check时,eofbit置位,于是输出在循环内部输出1
2、最后这个3发生在第三次while check时,这次check失败,跳出循环,同时failbit置位。跳出循环后、clear之前,eofbitfailbit同时存在。如前所述,3 = 1 + 2, 故在循环外部输出3。

需要指出的是,eofbitfailbit等几个值的实现细节是machine dependent 的(我甚至怀疑这部分就不是standard规定范畴)。上述代码的运行环境是:windows 8.1 + vs2013。
我试了一下Ubuntu14.04+gcc4.82,运行结果是不一样的。

关于疑问4。
你的疑问是源出SO大牛@dietmar Kühl的 回复吧:

To get std::ios_base::failbit set without also having std::ios_base::eofbit set you'll need to prevent it from seeing a non-whitespace character: the input operator for std::string by default start off skipping whitespace and then reads until it either reaches whitespace or EOF and it is successful if it could read at least one non-whitespace character. An approach to do that is to turn automatic skipping of whitespace off, e.g.:

std::istringstream in("test test");
Read(in >> std::noskipws);

此处 @Ocxs 在认知上出现了两处逻辑漏洞,从而导致整条逻辑链断裂。
其一:对大牛用意的解读;
其二:对std::noskipws的语义的理解。

大牛的意思用中文重新整理如下:

如果你想只对failbit置位又想避免eofbit同时被置位的话,你可以制造一个逻辑矛盾。默认状况下:std::string在从stream读取的时候,会跳过开头的所有空格,直到读到非空格的字符或者EOF才算读取成功,否则视为读取失败并对failbit置位。因此,我们可以用下述代码制造逻辑矛盾:

std::istringstream in("test test");
Read(in >> std::noskipws);

std::noskipws的语义是:禁止忽略开头的空格(也包括tab什么的)。于是,就制造了一个语义上的矛盾:
1、is >> buf要求跳掉开头的空格;
2、std::noskipws要求处理开头的空格;

上述代码中,第二次进行while check时,stream里待处理的内容是

[空格]test

这个开头处的空格,使程序落入了预先设计好的逻辑陷阱,于是程序什么也不干,直接给failbit置位,就跳出循环了。在循环外的时候显示为2。

用上述修改过的实验代码,可以比较清楚的显示出:该逻辑矛盾发生在第二次进行while checking :

while checking 1 --> 1 2 4
0
test

while checking 2 --> 

out of while loop before clearing

1 2 4
2


out of while loop after clearing

1 2 4
0
Press any key to continue . . . 

这里的2 的含义是:没有读到eof,但也fail了。failbit在vs上对应的值是2,所以显示就是2.
注意:这是大牛给 @Ocxs 设计的实验啦,建立在他对内部实现极其熟悉的基础上。 @pezy 说那家伙是C++标准委员会的。。

Ocxs commented

@Mooophy 一下子豁然开朗了,实在太感谢了,按照@pezy 说的,我也是荣幸之至啊,得到您的中文处女秀啊。
不过仍然有个小疑惑,我根据您说的,也自己在vs2013里看了,既然eofbit ailbit badbit是static const,不能改变值,那么我们所说的置位,实际上并没有置位,只是返回了一个状态,比如到达文件尾,eofbit值并没有改变,不过返回了一个1,告诉我们到达文件尾了,然后我们人为的给它一个定义,说这叫eofbit置位了,来方便人理解? 是这个意思吗?

Ocxs commented

@pezy 能把C++标准委员会的神牛都招来了,看来我之前还是太小看SO的威力了!

pezy commented

不过仍然有个小疑惑,我根据您说的,也自己在vs2013里看了,既然eofbit ailbit badbit是static const,不能改变值,那么我们所说的置位,实际上并没有置位,只是返回了一个状态,比如到达文件尾,eofbit值并没有改变,不过返回了一个1,告诉我们到达文件尾了,然后我们人为的给它一个定义,说这叫eofbit置位了,来方便人理解? 是这个意思吗?

看来你还没理解透。

  • 置位,置的是什么?是 std::ios_base::iostate
  • @Mooophy+,实际有失偏颇。实际是 |(或) 的关系。如 eofbit(1)failbit(2)两个状态同时发生的时候,实际是:
    0001
|   0010
----------
    0011

std::ios_base::iostate 等于 3,这是你在 SO 上原问题最大的疑惑(看见你追问了。。。)

  • Dietmar Kühl 大爷,是为了给你演示每一个状态出现的场景,才列出了各种情形。其他的你理解了,但 noskipws 好像还有疑问。@Mooophy 说的够清楚,我只补充一个示例代码:
char a, b, c;

std::istringstream iss ("  123");
iss >> std::skipws >> a >> b >> c;
std::cout << a << b << c << '\n';

iss.seekg(0);
iss >> std::noskipws >> a >> b >> c;
std::cout << a << b << c << '\n';

输出结果:

123
  1
pezy commented

另外,@Mooophy 提到

需要指出的是,eofbit 、failbit等几个值的实现细节是machine dependent 的(我甚至怀疑这部分就不是standard规定范畴)。上述代码的运行环境是:windows 8.1 + vs2013。
我试了一下Ubuntu14.04+gcc4.82,运行结果是不一样的。

我彻底一点,帮你找出 C++ 标准(N3690) 上的定义:

image

其中 Table 124. 的内容如下:

image

可以看到,具体的值的确是没定义的。但注意顺序。

以下是 Clang 的底层实现:

typedef unsigned int iostate;
typedef iostate      io_state;
static const iostate badbit  = 0x1;
static const iostate eofbit  = 0x2;
static const iostate failbit = 0x4;
static const iostate goodbit = 0x0;

可以看到,为啥说 Clang 是目前最棒的 C++ 编译器,连顺序都和标准上的一样。。。从低位到高位:

0001  // badbit
0010  // eofbit
0100  // failbit
0000  // goodbit

goodbit 的值,标准里定义了,必须是 0。其他的没说。

对比一下 VS 的吧,它又淘气了。。。 :godmode:

Ocxs commented

@pezy 我理解您的意思了,之前知道改变了iostate的值,但是书上说“failbit被置位”这样类似的话语,实际上failbit的值没有改变啊?我们说的failbit置位 实际上是通过改变iostate的值来表现出“置位”这个概念。所以可以说按照您说的“置位std::ios_base::iostate”,我理解的意思对吧!
原来是用\,长知识了 👍
关于noskipws的理解,根据你们二位的细心阐述,我觉得就是把空格当作一个字符来处理,不能忽略。
😄

pezy commented

恩,这次理解的没问题了。

但是书上说“failbit被置位”这样类似的话语

中文版扔掉好了。。。

SO 很强大,你用的也不错,看到 @Mooophy 给你点赞了,我也点了一个~
另,那老爷子多半不会再回复你了。。。基本可以结贴了。。。

Ocxs commented

@pezy 简直不能太赞!:+1: Clang编译器我第一次听说还是听您说的,不过目前对比来说最熟悉的编译器还是vs,有时间我试试clang,上次装linux时,准备试试gcc(windows下的gcc编译老是失败,估计我配置有问题),各种命令不会,实在耽误任务的完成,又换回windows了,有机会试试Clang。非常感谢!这个issue延伸知识的远超我的预计,赞赞赞 ! :smile:

Ocxs commented

@pezy 我在说那句话的时候,特意翻了英文版,英文版也说“the failbit is set ” ,英文不是太好,暂时扔不掉中文版,学习中文版可以更加迅速的帮我学习C++。Thx!我现在就去结贴!:smile:

pezy commented

英文版也说“the failbit is set ”

set 翻译为 置位,真令人费解呢!直译的话:failbit 被设置,即 设置为 failbit,感觉就不会迷惑了。。。

Ocxs commented

@pezy thx a lot ! 话说你英语当初怎么学的? 😄

pezy commented

@Ocxs 。。。我英语很次,明天等 @Mooophy 教你吧。或者看下他在知乎上曾经提到的一个答案:http://www.zhihu.com/question/27008201/answer/36651724 ,应该会有启发。

另外,我注意到你开了一个 repo 来记录读书笔记。我也有类似的习惯,并弄了个组织:https://github.com/ReadingLab

刚邀请了你加入来着,不知道你是否收到邮件了。期待你的参与~

pezy commented

既然翻到知乎上去了,就再给你贴一个我关于记笔记的答案吧。希望对你有帮助:

http://www.zhihu.com/question/21438053/answer/18790164

加油~ 😸

Ocxs commented

@pezy 我写的很烂,而且肯定用中文,更新肯定也不稳定,内容质量也不能保证,不嫌弃我就好,已加入!谢谢你的链接,我去瞅瞅,Thx!

Ocxs commented

@pezy 你记笔记的答案我以前看过,还收藏了! 😄

认同 @pezy 的修正。

我有英文求职的需要,自己的repo里不便讲中文。。但的确意识到中文讨论的必要性。

嗯。。同 @Ocxs 商量一下:能否把你这个repo,或者再单开个repo作为专门的中文讨论区,你来打理,反正你自己也很多问题。我主repo的readme里给这里单开个 链接。你看如何? @pezy 的意见呢?

pezy commented

@Mooophy 非常同意!🙆

⚠ issue 本身无 Git 机制,其实蛮不保险。我曾经有误点设置里某按钮而痛失全部 issue 的经历。

⚠ 整理问题与答案,看似费事,实际上是对知识的梳理。有人写blog的目的甚至就是这个。

Ocxs commented

@Mooophy @pezy 好的,这个我晚上再开一个repo,下午还有课。

Ocxs commented

@pezy 您看了我新建的repo了吗。里面那个内容是怎么回事?那个东西是干嘛的?是不是因为我repo名字的原因?

pezy commented

@Ocxs 看到了啊,里面好像什么都没有?什么东西呢?名字没啥问题呢。

你加个README之类的文件试试看,不要让repo完全空着。

Ocxs commented

@pezy @Mooophy 弄好了,不过我还是有个小疑问,之前没太理解 ,就是说专门开个repo作为中文讨论区,是在这个repo的issue里讨论,然后我整理一下,发布在repo里呢?(因为我看@Mooophy 在那个repo里发了一个test issue)还是就是直接在repo里提问问题,然后你们来回答呢?(这样你们好像没有权限编辑repo吧,好像只有fork之后commit才行)不知道二位是哪个意思啊?(原谅我太笨没理解啊)

我的想法是用新开的这个repo的issue进行中文讨论。

逐步取代原来豆瓣小组的作用, @Ocxs 你来打理就好了,其他人不需要额外的权限。

pezy commented

我觉得 @Mooophy 的意思也是这个。至于 README 的编辑就由你自己来。可以对问题进行自我总结,也可以整理一下你的一些思考(我觉得你很多想法,我都想不到。)

认同 @pezy
如果 @Ocxs 有更好的想法,就大胆尝试好了,不用客气的。

pezy commented

@Ocxs 加油~

image

我把你这个 repo 加到主页面链接里去了~ @Mooophy 觉得如何?

我擦,你俩真牛! 👍

Ocxs commented

@Mooophy @pezy 好的,那个repo正在弄,那个我一直很想知道你是怎么在这里发图片的,我看markdown的语法了,但是不知道图片的链接怎么弄,比如我想弄个截图,怎么生成链接呢?总不能上传图片的网上,然后弄链接吧,百度有人说上传的github的文件夹里,我也很疑问,上传到github的文件夹里不一样需要链接吗?

还有个问题,我新建的repo里的文件夹怎么重命名呢?我发现我新建的ch1,ch2的顺序没有排好,应该改成ch01的

pezy commented

那个我一直很想知道你是怎么在这里发图片的,我看markdown的语法了,但是不知道图片的链接怎么弄,比如我想弄个截图,怎么生成链接呢?

image

image

还有个问题,我新建的repo里的文件夹怎么重命名呢?

image

image

Ocxs commented

@pezy Thx!:smile:

pezy commented

@Ocxs 我强烈建议你,花点时间学习一下 Git 操作,最好习惯在本地操作与编辑文件。

下面有一些教程,非常简单:

希望您能找到自己喜欢的,熟悉 Git 的基本操作。对未来找工作,是有助力的。

Ocxs commented

@pezy 多谢,我会学的,但是你刚才教的那个重命名,我好像用的不行啊,我把ch1按照你说的,重命名为ch01,然后提交不了,我就在ch01里加了个readme,结果直接新建了ch01,ch1还在,所以刚才截图的那个edit之后如何操作呢?

pezy commented

@Ocxs 这就是为啥我紧接着给你推荐 Git 操作了,通过网页能做的事情太少了。譬如我说的 edit, 其实就是新建一个新文件夹,而删除,则根本无法通过网页操作(这个功能 Github 还未开发 https://github.com/isaacs/github/issues/225)。

所以,那些偷懒的办法也仅限于此,还是老老实实学 Git 命令吧。。。

Ocxs commented

@pezy 那您用的git客户端是什么啊?(windows)(我之前用过gitbash,还有githubforwindows图像客户端)

pezy commented

gitbash

image

Ocxs commented

@pezy 好的,那我去学习一下,之后在回来改,Thx! 👍

pezy commented

@Ocxs 要学的,不过这次我帮你改好了。。。你 merge 一下我的 pull request 即可。

Ocxs commented

@pezy 好的,多谢!:smile: