簡體   English   中英

如何使std :: istream_iterator只讀到行尾?

[英]How to make std::istream_iterator read only until the end of line?

擁有以下代碼:

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

我想讓它只讀到輸入流,直到行結束。 我怎么能用std::istream_iterator做到這一點?

你不能用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

如果您希望在不添加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;
}

改編自類似問題的早期答案( 此處 ):

#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函數,該函數將要復制的元素數作為輸入。 不過, 這里有一個怪癖。 根據您的平台,即使只將第一行保存到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中。

以這種方式改變istream_iterator是不可能的,因為它不知道換行符,甚至不知道字符。 它只知道int64 (或者你用它實例化的任何類型)以及流是否結束或失敗(當它在operator bool()上返回false時)。

這意味着我們的定制點必須是實際的流。

stream對象實際上只是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, " "));
}

現場直播!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM