C++ 多字节与宽字符串的相互转换

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


1.多字节字符与宽字符

说到多字节字符串与宽字符串,不得不说一下多字节字符与宽字符。多字节字符实际上是由多个字节来表示一个字符,在各个国家和地区采用不同的编码方案,不同编码方案字符码值是不同的,比如常见的中国大陆的GBK 和 GB18030、台湾同胞的 Big5,以及国际通用的 UTF8 编码等。宽字符指的是由统一码联盟制定的 Unicode 编码方案收录的字符,使用 4 个字节来表示一个字符。关于字符编码可参见博文精述字符编码。

C/C++中char*表示多字节字符串,wchar_t*表示宽字符串,由于编码不同,所以在char*wchar_t*之间无法使用强制类型转换。考察如下程序。

#include 
using namespace std;

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

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

2.多字节与宽字符串的相互转换

使用 C/C++ 实现多字节字符串与宽字符串的相互转换,需要使用 C 标准库函数 mbstowcs 和 wcstombs。

//将多字节编码转换为宽字节编码
size_t mbstowcs (wchar_t* dest, const char* src, size_t max);

//将宽字节编码转换为多字节编码
size_t wcstombs (char* dest, const wchar_t* src, size_t max);

这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。Linux下输入命名locale -a查看系统支持的编码类型。

andy@andy-linux:~$ locale -a
c
en_ag
en_au.utf8
en_bw.utf8
en_ca.utf8
en_dk.utf8
en_gb.utf8
en_hk.utf8
en_ie.utf8
en_in
en_ng
en_nz.utf8
en_ph.utf8
en_sg.utf8
en_us.utf8
en_za.utf8
en_zw.utf8
posix
zh_cn.gb18030
zh_cn.gbk
zh_cn.utf8
zh_hk.utf8
zh_sg.utf8
zh_tw.utf8

talk is cheap,show me the code!下面给出多字节与宽字符串相互转化的实现。

#include 
#include 

/********************************************
*@brief:不同编码字符串转Unicode
*@pram:cpMbs:多字节字符串;wcpWcs:宽字符串;wcsBuffLen:宽字符串缓冲区大小(单位宽字符);dEncodeType:多字节字符串编码类型,0:GBK,1:UTF8
*@ret:-1:出错;>=0:转换成功的字符个数
*@birth:created by dablelv on 20170804
*@revision:
********************************************/
int mbs2wcs(const char* cpMbs,wchar_t* wcpWcs,int wcsBuffLen,int dEncodeType)
{
	if(NULL==cpMbs||0==strlen(cpMbs)) return 0;
	
	//GBK转Unicode
	if(0==dEncodeType)
	{
		if(NULL==setlocale(LC_ALL,"zh_CN.gbk"))		//设置转换为unicode前的编码为gbk编码		
			return -1;
	}
	//UTF8转Unicode
	if(1==dEncodeType)
	{
		if(NULL==setlocale(LC_ALL,"zh_CN.utf8"))	//设置转换为unicode前的编码为utf8编码
			return -1;
	}
	
	int unicodeCNum=mbstowcs(NULL,cpMbs,0);				//计算待转换的字符数
	if(unicodeCNum<=0||unicodeCNum>=wcsBuffLen)			//转换失败或宽字符串缓冲区大小不足
	{
		return -1;
	}
	unicodeCNum=mbstowcs(wcpWcs,cpMbs,wcsBuffLen-1);	//进行转换,wcsBuffLen-1表示最大待转换的宽字符数,即宽字符串缓冲区大小
	return unicodeCNum;
}

/********************************************
*@brief:Unicode转指定编码字符串
*@pram:wcpWcs:宽字符串;cpMbs:多字节字符串缓冲区;dBuffLen:多字节字符串缓冲区大小(单位字节);dEncodeType:多字节字符串编码类型,0:GBK,1:UTF8
*@ret:-1:出错;>=0:转换成功的字节个数
*@birth:created by dablelv on 20180114
*@revision:
********************************************/
int wcs2mbs(const wchar_t* wcpWcs,char* cpMbs,int dBuffLen,int dEncodeType)
{
	if(wcpWcs==NULL || wcslen(wcpWcs)==0)
	{
		return 0;
	}
	
	//Unicode转GBK
	if(0==dEncodeType)
	{
		if(NULL==setlocale(LC_ALL,"zh_CN.gbk"))		//设置目标字符串编码为gbk编码
			return -1;
	}
	//Unicode转UTF8
	if(1==dEncodeType)
	{
		if(NULL==setlocale(LC_ALL,"zh_CN.utf8"))	//设置目标字符串编码为utf8编码
			return -1;
	}
	
	int dResultByteNum=wcstombs(NULL,wcpWcs,0);		//计算待转换的字节数
	if(dResultByteNum<=0 || dResultByteNum>=dBuffLen)
	{
		return -1;									//转换失败或多字节字符串缓冲区大小不足
	}
	wcstombs(cpMbs,wcpWcs,dBuffLen-1);
	return dResultByteNum;
}

测试代码文件使用UTF8编码,代码如下:

int main(int argc,char* argv[])
{
	char* cpMbs="I believe 中国民族将实现伟大复兴";
	wchar_t* wcpWcs=L"I believe 中国民族将实现伟大复兴";
	char cBuff[1024]={'\0'};
	wchar_t wcBuff[1024]={L'\0'};
	
	//将UTF8编码多字节字符串转换为Unicode字符串
	int ret=mbs2wcs(cpMbs,wcBuff,1024,1);
	//转换后字符串与字符串长度
	printf("返回值:%d,字符数:%d,宽字符串:%S\n",ret,wcslen(wcBuff),wcBuff);    //printf使用%ls也可以输出宽字符串
	
	//Unicode字符串转换为UTF8编码多字节字符串
	ret=wcs2mbs(wcpWcs,cBuff,1024,1);
	//转换后字符串与字符串字节数
	printf("返回值:%d,字符串字节数:%d,字符串:%s\n",ret,strlen(cBuff),cBuff);
}

测试输出结果为:

返回值:21,字符数:21,宽字符串:I believe 中国民族将实现伟大复兴
返回值:43,字符串字节数:43,字符串:I believe 中国民族将实现伟大复兴

注意:请不要同时使用 printf() 与 wprintf(),否则会出现后者无法输出的奇怪现象。该现象的解释与解决办法参见博文printf()详解之终极无惑。

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

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

#include 
#include 
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 <

程序输出结果:

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_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()

函数功能:多字节字符串到款字节字符串的转换
头文件:
函数原型:
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函数。 

[1] Linux C++ gbk转为utf-8
[2] 精述字符编码
[3] 陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008[P340-P344]
[4] 百度百科.MultiByteToWideChar

你可能感兴趣的:(C++)