C++中字符编码的转换(Unicode、UTF-8、ANSI)

C++的项目,字符编码是一个大坑,不同平台之间的编码往往不一样,如果不同编码格式用一套字符读取格式读取就会出现乱码。因此,一般都是转化成UTF-8这种平台通用,且支持性很好的编码格式。

Unicode、UTF-8的概念不做过多解释,这里说一下ANSI,我第一次看到这个名词,我看成了ASCII。被Mentor狠批一顿。

ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x00 ~ 0x7F范围的1 个字节来表示 1 个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码。
 
不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准。这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码。

以上内容摘自百度百科,可以看出,ANSI和ASCII还是有关系的。ANSI也叫本地码。

我们要做到能在Unicode、UTF-8、ANSI这三种编码格式中自由转换。如下图所示:

在这里插入图片描述

在C++中,要怎么做呢?当然是用标准库的东西啦,C++11对国际化标准做得还是可以的,提供了这些接口,正如图中虚线所示,标准库没有提供UTF-8到ANSI的互相转化接口,但是我们可以自己封转接口,借用这条路(UTF-8 <=> Unicode <=> ANSI)来实现。

因此,接下来就聊聊UTF8 <=> UnicodeUnicode <=> ANSI

UTF8 <=> Unicode

先看代码:

std::string UnicodeToUTF8(const std::wstring & wstr)
{
    std::string ret;
    try {
        std::wstring_convert< std::codecvt_utf8<wchar_t> > wcv;
        ret = wcv.to_bytes(wstr);
    } catch (const std::exception & e) {
        std::cerr << e.what() << std::endl;
    }
    return ret;
}

std::wstring UTF8ToUnicode(const std::string & str)
{
    std::wstring ret;
    try {
        std::wstring_convert< std::codecvt_utf8<wchar_t> > wcv;
        ret = wcv.from_bytes(str);
    } catch (const std::exception & e) {
        std::cerr << e.what() << std::endl;
    }
    return ret;
}

UTF-8是多字节字符串(multibyte string),而Unicode是宽字符字符串(wchar_t string)。

而C++11提供了wstring_convert这个类,这个类可以在wchar_t stringmultibyte string之间来回转换;

codecvt_utf8可以提供UTF-8的编码规则。这个类在#include <codecvt>中。有了wstring_convert提供宽字符字符串到多字节字符串的转化,而这个转换规则由codecvt_uft8提供。这样子就可以实现UTF8和Unicode的互相转换。

  • 从UTF8到Unicode调用成员函数wstring_convert::from_bytes
  • 从Unicode到UTF8调用成员函数wstring_convert::to_bytes

Unicode <=> ANSI

std::string UnicodeToANSI(const std::wstring & wstr)
{
    std::string ret;
    std::mbstate_t state = {};
    const wchar_t *src = wstr.data();
    size_t len = std::wcsrtombs(nullptr, &src, 0, &state);
    if (static_cast<size_t>(-1) != len) {
        std::unique_ptr< char [] > buff(new char[len + 1]);
        len = std::wcsrtombs(buff.get(), &src, len, &state);
        if (static_cast<size_t>(-1) != len) {
            ret.assign(buff.get(), len);
        }
    }
    return ret;
}

std::wstring ANSIToUnicode(const std::string & str)
{
    std::wstring ret;
    std::mbstate_t state = {};
    const char *src = str.data();
    size_t len = std::mbsrtowcs(nullptr, &src, 0, &state);
    if (static_cast<size_t>(-1) != len) {
        std::unique_ptr< wchar_t [] > buff(new wchar_t[len + 1]);
        len = std::mbsrtowcs(buff.get(), &src, len, &state);
        if (static_cast<size_t>(-1) != len) {
            ret.assign(buff.get(), len);
        }
    }
    return ret;
}

标准库提供了wcsrtombsmbsrtowcs这两个函数,当然C的标准库也提供了这两个函数。

讲下wcsrtombs,这个函数把宽字符串转成多字节字符串。编码规则受地域的LC_CTYPE影响。因此这个函数可以用于本地码的转化(和本地的编码息息相关)。

因此,有关于本地码的使用,在代码中要加上下列语句:

setlocale(LC_CTYPE, "");

目的是让本地码生效,这条代码的作用就是让C++语言的Locale(地域)和本地的地域相同。在Linux下可以运行locale命令看看:

在这里插入图片描述

可以看到,LC_CTYPE = en_US.UTF-8,这表示英文,英国,UTF-8编码,也就是说本地码就是这个。

当然,你也可以在setlocale中指定一些编码规则,把wcsrtombs用于别的编码转化,但是,这里不推荐,因为setlocale是全局的,设置了这个就会影响其他地方的编码。

wcsrtombs的四个参数分别代表什么意思呢?

std::size_t wcsrtombs( char* dst, const wchar_t** src, std::size_t len, std::mbstate_t* ps );

  • dst,转化后的结果存入dst指向的内存;
  • src,待转化的字符串的指针的指针;
  • lendst指向内存的可用字节数;
  • ps,转换的状态,一般默认初始化就好了;
  • return type,转化后结果的长度,不包含\0

