将内存中的 16 位转换为 std::string

Posted

技术标签:

【中文标题】将内存中的 16 位转换为 std::string【英文标题】:Convert 16 bits in memory into std::string 【发布时间】:2013-07-29 16:26:15 【问题描述】:

我从内存中的结构中获取 16 位,我需要将它们转换为字符串。 16位代表一个unicode字符:

typedef struct my_struct 
    unsigned    unicode     : 16;
 my_struct;

我首先将这些位转换为一个无符号字符,它适用于小到足以容纳一个字符的值。但是,对于像“♪”这样的字符,它会错误地截断。这是我目前所拥有的:

        char buffer[2] =  0 ;
        wchar_t wc[1] =  0 ;

        wc[0] = page->text[index].unicode;
        std::cout << wc[0] << std::endl; //PRINT LINE 1
        int ret = wcstombs(buffer, wc, sizeof(buffer));
        if(ret < 0)
            printf("SOMETHING WENT WRONG \n");
        std::string my_string(buffer);
        printf("%s \n", my_string.c_str()); //PRINT LINE 2

打印第 1 行当前打印:“9834”,打印第 2 行打印:“”(空字符串)。我试图让 my_string 包含 '♪'。

【问题讨论】:

你不能将 16 位放入 8 位而不丢失任何东西。您的选择是从(显然)UTF-16 转换为 UTF-8(使用多个 8 位字符来保存一个 16 位代码单元)或将其保留为 UTF-16(例如,std::wstring 保存单位为 @987654325 @,可能是 UTF-16)。如果这些都不起作用,您可以直接在您的my_struct 上实例化std::basic_stringstd::basic_string&lt;my_struct&gt; whatever; 你不能把 16 磅面粉放在一个 8 磅的袋子里。 @Jerry Coffin:有点迂腐,但 std::*string 不存储(或关心)字符编码。即使 wchar_t 是 16 位的,它也可能是 UCS-2。通常,您需要 UCS-4 或 UTF-8。 UTF-16 结合了两者的缺点,没有任何好处。 @DanielKO:我当然不会推荐 UTF-16 作为一般规则——这只是反映了 OP 对 16 位的使用。 UCS-2 obsolete 已经很长时间了。 @mirandak:除非库真的很旧(并且在过去十年左右没有更新),否则它可能是 UTF-16 而不是 UCS-2。 【参考方案1】:

请阅读一下“字符编码”的含义,例如:What is character encoding and why should I bother with it

然后弄清楚您输入的是什么编码,以及您需要在输出中使用什么编码。这意味着要弄清楚您的文件格式/GUI 库/控制台期望什么。

然后使用像 libiconv 这样可靠的东西在它们之间进行转换,而不是使用如此实现定义的几乎无用的 wcstombs()+wchar_t。

例如,您可能会发现您的输入是 UCS-2,您需要将其输出为 UTF-8。我的系统有 32 位 wchar_t,我不会指望它从 UCS-2 转换为 UTF-8。

【讨论】:

【参考方案2】:

要将 UTF-16 转换为 UTF-8,请使用 codecvt_utf8&lt;char16_t&gt;

#include <iostream>
#include <string>
#include <locale>
#include <codecvt>

int main() 
    char16_t wstr16[2] = 0x266A, 0;
    auto conv = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>;
    auto u8str = std::stringconv.to_bytes(wstr16);
    std::cout << u8str << '\n';

【讨论】:

auto u8str = std::string conv.to_bytes( wstr16 ) ; 的意义何在,而不是 std::string u8str( conv.to_bytes( wstr16 ) );,除了可能混淆? @JamesKanze 这是 AAA 风格:herbsutter.com/2013/06/13/… @mirandak 您在评论 std::string s("\u266A"); 中使用了嵌入式 Unicode 代码点,这是 C++11 的一项功能。 @ecatmur 另一个反模式。如果您不想命名类型,请使用 Python。但除了少数特殊情况外,您确实想要命名类型,以便读者对发生的事情有所了解。 AAA 只是糟糕的工程。 @ecatmur 它们在我的 C++98 标准副本中(在 C90 中也是如此)。【参考方案3】:

如果我正确完成了转换,UTF-16 中的 0x9834(16 位 Unicode) 转换为三个字节序列 0xE9、0xA0、 UTF-8(8 位 Unicode)中的 0xB4。不知道其他窄 字节编码,但我怀疑任何会短于 2 个字节。 您将两个字节的缓冲区传递给wcstombs,这意味着 返回的最多 1 个字节的字符串。 wcstombs 停止 翻译(没有失败!)当没有更多空间时 目标缓冲区。您也未能L'\0' 终止 输入缓冲区。目前这不是问题,因为 wcstombs 将在它到达那里之前停止翻译,但你 通常应该添加额外的L'\0'

那该怎么办:

首先,最重要的是,在调试此类事情时,请查看 wcstombs 的返回值。我敢打赌它是0,因为 空间不足。

其次,我会给自己一点余地。合法的统一码 最多可以在 UTF-8 中产生四个字节,所以我会分配 输出至少 5 个字节(不要忘记结尾的 '\0')。 同样,您需要一个尾随L'\0' 作为输入。 所以:

char buffer[ 5 ];
wchar_t wc[] =  page->text[index].unicode, L'\0' ;
int ret = wcstombs( buffer, wc, sizeof( buffer ) );
if ( ret < 1 )     //  And *not* 0
    std::cerr << "OOPS\n";

std::string str( buffer, buffer + ret );
std::cout << str << '\n';

当然,毕竟,还有一个问题是什么 (最终)显示设备使用 UTF-8(或任何 多字节窄字符编码是---UTF-8差不多 在 Unix 下通用,但我不确定 Windows。)但是 既然你说显示"\u9834" 似乎有效,它 应该没问题。

【讨论】:

Windows 控制台理论上可以显示 UTf-8,但要真正做到这一点很棘手。 我知道你无法窥视我的计算机,但是一旦出现值 > 127 的字符,wcstombs 就会返回 -1。 编辑: 错误不是字符,但你知道我的意思 认为这是一个语言环境问题,因为我打了 "setlocale(LC_ALL, "");"在那里,它突然起作用了!现在要弄清楚我真正需要什么语言环境...但是谢谢!!! 问题中的 9834 值似乎是十进制的。显示的音符是 U+266A(恰好是 9834 的十六进制)。 @mirandak 是的。 wcstombs 对区域设置敏感,并且可能在默认 "C" 区域设置中翻译大于 127 的字符。我应该提到这一点。 (但事实上你没有提到从中得到错误,并且你可以显示"\9834" 让我相信你已经解决了这些方面。)

以上是关于将内存中的 16 位转换为 std::string的主要内容,如果未能解决你的问题,请参考以下文章

如何将 16 位值的数组转换为 base64?

将 std::string 转换为 QString

将 std::string 转换为无符号字符数组

MFC:无法将 std::string 转换为 LPWSTR 放入函数 [重复]

将包含位的字符串转换为向量<bool>

如何在 C++11 中将 std::string 转换为 std::u32string?