35

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

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

或者

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

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

4

5 回答 5

80

由于 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 方面的语言环境,该方面仅将换行符分类为空白:

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通常会失败)。

于 2009-10-14T17:10:50.303 回答
8

我拥有的是 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

所以你的循环可以用算法替换(C++ 中的另一种推荐做法):

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

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

vector<string> lines((LineInputIterator<>(stream)), LineInputIterator<>());
于 2009-10-14T16:07:34.013 回答
1

第一个。

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

于 2009-10-14T15:29:33.513 回答
0

使用 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++;
}

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

于 2009-10-14T15:30:11.990 回答
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
于 2021-02-15T03:46:47.053 回答