Unicode 有两套编码集,UCS-2 和 UCS-4。Windows 的内部其实是用的 UCS-2 标准,并用 UTF-16 来实现。而非 Windows 系统大多采用了 UTF-8 实现。

      大家都知道在windows上wchar_t是2个字节表示,而在Linux上wchar_t是4个字节表示的。这样写跨平台的程序时,就会不统一。

下面是我用到的几个函数,wchar_t与UTF-8编码之间的转换和UTF-16和wchar_t之间的转换。

#ifdef WINDOWS
#include <windows.h>
#include <stdio.h>
#include <ctype.h>
#else
#include <iconv.h>
#include <wctype.h>
#include <wchar.h>
#include <errno.h>
#endif

//wchar_t转成UTF-8
int FW2UTF8Convert(const wchar_t* a_szSrc, int a_nSrcSize, char* a_szDest, int a_nDestSize)
{
#ifdef WINDOWS
    return WideCharToMultiByte(CP_UTF8, 0, a_szSrc, -1, a_szDest, a_nDestSize, NULL, NULL);
#else
    size_t result;
    iconv_t env;
    env = iconv_open("UTF-8", "WCHAR_T");
    if (env == (iconv_t)-1)
    {
        printf("iconv_open WCHAR_T->UTF8 error%s %d/n", strerror(errno), errno);
        return -1;
    }
    result = iconv(env, (char**)&a_szSrc, (size_t*)&a_nSrcSize, (char**)&a_szDest, (size_t*)&a_nDestSize);
    if (result == (size_t)-1)
    {
        printf("iconv WCHAR_T->UTF8 error %d/n", errno);
        return -1;
    }
    iconv_close(env);
    return (int)result;
#endif
}

//UTF-8转成wchar_t
int FUTF82WConvert(const char* a_szSrc, wchar_t* a_szDest, int a_nDestSize)
{
#ifdef WINDOWS
    return MultiByteToWideChar(CP_UTF8, 0, a_szSrc, -1, a_szDest, a_nDestSize);
#else
    size_t result;
    iconv_t env;
    int size = strlen(a_szSrc) + 1;
    env = iconv_open("WCHAR_T", "UTF-8");
    if (env == (iconv_t)-1)
    {
        printf("iconv_open UTF8->WCHAR_T error %d/n", errno);
        return -1;
    }
    result = iconv(env, (char**)&a_szSrc, (size_t*)&size, (char**)&a_szDest, (size_t*)&a_nDestSize);
    if (result == (size_t)-1)
    {
        printf("iconv UTF8->WCHAR_T error %d/n", errno);
        return -1;
    }
    iconv_close(env);
    return (int)result;
#endif
}

//wchar_t转成utf16
int FW2UConvert(const wchar_t* a_szSrc, int  a_nSize, char* a_szDest, int a_nDestSize)
{
#ifdef WINDOWS
    memcpy_s((wchar_t*)a_szDest, a_nDestSize, a_szSrc, a_nSize);
    return a_nSize;
#else
    size_t result;
    iconv_t env;
    env = iconv_open("UCS-2-INTERNAL", "UCS-4-INTERNAL");
    if (env == (iconv_t)-1)
    {
        printf("iconv_open WCHAR_T->UTF16 error%s %d/n", strerror(errno), errno);
        return -1;
    }
    result = iconv(env, (char**)&a_szSrc, (size_t*)&a_nSize, (char**)&a_szDest, (size_t*)&a_nDestSize);
    if (result == (size_t)-1)
    {
        printf("iconv WCHAR_T->UTF16 error %s %d/n", strerror(errno), errno);
        return -1;
    }
    iconv_close(env);
    return (int)result;
#endif
}

