Чтение и запись строк в бинарных файлах С++

Я пытаюсь разработать небольшое приложение для Windows, чтобы улучшить свои навыки C++ вне рамок MFC и помочь в изучении иностранных языков.
Я хотел бы создать небольшое, личное и простое приложение. -port_and_use словарь, и, хотя у меня нет проблем с разработкой графического интерфейса, у меня большие проблемы с сохранением и восстановлением данных.

Моя идея состоит в том, чтобы записать двоичные файлы со следующей структурой:

int (representing the number of words)
int (representing the string length + \0)
sequence of characters zero-terminated.
Теперь я я изучаю русский язык, а мой основной язык итальянский, поэтому я не могу использовать старый добрый std::string для записи слов, более того, спасибо Microsoft, я использую VS2010 со всеми благами и плохие файлы, которые идут с ним. Я показываю вам свои подпрограммы для записи int и wstring:
//Writing int
void CDizionario::ScriviInt( int nInt, wofstream& file ) const
{
    file.write( reinterpret_cast < const wchar_t * > ( &nInt ), sizeof( nInt ) );
    file.flush();
}
// Writing string
void CDizionario::ScriviWString( int nLStringa, const wstring* pStrStringa, wofstream& file ) const
{
    wchar_t cTerminatore;
    string strStringa;
    file.write( pStrStringa->c_str(), nLStringa );
    file.flush();
    cTerminatore = L'\0';
    file.write( &cTerminatore, sizeof( wchar_t ) );
    file.flush();
}
// Reading int
void CDizionario::LeggiInt( int *pInt, wifstream& file )
{
    file.read( reinterpret_cast < wchar_t * >( pInt ), sizeof( int ) );
}
// Reading wstring
void CDizionario::LeggiWString( int nLStringa, wstring& strStringa, wifstream& file )
{
    wchar_t *pBuf;
    streamsize byteDaLeggere;
    byteDaLeggere = nLStringa;
    pBuf = new wchar_t[(unsigned int)( byteDaLeggere * sizeof( wchar_t ) )];
    file.read( pBuf, byteDaLeggere * sizeof( wchar_t ) );
    strStringa.append( pBuf );
    delete [] pBuf;
}
// Constructor
CDizionario::CDizionario( void )
{
    m_pLoc = new locale( locale::classic(), new codecvt_utf8_utf16 );
}
// Somewhere in my code before calling LeggiInt/ScriviInt/LeggiWString/ScriviWString:
// ...
file.imbue( *m_pLoc );

Что ж, мой первый тест был: ciao - привет, результат:

01 00 ee bc 90 22 05 00 ee bc 90 22 63 69 61 6f
00 ec b3 8c 07 00 ee bc 90 22 d0 bf d1 80 d0 b8
d0 b2 d0 b5 d1 82 00 ec b3 8c
Числа читаются правильно, проблема возникает, когда я записываю строки: я ожидал, что ciao ( 63 69 61 6f 00 ec b3 8c) было записано в 10 байтах (размер wchar_t), а не в 5, как это бывает при переводе на русский язык ( d0 bf d1 80 d0 b8 d0 b2 d0 b5 d1 82 00 ec b3 8c).
Явно что-то упускаю, но не могу понять что. Ребята, вы можете мне помочь? Кроме того, если вы знаете лучший подход к решению проблемы, я не предубежден.

РЕДАКТИРОВАТЬ: РЕШЕНИЕ

Следуя первому из двух методов, представленных @JamesKanze, я решил пожертвовать некоторой переносимостью и позволить системе сделать мою домашнюю работу:

void CDizionario::LeggiInt( int *pInt, ifstream& file )
{
    file.read( reinterpret_cast( pInt ), sizeof( int ) );
}

void CDizionario::LeggiWString( int nLStringa, wstring& strStringa, ifstream& file ) { char *pBuf; streamsize byteDaLeggere; wstring_convert> converter; byteDaLeggere = nLStringa; pBuf = new char[byteDaLeggere]; file.read( pBuf, byteDaLeggere ); strStringa = converter.from_bytes( pBuf ); delete [] pBuf; }

void CDizionario::ScriviInt( int nInt, ofstream& file ) const { file.write( reinterpret_cast( &nInt ), sizeof( nInt ) ); file.flush(); } void CDizionario::ScriviWString( const wstring* pStrStringa, ofstream& file ) const { char cTerminatore; string strStringa; wstring_convert> converter; strStringa = converter.to_bytes( pStrStringa->c_str() ); ScriviInt( strStringa.length() + 1, file ); file.write( strStringa.c_str(), strStringa.length() ); file.flush(); cTerminatore = '\0'; file.write( &cTerminatore, sizeof( char ) ); file.flush(); }


person IssamTP    schedule 23.05.2014    source источник


Ответы (2)


