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

发布于 2024-08-07 23:08:36 字数 292 浏览 9 评论 0原文

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

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

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

?执行此操作的正常方法是什么?

I want to iterate over std::cin, line by line, addressing each line as a 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

辞别 2024-08-14 23:08:36

自从 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));

关键点是,当您读取某些内容时,您指定一行 - 但否则,你只有字符串。

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

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

要使用它,您需要注入您正在处理的流将使用该方面从语言环境中读取,然后正常读取字符串,然后使用运算符>>因为字符串总是读取整行。例如,如果我们想要读取行,并按排序顺序写出唯一的行,我们可以使用如下代码:

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

Since UncleBen brought up his LineInputIterator, I thought I'd add a couple more alternative methods. 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. 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. 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:

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. 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 (e.g. reading a number from the stream using stream>>my_integer would normally fail).

相思故 2024-08-14 23:08:36

我所拥有的(作为练习编写,但也许有一天会变得有用)是 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<>());

What I have (written as an exercise, but perhaps turns out useful one day), is 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++):

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<>());
小矜持 2024-08-14 23:08:36

第一个。

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

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)

耳根太软 2024-08-14 23:08:36

使用 while 语句。

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

引用:

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

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

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

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

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

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

Go with the while statement.

See Chapter 16.2 (specifically pages 374 and 375) of Code Complete 2 by Steve McConell.

To quote:

Don't use a for loop when a while loop is more appropriate. 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++ Example of a while loop abusively Crammed into a for Loop Header

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

C++ Example of appropriate use of a while loop

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.

庆幸我还是我 2024-08-14 23:08:36

这是基于杰里科芬的回答。我想展示 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

This is based on Jerry Coffin's answer. I wanted to show c++20's std::ranges::istream_view. I also added a line number to the class. I did this on godbolt, so I could see what happened. This version of the line class still works with 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;
    }
}

prints out:

0 a
1 b
2 c
3 d
4 e
5 f
6 g
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文