繁体   English   中英

如何在 C++ 中逐行迭代 cin?

[英]How do I iterate over cin line by line in C++?

我想逐行迭代std::cin ,将每一行作为std::string寻址。 哪个更好:

string line;
while (getline(cin, line))
{
    // process line
}

要么

for (string line; getline(cin, line); )
{
    // process line
}

? 这样做的正常方法是什么?

由于 UncleBen 提出了他的 LineInputIterator,我想我会添加更多的替代方法。 首先,一个非常简单的类充当字符串代理:

class line {
    std::string data;
public:
    friend std::istream &operator>>(std::istream &is, line &l) {
        std::getline(is, l.data);
        return is;
    }
    operator std::string() const { return data; }    
};

有了这个,您仍然可以使用普通的 istream_iterator 进行阅读。 例如,要将文件中的所有行读入字符串向量,您可以使用以下内容:

std::vector<std::string> lines;

std::copy(std::istream_iterator<line>(std::cin), 
          std::istream_iterator<line>(),
          std::back_inserter(lines));

关键在于,当您阅读某些内容时,您指定了一行——否则,您只有字符串。

另一种可能性是使用大多数人几乎不知道存在的标准库的一部分,更不用说实际用途了。 当您使用 operator>> 读取字符串时,流将返回一串字符,直到该流的语言环境所说的空白字符为止。 特别是如果你正在做很多面向行的工作,创建一个带有只将换行符归类为空白的 ctype facet 的语言环境会很方便:

struct line_reader: std::ctype<char> {
    line_reader(): std::ctype<char>(get_table()) {}
    static std::ctype_base::mask const* get_table() {
        static std::vector<std::ctype_base::mask> 
            rc(table_size, std::ctype_base::mask());

        rc['\n'] = std::ctype_base::space;
        return &rc[0];
    }
};  

要使用它,您需要使用该方面的语言环境为您要读取的流注入数据,然后正常读取字符串,而字符串的 operator>> 始终读取整行。 例如,如果我们想按行读取,并按排序顺序写出唯一的行,我们可以使用如下代码:

int main() {
    std::set<std::string> lines;

    // Tell the stream to use our facet, so only '\n' is treated as a space.
    std::cin.imbue(std::locale(std::locale(), new line_reader()));

    std::copy(std::istream_iterator<std::string>(std::cin), 
        std::istream_iterator<std::string>(), 
        std::inserter(lines, lines.end()));

    std::copy(lines.begin(), lines.end(), 
        std::ostream_iterator<std::string>(std::cout, "\n"));
    return 0;
}

请记住,这会影响来自流的所有输入。 使用这个几乎排除了将面向行的输入与其他输入混合(例如,使用stream>>my_integer从流中读取数字通常会失败)。

我所拥有的(作为练习写的,但也许有一天会很有用)是 LineInputIterator:

#ifndef UB_LINEINPUT_ITERATOR_H
#define UB_LINEINPUT_ITERATOR_H

#include <iterator>
#include <istream>
#include <string>
#include <cassert>

namespace ub {

template <class StringT = std::string>
class LineInputIterator :
    public std::iterator<std::input_iterator_tag, StringT, std::ptrdiff_t, const StringT*, const StringT&>
{
public:
    typedef typename StringT::value_type char_type;
    typedef typename StringT::traits_type traits_type;
    typedef std::basic_istream<char_type, traits_type> istream_type;

    LineInputIterator(): is(0) {}
    LineInputIterator(istream_type& is): is(&is) {}
    const StringT& operator*() const { return value; }
    const StringT* operator->() const { return &value; }
    LineInputIterator<StringT>& operator++()
    {
        assert(is != NULL);
        if (is && !getline(*is, value)) {
            is = NULL;
        }
        return *this;
    }
    LineInputIterator<StringT> operator++(int)
    {
        LineInputIterator<StringT> prev(*this);
        ++*this;
        return prev;
    }
    bool operator!=(const LineInputIterator<StringT>& other) const
    {
        return is != other.is;
    }
    bool operator==(const LineInputIterator<StringT>& other) const
    {
        return !(*this != other);
    }
private:
    istream_type* is;
    StringT value;
};

} // end ub
#endif

因此,您的循环可以用算法替换(C++ 中的另一种推荐做法):

for_each(LineInputIterator<>(cin), LineInputIterator<>(), do_stuff);

也许一个常见的任务是将每一行存储在一个容器中:

vector<string> lines((LineInputIterator<>(stream)), LineInputIterator<>());

第一个。

两者都做同样的事情,但第一个更具可读性,而且您可以在循环完成后保留字符串变量(在第二个选项中,它包含在 for 循环范围内)

使用 while 语句。

请参阅 Steve McConell 编写的 Code Complete 2 的第 16.2 章(特别是第 374 和 375 页)。

去引用:

当 while 循环更合适时,不要使用 for 循环。 在 C++、C# 和 Java 中对灵活的 for 循环结构的常见滥用是随意将 while 循环的内容塞入 for 循环标头中。

.

C++ 滥用 while 循环的示例塞进 for 循环头

for (inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile(); recordCount++) {
    inputFile.GetRecord();
}

C++ 适当使用 while 循环的示例

inputFile.MoveToStart();
recordCount = 0;
while (!InputFile.EndOfFile()) {
    inputFile.getRecord();
    recordCount++;
}

我在中间省略了一些部分,但希望这能给你一个好主意。

这是基于 Jerry Coffin 的回答。 我想展示 c++20 的std::ranges::istream_view 我还在类中添加了一个行号。 我在 Godbolt 上做了这个,所以我可以看到发生了什么。 这个版本的 line 类仍然适用于std::input_iterator

https://en.cppreference.com/w/cpp/ranges/basic_istream_view

https://www.godbolt.org/z/94Khjz

class line {
    std::string data{};
    std::intmax_t line_number{-1};
public:
    friend std::istream &operator>>(std::istream &is, line &l) {
        std::getline(is, l.data);
        ++l.line_number;
        return is;
    }
    explicit operator std::string() const { return data; }
    explicit operator std::string_view() const noexcept { return data; }
    constexpr explicit operator std::intmax_t() const noexcept { return line_number; }    
};
int main()
{
    std::string l("a\nb\nc\nd\ne\nf\ng");
    std::stringstream ss(l);
    for(const auto & x : std::ranges::istream_view<line>(ss))
    {
        std::cout << std::intmax_t(x) << " " << std::string_view(x) << std::endl;
    }
}

打印出来:

0 a
1 b
2 c
3 d
4 e
5 f
6 g

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM