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

时间:2009-10-14 15:27:41

标签: c++ string line-processing

我希望逐行迭代std::cin,将每一行作为std::string进行处理。哪个更好:

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

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

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

5 个答案:

答案 0 :(得分:71)

由于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));

关键的一点是,当你某事时,你要指定一条线 - 否则,你只需要字符串。

另一种可能性是使用标准库的一部分,大多数人几乎不知道存在,更不用说具有实际用途了。当您使用运算符&gt;&gt;读取字符串时,该流会返回一个字符串,直到该流的区域设置所说的白色空格字符为止。特别是如果您正在进行大量面向行的工作,那么使用ctype facet创建一个仅将new-line分类为空白区域的区域设置会很方便:

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];
    }
};  

要使用此功能,您可以使用该构面将您要读取的流填充到区域设置,然后只需正常读取字符串,然后运算符&gt;&gt;对于一个字符串总是读取整行。例如,如果我们想要读取行,并按排序顺序写出唯一行,我们可以使用这样的代码:

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从流中读取数字通常会失败)。

答案 1 :(得分:8)

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<>());

答案 2 :(得分:1)

第一个。

两者都做同样的事情,但第一个更易读,另外你在循环完成后保持字符串变量(在第二个选项中,它包含在for循环范围内)

答案 3 :(得分:0)

使用while语句。

见Steve McConell的Code Complete 2的第16.2章(特别是第374和375页)。

引用:

  

当while循环更合适时,不要使用for循环。 C ++,C#和Java中常见的灵活for循环结构滥用是偶然地将while循环的内容填入一个for循环标题。

  

C ++一个while循环的例子被滥用地塞进for for循环标题

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

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

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

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

答案 4 :(得分:0)

这是基于 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