C/C++中char*与wchar_t*之间的转换

代码编译运行环境:Windows 64bits+VS2017+Debug+Win32


1.问题描述

char*表示的是多字节字符串,比如ASCII、GB2312、GBK等,wchar_t*表示的是宽字符串,即Unicode字符串,由于编码不同,所以在char*和wchar_t*之间无法使用强制类型转换。考察如下程序。

#include <iostream>
using namespace std;

int main()
{
    const wchar_t*  str=L"ABC我们";
    char* s=(char*)str;
    cout<<s<<endl;
}

输出结果出错:只输出A。经过强制类型转换,s指向了宽字符串,字符串数据没有发生任何变化,只是用多字节字符字符编码重新对它进行解释,输出的结果自然是错误的。

2.char*与wchar_t*之间相互转换

要想将宽字符串转换成多字节编码字符串(或者反过来),必须先读懂原来的字符串,然后再重新对它进行编码。只有这样才能到达转换的目的。利用标准库函数可以完成char*wchar_t*之间的转换,关键函数有setlocale()、wcstombs_s()mbstowcs_s()

2.1关键函数简介

(1)setlocale()

功能:配置地域化信息
头文件:< locale.h>
函数原型:char* setlocale(int category, const char* locale)
函数参数:
    category表示对本地化的某项内容进行设置,可取如下值:
        LC_ALL 包括下面的全部选项都要;
        LC_COLLATE 配置字符串比较;
        C_CTYPE 配置字符类别及转换,例如全变大写strtoupper();
        LC_MONETARY 配置金融货币;
        LC_NUMERIC 配置小数点后的位数;
        LC_TIME 配置时间日期格式,与strftime()合用。
    locale表示地域代号:如果为NULL,则返回当前的locale名称(一般为C);如果非空,则根据category和locale进行设置,如果成功,则返回新的locale名称(地域名称),如果失败,则返回NULL。

(2)wcstombs_s ()

功能:将宽字符编码字符串转换成多字节编码字符串
头文件:<stdlib.h>
函数原型:errno_t __cdecl wcstombs_s(size_t * _PtNumOfCharConverted, char * _Dst, size_t _DstSizeInBytes, const wchar_t * _Src, size_t _MaxCountInBytes)
函数参数:
    PtNumOfCharConverted:指向转换后的字符串的长度加上结束符(单位字节);
    Dst:指向转换后的字符串首地址;
    DstSizeInBytes:目的地址最大字节空间(单位字节);
    _Src:源宽字符串首地址;
    _MaxCountInBytes:最多可存入多字节字符串缓冲最的字节数,用于裁剪转换后的字符串。
返回值:成功返回0, 失败则返回失败代码。

(3)mbstowcs_s ()

函数功能:将多字节编码字符串转换成宽字符编码字符串
头文件:<stdlib.h>
函数原型:errno_t __cdecl mbstowcs_s(size_t * _PtNumOfCharConverted, wchar_t * _DstBuf, size_t _SizeInWords, const char * _SrcBuf, size_t _MaxCount );
参数说明:
    PtNumOfCharConverted:转换后的字符串的长度加上结束符的字符个数;
    _DstBuf:指向转换后的字符串首地址;
    _SizeInWords:目的地址最大字空间大小(单位wchar\_t);
    _SrcBuf:源多字节字符串首地址;
    _MaxCount:最多可存入宽字符串缓冲中的字符个数,用于裁剪转换后的宽字符串。
返回值:成功返回0, 失败则返回失败代码。

2.2转换实例

#include <locale.h>
#include <iostream>
#include <string>
using namespace std;

string ws2s(const wstring& ws)
{
    string curLocale = setlocale(LC_ALL, NULL);     //curLocale="C"
    setlocale(LC_ALL, "chs");
    const wchar_t* wcs = ws.c_str();
    size_t dByteNum = sizeof(wchar_t)*ws.size()+1;
    cout << "ws.size():" << ws.size() << endl;      //5:宽字符串L"ABC我们"有5个自然字符

    char* dest = new char[dByteNum];
    wcstombs_s(NULL,dest,dByteNum,wcs,_TRUNCATE);
    string result = dest;
    delete[] dest;
    setlocale(LC_ALL,curLocale.c_str());
    return result;
}

wstring s2ws(const string& s)
{
    string curLocale = setlocale(LC_ALL,NULL);  //curLocale="C"
    setlocale(LC_ALL,"chs");
    const char* source = s.c_str();
    size_t charNum=s.size()+1;
    cout <<"s.size():"<<s.size()<<endl;         //7:多字节字符串"ABC我们"有7个字节

    wchar_t* dest = new wchar_t[charNum];
    mbstowcs_s(NULL,dest,charNum,source,_TRUNCATE);
    wstring result = dest;
    delete[] dest;
    setlocale(LC_ALL,curLocale.c_str());
    return result;
}