//utf16转成wchar_t
int FU2WConvert(const  char* a_szSrc, int a_nSize, wchar_t* a_szDest, int a_nDestSize)
{
#ifdef WINDOWS
    memcpy_s(a_szDest, a_nDestSize, (const wchar_t*)a_szSrc, a_nSize);
    return a_nSize;
#else
    size_t result;
    iconv_t env;
    env = iconv_open("UCS-4-INTERNAL", "UCS-2-INTERNAL");
    if (env == (iconv_t)-1)
    {
        printf("iconv_open error %d/n", errno);
        return -1;
    }
    result = iconv(env, (char**)&a_szSrc, (size_t*)&a_nSize, (char**)&a_szDest, (size_t*)&a_nDestSize);
    if (result == (size_t)-1)
    {
        printf("UTF16 -> WCHAR_T conv error %d/n", errno);
        return -1;
    }
    iconv_close(env);
    return (int)result;
#endif
}

ps:在Linux上我用的是iconv库.其中wchar_t一般以UCS-4标准。

     UCS-4-INTERNAL ,UCS-2-INTERNAL  会根据本机的存储方式(大端、小端)进行处理。

     还有UCS-2LE和UCS-2BE 分别代表小端和大端模式。

字符串乱码往往是由于编码不一致或编码没有对应的字符所致,为了能够正常显示字符串,经常会有需要编码转换的需要,为了方便使用这里整理成一个head-only文件,这里提供了char、wchar_t、utf-8之间的转换,在实际的项目中建议使用wchar_t/utf-8,强烈建议使用utf-8。

#pragma once
#include <Windows.h>
#include <string>
#include <vector>
#include <assert.h>
 
/*!
 * 编码转换命名空间
 * 
 */
namespace ZEncode
{
    /*!
     * 窄字节转宽字节
     * 
     * \param str 窄字节
     * \param uCodePage 窄字节编码
     * \return 宽字节
     */
    static std::wstring A2W(const std::string &str, UINT uCodePage)
    {
        int nLength = ::MultiByteToWideChar(uCodePage, 0, str.c_str(), -1, NULL, 0);
        if (0 == nLength)
        {
            throw std::exception("A2W Error");
        }
        std::wstring strW(nLength, L'\0');
        int nResult = ::MultiByteToWideChar(uCodePage, 0, str.c_str(), -1, &strW[0], nLength);
        if (nResult != nLength)
        {
            throw std::exception("A2W Error");
        }
        strW.resize(nLength - 1);
        return strW;
    }
 
    /*!
     * 宽字节转窄字节
     * 
     * \param str 宽字节
     * \param uCodePage 窄字节编码
     * \return 窄字节
     */
    static std::string W2A(const std::wstring &str, UINT uCodePage)
    {
        int nLength = ::WideCharToMultiByte(uCodePage, 0, str.c_str(), -1, NULL, 0, NULL, NULL);
        if (0 == nLength)
        {
            throw std::exception("W2A Error");
        }
        std::string strA(nLength, '\0');
        int nResult = ::WideCharToMultiByte(uCodePage, 0, str.c_str(), -1, &strA[0], nLength, NULL, NULL);
        if (nResult != nLength)
        {
            throw std::exception("W2A Error");
        }
        strA.resize(nLength - 1);
        return strA;
    }
 
    /*!
     * 窄字节转窄字节
     * 
     * \param str 窄字节
     * \param uCodePageFrom 源始字节编码
     * \param uCodePageTo 目标字节编码
     * \return 窄字节
     */
    static std::string A2A(const std::string &str, UINT uCodePageFrom, UINT uCodePageTo)
    {
        return W2A(A2W(str, uCodePageFrom), uCodePageTo);
    }
 
