我希望逐行迭代std::cin
,将每一行作为std::string
进行处理。哪个更好:
string line;
while (getline(cin, line))
{
// process line
}
或
for (string line; getline(cin, line); )
{
// process line
}
?这样做的正常方法是什么?
答案 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