Чтение форматированных данных с помощью оператора потока C++ ››, если в данных есть пробелы

У меня есть данные в следующем формате:

4:How do you do?
10:Happy birthday
1:Purple monkey dishwasher
200:The Ancestral Territorial Imperatives of the Trumpeter Swan

Число может быть от 1 до 999, а длина строки не должна превышать 255 символов. Я новичок в C++, и кажется, что несколько источников рекомендуют извлекать форматированные данные с помощью оператора потока >>, но когда я хочу извлечь строку, она останавливается на первом символе пробела. Есть ли способ настроить поток, чтобы остановить синтаксический анализ строки только в новой строке или в конце файла? Я видел, что есть метод getline для извлечения всей строки, но мне все равно приходится разбивать ее вручную [с find_first_of], не так ли?

Есть ли простой способ парсить данные в этом формате, используя только STL?


person dreamlax    schedule 26.02.2010    source источник
comment
Потоки в C++ — одна из вещей, которые я ненавижу в C++.   -  person AraK    schedule 26.02.2010
comment
Поскольку я новичок в C++, я надеялся, что потоки будут одной из тех вещей, которые в конечном итоге приведут к прозрению оооооо, это умно, но после вашего комментария я начинаю думать, что этого никогда не произойдет. :(   -  person dreamlax    schedule 26.02.2010


Ответы (5)


Вы можете прочитать номер, прежде чем использовать std::getline, который считывается из поток и сохраняется в объект std::string. Что-то вроде этого:

int num;
string str;

while(cin>>num){
    getline(cin,str);

}
person codaddict    schedule 26.02.2010
comment
Это выглядит чистым; Я предполагаю, что будет безопасно заменить cin на istream, который мне дали? - person dreamlax; 26.02.2010
comment
Если вы читаете из файла, вы можете заменить cin допустимым объектом ifstream. - person codaddict; 26.02.2010
comment
Мне только что дали поток, и ожидается, что мой код будет анализировать данные, манипулировать ими и записывать их в другой поток. Я не создаю ни один поток. Я предполагаю, что мой фильтр не будет вызываться, если istream или ostream будут недействительными, но в то же время я не думаю, что это касается меня. Мусор в мусор :) . . . а может фигня в segfault вышла. - person dreamlax; 26.02.2010
comment
У меня была дополнительная переменная char, и я сделал while (cin >> num >> dummy), чтобы избавиться от символа двоеточия. - person dreamlax; 26.02.2010

В библиотеке C++ String Toolkit (StrTk) есть следующее решение вашей проблемы:

#include <string>
#include <deque>
#include "strtk.hpp"

int main()
{
   struct line_type
   {
      unsigned int id;
      std::string str;
   };

   std::deque<line_type> line_list;

   const std::string file_name = "data.txt";

   strtk::for_each_line(file_name,
                        [&line_list](const std::string& line)
                        {
                           line_type temp_line;
                           const bool result = strtk::parse(line,
                                                            ":",
                                                            temp_line.id,
                                                            temp_line.str);
                           if (!result) return;
                           line_list.push_back(temp_line);
                        });

   return 0;
}

Дополнительные примеры можно найти здесь

person Community    schedule 30.12.2010

Вам уже рассказывали о std::getline, но они не упомянули одну деталь, которая, вероятно, окажется вам полезной: когда вы вызываете getline, вы также можете передать параметр, указывающий, какой символ следует рассматривать как конец ввода. Чтобы прочитать свой номер, вы можете использовать:

std::string number;
std::string name;

std::getline(infile, number, ':');
std::getline(infile, name);   

Это поместит данные до ':' в number, отбросит ':' и прочитает оставшуюся часть строки в name.

Если вы хотите использовать >> для чтения данных, вы тоже можете это сделать, но это немного сложнее и углубляется в область стандартной библиотеки, которую большинство людей никогда не касается. Поток имеет связанный locale, который используется для таких вещей, как форматирование чисел и (что важно) определение того, что представляет собой «пробел». Вы можете определить свою собственную локаль, чтобы определить ":" как пробел, а пробел (" ") как не пробел. Скажите потоку использовать эту локаль, и он позволит вам напрямую читать ваши данные.

#include <locale>
#include <vector>

struct colonsep: std::ctype<char> {
    colonsep(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table() {
        static std::vector<std::ctype_base::mask> 
            rc(std::ctype<char>::table_size,std::ctype_base::mask());

        rc[':'] = std::ctype_base::space;
        rc['\n'] = std::ctype_base::space;
        return &rc[0];
    }
};

Теперь, чтобы использовать его, мы «наполняем» поток локалью:

#include <fstream>
#include <iterator>
#include <algorithm>
#include <iostream>

typedef std::pair<int, std::string> data;

namespace std { 
    std::istream &operator>>(std::istream &is, data &d) { 
       return is >> d.first >> d.second;
    }
    std::ostream &operator<<(std::ostream &os, data const &d) { 
        return os << d.first << ":" << d.second;
    }
}

int main() {
    std::ifstream infile("testfile.txt");
    infile.imbue(std::locale(std::locale(), new colonsep));

    std::vector<data> d;

    std::copy(std::istream_iterator<data>(infile), 
              std::istream_iterator<data>(),
              std::back_inserter(d));

    // just for fun, sort the data to show we can manipulate it:
    std::sort(d.begin(), d.end());

    std::copy(d.begin(), d.end(), std::ostream_iterator<data>(std::cout, "\n"));
    return 0;
}

Теперь вы знаете, почему этой частью библиотеки так пренебрегают. Теоретически, заставить стандартную библиотеку делать вашу работу за вас — это здорово, но на самом деле в большинстве случаев проще сделать такую ​​работу самостоятельно.

person Jerry Coffin    schedule 26.02.2010

Просто прочитайте данные построчно (всю строку) с помощью getline и проанализируйте их.
Для разбора используйте find_first_of().

person Dmitriy    schedule 26.02.2010

int i;
char *string = (char*)malloc(256*sizeof(char)); //since max is 255 chars, and +1 for '\0'
scanf("%d:%[^\n]s",&i, string); //use %255[^\n]s for accepting 255 chars max irrespective of input size
printf("%s\n", string);

Это C и будет работать и на C++. scanf обеспечивает больший контроль, но не управление ошибками. Так что используйте с осторожностью :).

person N 1.1    schedule 26.02.2010
comment
Похоже, что флаг m не стандартизирован, поэтому я не могу его использовать. Но, опять же, не будет ли это по-прежнему считываться только до первого символа пробела, а не до конца строки? - person dreamlax; 26.02.2010
comment
Он по-прежнему читает только первое слово строки, а не всю строку, и у вас есть ошибка в вашем коде: вы предоставляете i, но scanf нужен указатель на i (&i). - person dreamlax; 26.02.2010