对 'std::istreambuf_iterator' 的使用感到困惑

14

以下是cppreference.com的一个示例:

The Code is:
#include <vector>
#include <sstream>
#include <iostream>
#include <iterator>

int main()
{
// typical use case: an input stream represented as a pair of iterators
std::istringstream in("Hello, world");
std::vector<char> v( (std::istreambuf_iterator<char>(in)),
                      std::istreambuf_iterator<char>() );
std::cout << "v has " << v.size() << " bytes. ";
v.push_back('\0');
std::cout << "it holds \"" << &v[0] << "\"\n";


// demonstration of the single-pass nature
std::istringstream s("abc");
std::istreambuf_iterator<char> i1(s), i2(s);
std::cout << "i1 returns " << *i1 << '\n'
          << "i2 returns " << *i2 << '\n';
++i1;
std::cout << "after incrementing i1, but not i2\n"
          << "i1 returns " << *i1 << '\n'
          << "i2 returns " << *i2 << '\n';
++i2; // this makes the apparent value of *i2 to jump from 'a' to 'c'
std::cout << "after incrementing i2, but not i1\n"
          << "i1 returns " << *i1 << '\n'
          << "i2 returns " << *i2 << '\n';

}

我有两个问题:

  1. 有人可以详细解释一下这段代码吗:std::vector<char> v( (std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>() );,我不太理解它在做什么..以及为什么可以通过使用cout<<&v[0]输出字符串“Hello, world”。
  2. 为什么*i2的学徒值从“a”跳到“c”? 有人可以解释一下背后的机制吗?

非常感谢!

2个回答

25

有人可以详细解释一下这段代码吗...

std::vector<T>有一个构造函数,它接受两个迭代器作为参数-一个代表范围的起始位置,另一个代表结束位置。

这个构造函数将输入流in转化为一个输入流迭代器:

std::istreambuf_iterator<char>(in)

您可以前进访问其元素,直到到达流的结尾。一旦到达流的结尾,迭代器将等同于使用默认构造函数创建的迭代器:

std::istreambuf_iterator<char>()
因此,通过传递这对迭代器,可以从输入流中读取的数据构建一个vector<T>。整个流将被消耗。
“为什么*i2的实际值在"a""c"之间跳跃?”
两个迭代器都从同一流中读取。当您增加第一个迭代器时,它会从基础流中消耗'b'。同时,i2指向了流的第一个字符,它是在构造时获取的,此时没有推进。
一旦您增加了i2,它会向流请求下一个字符。字符'b'已经被消耗了,因此下一个字符是'c'
最后,代码施展了一个小技巧,可能会被忽视:它向vector<char>中推入空终止符,以便能够使用operator <<(...)const char*重载来打印该向量。

另外,他是如何将消息打印为&v[0]的呢? :) - David G
因为 v[0] 是一个 char,所以 &v[0] 是一个 char* - Jonathan Wakely
@JonathanWakely 我知道,我在要求他在回答中解释。 :) - David G
2
@JonathanWakely char* 只是其中一部分:代码通过手动添加 '\0' 来进行小技巧。我想这就是为什么 0x499... 在评论旁边加了一个笑脸 :) - Sergey Kalinichenko
与此同时,i2i1递增后立即指向第二个字符。另外,下一段听起来好像i2最初指向"a",但因为"b"已经被使用了,所以跳到了"c"。但实际上,在i1递增之后(在i2递增之前),i2指向的是"b",你能详细解释一下吗? - starriet
哦,我以为你对'\0'的技巧的评论是正确的,但是即使没有v.push_back('\0');,原始代码的输出结果仍然相同。这里发生了什么? - starriet

9

一个默认构造的istreambuf_iterator基本上是一个文件结束迭代器,也就是说,只有当另一个迭代器到达文件结尾时,它才与之相等。

因此,以下代码:

std::vector<char> v( (std::istreambuf_iterator<char>(in)),
                      std::istreambuf_iterator<char>() );

in读取char,直到第一个迭代器被递增到等于第二个迭代器时(也只有在这种情况下),第一个迭代器达到文件的末尾(在这种情况下是stringstream)。简而言之,它将整个文件的内容复制到向量中。

打印“hello world”的部分要简单一些:ostream具有operator<<重载,用于char *,它假定char *指向C风格的字符串,因此应该打印指向的整个字符串。由于他们已经进行了push_back以添加'\0'到字符串,这使得它成为C风格的字符串。

第二部分演示了即使你有两个迭代器进入流中,你仍然只有一个流和一个读取位置。同时,每个迭代器都保存了它从流中读取的最近项目的副本。

因此,无论何时将任何迭代器(或到相同流的任何迭代器)递增,它都会递增当前的读取位置。所以,你从i1i2都指向流的开头开始。然后递增i1。这递增了读取位置,并将b读入i1,因此当您对i1进行解引用时,那就是您将获得的内容。当你递增i2时,它再次移动读取位置并将c读入i2,所以解引用i2将给出c

使用两个(或更多)迭代器不会改变流的性质--每次你递增相同流中的任何迭代器时,它都会从该流中读取下一个项目--而“下一个项目”始终由流本身确定,基于它的一个读取位置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接