Unicode代码指向字节和反向:你如何在C ++中做到这一点?

时间:2014-04-02 16:35:37

标签: java c++ c++11 character-encoding

作为介绍,我做Java并且过去做了很多C。

在Java中,String文字可以包含任何一组字素,只要您可以在编辑环境中输入它们;然后,编辑环境会将源文件保存在当时使用的任何字符编码中。

在运行时,只要编译器支持编码,字节代码将所有String文字表示为一组char s,其中char代表一个UTF-16代码单元。 (因此,BMP外部的Unicode代码点需要两个char;您可以使用Character.toChars()获取表示BMP外部的Unicode代码点所需的char数组。

您有一个字符编码类(Charset),即将char s序列编码为字节序列(CharsetEncoder)的过程,以及反向({{ 3}})。因此,无论您的源/目标使用哪种字符编码,无论是文件,套接字还是其他,您都可以根据需要进行编码/解码。

现在,让我们假设C ++ 11。它引入了std::u32stringstd::u16string;据我所知,这些是&#34;别名&#34;到std::basic_string<char32_t>std::basic_string<char16_t>,它们的净效果是在运行时,你声明的字符串常量(使用{{ 1}}和u"")由16位或32位实体组成,分别代表UTF-16或UTF-32代码单元。还有U""(后者的u8""类型是什么,因为它没有固定的长度?)。

其他重点:UTF-16有两种变体,LE和BE; java确实是因为在字节码级别,所有东西都是BE。 basic_string取决于代码中的字节顺序吗?

但即使经过几个小时的搜索,我也找不到答案:标准的C ++ 11可以做标准的JDK,即将任何字符串常量转换为合适的字节序列,反之,给定一个字符编码?我怀疑这会变得更加困难,因为在运行时基本上有三个字符串文字的表示,甚至没有char{16,32}_t这基本上是一个字节数组...


(编辑:添加了相关javadoc的链接)

3 个答案:

答案 0 :(得分:2)

您可以使用codecvt locale facet进行转换。

用法有点不直观,但这就是我所做的:

/** Convert utf8 stream to UCS-4 stream */
u32string decode(string utf8)
{
    std::wstring_convert<std::codecvt_utf8<char32_t>,char32_t> convert;
    return convert.from_bytes(utf8);
}

/** Convert UCS-4 stream to utf8 stream */
string encode(u32string ucs4)
{
    std::wstring_convert<std::codecvt_utf8<char32_t>,char32_t> convert;
    return convert.to_bytes(ucs4);
}

它需要一个不错的编译器,对我来说只有clang工作正常,gcc编译但生成了无效的结果(更新版本的gcc可能没问题。)

答案 1 :(得分:2)

C ++没有指定源文件编码。实际上,它支持EBCDIC。所有C ++ 11编译器都支持UTF-8,许多编译器通过传递适当的标志来支持其他编码。

该标准为基本源字符集之外的字符指定了转义码语法,该语法基本上包含该语言使用的字符。基本源字符集之外的字符称为“扩展字符”,在编译源代码之前,它们将被相应的代码替换,甚至是预处理的。这可以确保源代码的含义与其编码无关。

char32_tchar16_t没有内置的字节顺序。它们只相当于uint32_tuint16_t。您可以说它们继承了本机字节序,但直接将对象表示序列化为字节是滥用。

要可靠地指定UTF-8文字,并覆盖任何相反的编译器设置,请使用准备进行序列化的u8""u""U""没有字节序,因为这些值已经烘焙到程序中。

要序列化,您可以使用codecvt_utf8codecvt_utf16类模板,这些模板采用指定文件格式的编译时模板标志:

enum codecvt_mode {
    consume_header = 4,
    generate_header = 2,
    little_endian = 1
};

要设置流file(在二进制模式下)将char32_t字符串编码为带字节顺序标记的UTF-16LE,您可以使用

std::basic_ofstream< char32_t > file( path, std::ios::binary );

file.imbue( std::locale( file.locale(), new std::codecvt_utf16<
        char32_t,
        std::codecvt_mode::generate_header | std::codecvt_mode::little_endian
     >{} ) );

这比在输出之前进行翻译更可取。

答案 2 :(得分:1)

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

template<typename Facet>
struct usable_facet : Facet {
  using Facet::Facet;
  ~usable_facet() = default;
};

int main() {    
    using utf16_codecvt = usable_facet<std::codecvt<char16_t, char, std::mbstate_t>>;
    using utf32_codecvt = usable_facet<std::codecvt<char32_t, char, std::mbstate_t>>;

    std::wstring_convert<utf16_codecvt, char16_t> u16convert; // bidirectional UTF-16/UTF-8 conversion
    std::wstring_convert<utf32_codecvt, char32_t> u32convert; // bidirectional UTF-32/UTF-8 

    std::string utf8 = u16convert.to_bytes(u"UTF-16 data");
    std::u16string utf16 = u16convert.from_bytes(u8"UTF-8 data");

    utf8 = u32convert.to_bytes(U"UTF-32 data");
    std::u32string utf32 = u32convert.from_bytes(u8"UTF-8 data");
}

你也可以使用其他方面,但要小心,因为它们并不都是听起来像他们应该做的那样。如果您使用codecvt_utf8,则char16_t不会转换为UTF-16,codecvt_utf16会使用UTF-16作为 narrow 编码等。这些名称有意义它们的预期用途,但它们与wstring_convert混淆。

您也可以wstring_convert使用codecvt_byname支持的区域设置使用的任何编码(但是,您只能在该区域设置的char编码和自己的wchar_t编码之间进行转换不在语言环境窄编码和固定Unicode编码之间。语言环境指定自己的wchar_t编码,它不一定是Unicode编码,也不一定与其他语言环境使用的wchar_t编码相同。)

    using locale_codecvt = usable_facet<std::codecvt_byname<wchar_t, char, std::mbstate_t>>;

    std::wstring_convert<locale_codecvt, wchar_t> legacy_russian(new locale_codecvt("ru_RU")); // non-portable locale name

    std::string legacy_russian_data = /* ... some source of legacy encoded data */
    std::wstring w = legacy_russian.from_bytes(legacy_russian_data);

在任意区域设置编码文本和任何Unicode编码之间进行转换的唯一标准方法是支持度较低的<cuchar> header,其功能较低,如c16rtombc32rtomb