int main()
{
    const wchar_t* wstr=L"ABC我们";
    const char* str="ABC我们";

    //宽字符串转换为多字节字符串
    string obj=ws2s(wstr);
    cout<<obj<<endl;

    //多字节字符串转换为宽字符串
    wstring objw = s2ws(str);
    wcout.imbue(locale("chs"));
    wcout << objw << endl;
}

程序输出:

ABC我们
ABC我们

程序运行结果表明,char*wchar_t*双向转换成功,但要注意的是,执行转换的函数mbstowcs_swcstombs_s的运行是依赖于当前的locale设置。在程序中去除相关的setlocale()函数调用,就得不到正确的结果。locale实际的作用是告诉操作系统,多字节字符串采用的是何种编码,“chs”表示简体中文。

3.利用Windows API实现字符编码的转换

除了利用标准库函数解决字符编码的转换问题,还可以利用特定操作系统下提供的函数。例如,利用Windows API实现字符编码的转换。

#include <windows.h>
#include <iostream>
using namespace std;

int main()
{
    const wchar_t* ws=L"测试字符串";
    const char* ss="ABC我们";

    //宽字符串转换为多字节字符串
    int bufSize = WideCharToMultiByte(CP_ACP, NULL, ws, -1, NULL, 0, NULL, FALSE);
    cout << bufSize << endl;
    char *sp = new char[bufSize];
    WideCharToMultiByte(CP_ACP, NULL, ws, -1, sp, bufSize, NULL, FALSE);
    cout << sp << endl;

    //宽字符串转换为多字节字符串
    bufSize = MultiByteToWideChar(CP_ACP, 0, ss, -1, NULL, 0);
    cout << bufSize << endl;
    wchar_t* wp = new wchar_t[bufSize];
    MultiByteToWideChar(CP_ACP, 0, ss, -1, wp, bufSize);
    wcout.imbue(locale("chs"));
    wcout<< wp <<endl;
}

程序输出结果:

11
测试字符串
6
ABC我们

其中函数int bufSize=WideCharToMultiByte(CP_ACP,NULL,ws,-1,NULL,0,NULL,FALSE);是用来获取宽字符串转换成多字节字符串所占据的空间大小(单位字节),这是将第5个参数设置为NULL达到的效果。同样,函数调用bufSize=MultiByteToWideChar(CP_ACP,0,ss,-1,NULL,0);是用来获取多字节字符串转换成宽字节字符串后所占用空间的大小(单位宽字符个数),这是将第5个参数设置为NULL之后达到的效果。

以下将具体讲解上面两个关键函数。 (1)WideCharToMultiByte()

函数功能:将宽字符串转换成多字节字符串
头文件:< windows.h>
函数原型:
    int WINAPI WideCharToMultiByte(
        _In_ UINT CodePage,
        _In_ DWORD dwFlags,
        _In_NLS_string_(cchWideChar) LPCWCH lpWideCharStr,
        _In_ int cchWideChar,
        _Out_writes_bytes_to_opt_(cbMultiByte, return) LPSTR lpMultiByteStr,
        _In_ int cbMultiByte,
        _In_opt_ LPCCH lpDefaultChar,
        _Out_opt_ LPBOOL lpUsedDefaultChar
);

参数详解:
    CodePage:指定执行转换的代码页字符集,可以为操作系统已安装或有效的任何代码页字符集,也可以指定其为下面的任意一值:CP_ACP:ANSI代码页;CP_ACP:ANSI代码页;CP_MACCP:Macintosh代码页;CP_OEMCP:OEM代码页;CP_SYMBOL:符号代码页;CP_THREAD_ACP:当前线程ANSI代码页;CP_UTF7:使用UTF-7转换;CP_UTF8:使用UTF-8转换。使用最多的就是CP_ACP和CP_UTF8;
    dwFlags:指定如何处理没有转换成功的字符,也可以不设此参数(设置为0),函数会运行的更快一些。对于UTF-8,dwflags必须为0或者WC_ERR_INVALID_CHARS,否则函数都将失败返回并设置错误码ERROR_INVALID_FLAGS,可以调用GetLastError获得;
    lpWideCharStr:待转换为宽字符串;
    cchWideChar:待转换的宽字符串的长度(字符个数),-1表示转换到字符串结尾;
    lpMultiByteStr:转换后目的字符串缓冲区;
    cbMultiByte:目的字符串缓冲区大小(单位字节)。如果设置为0,函数将返回所需缓冲区大小而忽略lpMultiByteStr;
    lpDefaultChar:指向字符的指针,在指定编码里找不到相应字符时使用此字符作为默认字符替代。如果为NULL,则使用系统默认字符。使用dwFlags时不能使用此参数,否则报ERROR_INVLID_PARAMETER错误;
    lpUsedDefaultChar:开关变量的指针,表明是否使用过默认字符。对于要求此参数为NULL的dwflags而使用此参数,函数将失败返回,并设置错误码ERROR_INVLID_PARAMETER。lpDefaultChar和lpUsedDefaultChar都设为NULL,函数会更快一些。