注意:如果dst == nullptr,这个时候wcstombs的返回值表示会有这么多字节的结果产生,因此,我们可以拿到这个返回值去新建一个数组来存储new char[len + 1]。所以,一般调用两次wcstombs

mbsrtowcs同理。

UTF-8 <=> ANSI

Unicode为中介装换便是。

std::string UTF8ToANSI(const std::string & str)
{
    return UnicodeToANSI(UTF8ToUnicode(str));
}

std::string ANSIToUTF8(const std::string & str)
{
    return UnicodeToUTF8(ANSIToUnicode(str));
}

总结

C++11的标准库还是挺强大的,虽然这么强大,但是很多特性还不了解,因此还是要多扩宽自己的视野,不然有好东西都不知道用,那就棒槌了。

对了,在Linux下加上setlocale(LC_CTYPE, "")后程序在命令行中可以正常显示,不加有可能不正常显示,原因是setlocale(LC_CTYPE, "")也影响了cout,全局的嘛;而在CodeBlocks下不能正常显示,不知道为什么,但是调试的过程中,观察到了正常的结果;Visual Studio中没有做实验,不过应该没问题。


参考:

  • 48
    点赞
  • 243
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
### 回答1: 将UTF-8编码转换ANSI编码是一种常见的转换需求。UTF-8是一种变长编码方式,能够表示全球各种文字字符,而ANSI则是一种单字节编码,通常用于英语等西方语言。下面是一种将UTF-8转换ANSI的方法: 1. 首先,明确一点,UTF-8编码字符可能需要多个字节来表示,而ANSI编码只需要一个字节。因此,在转换过程,如果UTF-8编码字符超出了ANSI编码范围,将无法直接进行转换。 2. 创建一个空字符串,用于存储转换后的ANSI编码。 3. 遍历UTF-8编码的每个字节: - 如果字节的最高位是0,表示该字节单独可以表示一个ANSI字符,直接将该字节添加到ANSI编码。 - 如果字节的最高位是1,表示该字节与后续的字节一起组成一个UTF-8字符。读取后续的字节,构成完整的UTF-8字符。 - 判断UTF-8字符是否超出了ANSI编码范围。如果超出了,则不能进行转换。 - 如果没有超出ANSI编码范围,找到对应的ANSI字符,将其添加到ANSI编码。 4. 返回转换后的ANSI编码。 需要注意的是,由于ANSI编码只能表示一部分字符,因此某些UTF-8字符可能无法转换为对应的ANSI字符。在实际转换时,可能需要根据具体的需求和使用环境进行处理和调整。 ### 回答2: C和UTF-8是不同的字符编码标准。C是一种早期的编程语言,它使用的是ANSI字符集作为默认的字符编码ANSI字符集使用1个字节来表示一个字符,总共有256个字符,包括标点符号、数字和一些基本的拉丁字母。UTF-8是一种现代的字符编码标准,它是Unicode的一种实现方式,使用1到4个字节来表示一个字符,总共可以表示超过1百万个字符。 要将UTF-8编码字符转换ANSI编码,需要注意的是ANSI字符集无法表示一些特殊的字符,因此一些不在ANSI字符字符可能会丢失或替换为问号或其他无效字符。可以使用一些文本编辑软件或编程语言提供的转换函数来实现这个转换过程。 然而,需要注意的是,由于ANSI字符集的限制和UTF-8编码字符集的广泛使用,将UTF-8转换ANSI可能会导致信息的丢失或错误的表示,特别是对于包含非拉丁字母或特殊符号的文本。因此,更推荐使用支持UTF-8字符编码,以保留原始文本的完整性和准确性。 ### 回答3: 将UTF-8编码转换ANSI编码需要使用特定的软件或工具。由于UTF-8编码包含了更多的字符字符集,而ANSI编码只包含较少的字符集,所以转换时可能会有一些字符无法转换或出现乱码的情况。 首先,可以使用一些文本编辑器软件来进行这种转换。比如在Windows操作系统,可以使用记事本打开UTF-8编码的文本文件,然后选择“另存为”选项,将编码格式选择为ANSI,然后保存即可。这样就将UTF-8编码转换为了ANSI编码。但需要注意的是,如果文本包含某些特殊字符转换后可能会出现乱码或字符丢失的情况。 另外,也可以使用一些第三方的编码转换工具来进行转换。这些工具可以将UTF-8编码的文本文件导入,然后选择转换ANSI编码,最后保存转换后的文件。这种方式相对来说更加灵活,可以对特定字符进行自定义的转换设置。 无论是使用文本编辑器还是编码转换工具,都需要注意转换后的结果可能不完全准确,特别是对于一些特殊字符和格式的处理。因此,在进行编码转换时,应该仔细检查转换后的文件,确保没有出现乱码或字符丢失的情况。对于一些重要的数据和文件,最好备份原始的UTF-8编码版本,以防止转换后无法还原的情况发生。
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值