【问题标题】:Unicode to UTF8 ConversationUnicode 到 UTF8 的对话
【发布时间】:2017-04-10 11:40:47
【问题描述】:

我正在尝试将 Unicode 字符串转换为 UTF8 字符串:

#include <stdio.h>
#include <string>
#include <atlconv.h>
#include <atlstr.h>

using namespace std;

CStringA ConvertUnicodeToUTF8(const CStringW& uni)
{
    if (uni.IsEmpty()) return "";
    CStringA utf8;
    int cc = 0;

    if ((cc = WideCharToMultiByte(CP_UTF8, 0, uni, -1, NULL, 0, 0, 0) - 1) > 0)
    {
        char *buf = utf8.GetBuffer(cc);
        if (buf) WideCharToMultiByte(CP_UTF8, 0, uni, -1, buf, cc, 0, 0);
        utf8.ReleaseBuffer();
    }
    return utf8;
}

int main(void)
{
    string u8str = ConvertUnicodeToUTF8(L"gökhan");

    printf("%d\n", u8str.size());

    return 0;
}

我的问题是:u8str.size() 返回值应该是 6 吗?现在打印 7!

【问题讨论】:

  • 首先,Unicode 不是一种编码方案,它是一大堆,UTF-8,UTF-16。微软可能主要为此负责,因为他们的 Unicode 是 UTF-16。但他们也叫 ASCII ANSI,其实不是。
  • @Titone 他们不调用 ASCII ANSI。 ASCII 是 7 位编码,MS 称之为 ANSI 是 8 位编码。在微软的辩护中,当他们引入 Unicode 时,世界看起来非常不同。 UTF-8 和 UTF-16 和 UTF-32 不存在。当时是UCS-2。 MS 计划使用 UTF-16 而不是 UTF-8,但它有完全合理的历史原因。

标签: c++ winapi unicode utf-8


【解决方案1】:

7 是正确的。非 ASCII 字符 ö 用两个字节编码。

【讨论】:

  • 这里的重要问题是大多数strlen风格的函数,包括std::string.size(),返回代码单元的计数,而不是代码的数量点。可以使用多个代码单元来表示单个代码点。 ö 是单个代码点,但在像 UTF-8 这样的单字节编码中,它实际上是使用两个代码单元进行编码的。如果我有一个非常好的链接来解释这种差异,我会将其编辑为答案,但我不知道有一个。维基百科有效,但需要一些努力才能理解其中的区别。
【解决方案2】:

根据定义,“多字节”是指每个 unicode 实体最多可以占用 6 个字节,见这里:How many bytes does one Unicode character take?

延伸阅读:http://www.joelonsoftware.com/articles/Unicode.html

【讨论】:

  • 在实践中不使用 UTF-8 的 5 字节和 6 字节变体,因为这样的高代码点不是由 Unicode 定义的,并且与 UTF-16 不兼容。 UTF-8 被 RFC 3629 限制为 4 个字节,因此它只能编码 UTF-16 支持的代码点范围。
【解决方案3】:

Unicode 代码点在 UTF-16 中使用 2 或 4 个字节,但在 UTF-8 中使用 1-4 个字节,具体取决于其值。 UTF-16 中的 2 字节代码点值可能在 UTF-8 中使用 3-4 字节,因此 UTF-8 字符串可能使用比相应的 UTF-16 字符串更多的字节。 UTF-8 对于拉丁/西方语言往往更紧凑,但 UTF-16 对于东亚语言往往更紧凑。

std::(w)string::size()CStringT::GetLength() 计算编码代码单元的数量,而不是代码点的数量。在您的示例中,"gökhan" 编码为:

UTF-16LE:0x0067 0x00f6 0x006b 0x0068 0x0061 0x006e
UTF-16BE:0x6700 0xf600 0x6b00 0x6800 0x6100 0x6e00
UTF-8:0x67 0xc3 0xb6 0x6b 0x68 0x61 0x6e

请注意,ö 在 UTF-16 中使用 1 个代码单元(LE:0x00f6,BE:0xf600)进行编码,但在 UTF-8 中使用 2 个代码单元(0xc3 0xb6)。这就是为什么您的 UTF-8 字符串的大小为 7 而不是 6。

话虽如此,当以 -1 作为源长度调用 WideCharToMultiByte()MultiByteToWideChar() 时,函数必须手动计算字符数,并且当目标指针为 NULL 时,返回值将包含空终止符的空间.使用CStringA/Wstd::(w)string 等时,您不需要额外的空间,并且当源已经知道其长度时,您不需要计算字符的开销。当你知道它时,你应该总是指定实际的源长度,例如:

CStringA ConvertUnicodeToUTF8(const CStringW& uni)
{
    CStringA utf8;

    int cc = WideCharToMultiByte(CP_UTF8, 0, uni, uni.GetLength(), NULL, 0, 0, 0);
    if (cc > 0)
    {
        char *buf = utf8.GetBuffer(cc);
        if (buf)
        {
            cc = WideCharToMultiByte(CP_UTF8, 0, uni, uni.GetLength(), buf, cc, 0, 0);
            utf8.ReleaseBuffer(cc);
        }
    }

    return utf8;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-14
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    • 2011-06-09
    • 2017-01-29
    相关资源
    最近更新 更多