在二进制文件中读取和写入字符串c ++

Posted

技术标签:

【中文标题】在二进制文件中读取和写入字符串c ++【英文标题】:Reading and write strings in binary files c++ 【发布时间】:2014-05-23 08:49:00 【问题描述】:

我正在尝试开发一个小型 Windows 应用程序,以提高我在 MFC 框架之外的 C++ 技能并帮助我学习外语。我想做一个小型的、个人的和易于移植和使用的字典,虽然我在开发 GUI 方面没有任何问题,但我在保存和恢复数据方面确实很痛苦。我的想法是写下一个结构如下的二进制文件:

int (representing the number of words)
int (representing the string length + \0)
sequence of characters zero-terminated.
现在,我正在学习俄语,我的主要语言是意大利语,所以我不能使用普通的旧 std::string 来写单词,另外,谢谢微软,我正在使用 VS2010 与所有商品和bads随之而来。我正在向您展示我写下 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& 文件) 字符 *pBuf; 流大小字节DaLeggere; wstring_convert> 转换器; byteDaLeggere = nLStringa; pBuf = new char[byteDaLeggere]; file.read(pBuf, byteDaLeggere); strStringa = converter.from_bytes(pBuf); 删除 [] pBuf;

void CDizionario::ScriviInt(int nInt, ofstream& 文件) const file.write(reinterpret_cast(&nInt),sizeof(nInt)); 文件.flush(); void CDizionario::ScriviWString( const wstring* pStrStringa, ofstream& 文件 ) const 字符终止; 字符串 strStringa; wstring_convert> 转换器; strStringa = converter.to_bytes(pStrStringa->c_str()); ScriviInt(strStringa.length() + 1, 文件); file.write(strStringa.c_str(), strStringa.length()); 文件.flush(); c终止 = '\0'; file.write( &cTerminatore, sizeof( char ) ); 文件.flush();

【问题讨论】:

【参考方案1】:

您没有充分指定二进制文件的格式。 你如何表示int(多少字节,大端或 little-endian),也不是编码和格式 人物。经典的网络表示将是 大端四字节(无符号)整数和 UTF-8。自从 这是你为自己做的事情,你可以(并且 可能应该)简化,对整数使用 little-endian,并且 UTF-16LE;这些格式对应于下的内部格式 窗户。 (请注意,这样的代码不能移植,甚至不能移植 到同一架构上的 Apple 或 Linux,并且有 数据在新系统上变得不可读的可能性很小。) 这基本上是你似乎正在尝试的,但是......

您正在尝试编写原始二进制文件。唯一的标准方法 这将是使用std::ofstream(和std::ifstream 读取),以二进制模式打开文件 "C" 语言环境。对于其他任何事情,将会(或可能)有一些 std::filebuf 中的某种代码翻译和映射。 鉴于此(以及这种写入数据的方式不是 可移植到任何其他系统),您可能只想使用 系统级功能:CreateFile 打开,WriteFileReadFile 读写,CloseHandle 关闭。 (看 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364232%28v=vs.85%29.aspx)。

另一方面,如果您想要便携,我会推荐 使用数据的标准网络格式。将其格式化为 一个缓冲区(std::vector&lt;char&gt;),然后写下它;另一方面 结束,读入一个缓冲区,然后解析它。读和写 整数(实际上是无符号整数)的例程可能是 类似:

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 转换为 UTF-8 中的 std::string,使用众多转换例程之一 在网络上可用。 (问题是编码 std::wstring,甚至wchar_t 的大小,都不是 标准化。在我熟悉的系统中,Windows 和 AIX 使用 UTF-16,大多数其他 UTF-32;在这两种情况下,字节 订单取决于平台。这使得可移植代码有点 更难。)

在全球范围内,我发现直接在 UTF-8,使用char。这不适用于 Windows 然而,界面。

最后,如果你输出,你不需要尾随 '\0' 长度。

【讨论】:

好吧,阅读本文并考虑到我正在开发 MFC GUI 的事实,我想移植现在不应该引起人们的兴趣。我会检查你的第一个提示。 使用像 sqlite 这样的小型 dbms 怎么样,把脏工作留给它? 按照您的第一个提示,我让系统决定如何转换字节。我会为有兴趣的人更新问题的解决方案。【参考方案2】:

@IssamTP, привет

正如@James Kanze 所提到的,使用外国非拉丁语言不可避免地会将您推向按字节的格式约定和语言环境。因此,不重新发明***并使用现有技术(如 XML)可能是值得的(因此该技术将服务于细微差别并正确编码/解码非拉丁字符)。

【讨论】:

привет @YuriSchkatula,你手头有这些库之一的链接吗? 这绝对是我在生产环境中推荐的解决方案,其中可移植性很重要,并且数据结构复杂且不断发展。然而,对于他正在尝试做的事情,它们可能有点过头了:例如,与 Xerces 的接口将需要更多的工作,而他需要手动实现他需要的一点点。 @IssamTP,例如,您可以依靠 MS XML microsoft.com/en-us/download/details.aspx?id=3988 或 Xerces 或 TinyXML。有各种各样的库,因此至少值得将来尝试一下。 @YurySchkatula большое спасибо。非常感谢,我会检查这些东西。

以上是关于在二进制文件中读取和写入字符串c ++的主要内容,如果未能解决你的问题,请参考以下文章

C语言二进制流写入文件

在 Matlab 中读取和写入二进制文件

VB.NET 怎么读写二进制文件,类似Open

C语言对结构体文件的读取

如何用Java或C语言解析二进制文件为文本文件?

13-Groovy-读取内容和写入文件