【问题标题】:How to make std::istream_iterator read only until the end of line?如何使 std::istream_iterator 只读到行尾?
【发布时间】:2019-10-17 20:50:02
【问题描述】:

有以下代码:

std::vector<int64> values;
std::copy(
  std::istream_iterator<int64>(std::cin),
  std::istream_iterator<int64>(),
  std::back_inserter(values)
);

我想让它只读取输入流直到行尾。我如何使用std::istream_iterator 做到这一点?

【问题讨论】:

  • cononical 方法是使用std::getline 读取行,将其放入std::istringstream,然后按照您在此处执行的方式复制到向量中。
  • 是的,我知道这是可能的,但是我们可以通过处理std::string 来获得更多的复制,然后再次重复它以 push_back 进入向量
  • istream_iterator 将始终迭代到流的末尾。对此您无能为力,除了创建一个新的流,其末端精确定义在您想要的位置。最简单的方法是使用stringstream

标签: c++ istream istream-iterator


【解决方案1】:

std::istream_iterator 无法做到这一点。

但编写输入迭代器相对容易。

#include <iterator>
#include <iostream>
#include <sstream>
#include <vector>
#include <cctype>

template<typename T>
class istream_line_iterator: public std::iterator<std::input_iterator_tag, T>
{
    std::istream*   stream;
    public:
        // Creating from a stream or the end iterator.
        istream_line_iterator(std::istream& s): stream(&s)      {dropLeadingSpace();}
        istream_line_iterator():                stream(nullptr) {}

        // Copy
        istream_line_iterator(istream_line_iterator const& copy): stream(copy.stream)   {}
        istream_line_iterator& operator=(istream_line_iterator const& copy) {stream = copy.stream;return *this;}

        // The only valid comparison is against the end() iterator.
        // All other iterator comparisons return false.
        bool operator==(istream_line_iterator const& rhs) const {return stream == nullptr && rhs.stream == nullptr;}
        bool operator!=(istream_line_iterator const& rhs) const {return !(*this == rhs);}

        // Geting the value modifies the stream and returns the value.
        // Note: Reading from the end() iterator is undefined behavior.
        T  operator*() const    {T value;(*stream) >> value;return value;}
        T* operator->() const;  // Not sure I want to implement this.

        // Input streams are funny.
        // Does not matter if you do a pre or post increment. The underlying stream has changed.
        // So the effect is the same.
        istream_line_iterator& operator++()     {dropLeadingSpace();return *this;}
        istream_line_iterator& operator++(int)  {dropLeadingSpace();return *this;}

    private:
        void dropLeadingSpace()
        {
            // Only called from constructor and ++ operator.
            // Note calling this on end iterator is undefined behavior.

            char c;
            while((*stream) >> std::noskipws >> c) {
                if (c == '\n') {
                    // End of line. So mark the iterator as reaching end.
                    stream = nullptr;
                    return;
                }
                if (!std::isspace(c)) {
                    // Found a non space character so put it back
                    stream->putback(c);
                    return;
                }
            }
            // End of stream. Mark the iterator as reaching the end.
            stream = nullptr;
        }
};

int main()
{
    std::stringstream    s{"0 1 2 3 4 5 6 7 8 9 10\n11 12 13 14 15 16\n17 18 19"};

    std::vector<int>    d{istream_line_iterator<int>(s), istream_line_iterator<int>()};
    for(auto v: d) {
        std::cout << "V: " << v << "\n";
    }
}

跑步:

> g++ -std=c++17 main.cpp
> ./a.out
V: 0
V: 1
V: 2
V: 3
V: 4
V: 5
V: 6
V: 7
V: 8
V: 9
V: 10

