Оператор перегрузки >> () для неизвестной длины / структуры ввода - PullRequest
0 голосов
/ 10 января 2020

Я пытаюсь прочитать файл с личной информацией. Каждая строка содержит данные одного человека, скажем, это выглядит так:

First(s) Last ID SSN
Peter Barker 1234 5678
James Herbert Bond 007 999
Barack Hussein Obama 2007 14165

Так что я хочу использовать std::copy, чтобы прочитать каждую строку и скопировать ее в (std::vector<Person>) следующим образом:

struct Person
{
    std::string firstName_s;
    std::string lastName;
    int ID;
    int SSD;
}

Я подумал, что было бы удобно перегрузить оператор извлечения для этого:

std::istringstream& operator>>(std::istringstream& in, struct Person& person)
{
    struct Person tmp;
    in  >> tmp.firstName_s 
        >> tmp.lastName 
        >> tmp.ID 
        >> tmp.SSN;
    person = std::move(tmp);
    return in;
}

Однако, проблема, с которой я столкнулся, заключается в том, что я не знаю сколько имен будет иметь человек .

Я думал о том, чтобы прочитать полное имя в одну строку, пока не встретил число, и они не разбили фамилию из строки, содержащей первые имена, это работало нормально, но выглядит «некрасиво». Было бы замечательно, если бы у кого-то было лучшее предложение или ссылка, на которую я мог бы взглянуть, я не могу найти что-то самостоятельно! Спасибо.

Ответы [ 2 ]

2 голосов
/ 10 января 2020

ОП не хочет менять файл, поэтому этот ответ больше не подходит. Работа в процессе.

Здесь есть две широкие стратегии

Мы можем выбрать:

  • У пользователя отформатировать данные для нас.
  • Программа отформатирует данные.

И затем мы их проанализируем.

1) Фиксированный формат данных :

Самое простое решение.
Заставьте пользователя вводить данные в формате, который может быть легко проанализирован . Здесь пользователь должен правильно вводить данные, и программа проверит правильность введенных данных. Это можно сделать разными способами, в том числе, но не полностью:

Peter-Richmond Barker 1234 5678        // hyphen(-) separated
"James Herbert" Bond 007 999           // enclosed in quotes("")
Barack_Hussein Obama 2007 14165        // underscore(_) separated

В первом и третьем случае достаточно std::cin >> person.first_names.
Во втором случае вам придется

std::getline(std::cin, person.first_names, '\"'); // any character delimiter

это после проверки разделителя открывания с помощью std::cin.get() == '\"'.

2) Медленно и устойчиво

Еще одно очень простое решение. Просто заставьте пользователя вводить одну вещь за раз :

std::cout << "Enter some datum 1: ";
std::cin >> person.some_datum_1;
...

(Вопреки распространенному мнению, данные единичны, а данные множественного числа ).
Для нескольких входов см. Токенизация линии :

Позвольте мне взять метод здесь:

std::cout << "Enter some data 1: ";
// Grab the line and put into a stream
std::getline(std::cin, line);
std::stringstream line_buffer(line);

// Prepare to iterate over the stream
std::istream_iterator<std::string> it(line_buffer);
std::istream_iterator<std::string> end;

// Set the name with a move assignment operator
person.first_names = std::move(std::vector<std::string>(it, end));
...

Обратите внимание, что этот метод потребует person.first_names, чтобы быть std::vector<std::string>.

3) Начало с конца

Здесь мы сначала вводим данные неопределенного размера.

Предупреждение : Это будет работать только для одного ввода неопределенного размера. Если имя и фамилия могут быть более двух, это не сработает. Я упоминаю об этом только для полноты.

Если вы не хотите принуждать пользователей к этому и портить им опыт, вам придется анализировать ввод самостоятельно. Введите всю строку со старым добрым std::getline(std::cin, line);.

Инициализируйте int read_from = std::string::npos;.
Теперь найдите последний пробел с помощью read_from = line.rfind(' ', read_from);. read_from == std::string::npos сообщит вам, что все входы были проанализированы или произошла ошибка.

A line.substr(read_from) выбирает последний вход. Преобразуйте его в соответствующий тип и сохраните. Вам также придется стереть проанализированный вход с помощью line.resize(read_from);

Промыть и повторить для других входов.

Примечание : рекомендуется сохранить неопределенные данные в std::vector соответствующего типа.

4) Марш байтов

Я знаю, вы бы сказали, что мы не рассмотрели вопрос ОП:

... читать в файле с личной информацией ...

Теперь, когда мы обсудили Принимая ввод от пользователя, мы также можем выбрать как , чтобы сохранить его (и получить его).

Самый простой способ:

personal_data_file.write((char*)&person_list[i], sizeof(Person));  // Write it...
personal_data_file.read((char*)&person_list[i], sizeof(Person));   // ...Now read it.

на всех oop, где person_list - это std::vector из Person с.

Примечание : не забудьте открыть файл в режиме std::ios::binary!

Elegant!


Но на всякий случай вы не знакомы с классами и некоторыми функциями, использованными в приведенных выше примерах. Вот несколько ссылок:

std :: getline https://www.geeksforgeeks.org/how-to-use-getline-in-c-when-there-are-black-lines-in-input/

std :: istream :: read http://www.cplusplus.com/reference/istream/istream/read/

std :: ostream :: write http://www.cplusplus.com/reference/ostream/ostream/write/

std :: vector https://www.geeksforgeeks.org/vector-in-cpp-stl/

std :: istream_iterator http://www.cplusplus.com/reference/iterator/istream_iterator/

1 голос
/ 10 января 2020

Если у вас есть строка переменной длины (с точки зрения количества слов), вы можете просто прочитать всю строку и либо обработать ее справа, либо кэшировать все слова и работать со смещениями. Пример ниже делает последний.

int to_int(std::string_view str)
{
    int val = 0;
    std::from_chars(str.data(), str.data() + str.size(), val);
    return val;
}

std::istream& operator>>(std::istream& in, Person& person)
{
    std::string line;

    // read whole line
    if (std::getline(in, line))
    {
        // split line into words
        std::vector<std::string> words;
        std::stringstream tmp_stream(line);
        for (std::string word; tmp_stream >> word; )
            words.push_back(word);

        // join first names
        tmp_stream.str(words[0]);
        for (std::size_t i = 1; i < words.size() - 3; i++)
            tmp_stream << ' ' << words[i];

        person.firstName_s = tmp_stream.str();
        person.lastName = words[words.size() - 3];
        person.ID = to_int(words[words.size() - 2]);
        person.SSN = to_int(words[words.size() - 1]);
    }

    return in;
}

Я думаю, что код не требует пояснений. Здесь - полный пример.

...