Строковый поток С++ читается в строке фиксированной длины в массив символов

Учитывая формат данных как «int, int,..., int, string, int», возможно ли использовать stringstream (только) для правильного декодирования полей?

[Код]

int main(int c, char** v)
{
    std::string line = "0,1,2,3,4,5,CT_O,6";
    char delimiter[7];
    int id, ag, lid, cid, fid, did, j = -12345;
    char dcontact[4]; // <- The size of <string-field> is known and fixed
    std::stringstream ssline(line);
    ssline >> id >> delimiter[0]
    >> ag >> delimiter[1]
    >> lid >> delimiter[2]
    >> cid >> delimiter[3]
    >> fid >> delimiter[4]
    >> did >> delimiter[5]  // <- should I do something here?
    >> dcontact >> delimiter[6]
    >> j;
    std::cout << id << ":" << ag << ":" << lid << ":" << cid << ":" << fid << ":" << did << ":";
    std::cout << dcontact << "\n";
}

[Output] 0:1:2:3:4:5:CT_6,0:-45689, часть, выделенная полужирным шрифтом, показывает, что stringstream не смог прочитать 4 символа только для dcontact. dcontact на самом деле содержит более 4 символов, оставляя j с мусорными данными.


person YamHon.CHAN    schedule 03.01.2013    source источник
comment
stackoverflow.com/ вопросы/53849/   -  person user93353    schedule 03.01.2013


Ответы (5)


Да, нет конкретной перегрузки operator >> (istream&, char[N]) для N и есть для char*, поэтому он считает это лучшим соответствием. Перегрузка для char* читается до следующего символа пробела, поэтому он не останавливается на запятой.

Вы можете обернуть свой dcontact в структуру и иметь специальную перегрузку для чтения в вашу структуру. В противном случае вы могли бы использовать чтение, хотя это и разорвет вашу прекрасную цепочку операторов >>.

ssline.read( dcontact, 4 );

будет работать в этот момент.

Между прочим, чтобы прочитать до разделителя, вы можете использовать getline. (get также будет работать, но getline запись свободной функции в std::string означает, что вам не нужно угадывать длину).

(Обратите внимание, что другие люди указали использовать get, а не read, но это не сработает в вашем случае, поскольку у вас нет дополнительного байта в конце массива dcontact для нулевого терминатора. ЕСЛИ вы хотите, чтобы dcontact заканчивался нулем затем сделайте его 5 символов и используйте «get», и для вас будет добавлен нуль).

person CashCow    schedule 03.01.2013
comment
спасибо за информацию, я также искал ссылку на stringstream.read() - person YamHon.CHAN; 03.01.2013
comment
Поскольку длина строки известна, вы можете использовать std::setw(4), как в ssline >> std::setw(4) >> dcontact; - person bames53; 03.01.2013

Чуть более надежный (правильно обрабатывает разделитель ','):

template <char D>
std::istream& delim(std::istream& in)
{
  char c;
  if (in >> c && c != D) in.setstate(std::ios_base::failbit);
  return in;
}

int main()
{
  std::string line = "0,1,2,3,4,5,CT_O,6";
  int id, ag, lid, cid, fid, did, j = -12345;
  char dcontact[5]; // <- The size of <string-field> is known and fixed
  std::stringstream ssline(line);
  (ssline >> id >> delim<','>
          >> ag >> delim<','>
          >> lid >> delim<','>
          >> cid >> delim<','>
          >> fid >> delim<','>
          >> did >> delim<','> >> std::ws
          ).get(dcontact, 5, ',') >> delim<','>
          >> j;
  std::cout << id << ":" << ag << ":" << lid << ":"
            << cid << ":" << fid << ":" << did << ":";
            << dcontact << "\n";
}
person ipc    schedule 03.01.2013
comment
Не используйте манипулятор для обработки разделителей, но обратите внимание, что istream::get не пропускает пробелы, независимо от флага skipws. В отличие от >>, который используется для всего остального. Таким образом, ввод будет неудачным, если он имеет "...5, CT_0, 6". - person James Kanze; 03.01.2013
comment
+1, потому что вы не забыли исправить dcontact на 5 символов, чтобы использовать get. Конечно, вы предполагаете, что это строка и, следовательно, есть такой терминатор. - person CashCow; 03.01.2013