【讨论】:

    【解决方案2】:

    如果您想要该功能而不添加std::getlinestd::stringstream,您可以更改流的字符分类,以便换行符不被视为可丢弃的空格。这是一个最小的例子:

    struct set_newline_as_ws : std::ctype<char> {
      static const mask* make_table( std::ctype_base::mask m ) {
        static std::vector<mask> v(classic_table(), classic_table() + table_size);
        v['\n'] &= m;
        return &v[0];
      }
      set_newline_as_ws( bool skip, std::size_t refs = 0 ) : ctype(make_table(skip ? ~space : space), false, refs) {}
    };
    
    std::istream& skipnewline( std::istream& is ) {
      is.imbue(std::locale(is.getloc(), new std::ctype<char>));
      return is;
    }
    
    std::istream& noskipnewline( std::istream& is ) {
      is.imbue(std::locale(is.getloc(), new set_newline_as_ws(true)));
      return is;
    }
    
    int main() {
      std::vector<int64> values;
      std::cin >> noskipnewline;
      std::copy(
        std::istream_iterator<int64>(std::cin),
        std::istream_iterator<int64>(),
        std::back_inserter(values)
      );
      std::cin >> skipnewline;
    }
    

    【讨论】:

    • 这是对语言环境的巧妙使用。
    • 这不会设置failbit 吗?您可能应该在阅读一行后致电cin.clear()。另外,What do I do if I want to get a second line?
    • @CássioRenan 然后你必须做两件事:调用clear() 并删除换行符。
    【解决方案3】:

    改编自类似问题 (here) 的较早答案:

    #include <vector>
    #include <algorithm>
    #include <string>
    #include <iterator>
    
    namespace detail 
    {
        class Line : public std::string 
        { 
            friend std::istream & operator>>(std::istream & is, Line & line)
            {   
                return std::getline(is, line);
            }
        };
    }
    
    template<class OutIt> 
    void read_lines(std::istream& is, OutIt dest)
    {
        typedef std::istream_iterator<detail::Line> InIt;
        std::copy_n(InIt(is), 1, dest);
    }
    
    int main()
    {
        std::vector<std::string> v;
        read_lines(std::cin, std::back_inserter(v));
    
        return 0;
    }
    

    这段代码应该只占用一行并将其保存到向量 v 中。这要归功于 std::copy_n 函数的使用,该函数将要复制的元素的数量作为输入。不过,有一个怪癖报告here。根据您的平台,即使只将第一行保存到 v 中,也会读取一到两行。

    话虽这么说,如果你想让它失效安全,你可以实现自己的 copy_n(...) 函数,如下所示:

    template<class InIt, class Range, class OutIt>
    OutIt own_copy_n(InIt first, Range count, OutIt dest)
    {
      *dest = *first;
      while (0 < --count)
          *++dest = *++first;
      return (++dest);
    }
    

    然后,您可以使用 own_copy_n(...),而不是在代码中使用 std::copy_n(...)。 这样您就可以确保您只需要输入一行,并且该行将被保存到您的向量 v 中。

    【讨论】:

    • 这将读取所有行,但 OP 只希望将单行中的整数读入向量中。
    • @0x499602D2 感谢您指出这一点。相应地进行了编辑。
    【解决方案4】:

    不可能以这种方式更改istream_iterator,因为它不知道换行符,甚至不知道字符。它只知道int64(或您实例化它的任何类型)以及流是否结束或失败(然后它将在operator bool() 上返回false)。

    这意味着我们的定制点必须是实际的流。

    流对象实际上只是std::basic_streambuf 的包装器。 streambuf 将吐出字符,直到找到EOF。您可以简单地将其调整为在找到换行符后返回EOF,然后流将暂时将其视为流的结尾。

    这样做很容易:可以通过成员函数rdbuf() 访问streambuf 对象。您可以使用此功能将缓冲区替换为自定义缓冲区。完成后要在换行符后继续阅读,只需将原始 std::basic_streambuf 返回到流中即可。

    #include <algorithm>
    #include <iostream>
    #include <iterator>
    #include <sstream>
    #include <streambuf>
    #include <vector>
    
    // This is our custom std::basic_streambuf object.
    // We chose the underflow function as our point of customization, because
    // it is there that the action is happening: it is this function that
    // that is responsible for reading more data from the stream.
    class newline_buf : public std::streambuf {
        std::streambuf* src;
        char ch; // single-byte buffer
    protected:
        int underflow() {
            if( (ch= src->sbumpc()) == '\n') {
                return traits_type::eof(); // return EOF on new line.
            }
            setg(&ch, &ch, &ch+1); // make one read position available
            return ch; // may also return EOF by the regular means
        }
    public:
        newline_buf(std::streambuf* buf) : src(buf) {
            setg(&ch, &ch+1, &ch+1); // buffer is initially full
        }
    };
    
    int main() {
        // testing with a stringstream to make things easier to reproduce.
        // Should work fine with any type of input stream.
        std::istringstream iss(R"(12345 12345 12345 
        67890 67890 67890)");
    
        // We store the original rdbuf so we can recover it later.
        auto original_rdbuf = iss.rdbuf();
        newline_buf buf(original_rdbuf);
        iss.basic_ios::rdbuf(&buf);
    
        // Do the copy and then recover the original rdbuf
        std::copy(std::istream_iterator<int>(iss), std::istream_iterator<int>(), std::ostream_iterator<int>(std::cout, " "));
        iss.basic_ios::rdbuf(original_rdbuf);
    
        // You can try doing a new copy, just to convince yourself that the stream is still in a valid state.
        //std::copy(std::istream_iterator<int>(iss), std::istream_iterator<int>(), std::ostream_iterator<int>(std::cout, " "));
    }
    

    See it live!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-01
      • 2020-12-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多