C++ UTF-16 到 char 的转换(Linux/Ubuntu)



我正在尝试帮助一个朋友完成一个应该是 1H 的项目,现在已经是 3 天了。不用说,我感到非常沮丧和愤怒;-)呜呜呜...我呼吸。

所以用C++编写的程序只是读取一堆文件并处理它们。问题是我的程序读取使用UTF-16编码的文件(因为文件包含用不同语言编写的单词),并且简单地使用ifstream似乎不起作用(它读取并输出垃圾)。我花了一段时间才意识到这是因为文件是 UTF-16 格式的。

现在,我花了整个下午的时间在网上试图查找有关读取UTF16文件并将UTF16行的内容转换为字符的信息!我似乎不能!这是一场噩梦。我尝试学习我以前从未使用过的<locale><codecvt>,wstring等(我专门研究图形应用程序,而不是桌面应用程序)。我就是不明白。

这是我所做的票价(但不起作用):

std::wifstream file2(fileFullPath);
std::locale loc (std::locale(), new std::codecvt_utf16<char32_t>);
std::cout.imbue(loc);
while (!file2.eof()) {
    std::wstring line;
    std::getline(file2, line);
    std::wcout << line << std::endl;
}

这是我能想到的最大值,但它甚至不起作用。而且它并没有做得更好。但问题是,无论如何,我都不明白自己在做什么。

所以请帮忙!这真的让我发疯,我什至可以读取 G*** D*** 文本文件。

最重要的是,我的朋友使用 Ubuntu(我使用 clang++),这段代码需要 -stdlib=libc++,这似乎不受 gcc 的支持(即使他使用了一个非常高级的 gcc 版本,我相信是 4.6.3)。所以我什至不确定使用编解码器和语言环境是一个好主意(如"可能")。会有更好的(另一个)选择吗?

如果我仅从命令行(使用 linux 命令)将所有文件转换为 utf-8,我可能会丢失信息吗?

非常感谢,如果你在这方面帮助我,我将永远感激你。

如果我仅从命令行(使用 linux 命令)将所有文件转换为 utf-8,我可能会丢失信息吗?

不可以,所有 UTF-16 数据都可以无损转换为 UTF-8。这可能是最好的办法。


当引入宽字符时,它们旨在成为专门用于程序内部的文本表示形式,并且永远不会作为宽字符写入磁盘。宽流通过将您写出的宽字符转换为输出文件中的窄字符,并在读取时将文件中的窄字符转换为内存中的宽字符来反映这一点。

std::wofstream wout("output.txt");
wout << L"Hello"; // the output file will just be ASCII (assuming the platform uses ASCII).
std::wifstream win("ascii.txt");
std::wstring s;
wout >> s; // the ascii in the file is converted to wide characters.

当然,实际编码取决于流的浸润区域设置中的codecvt方面,但流所做的是使用该codecvt在写入时使用该分面从wchar_t转换为char,并在读取时从char转换为wchar_t


然而,自从有些人开始用 UTF-16 写出文件以来,其他人只需要处理它。他们对C++流执行此操作的方式是创建codecvt面,这些分面将char视为持有半个 UTF-16 代码单元,这就是codecvt_utf16所做的。

因此,通过该解释,以下是代码的问题:

std::wifstream file2(fileFullPath); // UTF-16 has to be read in binary mode
std::locale loc (std::locale(), new std::codecvt_utf16<char32_t>); // do you really want char32_t data? or do you want wchar_t?
std::cout.imbue(loc); // You're not even using cout, so why are you imbuing it?
// You need to imbue file2 here, not cout.
while (!file2.eof()) { // Aside from your UTF-16 question, this isn't the usual way to write a getline loop, and it doesn't behave quite correctly
    std::wstring line;
    std::getline(file2, line);
    std::wcout << line << std::endl; // wcout is not imbued with a locale that will correctly display the original UTF-16 data
}

这是重写上述内容的一种方法:

// when reading UTF-16 you must use binary mode
std::wifstream file2(fileFullPath, std::ios::binary);
// ensure that wchar_t is large enough for UCS-4/UTF-32 (It is on Linux)
static_assert(WCHAR_MAX >= 0x10FFFF, "wchar_t not large enough");
// imbue file2 so that it will convert a UTF-16 file into wchar_t data.
// If the UTF-16 files are generated on Windows then you probably want to
// consume the BOM Windows uses
std::locale loc(
    std::locale(),
    new std::codecvt_utf16<wchar_t, 0x10FFFF, std::consume_header>);