Проблема в том, что оператор >> для строки (std::string или строки в стиле C) фактически реализует семантику слова с конкретным определением слова. Решение произвольное (я бы сделал это строкой), но поскольку строка может представлять много разных вещей, им нужно было что-то выбрать.

Решение, как правило, состоит в том, чтобы никогда не использовать >> в строке. Определите нужный класс (здесь, вероятно, что-то вроде Symbol) и определите для него оператор >>, который учитывает его семантику. Ваш код будет намного понятнее для него, и вы можете добавить различные элементы управления invarant по мере необходимости. Если вы знаете, что в поле всегда ровно четыре символа, вы можете сделать что-то простое, например:

class DContactSymbol
{
    char myName[ 4 ];
public:
    //  ...
    friend std::istream&
    operator>>( std::istream& source, DContactSymbol& dest );
    //  ...
};

std::istream&
operator>>( std::istream& source, DContactSymbol& dest )
{
    std::sentry guard( source );
    if ( source ) {
        std::string tmp;
        std::streambuf* sb = source.rdbuf();
        int ch = sb->sgetc();
        while ( source && (isalnum( ch ) || ch == '_') ) {
            tmp += static_cast< char >( ch );
            if ( tmp.size() > sizeof( dest.myName ) ) {
                source.setstate( std::ios_base::failbit );
            }
        }
        if ( ch == source::traits_type::eof() ) {
            source.setstate( std::ios_base::eofbit );
        }
        if ( tmp.size() != sizeof( dest.myName ) ) {
            source.setstate( std::ios_base::failbit );
        }
        if ( source ) {
            tmp.copy( dest.myName, sizeof( dest.myName ) );
        }
    }
    return source;
}

(Обратите внимание, что в отличие от некоторых других предложений, например, с использованием std::istream::read, это поддерживает все обычные соглашения, такие как пропуск начального пробела, зависящего от флага skipws.)

Конечно, если вы не можете на 100% гарантировать, что символ всегда будет состоять из 4 символов, вам следует использовать для него std::string и соответствующим образом изменить оператор >>.

И кстати, вы, кажется, хотите прочитать четыре символа в dcontact, хотя этого достаточно только для трех (поскольку >> будет вставлять завершающий '\0'). Если вы прочитаете в нем больше трех, у вас неопределенное поведение.

person James Kanze    schedule 03.01.2013
comment
Вы можете безопасно прочитать 4 символа в dcontact, это будет проблемой, только если вы будете рассматривать его как строку с нулевым завершением, потому что это не так. read() поместит следующие 4 байта из потока в dcontact, если в потоке осталось столько байтов. Вы правы, что он не будет пропускать пробелы или интерпретировать разделители. Это просто тупая копия. Иногда это то, чего вы хотите — чтобы он просто дал вам то, что есть, а не пытался реализовать то, что, по его мнению, вы хотите. Предполагая, что ваша программа записывает файл, вы должны знать, что в нем. - person CashCow; 04.01.2013
comment
Это текстовый файл. Вы ничего не можете предполагать об этом. Даже если ваша программа написала это, кто-то мог отредактировать это с тех пор. И поскольку ему, вероятно, нужен класс для типа в другом месте, определение >> для него будет наиболее естественным решением. - person James Kanze; 04.01.2013

попробуй это

  int main(int c, char** v) {
    string line = "0,1,2,3,4,5,CT_O,6";
    char delimiter[7];
    int id, ag, lid, cid, fid, did, j = -12345;
    char dcontact[5]; // <- The size of <string-field> is known and fixed

    stringstream ssline(line);

    ssline >> id >> delimiter[0]
            >> ag >> delimiter[1]
            >> lid >> delimiter[2]
            >> cid >> delimiter[3]
            >> fid >> delimiter[4]
            >> did >> delimiter[5];

    ssline.get(dcontact, 5);

    ssline >> delimiter[6]
            >> j;
    std::cout << id << ":" << ag << ":" << lid << ":" << cid << ":" << fid << ":" << did << ":";
    std::cout << dcontact << "\n" << j;
    }
person Khaledvic    schedule 03.01.2013
comment
Если вы используете get, вам нужно отредактировать dcontact, сделав его 5-символьным, иначе он переполнится. get добавляет для вас нулевой терминатор. читать не дает. - person CashCow; 03.01.2013

Поскольку длина строки известна, вы можете использовать std::setw(4), как в

ssline >> std::setw(4) >> dcontact >> delimiter[6];
person bames53    schedule 05.01.2013