std::cin
我想逐行迭代,将每一行作为std::string
. 哪个更好:
string line;
while (getline(cin, line))
{
// process line
}
或者
for (string line; getline(cin, line); )
{
// process line
}
? 这样做的正常方法是什么?
std::cin
我想逐行迭代,将每一行作为std::string
. 哪个更好:
string line;
while (getline(cin, line))
{
// process line
}
或者
for (string line; getline(cin, line); )
{
// process line
}
? 这样做的正常方法是什么?
由于 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
通常会失败)。
我拥有的是 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<>());
第一个。
两者都做同样的事情,但第一个更具可读性,而且你可以在循环完成后保留字符串变量(在第二个选项中,它包含在 for 循环范围内)
使用 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++;
}
我在中间省略了一些部分,但希望这能给你一个好主意。
这是基于 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