    /*!
     * 检查缓冲区数据是否是UTF-8
     * 
     * \param pBuffer 缓冲区
     * \param size 大小
     * \return 如果是返回true,否则返回false。
     * 
     * \note 返回结果并不一定完全正确,仅作为一个参考用途。
     */
    static bool IsUTF8(const void* pBuffer, size_t size)
    {
        //参考 http://blog.csdn.net/bladeandmaster88/article/details/54767487
        bool bIsUTF8 = true;
        unsigned char* start = (unsigned char*)pBuffer;
        unsigned char* end = (unsigned char*)pBuffer + size;
 
        while (start < end)
        {
            if (*start < 0x80) // (10000000): 值小于0x80的为ASCII字符     
            {
                start++;
            }
            else if (*start < (0xC0)) // (11000000): 值介于0x80与0xC0之间的为无效UTF-8字符     
            {
                bIsUTF8 = false;
                break;
            }
            else if (*start < (0xE0)) // (11100000): 此范围内为2字节UTF-8字符     
            {
                if (start >= end - 1)
                    break;
 
                if ((start[1] & (0xC0)) != 0x80)
                {
                    bIsUTF8 = false;
                    break;
                }
                start += 2;
            }
            else if (*start < (0xF0)) // (11110000): 此范围内为3字节UTF-8字符
            {
                if (start >= end - 2)
                    break;
 
                if ((start[1] & (0xC0)) != 0x80 || (start[2] & (0xC0)) != 0x80)
                {
                    bIsUTF8 = false;
                    break;
                }
                start += 3;
            }
            else
            {
                bIsUTF8 = false;
                break;
            }
        }
 
        return bIsUTF8;
    }
 
    /*!
    * 检查字符串是不是UTF-8编码
    *
    * \param str 缓冲区
    * \return 如果是返回true,否则返回false。
    * 
    * \note 返回结果并不一定完全正确,仅作为一个参考用途。
    */
    static bool IsUTF8(const std::string &str)
    {
        return IsUTF8(str.c_str(), str.size());
    }
}
 
//为了方便方便使用定义的宏
#define ANSI_TO_WCHAR(str)         (ZEncode::A2W(str, CP_ACP))
#define ANSI_TO_UTF8(str)          (ZEncode::A2A(str, CP_ACP, CP_UTF8))
 
#define UTF8_TO_ANSI(str)          (ZEncode::A2A(str, CP_UTF8, CP_ACP))
#define UTF8_TO_WCHAR(str)         (ZEncode::A2W(str, CP_UTF8))
 
#define WCHAR_TO_ANSI(str)         (ZEncode::W2A(str, CP_ACP))
#define WCHAR_TO_UTF8(str)         (ZEncode::W2A(str, CP_UTF8))


    TEST(ZEncode, ansi_to_wchar_to_ansi)
    {
        std::string strANSI("中华人民共和国");
        std::wstring strWChar = ANSI_TO_WCHAR(strANSI);
        EXPECT_STREQ(strWChar.c_str(), L"中华人民共和国");
        EXPECT_STREQ(WCHAR_TO_ANSI(strWChar).c_str(), "中华人民共和国");
    }
 
    TEST(ZEncode, ansi_to_utf8_to_wchar)
    {
        std::string strANSI("中华人民共和国");
        std::string strUTF8 = ANSI_TO_UTF8(strANSI);
        EXPECT_FALSE(ZEncode::IsUTF8(strANSI));
        EXPECT_TRUE(ZEncode::IsUTF8(strUTF8));
        EXPECT_STREQ(UTF8_TO_WCHAR(strUTF8).c_str(), L"中华人民共和国");
    }
 
    TEST(ZEncode, ansi_to_utf8_to_ansi)
    {
        std::string strANSI("中华人民共和国");
        std::string strUTF8 = ANSI_TO_UTF8(strANSI);
        EXPECT_FALSE(ZEncode::IsUTF8(strANSI));
        EXPECT_TRUE(ZEncode::IsUTF8(strUTF8));
        EXPECT_STREQ(UTF8_TO_ANSI(strUTF8).c_str(), strANSI.c_str());
    }
 
    TEST(ZEncode, wchar_to_utf8_to_wchar)
    {
        std::wstring strWChar(L"中华人民共和国");
        std::string strUTF8 = WCHAR_TO_UTF8(strWChar);
        EXPECT_TRUE(ZEncode::IsUTF8(strUTF8));
        EXPECT_STREQ(UTF8_TO_WCHAR(strUTF8).c_str(), strWChar.c_str());
    }

 其它:在C++11中,如果希望初始化的字符串编码为utf-8,只需要在字符前面新增u8即可,如下:

1

2

std::string s1 = "中华人民共和国";  //取决于文件编码

std::string s2 = u8"中华人民共和国";  //utf-8编码

Logo

更多推荐