具有以下代码:
std::vector<int64> values;
std::copy(
std::istream_iterator<int64>(std::cin),
std::istream_iterator<int64>(),
std::back_inserter(values)
);
我想让它只读取输入流直到行尾。我该怎么做std::istream_iterator
?
具有以下代码:
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::getline
or std::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
(或您实例化它的任何类型)以及流是否结束或失败(何时返回)false
。operator bool()
这意味着我们的定制点必须是实际的流。
流对象实际上只是一个std::basic_streambuf
. streambuf 将吐出字符,直到找到一个EOF
. 您可以简单地调整它以EOF
在找到换行符后返回 a ,然后流将暂时将其视为流的结尾。
这样做很容易:可以通过成员函数访问 streambuf 对象rdbuf()
。您可以使用此功能将缓冲区替换为自定义缓冲区。完成后要让它在换行符后继续阅读,您只需将原件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, " "));
}