简体   繁体   English

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

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

I want to iterate over std::cin , line by line, addressing each line as a std::string .我想逐行迭代std::cin ,将每一行作为std::string寻址。 Which is better:哪个更好:

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

or要么

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

? ? What is the normal way to do this?这样做的正常方法是什么?

Since UncleBen brought up his LineInputIterator, I thought I'd add a couple more alternative methods.由于 UncleBen 提出了他的 LineInputIterator,我想我会添加更多的替代方法。 First up, a really simple class that acts as a string proxy:首先,一个非常简单的类充当字符串代理:

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

With this, you'd still read using a normal istream_iterator.有了这个,您仍然可以使用普通的 istream_iterator 进行阅读。 For example, to read all the lines in a file into a vector of strings, you could use something like:例如,要将文件中的所有行读入字符串向量,您可以使用以下内容:

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

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

The crucial point is that when you're reading something, you specify a line -- but otherwise, you just have strings.关键在于,当您阅读某些内容时,您指定了一行——否则,您只有字符串。

Another possibility uses a part of the standard library most people barely even know exists, not to mention being of much real use.另一种可能性是使用大多数人几乎不知道存在的标准库的一部分,更不用说实际用途了。 When you read a string using operator>>, the stream returns a string of characters up to whatever that stream's locale says is a white space character.当您使用 operator>> 读取字符串时,流将返回一串字符,直到该流的语言环境所说的空白字符为止。 Especially if you're doing a lot of work that's all line-oriented, it can be convenient to create a locale with a ctype facet that only classifies new-line as white-space:特别是如果你正在做很多面向行的工作,创建一个带有只将换行符归类为空白的 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];
    }
};  

To use this, you imbue the stream you're going to read from with a locale using that facet, then just read strings normally, and operator>> for a string always reads a whole line.要使用它,您需要使用该方面的语言环境为您要读取的流注入数据,然后正常读取字符串,而字符串的 operator>> 始终读取整行。 For example, if we wanted to read in lines, and write out unique lines in sorted order, we could use code like this:例如,如果我们想按行读取,并按排序顺序写出唯一的行,我们可以使用如下代码:

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

Keep in mind that this affects all input from the stream.请记住,这会影响来自流的所有输入。 Using this pretty much rules out mixing line-oriented input with other input (eg reading a number from the stream using stream>>my_integer would normally fail).使用这个几乎排除了将面向行的输入与其他输入混合(例如,使用stream>>my_integer从流中读取数字通常会失败)。

What I have (written as an exercise, but perhaps turns out useful one day), is LineInputIterator:我所拥有的(作为练习写的,但也许有一天会很有用)是 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

So your loop could be replaced with an algorithm (another recommended practice in C++):因此,您的循环可以用算法替换(C++ 中的另一种推荐做法):

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

Perhaps a common task is to store every line in a container:也许一个常见的任务是将每一行存储在一个容器中:

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

The first one.第一个。

Both do the same, but the first one is much more readable, plus you get to keep the string variable after the loop is done (in the 2nd option, its enclosed in the for loop scope)两者都做同样的事情,但第一个更具可读性,而且您可以在循环完成后保留字符串变量(在第二个选项中,它包含在 for 循环范围内)

Go with the while statement.使用 while 语句。

See Chapter 16.2 (specifically pages 374 and 375) of Code Complete 2 by Steve McConell.请参阅 Steve McConell 编写的 Code Complete 2 的第 16.2 章(特别是第 374 和 375 页)。

To quote:去引用:

Don't use a for loop when a while loop is more appropriate.当 while 循环更合适时,不要使用 for 循环。 A common abuse of the flexible for loop structure in C++, C# and Java is haphazardly cramming the contents of a while loop into a for loop header.在 C++、C# 和 Java 中对灵活的 for 循环结构的常见滥用是随意将 while 循环的内容塞入 for 循环标头中。

. .

C++ Example of a while loop abusively Crammed into a for Loop Header C++ 滥用 while 循环的示例塞进 for 循环头

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

C++ Example of appropriate use of a while loop C++ 适当使用 while 循环的示例

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

I've omitted some parts in the middle but hopefully that gives you a good idea.我在中间省略了一些部分,但希望这能给你一个好主意。

This is based on Jerry Coffin's answer.这是基于 Jerry Coffin 的回答。 I wanted to show c++20's std::ranges::istream_view .我想展示 c++20 的std::ranges::istream_view I also added a line number to the class.我还在类中添加了一个行号。 I did this on godbolt, so I could see what happened.我在 Godbolt 上做了这个,所以我可以看到发生了什么。 This version of the line class still works with std::input_iterator .这个版本的 line 类仍然适用于std::input_iterator

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

https://www.godbolt.org/z/94Khjz 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;
    }
}

prints out:打印出来:

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