【问题标题】:How do I iterate over cin line by line in C++?如何在 C++ 中逐行迭代 cin?
【发布时间】:2010-12-06 17:28:46
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c++ string line-processing


    【解决方案1】:

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

    【讨论】:

    • 谢谢——我也有点喜欢。 :-)
    • 我知道有一种方法可以指定在哪个字符停止...好吧我仍然希望有一种更简单的方法...我想知道这种语言怎么变得如此粗俗,这让我有时很难过。 +1 为代理。易于编写、易于使用且更加灵活。
    • 各位,他的名字是 cppLearner。真的。
    • 这个被大量引用的带有 line 的示例对我来说无法编译,除非我这样做 operator std::string() constideone.com/xcJ4Z vs ideone.com/YY8cQ
    • @Cubbi: 嗯...我不知道这段时间是怎么错过的,但我已经修好了。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      第一个。

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

      【讨论】:

      • 将行保留在 for 范围内不是一件好事吗?在范围之外它没有多大用处,因为它最终会保留最后一行的值或其他东西?
      • @cppLearner:好点,但也许你应该把它放在它自己的函数中,所以临时使用的字符串无论如何都会超出范围。
      • 如果有充分的理由限制字符串的范围,但也有充分的理由在循环之前和/或之后的一些其他代码位应该处于相同的功能。我不认为限制范围应该决定是使用“for”还是“while”,应该决定的是你是在等待某个东西是假的(while),还是遍历概念上是一个范围的东西(for)。显然,两者之间的区别是一个模糊的边界。它们在逻辑上是等价的,只是关于你如何构思循环。
      • “裸括号”是指{ string line; while (getline(cin,line)) { // process line } } // more code goes here
      【解决方案4】:

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

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

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

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

      【讨论】:

      • 像往常一样,史蒂夫有一个好主意,但执行力很差。首先,for 循环的这种使用并不是特别滥用。其次,更重要的是,两个版本(使用 for 或 while)都显示了一种反模式,使用 EndOfFile() 作为循环退出条件,这可以保证给出不正确的结果。
      • 同意 for(;;) 滥用原则。但我不同意滥用的例子。就像 Jerry 进行 EndOfFile() 测试一样,这是一个禁忌。喊蚂蚁模式。虽然我可能会将 recordCount 移入正文并将 GetRecord() 移入 for(;;)
      • @Martin,他提供了for 的另一个示例,他认为这比您描述的要好一些
      【解决方案5】:

      这是基于 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-05-21
        • 2021-03-02
        • 2011-05-10
        • 2011-01-18
        • 2011-02-16
        • 1970-01-01
        • 2019-01-30
        • 1970-01-01
        • 2018-04-04
        相关资源
        最近更新 更多