Как перебирать cin построчно в C++?
Я хочу перебрать std::cin
, строка за строкой, обращаясь к каждой строке как std::string
, Что лучше:
string line;
while (getline(cin, line))
{
// process line
}
или же
for (string line; getline(cin, line); )
{
// process line
}
? Каков нормальный способ сделать это?
5 ответов
С тех пор как 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
нормально бы провалился).
То, что у меня есть (написано в качестве упражнения, но, возможно, однажды окажется полезным), это 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)
Перейти с заявлением в то время как.
См. Главу 16.2 (в частности, страницы 374 и 375) Code Complete 2 Стива Макконелла.
Цитировать:
Не используйте цикл for, когда цикл while более уместен. Распространенным злоупотреблением структурой гибкого цикла for в C++, C# и Java является случайное введение содержимого цикла while в заголовок цикла for.
,
Пример недопустимого цикла while C++, встроенного в заголовок цикла for
for (inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile(); recordCount++) {
inputFile.GetRecord();
}
C++ Пример правильного использования цикла while
inputFile.MoveToStart();
recordCount = 0;
while (!InputFile.EndOfFile()) {
inputFile.getRecord();
recordCount++;
}
Я опустил некоторые части в середине, но, надеюсь, это даст вам хорошую идею.
Это основано на ответе Джерри Коффина. Я хотел показать С ++20
std::ranges::istream_view
. Я также добавил в класс номер строки. Я сделал это на Godbolt, чтобы увидеть, что произошло. Эта версия линейного класса все еще работает с
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