Вы недостаточно указали формат двоичного файла. Как вы представляете int (сколько байтов, с прямым порядком байтов или с прямым порядком байтов), а также кодировку и формат символов. Классическим сетевым представлением будет четыре байта (без знака) с обратным порядком байтов и кодировка UTF-8. Поскольку это то, что вы делаете для себя, вы можете (и, вероятно, должны) упростить, используя прямой порядок байтов для целых чисел и UTF-16LE; эти форматы соответствуют внутреннему формату под Windows. (Обратите внимание, что такой код не будет переносим даже в Apple или Linux на той же архитектуре, и есть небольшая вероятность того, что данные станут нечитаемыми в новой системе.) Это в основном то, что вы пытаетесь сделать, но ...

Вы пытаетесь написать необработанный двоичный файл. Единственный стандартный способ сделать это — использовать std::ofstreamstd::ifstream для чтения), при этом файл открывается в двоичном режиме и с локалью "C". Для всего остального будет (или может быть) какая-то трансляция кода и сопоставление в файле std::filebuf. Учитывая это (и тот факт, что этот способ записи данных не переносим ни на какую другую систему), вы можете просто использовать функции системного уровня: CreateFile для открытия, WriteFile и ReadFile для записи и чтения и CloseHandle для закрытия. (См. http://msdn.microsoft.com/en-us/library/windows/desktop/aa364232%28v=vs.85%29.aspx).

С другой стороны, если вы хотите быть переносимым, я бы рекомендовал использовать стандартный сетевой формат для данных. Отформатируйте его в буфер (std::vector<char>) и напишите это; на другом конце, прочитайте в буфер и проанализируйте его. Процедуры чтения и записи для целого числа (на самом деле целого числа без знака) могут выглядеть примерно так:

void
writeUnsignedInt( std::vector<char>& buffer, unsigned int i )
{
    buffer.push_back( (i >> 24) & oxFF );
    buffer.push_back( (i >> 16) & oxFF );
    buffer.push_back( (i >>  8) & oxFF );
    buffer.push_back( (i      ) & oxFF );
}

unsigned int
readUnsignedInt( 
    std::vector<char>::const_iterator& current,
    std::vector<char>::const_iterator end )
{
    unsigned int retval = 0;
    int shift = 32;
    while ( shift != 0 && current != end ) {
        shift -= 8;
        retval |= static_cast<unsigned char>( *current ) << shift;
        ++ current;
    }
    if ( shift != 0 ) {
        throw std::runtime_error( "Unexpected end of file" );
    }
    return retval;
}

Для символов вам придется преобразовать std::wstring в std::string в UTF-8, используя одну из многих процедур преобразования, доступных в сети. (Проблема в том, что ни кодировка std::wstring, ни даже размер wchar_t не стандартизированы. Из систем, с которыми я знаком, Windows и AIX используют UTF-16, большинство других UTF-32; в обоих случаях с байтом порядок зависит от платформы, что немного усложняет переносимый код.)

В глобальном масштабе мне проще делать все напрямую в UTF-8, используя char. Однако это не будет работать с интерфейсом Windows.

И, наконец, вам не нужен конечный '\0', если вы выводите длину.

person James Kanze    schedule 23.05.2014
comment
Что ж, читая это и учитывая тот факт, что я разрабатываю графический интерфейс MFC, я думаю, что портирование не должно представлять интереса на данный момент. Я проверю вашу первую подсказку. - person IssamTP; 23.05.2014
comment
Как насчет использования небольшой СУБД, такой как sqlite, оставив ей грязную работу? - person IssamTP; 23.05.2014
comment
Следуя вашим первым подсказкам, я позволил системе решить, как должны быть преобразованы байты. Я обновлю вопрос с решением для тех, кто заинтересован. - person IssamTP; 23.05.2014

@IssamTP, привет

Как упомянул @James Kanze, работа с иностранными нелатинскими языками неизбежно подталкивает вас к соглашениям о формате и локали для каждого байта. Поэтому, возможно, стоит не изобретать колесо и использовать существующие технологии, такие как XML (чтобы технология учитывала нюансы и правильно кодировала/декодировала нелатинские символы).

person Yury Schkatula    schedule 23.05.2014
comment
привет @YuriSchkatula, у тебя есть ссылка на одну из этих библиотек под рукой? - person IssamTP; 23.05.2014
comment
Это определенно решение, которое я бы порекомендовал в производственной среде, где важна переносимость, а структуры данных сложны и эволюционны. Однако для того, что он пытается сделать, они могут быть излишними: взаимодействие с Xerces, например, повлечет за собой больше работы, чем то, что ему нужно, чтобы реализовать то немногое, что ему нужно вручную. - person James Kanze; 23.05.2014
comment
@IssamTP, вы можете рассчитывать на MS XML microsoft.com/en -us/download/details.aspx?id=3988 или, например, Xerces или TinyXML. Существуют различные библиотеки, поэтому, возможно, стоит попробовать их в будущем. - person Yury Schkatula; 23.05.2014
comment
@YurySchkatula большое спасибо. Большое спасибо, я проверю эти материалы. - person IssamTP; 23.05.2014