Подтвердить что ты не робот

Как перебирать cin по строкам в С++?

Я хочу перебирать строку std::cin, строка за строкой, обращаясь к каждой строке как std::string. Что лучше:

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

или

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

? Каков нормальный способ сделать это?

4b9b3361

Ответ 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));

Решающим моментом является то, что когда вы что-то читаете, вы указываете строку, но в противном случае у вас есть только строки.

Другая возможность использует часть стандартной библиотеки, которую большинство людей почти не знают, не говоря уже о том, что они очень полезны. Когда вы читаете строку с использованием оператора → , поток возвращает строку символов до того, что говорит этот язык, это символ пробела. Особенно, если вы выполняете большую работу, которая ориентирована на все линии, может быть удобно создать языковой стандарт с типом 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 обычно не выполняется).

Ответ 2

То, что у меня (написано как упражнение, но, возможно, полезно когда-нибудь полезно), является 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

Итак, ваш цикл может быть заменен алгоритмом (другая рекомендуемая практика на С++):

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

Возможно, общей задачей является сохранение каждой строки в контейнере:

vector<string> lines((LineInputIterator<>(stream)), LineInputIterator<>());

Ответ 3

Первый.

Оба делают то же самое, но первый из них гораздо читабельнее, плюс вы можете сохранить строковую переменную после завершения цикла (во втором варианте, заключенном в область цикла for)

Ответ 4

Перейдите с инструкцией while.

См. главу 16.2 (в частности, страницы 374 и 375) кода Complete 2 Стив МакКонелл.

Цитата:

Не используйте цикл for, когда цикл while более уместен.. Обычное злоупотребление гибкой структурой цикла в С++, С# и Java беспорядочно заполняет содержимое цикла while заголовок цикла.

.

С++ Пример цикла while оскорбительно Забита в заголовок для цикла

for (inputFile.MoveToStart(), recordCount = 0; !inputFile.EndOfFile(); recordCount++) {
    inputFile.GetRecord();
}

С++ Пример соответствующего использования цикла while

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

Я опустил некоторые части в середине, но, надеюсь, это даст вам хорошую идею.