file2.imbue(loc);
// imbue wcout so that wchar_t data printed will be converted to the system's
// encoding (which is probably UTF-8).
std::wcout.imbue(std::locale(""));
// Note that the above is doing something that one should not do, strictly
// speaking. The wchar_t data is in the wide encoding used by `codecvt_utf16`,
// UCS-4/UTF-32. This is not necessarily compatible with the wchar_t encoding
// used in other locales such as std::locale(""). Fortunately locales that use
// UTF-8 as the narrow encoding will generally also use UTF-32 as the wide
// encoding, coincidentally making this code work
std::wstring line;
while (std::getline(file2, line)) {
  std::wcout << line << std::endl;
}

我调整、纠正和测试了 Mats Petersson 令人印象深刻的解决方案。

int utf16_to_utf32(std::vector<int> &coded)
{
    int t = coded[0];
    if (t & 0xFC00 != 0xD800)
    {
    return t;
    }
    int charcode = (coded[1] & 0x3FF); // | ((t & 0x3FF) << 10);
    charcode += 0x10000;
    return charcode;
}

#ifdef __cplusplus    // If used by C++ code,
extern "C" {          // we need to export the C interface
#endif
void convert_utf16_to_utf32(UTF16 *input,
                            size_t input_size,
                            UTF32 *output)
{
     const UTF16 * const end = input + 1 * input_size;
     while (input < end){
       const UTF16 uc = *input++;
       std::vector<int> vec; // endianess
       vec.push_back(U16_LEAD(uc) & oxFF);
       printf("LEAD + %.4xn",U16_LEAD(uc) & 0x00FF);
       vec.push_back(U16_TRAIL(uc) & oxFF);
       printf("TRAIL + %.4xn",U16_TRAIL(uc) & 0x00FF);
       *output++ = utf16_to_utf32(vec);
     }
}
#ifdef __cplusplus
}
#endif

UTF-8 能够表示所有有效的 Unicode 字符(码位),这比 UTF-16(涵盖前 110 万个码位)要好。[虽然,正如评论所解释的那样,没有超过 110 万值的有效 Unicode 代码点,因此 UTF-16 对于所有当前可用的代码点都是"安全的"——并且可能在未来很长一段时间内,除非我们确实有额外的外星访问者拥有非常复杂的书写语言......]

它通过在必要时使用多个字节/字来存储单个代码点(我们称之为字符)来实现这一点。在 UTF-8 中,这由设置的最高位标记 - 在"多字节"字符的第一个字节中,设置前两位,在下一个字节中设置顶部位,从顶部开始的下一个位为零。

要将任意代码点转换为 UTF-8,您可以使用我之前答案中的代码。(是的,这个问题谈论的与您要求的相反,但我答案中的代码涵盖了转换的两个方向)

从 UTF16 转换为"整数"将是一种类似的方法,但输入的长度除外。如果幸运的话,你甚至可以不这样做......

UTF16 使用范围 D800-DBFF 作为第一部分,其中包含 10 位数据,然后以下项是 DC00-DFFF,包含以下 10 位数据。

遵循 16 位的代码...

用于 16 位到 32 位转换的代码(我只对此进行了一点测试,但它似乎工作正常):

std::vector<int> utf32_to_utf16(int charcode)
{
    std::vector<int> r;
    if (charcode < 0x10000)
    {
    if (charcode & 0xFC00 == 0xD800)
    {
        std::cerr << "Error bad character code" << std::endl;
        exit(1);
    }
    r.push_back(charcode);
    return r;
    }
    charcode -= 0x10000;
    if (charcode > 0xFFFFF)
    {
    std::cerr << "Error bad character code" << std::endl;
    exit(1);
    }
    int coded = 0xD800 | ((charcode >> 10) & 0x3FF);
    r.push_back(coded);
    coded = 0xDC00 | (charcode & 0x3FF);
    r.push_back(coded);
    return r;
}

int utf16_to_utf32(std::vector<int> &coded)
{
    int t = coded[0];
    if (t & 0xFC00 != 0xD800)
    {
    return t;
    }
    int charcode = (coded[1] & 0x3FF) | ((t & 0x3FF) << 10);
    charcode += 0x10000;
    return charcode;
}

最新更新