函数返回值:如果函数运行成功,并且cbMultiByte不为零,返回值是由 lpMultiByteStr指向的缓冲区中写入的字节数;如果函数运行成功,并且cbMultiByte为零,返回值是接存放目的字符串缓冲区所必需的字节数。如果函数运行失败,返回值为零。若想获得更多错误信息,请调用GetLastError函数。 

(2)MultiByteToWideChar()

函数功能:多字节字符串到款字节字符串的转换
头文件:<windows.h>
函数原型:
int WINAPI MultiByteToWideChar(
    _In_ UINT CodePage,
    _In_ DWORD dwFlags,
    _In_NLS_string_(cbMultiByte) LPCCH lpMultiByteStr,
    _In_ int cbMultiByte,
    _Out_writes_to_opt_(cchWideChar, return) LPWSTR lpWideCharStr,
    _In_ int cchWideChar
);
参数详解:
    CodePage:同上;
    dwFlags:指定是否转换成预制字符或合成的宽字符,是否使用象形文字替代控制字符,以及如何处理无效字符。对于UTF-8,dwflags必须为0或者WC\_ERR\_INVALID\_CHARS,否则函数都将失败返回并设置错误码ERROR\_INVALID\_FLAGS,可以调用GetLastError获得;
    lpMultiByteStr:多字节字符串;
    cbMultiByte:待转换的多字节字符串长度,-1表示转换到字符串结尾;
    lpWideCharStr:存放转换后的宽字符串缓冲;
    cchWideChar:宽字符串缓冲的大小(单位字符数)。

返回值:如果函数运行成功,并且cchWideChar不为零,返回值是由 lpWideCharStr指向的缓冲区中写入的字符数;如果函数运行成功,并且cchWideChar为零,返回值是接存放目的字符串缓冲区所必需的字符数。如果函数运行失败,返回值为零。若想获得更多错误信息,请调用GetLastError函数。 

Linux同样提供的相关的系统调用来实现char*wchar_t*之间的转换,char*wchar_t*的转换使用mbstowcs(),反之使用wcstombs(),感兴趣的读者可自行实现。


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008[P340-P344] [2]百度百科.MultiByteToWideChar

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOS 开发杂谈

浅谈 KVC

KVC 是 KeyValue Coding 的简称,遵循 NSKeyValueCoding 协议,它是一种可以直接通过字符串的名字 key 来访问类属性的机制,...

10530
来自专栏技术点滴

Windows字符集的统一与转换

Windows字符集的统一与转换 一、字符集的历史渊源 在Windows编程时经常会遇到编码转换的问题,一直以来让刚接触的人摸不着头脑。其实只要弄清Win32程...

217100
来自专栏林德熙的博客

win10 uwp xaml 绑定接口

早上快乐 就在你的心问了我一个问题,他使用的属性是显式继承,但是无法在xaml绑定

7820
来自专栏Android相关

X86 Assemble指令--ptr

在Assemble中通常会看到WORD ptr或者DWORD ptr或者BYTE ptr等关键字,这些关键字主要用来标识指令操作数的大小(或者说长度) WOR...

10320
来自专栏数据结构与算法

洛谷P1456 Monkey King

题目描述 Once in a forest, there lived N aggressive monkeys. At the beginning, they ...

28440
来自专栏码匠的流水账

聊聊GenericObjectPool的泄露检测

本文主要聊聊GenericObjectPool的abandon参数。主要用来做连接池的泄露检测用。

18720
来自专栏hbbliyong

c# 获取串口设备的输入(unsigned char *和 char*)

因为是C#,所以平台肯定是.NET了。 之前因为一个小小的业务需要接触了下密码键盘的操作。其实就是简单的获取用户输入密码的操作,没碰到什么大的问题,但是查资料的...

393110
来自专栏GreenLeaves

Attribute基本介绍

一、基础知识点 1、什么是Attribute? MSDN:公共语言运行时允许你添加类似关键字的说明,叫做Attribute,它可以对程序中的元素进行标注,如类型...

20460
来自专栏Java与Android技术栈

Scala学习笔记(七)

在这里what()方法报错了,主要是因为还缺少了对Cylinder的匹配,只要改成如下的代码就可以正常运行了。

9630
来自专栏10km的专栏

java nio:Files.isSameFile判断两个路径(Path)是否相等

不论在windows还是linux下,仅凭字符串比较判断两个文件路径是否相等是不靠谱的。因为有link,Disk map等技术的存在,两个不同的路径有可能指向同...

23460

扫码关注云+社区

领取腾讯云代金券