字符编码转换

宽窄字符和字符编码的关系

多字节(窄)字符:在C/C++中,char是一种数据类型,规定sizeof(char)=1,即一个char占用一个字节,1Byte=8bit。并没有规定一个char就要与ASCII对应,不过,通常情况下char值与ASCII编码对应。

宽字符:标准并没有规定wchar_t占多少位,标准只是要求一个wchar_t可以表示任何系统所能认识的字符,在win32 中,wchar_t为16位;Linux中是32位。

wchar_t同样没有规定编码,一个wchar_t可以不同的编码表示,不过鉴于UTF16正好是两个字节,且为通用编码方式,所以通常情况下将wchar_t与uinicode编码对应。在 win32中,wchar_t的编码是UCS-2BE(utf16BE);而Linux中是UTF-32BE(等价于UCS-4BE)。

如果想明确声明使用两个字节大小的类型存储UTF-16编码字符,可使用C++提供的另一个宽字符类型char16_t。

char16_t和wchar_t是两种表示宽字符的数据类型,其在不同的编译器和平台下可能有不同的实现和用途。

char16_t: 是C++11引入的数据类型,用于表示16位宽字符,常用于存储UTF-16编码字符。在大多数平台上,它是确切的16位宽字符类型,每个字符占用2个字节。这种数据类型适用于处理Unicode字符,并且可以使用u""前缀来定义UTF-16编码的字符串字面量。

wchar_t: 是用于表示宽字符的数据类型,其宽度在不同的平台和编译器下可能不同。在Windows平台和Visual C++编译器中,wchar_t通常表示16位宽字符,用于存储UTF-16编码的字符。而在其他平台(Linux)和编译器中,wchar_t通常表示32位宽字符,用于存储UTF-32编码的字符。wchar_t一般用于处理宽字符数据,如文件路径、系统命令等。

所以,char16_t和wchar_t的区别主要在于它们的宽度和使用的场景。char16_t固定为16位,主要用于存储UTF-16编码字符;而wchar_t的宽度可能是16位或32位,用于不同平台和编译器下的宽字符处理。

在使用这两个数据类型时,需要注意平台和编译器的不同实现,以及与其他代码/库的兼容性,在Windows系统下,char16_t和wchar_t都占两个字节(16位)大小。但是,为了避免混淆和错误,建议在代码中明确指定所使用的字符编码和数据类型,以确保程序在不同环境中的正确性。

因此后续会使用char16_t存储UTF-16编码的内容

UTF-16BE、UTF-16LE、UTF-16 三者之间的区别。
用两个字节表示必然存在字节序的问题,即大端小端的问题,因此UTF-16后缀就是大小端的意思了。

UTF-16BE,其后缀是 BE 即 big-endian,大端的意思。大端就是将高位的字节放在低地址表示。
UTF-16LE,其后缀是 LE 即 little-endian,小端的意思。小端就是将高位的字节放在高地址表示。
UTF-16,没有指定后缀,即不知道其是大小端,所以其开始的两个字节表示该字节数组是大端还是小端。即FE FF表示大端,FF FE表示小端。

宽窄字符的转换

首先介绍下如何声明各种编码字符串常量:

char ch_ansi[] = "ABC这是一个测试文本123";  //取决于系统默认编码,简体系统为GBK,繁体系统为BIG5
char ch_utf8[] = u8"ABC这是一个测试文本123";  //明确这是一个UTF-8编码的字符串常量
char16_t ch16_utf16[] = u"ABC这是一个测试文本123";    //明确这是一个UTF-16编码的字符串常量,且存储在16位的宽字符数组中
wchar_t wch_utf16[] = L"ABC这是一个测试文本123";      //声明了一个宽字符编码的字符串常量,具体宽度可能是16位(UTF-16,Windows)或32位(UTF-32,Linux)

u"“前缀和L”"前缀是用于定义字符串字面量的前缀,在C++中有不同的含义和用途。

u"“前缀:u”"前缀表示引号内的字符串是以UTF-16编码的宽字符字符串。它用于定义UTF-16字符串字面量,其中每个字符通常占用2个字节。这种前缀主要用于支持Unicode字符集,并且适用于处理宽字符数据。例如,u"你好"定义了一个UTF-16编码的字符串字面量。

L"“前缀:L”“前缀表示引号内的字符串是以宽字符编码的字符串。它的宽度可能是平台和编译器相关的,可能是16位(UTF-16)或32位(UTF-32)。L”"前缀的主要目的是用于处理宽字符数据,例如文件路径、系统命令等。例如,L"Hello"定义了一个宽字符编码的字符串字面量。

总结来说,u"“前缀表示UTF-16编码的宽字符字符串,L”"前缀表示宽字符编码的字符串。它们的使用取决于编译器和平台的实现,并且在处理宽字符数据时起到重要的作用。

注意:不能使用char16_t接收L"“前缀声明的字符串常量,同样不能使用char16_t存储u”"前缀声明的字符串常量:
1

回到正题,对于宽窄字符的转换,这里有两种转换方式:

一种是保持原有字符编码不变,仅进行宽窄字符类型的转换

另一种是即进行宽窄字符类型的转换,也进行字符编码的转换

保持原编码转换:只转换类型,不转换编码

char*、char16_t*、wchar* 、UTF-16 等存储的都是二进制数据,不涉及字符编码,因此是能够在不改变字符编码的情况下进行相互转换的。

char* 转 char16_t*

示例:

    char ch_ansi[] = "ABC这是一个测试文本123";//char* 转 char16_t*size_t srcLen = strlen(ch_ansi);size_t wideLen = srcLen / 2;char16_t* wideStr = new char16_t[wideLen + 1];for (int i = 0; i < wideLen; i++){reinterpret_cast<char*>(&wideStr[i])[0] = ch_ansi[i * 2];reinterpret_cast<char*>(&wideStr[i])[1] = ch_ansi[i * 2 + 1];}wideStr[wideLen] = u'\0';QString qsLocal = QString::fromLocal8Bit((char*)wideStr);QString qsUTF16 = QString::fromUtf16(wideStr);delete[]wideStr;

解码结果:
2

在这段代码中,reinterpret_cast<char*>(&wideString[i])将wchar_t类型的指针&wideString[i]转换为char类型的指针。这样做是为了访问wchar_t类型的指针所指向的内存,并按字节存储到char类型的指针中。

然后,[0]表示通过char类型的指针获取指针所指向的第一个字节的值。因为UTF-16编码的字符由两个字节组成,所以通过reinterpret_cast<char>(&wideString[i])[0]可以获取到UTF-16字符的第一个字节。

在循环中,我们将每个UTF-16编码的字符拆分为两个字节,并分别存储到char*数组中。拆分过程中,使用[0]和[1]分别获取两个字节的值,并存储到对应的位置上。

为什么使用reinterpret_cast而不是static_cast或dynamic_cast

在上述代码中,使用reinterpret_cast而不是static_cast或dynamic_cast是因为需要进行指针类型之间的转换,而不是从一个类层次结构中进行类型转换。

reinterpret_cast是最强大的类型转换操作符,可以在指针类型之间进行任意的转换。它不进行任何运行时的类型检查,而是仅仅对二进制数据进行重新解释。

而static_cast用于在静态类型转换中,包括基本类型的转换,指针或引用之间的上下转换以及类之间的上下转换。但它不能用于没有直接或间接转换关系的类型之间的转换,也不能用于指针类型的底层二进制表示的转换。

同样,dynamic_cast主要用于在具有多态关系的类之间进行类型转换,而不是用于指针类型的底层二进制表示的转换。

在这种情况下,我们需要将wchar_t类型的指针转换为char类型的指针,以进行字节级别的内存操作。由于这两种指针类型之间没有直接的转换关系,也没有多态关系,因此使用reinterpret_cast是最合适的选择。*

直接使用强制类型转换也可以,但不推荐:

    char16_t* pCh16 = (char16_t*)ch_ansi;QString qsLocal_2 = QString::fromLocal8Bit((char*)pCh16);QString qsUTF16_2 = QString::fromUtf16(pCh16);

解码结果:

3

这段代码直接将wideString数组指针的类型直接转换为char*类型,而不是对数组中的每个元素进行单独的字节拆分和转换,这会使pCH16按照宽字符的内存布局来处理数据。

char* 转 wchar_t*

示例:

    //char* 转 wchar_t*char ch_ansi[] = "ABC这是一个测试文本123";size_t srcLen = strlen(ch_ansi);size_t wideLen = srcLen / 2;wchar_t* wideStr = new wchar_t[wideLen + 1];for (int i = 0; i < wideLen; i++){reinterpret_cast<char*>(&wideStr[i])[0] = ch_ansi[i * 2];reinterpret_cast<char*>(&wideStr[i])[1] = ch_ansi[i * 2 + 1];}wideStr[wideLen] = L'\0';QString qsLocal = QString::fromLocal8Bit((char*)wideStr);QString qsUTF16 = QString::fromWCharArray(wideStr);delete[]wideStr;

解码结果:
4

char16_t* 转 char*

示例:

    //char16_t* 转 char*,已测试可行char16_t ch16_utf16[] = u"SOP这是一个宽字符测试文本456"; // UTF-16编码的字符串// 计算UTF-16编码字符串的长度int length = std::char_traits<char16_t>::length(ch16_utf16);// 计算转换后的char*所需的长度 (每个UTF-16字符需要2个字节)int charArrayLength = length * 2;// 创建用于存储转换后的char*的缓冲区char* charArray = new char[charArrayLength + 1]; // 需要添加额外的终止空字符// 将UTF-16编码的字符串转换为char*for (int i = 0; i < length; i++) {charArray[i * 2] = reinterpret_cast<char*>(&ch16_utf16[i])[0]; // 高位字节charArray[i * 2 + 1] = reinterpret_cast<char*>(&ch16_utf16[i])[1]; // 低位字节}charArray[charArrayLength] = '\0'; // 添加终止空字符QString qsLocal = QString::fromLocal8Bit(charArray);QString qsUtf16 = QString::fromWCharArray((wchar_t*)charArray);QString qsUtf16_2 = QString::fromUtf16((char16_t*)charArray);// 释放分配的内存delete[] charArray;

解码结果:
5

char16_t* 转 wchar_t*

示例:

//char16_t* 转 wchar_t*
char16_t ch16_utf16[] = u"SOP这是一个宽字符测试文本456";
std::wstring result;
for (int i = 0; ch16_utf16[i] != u'\0'; ++i)
{result.push_back(static_cast<wchar_t>(ch16_utf16[i]));
}const wchar_t* wch_utf16 = result.c_str();

将UTF-16编码的char16_t数组转换为wchar_t时,相当于将UTF-16编码直接存储在wchar_t中,因为它们的大小和编码方式相匹配。所以可以理解为使用wchar_t*存储UTF-16编码的内容。

解码结果:
6

但在Linux系统上,将UTF-16编码的char16_t数组转换为wchar_t后,并不能直接理解为使用wchar_t存储UTF-16编码的内容。
在Linux系统上,wchar_t的大小通常是4个字节,与UTF-32编码中每个字符的大小相匹配。而UTF-16编码中每个字符的大小是2个字节。
如果您将UTF-16编码的char16_t数组直接转换为wchar_t*,会导致字符编码不匹配。因为在Linux系统上,wchar_t*所表示的是UTF-32编码的内容,而不是UTF-16编码的内容。

wchar_t* 转 char*

示例:

    //wchar_t* 转 char*,已测试可行wchar_t wideString[] = L"SOP这是一个宽字符测试文本456"; // UTF-16编码的wchar_t数组// 获取宽字符数组的长度int length = wcslen(wideString);// 创建存储UTF-16编码字符的char数组char* narrowString = new char[length * 2 + 1]; // 每个UTF-16字符由两个字节组成// 将UTF-16编码的wchar_t*数组转换为char*数组for (int i = 0; i < length; i++) {narrowString[i * 2] = reinterpret_cast<char*>(&wideString[i])[0];narrowString[i * 2 + 1] = reinterpret_cast<char*>(&wideString[i])[1];}narrowString[length * 2] = '\0'; // 添加字符串结束符QString qsWArr = QString::fromWCharArray((wchar_t*)narrowString);QString qsUtf16 = QString::fromUtf16((char16_t*)narrowString);// 释放内存delete[] narrowString;

解码结果:
7

wchar_t* 转 char16_t*

在Windows系统上, 将UTF-16编码的wchar_t数组转换为char16_t后,可以理解为使用char16_t存储UTF-16编码的内容。

    //wchar_t* 转 char16_t*wchar_t wch_utf16[] = L"SOP这是一个宽字符测试文本456"; // UTF-16编码的宽字符数组// 计算宽字符数组的长度int wideLength = wcslen(wch_utf16);// 计算转换后的char16_t*所需的长度 (每个宽字符字符需要2个字节)int charArrayLength = wideLength;// 创建用于存储转换后的char16_t*的缓冲区char16_t* char16Array = new char16_t[charArrayLength + 1]; // 需要添加额外的终止空字符// 将宽字符数组转换为char16_t*for (int i = 0; i < wideLength; i++) {char16Array[i] = static_cast<char16_t>(wch_utf16[i]);}char16Array[charArrayLength] = u'\0';QString qsWArr = QString::fromWCharArray((wchar_t*)char16Array);QString qsUTF16 = QString::fromUtf16(char16Array);tf16(char16Array);// 释放分配的内存delete[] char16Array;

解码结果:
8

同样在Linux系统上,将wchar_t数组转换为char16_t时,不能直接理解为使用char16_t存储UTF-32编码的内容。这是因为wchar_t和char16_t是不同的数据类型,它们在内存中使用的字节数不同。

小结

本质上,所有字符类型容器存储的都是二进制数据,这些二进制的编码可以是任何字符编码,至于怎么解析这些二进制数据,其值代表什么字符,怎么显示给你看的,就是解码的过程了。

只要清楚源数据的字符编码,无论使用何种容器存储,只能能使用相应的编码方式解码,就一定不会有乱码问题。

字符编码转换:既转换类型,也转换编码

宽字符转多字节字符

WideCharToMultiByte

WideCharToMultiByte是Windows API中的一个函数,用于将宽字符字符串(UTF-16或UCS-2编码)转换为多字节字符字符串(例如ASCII或UTF-8编码)。它通常在需要支持多种字符编码的Windows应用程序中使用。

原型如下:

#include <Windows.h>
int WideCharToMultiByte(UINT CodePage,              // 字符编码标识符DWORD dwFlags,              // 标志位LPCWCH lpWideCharStr,       // 宽字符字符串地址int cchWideChar,            // 宽字符字符串长度LPSTR lpMultiByteStr,       // 多字节字符字符串地址int cbMultiByte,            // 多字节字符字符串长度LPCCH lpDefaultChar,        // 默认字符LPBOOL lpUsedDefaultChar    // 指向布尔值的指针,用于指示是否使用了默认字符);

注意:WideCharToMultiByte的CodePage参数用于指定目标字符编码。它表示将要转换成的多字节字符的编码方式。即这个参数的值决定了将宽字符转换为多字节字符时所使用的字符集。

#include <Windows.h>
#include <string>// 宽字符转多字节字符
std::string WideCharToMultiByteWithAcp(const wchar_t* wideStr, UINT codePage)
{int bufferSize = WideCharToMultiByte(codePage, 0, wideStr, -1, nullptr, 0, nullptr, nullptr);if (bufferSize == 0){// 转换失败return "";}std::string multiStr(bufferSize, '\0');if (WideCharToMultiByte(codePage, 0, wideStr, -1, &multiStr[0], bufferSize, nullptr, nullptr) == 0){// 转换失败return "";}return multiStr;
}

codePage参数是用来指定字符编码的参数,其中不同的值代表不同的字符编码。以下是一些常用的codePage值及其对应的字符编码:

CP_ACP(常用):系统默认的 Windows ANSI 代码页。简体-GBK,繁体-BIG5

CP_MACCP:当前系统 Macintosh 代码页。

CP_OEMCP:当前系统 OEM 代码页。

CP_SYMBOL:Windows 2000: 符号代码页 (42) 。

CP_THREAD_ACP:Windows 2000: 当前线程的 Windows ANSI 代码页。

CP_UTF7:UTF-7。 仅当受 7 位传输机制强制使用此值。 最好使用 UTF-8。 设置此值后, 必须将 lpDefaultChar 和 lpUsedDefaultChar 设置为 NULL。

CP_UTF8(常用):UTF-8。 设置此值后, 必须将 lpDefaultChar 和 lpUsedDefaultChar 设置为 NULL。

测试示例:

char16_t ch16_utf16[] = u"这是一个UTF-16编码的测试文本";
wchar_t wch_utf16[] = L"在Windows系统上,这是一个UTF-16编码的测试文本";//接口测试
// 测试环境:Windows系统,CodePage: 936 简体中文 - GB2312//WideCharToMultiByte & MultiByteToWideChar//宽字符编码(Windows系统上是UTF-16) 的宽字符 转 UTF-8编码的多字节
std::string sUtf8_fromWCH = WideCharToMultiByteWithAcp(wch_utf16, CP_UTF8); //宽字符编码(Windows系统上是UTF-16) 的宽字符 转 ANSI(当前系统是GB2312)编码的多字节
std::string sGB2312_fromWCH = WideCharToMultiByteWithAcp(wch_utf16, CP_ACP);//UTF-16编码的宽字符 转 UTF-8编码的多字节
std::string sUTF8_fromCh16 = WideCharToMultiByteWithAcp((wchar_t*)ch16_utf16, CP_UTF8);//UTF-16编码的宽字符 转 ANSI(当前系统是GB2312)编码的多字节
std::string sGB2312_fromCh16 = WideCharToMultiByteWithAcp((wchar_t*)ch16_utf16, CP_ACP);

测试结果:
9

优点:

可指定转换时的编码

缺点:

Windows接口,不能跨平台

W2A

W2A宏是特定于Windows平台的,依赖于MFC库。

W2A定义如下:

//atlconv.h
#define W2A(lpw) (\((_lpw = lpw) == NULL) ? NULL : (\(_convert = (static_cast<int>(wcslen(_lpw))+1), \(_convert>INT_MAX/2) ? NULL : \ATLW2AHELPER((LPSTR) alloca(_convert*sizeof(WCHAR)), _lpw, _convert*sizeof(WCHAR), _acp))))#define ATLW2AHELPER AtlW2AHelper_Ret_maybenull_z_ _Post_writable_byte_size_(nChars) inline LPSTR WINAPI AtlW2AHelper(_Out_writes_opt_z_(nChars) LPSTR lpa,_In_opt_z_ LPCWSTR lpw,_In_ int nChars) throw()
{return AtlW2AHelper(lpa, lpw, nChars, CP_ACP);
}ATLPREFAST_SUPPRESS(6054)
_Ret_maybenull_z_ _Post_writable_byte_size_(nChars) inline LPSTR WINAPI AtlW2AHelper(_Out_writes_opt_z_(nChars) LPSTR lpa,_In_opt_z_ LPCWSTR lpw,_In_ int nChars,_In_ UINT acp) throw()
{ATLASSERT(lpw != NULL);ATLASSERT(lpa != NULL);if (lpa == NULL || lpw == NULL)return NULL;// verify that no illegal character present// since lpa was allocated based on the size of lpw// don't worry about the number of chars*lpa = '\0';int ret = WideCharToMultiByte(acp, 0, lpw, -1, lpa, nChars, NULL, NULL);if(ret == 0){ATLASSERT(FALSE);return NULL;}return lpa;
}
ATLPREFAST_UNSUPPRESS()

可以看到W2A宏也是使用Windows接口WideCharToMultiByte进行宽字符到多字节字符的转换,因此使用W2A时,还需要包含Windows.h头文件:

#include <Windows.h>

需要注意的是,使用W2A宏进行字符串转换时,会使用系统默认的字符编码(CP_ACP)进行转换。如果需要使用其他字符编码进行转换,可以使用上面用WideCharToMultiByte实现的转换函数并指定相应的字符编码。

这里使用alloca在在W2A宏函数所在的作用域内的栈上分配内存,无需再手动释放内存!

自定义转换接口如下:

std::string WToA(const wchar_t* wideStr)
{USES_CONVERSION;return std::string(W2A(wideStr));
}

测试示例:

//W2A & A2W : 默认使用系统默认编码转换,不支持指定转换编码
char16_t ch16_utf16[] = u"这是一个UTF-16编码的测试文本";
wchar_t wch_utf16[] = L"在Windows系统上,这是一个UTF-16编码的测试文本";//宽字符编码(Windows系统上是UTF-16) 的宽字符 转 ANSI(当前系统是GB2312)编码的多字节
std::string sAnsi_fromWCH = WToA(wch_utf16);//UTF-16编码的宽字符 转 ANSI(当前系统是GB2312)编码的多字节
std::string sAnsi_fromCH16 = WToA((wchar_t*)ch16_utf16);

测试结果:
10

不能指定编码,且不能跨平台,直接拉黑。

wcstombs

wcstombs 是一个 C/C++ 标准库函数,用于将宽字符字符串(wchar_t 类型)转换为多字节字符串(char 类型)。

函数原型:

size_t wcstombs(char* dest, const wchar_t* src, size_t destSize);
//dest:指向目标多字节字符串的指针,用于存储转换后的结果。
//src:指向源宽字符字符串的指针,要进行转换的字符串。
//destSize:目标多字节字符串的最大长度,包括终止符 \0。
//返回值:
//如果转换成功,则返回转换后的多字节字符数(不包括终止符 \0)。
//如果目标多字节字符串的空间不足以存储转换后的结果或发生了无效的宽字符,则返回 SIZE_MAX 并设置 errno 为 EILSEQ。

注意事项:

  • wcstombs 函数将宽字符字符串按照当前的本地编码进行转换。因此,转换结果可能因系统区域设置而异,对于非 ASCII 字符,可能需要使用 Unicode 或其他编码来转换。
  • 目标多字节字符串应该具备足够的空间,以确保转换后的结果不会溢出。
  • 如果 dest 参数为 NULL,则 wcstombs 函数会计算转换后的结果长度,但不进行实际转换。可以通过检查返回值来获取所需的缓冲区大小。

可使用std::setlocale获取、设置当前程序的本地化(locale)环境。

std::setlocale函数用于设置程序的本地化环境,以便程序能够正确处理与语言、字符集、日期时间格式等相关的操作。根据指定的本地化环境,不同的函数和库可能在字符转换、字符串比较、日期时间格式化等方面表现出不同的行为。

关于std::setlocale的使用,详见:std::setlocale详解

这里列举下常规的使用示例:

    //std::setlocale//获取本地化环境char* pCurLocale = std::setlocale(LC_ALL, nullptr);//仅传入可选值,设置失败返回NULL//当前系统环境(Chinese (Simplified)_China.936),使用GB2312编码char* pZh_CN_936 = std::setlocale(LC_ALL, ".936");//当前系统环境(Chinese (Simplified)_China.936),使用BIG5编码char* pZh_CN_950 = std::setlocale(LC_ALL, ".950");//当前系统环境(Chinese (Simplified)_China.936),UTF-8编码char* pZh_CN_65001 = std::setlocale(LC_ALL, ".65001");//恢复系统本地默认设置char* pDefault = std::setlocale(LC_ALL, "");

结果:
11

自定义转换接口(带编码参数)如下:

// 将宽字符字符串转换为多字节字符串
std::string WcsToMbs(const wchar_t* wideStr, const char* encoding)
{// 获取源宽字符字符串的长度size_t len = std::wcslen(wideStr);// 计算多字节字符串所需的缓冲区大小int nCnt = sizeof(wchar_t);size_t bufferSize = (len + 1) * nCnt;char* multiByteStr = new char[bufferSize];// 设置转换编码//先保存当前本地化设置const char* currentLocale = std::setlocale(LC_ALL, nullptr);std::setlocale(LC_ALL, encoding);// 将宽字符字符串转换为多字节字符串wcstombs(multiByteStr, wideStr, bufferSize);// 恢复之前的本地化设置std::setlocale(LC_ALL, currentLocale);std::string result(multiByteStr);delete[] multiByteStr;return result;
}

**注意:**MbsToWcs的encoding参数用于指定目标字符编码。它表示将要转换成的多字节字符的编码方式。即这个参数的值决定了将宽字符转换为多字节字符时所使用的字符集。同前面的WideCharToMultiByte。

测试示例:

char16_t ch16_utf16[] = u"这是一个UTF-16编码的测试文本";
wchar_t wch_utf16[] = L"在Windows系统上,这是一个UTF-16编码的测试文本";//宽字符编码(Windows系统上是UTF-16) 的宽字符 转 ANSI(当前系统是GB2312)编码的多字节
std::string sANSI_fromWCH = WcsToMbs(wch_utf16, "");//宽字符编码(Windows系统上是UTF-16) 的宽字符 转 UTF-8编码的多字节
std::string sUTF8_fromWCH = WcsToMbs(wch_utf16, ".65001");//UTF-16编码的宽字符 转 ANSI(当前系统是GB2312)编码的多字节
std::string sANSI_fromCh16 = WcsToMbs((wchar_t*)ch16_utf16, "");//UTF-16编码的宽字符 转 UTF-8编码的多字节
std::string sUTF8_fromCh16 = WcsToMbs((wchar_t*)ch16_utf16, ".65001");

测试结果:
12
优点:C/C++ 标准库接口实现,可跨平台,且能指定编码转换

std::wstring_convert

std::wstring_convert 是 C++ 标准库中的一个类模板,它提供了多字节字符串和宽字符字符串之间的转换功能。它是在 C++11 标准中引入的,定义在 <locale> 头文件中。

std::wstring_convert 类模板主要用于将多字节字符串(std::string)和宽字符字符串(std::wstring)之间进行转换。它提供了一种方便的方法来处理不同编码之间的字符串转换,而无需直接使用底层的 C 标准库函数。

下面是 std::wstring_convert 类模板的基本语法:

template <class Codecvt, class Elem = wchar_t, class Wide_alloc = std::allocator<Elem>, class Byte_alloc = std::allocator<char>>
class wstring_convert;

std::wstring_convert 模板有四个参数:

  • Codecvt:指定编码转换器的类型。这是一个实现了 std::codecvt 接口的类型,用于执行不同编码之间的转换。通常可以使用 std::codecvt_utf8, std::codecvt_utf16, std::codecvt_utf8_utf16 等标准实现的编码转换器。
  • Elem:目标字符类型,默认为 wchar_t,表示宽字符。
  • Wide_alloc:宽字符字符串的分配器类型,默认为 std::allocator<Elem>
  • Byte_alloc:多字节字符串的分配器类型,默认为 std::allocator<char>

std::wstring_convert 类模板提供了以下成员函数:

  1. wstring_convert(Codecvt* pcvt): 构造函数,使用指定的编码转换器 pcvt 创建一个 std::wstring_convert 对象。
  2. ~wstring_convert(): 析构函数,释放 std::wstring_convert 对象的资源。
  3. std::string to_bytes(const std::wstring& str): 将宽字符字符串 str 转换为多字节字符串,并返回转换后的结果。
  4. std::wstring from_bytes(const std::string& str): 将多字节字符串 str 转换为宽字符字符串,并返回转换后的结果。

std::wstring_convert 模板的 Codecvt 参数指定了编码转换器的类型,它是一个实现了 std::codecvt 接口的类型。下面是一些常见的可用的 Codecvt 参数值:

  1. std::codecvt_utf8<wchar_t>:用于将 UTF-8 编码的多字节字符串转换为宽字符字符串。
  2. std::codecvt_utf16<wchar_t>:用于将 UTF-16 编码的多字节字符串转换为宽字符字符串。
  3. std::codecvt_utf8_utf16<wchar_t>:用于在 UTF-8 编码和 UTF-16 编码之间进行转换。
  4. std::codecvt_byname<wchar_t, char, std::mbstate_t>:使用特定名称的编码转换器。
  5. std::codecvt<char16_t, char, std::mbstate_t>:用于将 UTF-16 编码的多字节字符串转换为 char16_t 类型的宽字符字符串。
  6. std::codecvt<char32_t, char, std::mbstate_t>:用于将 UTF-32 编码的多字节字符串转换为 char32_t 类型的宽字符字符串。

注意,具体可用的 Codecvt 参数值取决于你的编译器和标准库的实现,以及系统所支持的编码类型。

另外需注意,如果你使用的编译器和标准库不支持特定的编码转换器,你可以自定义实现 std::codecvt 接口的子类,并将其作为 Codecvt 参数传递给 std::wstring_convert 模板实例化。这样可以根据您的需要支持其他编码转换(例ANSI编码)。

std::wstring_convert 并不能直接使用 ANSI 编码进行转换。std::wstring_convert 模板的 Codecvt 参数是一个实现了 std::codecvt 接口的类型,但是标准库并没有提供直接支持 ANSI 编码的 std::codecvt 实现。

ANSI 编码是一个与操作系统和环境相关的编码方式,不同的操作系统和环境可能使用不同的 ANSI 编码。在 C++ 标准库中,提供了与 ANSI 编码相关的 std::codecvt 实现的特定名称,例如 std::codecvt_byname,它允许使用特定名称的编码来进行转换。但是,具体可用的编码名称取决于操作系统和环境的支持。

如果需要使用 ANSI 编码进行字符串转换,可以考虑以下几种方法:

  1. 使用底层的 C 语言标准库函数,例如 mbstowcswcstombs,来手动进行 ANSI 编码和宽字符字符串之间的转换。

  2. 使用第三方库,例如 iconv,它可以提供更灵活和全面的字符串编码转换功能。

  3. 根据具体需求,可以自己实现一个符合 ANSI 编码转换要求的 std::codecvt 子类。

自定义转换接口:

std::string ws_convert_to_s(const std::wstring& wideStr)
{try{std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;return converter.to_bytes(wideStr);}catch (...){return "";}
}

该方式不能指定ANSI编码进行转换,若想指定需自定义std::codecvt子类,较为麻烦。

但对于Unicode字符集中的编码转换较为方便。

测试示例:

//std::wstring_convert
char16_t ch16_utf16[] = u"这是一个UTF-16编码的测试文本";
wchar_t wch_utf16[] = L"在Windows系统上,这是一个UTF-16编码的测试文本";//宽字符编码(Windows系统上是UTF-16) 的宽字符 转 ANSI(当前系统是GB2312)编码的多字节
//不支持转 ANSI(当前系统是GB2312)编码的多字节//宽字符编码(Windows系统上是UTF-16) 的宽字符 转 UTF-8编码的多字节
std::string sANSI_fromWCH = ws_convert_to_s(wch_utf16);//UTF-16编码的宽字符 转 ANSI(当前系统是GB2312)编码的多字节
//不支持转 ANSI(当前系统是GB2312)编码的多字节//UTF-16编码的宽字符 转 UTF-8编码的多字节
std::string sANSI_fromCh16 = ws_convert_to_s((wchar_t*)ch16_utf16);

测试结果:
13

多字节字符转宽字符

MultiByteToWideChar

MultiByteToWideChar是Windows API中的一个函数,用于将多字节字符字符串(例如ASCII或UTF-8编码)转换为宽字符字符串(UTF-16或UCS-2编码)。它与WideCharToMultiByte相反,可以在Windows应用程序中进行字符编码的转换。该函数可以根据指定的代码页来处理字符转换,同时还可以处理不可转换的字符。

原型如下:

#include <Windows.h>
int MultiByteToWideChar(UINT CodePage,              // 字符编码标识符DWORD dwFlags,              // 标志位LPCCH lpMultiByteStr,       // 多字节字符字符串地址int cbMultiByte,            // 多字节字符字符串长度LPWSTR lpWideCharStr,       // 宽字符字符串地址int cchWideChar             // 宽字符字符串长度);

注意:MultiByteToWideChar函数中的CodePage参数是指定源字符编码的参数。它表示将要转换的多字节字符的编码方式。参数nCodePage指定了输入多字节字符的代码页,用于将多字节字符转换为宽字符(UTF-16或UCS-2编码)。即这个参数的值决定了多字节字符转换为宽字符时所使用的字符集。

#include <Windows.h>
#include <string>// 多字节字符转宽字符
std::wstring MultiByteToWideCharWithAcp(const char* multiStr, UINT codePage)
{int bufferSize = MultiByteToWideChar(codePage, 0, multiStr, -1, nullptr, 0);if (bufferSize == 0){// 转换失败return L"";}std::wstring wideStr(bufferSize, L'\0');if (MultiByteToWideChar(codePage, 0, multiStr, -1, &wideStr[0], bufferSize) == 0){// 转换失败return L"";}return wideStr;
}

测试示例:

char ch_ansi[] = "这是一个ANSI编码的测试文本";
char ch_utf8[] = u8"这是一个UTF-8编码的测试文本";//ANSI(当前系统是GB2312)编码的多字节 转 宽字符编码(Windows系统上是UTF-16) 的宽字符
std::wstring wsWide_fromANSI = MultiByteToWideCharWithAcp(ch_ansi, CP_ACP);//ANSI(当前系统是GB2312)编码的多字节 转 UTF-16编码的宽字符
const char16_t* pCh16_fromANSI = (const char16_t*)wsWide_fromANSI.c_str();//UTF-8编码的多字节 转 宽字符编码(Windows系统上是UTF-16) 的宽字符
std::wstring wsWide_fromUTF8 = MultiByteToWideCharWithAcp(ch_utf8, CP_UTF8);//UTF-8编码的多字节 转 UTF-16编码的宽字符
const char16_t* pCh16_fromUTF8 = (const char16_t*)wsWide_fromUTF8.c_str();

测试结果:
14

优点:

可指定转换时的编码

缺点:

Windows接口,不能跨平台

A2W

同理,A2W宏是特定于Windows平台的,依赖于MFC库。

A2W定义如下:

//atlconv.h
#define A2W(lpa) (\((_lpa = lpa) == NULL) ? NULL : (\_convert = (static_cast<int>(strlen(_lpa))+1),\(INT_MAX/2<_convert)? NULL :  \ATLA2WHELPER((LPWSTR) alloca(_convert*sizeof(WCHAR)), _lpa, _convert, _acp)))#define ATLA2WHELPER AtlA2WHelper_Ret_maybenull_z_ _Post_writable_byte_size_(nChars) inline LPWSTR WINAPI AtlA2WHelper(_Out_writes_opt_z_(nChars) LPWSTR lpw,_In_opt_z_ LPCSTR lpa,_In_ int nChars) throw()
{return AtlA2WHelper(lpw, lpa, nChars, CP_ACP);
}ATLPREFAST_SUPPRESS(6054)
_Ret_maybenull_z_ _Post_writable_byte_size_(nChars) inline LPWSTR WINAPI AtlA2WHelper(_Out_writes_opt_z_(nChars) LPWSTR lpw,_In_opt_z_ LPCSTR lpa,_In_ int nChars,_In_ UINT acp) throw()
{ATLASSERT(lpa != NULL);ATLASSERT(lpw != NULL);if (lpw == NULL || lpa == NULL)return NULL;// verify that no illegal character present// since lpw was allocated based on the size of lpa// don't worry about the number of chars*lpw = '\0';int ret = MultiByteToWideChar(acp, 0, lpa, -1, lpw, nChars);if(ret == 0){ATLASSERT(FALSE);return NULL;}return lpw;
}
ATLPREFAST_UNSUPPRESS()

注意:

使用A2W同样需要包含Windows.h

#include <Windows.h>

转换时也是使用系统默认的字符编码(CP_ACP)进行转换。

这里使用alloca在在A2W宏函数所在的作用域内的栈上分配内存,无需再手动释放内存!

自定义转换接口如下:

std::wstring AToW(const char* multiStr)
{USES_CONVERSION;return std::wstring(A2W(multiStr));
}

测试示例:

char ch_ansi[] = "这是一个ANSI编码的测试文本";//ANSI(当前系统是GB2312)编码的多字节 转 宽字符编码(Windows系统上是UTF-16) 的宽字符
std::wstring wsWide_fromANSI = AToW(ch_ansi);//ANSI(当前系统是GB2312)编码的多字节 转 UTF-16编码的宽字符
const char16_t* pCh16_fromAnsi = (const char16_t*)wsWide_fromANSI.c_str();

测试结果:

15

因默认使用系统默认编码进行转码,传入UTF-8编码内容时转码会乱码

//传入UTF-8编码内容转码 会 乱码
char ch_utf8[] = u8"这是一个UTF-8编码的测试文本";
std::wstring wsWide_fromUTF8 = AToW(ch_utf8);

测试结果:
16

mbstowcs

mbstowcs 是一个 C/C++ 标准库函数,用于将多字节字符串(char 类型)转换为宽字符字符串(wchar_t 类型)。

函数原型:

size_t mbstowcs(wchar_t* dest, const char* src, size_t destSize);
dest:指向目标宽字符字符串的指针,用于存储转换后的结果。
src:指向源多字节字符串的指针,要进行转换的字符串。
//destSize:目标宽字符字符串的最大长度,以宽字符的单位进行计数。
//返回值:
//如果转换成功,则返回转换后的宽字符数(不包括终止符 \0)。
//如果目标宽字符字符串的空间不足以存储转换后的结果或发生了无效的多字节字符,则返回 SIZE_MAX 并设置 errno 为 EILSEQ。

注意事项:

  • mbstowcs 函数将多字节字符串按照当前的本地化设置进行转换。因此,转换结果可能因系统区域设置而异,对于非 ASCII 字符,可能需要使用 Unicode 或其他宽字符编码来转换。
  • 目标宽字符字符串应该具备足够的空间,以确保转换后的结果不会溢出。
  • 如果 dest 参数为 NULL,则 mbstowcs 函数会计算转换后的结果长度,但不进行实际转换。可以通过检查返回值来获取所需的缓冲区大小。

自定义转换接口(带编码参数)如下:

// 将多字节字符串转换为宽字符字符串
std::wstring MbsToWcs(const char* multiByteStr, const char* encoding)
{// 获取源多字节字符串的长度size_t len = std::strlen(multiByteStr);// 计算宽字符字符串所需的缓冲区大小size_t bufferSize = len + 1;wchar_t* wideStr = new wchar_t[bufferSize];// 设置转换编码//先保存当前本地化设置const char* currentLocale = std::setlocale(LC_ALL, nullptr);std::setlocale(LC_ALL, encoding);// 将多字节字符串转换为宽字符字符串mbstowcs(wideStr, multiByteStr, bufferSize);// 恢复之前的本地化设置std::setlocale(LC_ALL, currentLocale);std::wstring result(wideStr);delete[] wideStr;return result;
}

注意:MbsToWcs函数中的encoding参数是指定源字符编码的参数。它表示将要转换的多字节字符的编码方式。参数nCodePage指定了输入多字节字符的代码页,用于将多字节字符转换为宽字符(UTF-16或UCS-2编码)。即这个参数的值决定了多字节字符转换为宽字符时所使用的字符集。同MultiByteToWideChar。

测试示例:

    char ch_ansi[] = "这是一个ANSI编码的测试文本";char ch_utf8[] = u8"这是一个UTF-8编码的测试文本";//ANSI(当前系统是GB2312)编码的多字节 转 宽字符编码(Windows系统上是UTF-16) 的宽字符std::wstring wsWide_fromANSI = MbsToWcs(ch_ansi, "");//ANSI(当前系统是GB2312)编码的多字节 转 UTF-16编码的宽字符const char16_t* pCh16_fromANSI = (const char16_t*)wsWide_fromANSI.c_str();//UTF-8编码的多字节 转 宽字符编码(Windows系统上是UTF-16) 的宽字符std::wstring wsWide_fromUTF8 = MbsToWcs(ch_utf8, ".65001");//UTF-8编码的多字节 转 UTF-16编码的宽字符const char16_t* pCh16_fromUTF8 = (const char16_t*)wsWide_fromUTF8.c_str();

测试结果:
17

若指定编码和原数据编码不一致时,会导致转码失败:

//若指定编码和原数据编码不一致时,会导致转码失败
//原数据是ANSI(当前系统是GB2312)编码,却指定UTF-8编码
std::wstring wsTmp = MbsToWcs(ch_ansi, ".65001");//原数据是UTF-8编码,却指定ANSI(当前系统是GB2312)编码
std::wstring wsTmp2 = MbsToWcs(ch_utf8, "");

结果:
18

因使用C/C++标准库接口,相较前两种转换,会更通用

std::wstring_convert

相关介绍同上。

自定义转换接口:

std::wstring s_convert_to_ws(const std::string& multiStr)
{try{std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;return converter.from_bytes(multiStr);}catch (...){return L"";}}

测试示例:

char ch_ansi[] = "这是一个ANSI编码的测试文本";
char ch_utf8[] = u8"这是一个UTF-8编码的测试文本";//ANSI(当前系统是GB2312)编码的多字节 转 宽字符编码(Windows系统上是UTF-16) 的宽字符
std::wstring wsWide_fromANSI = s_convert_to_ws(ch_ansi);//ANSI(当前系统是GB2312)编码的多字节 转 UTF-16编码的宽字符
const char16_t* pCh16_fromANSI = (const char16_t*)wsWide_fromANSI.c_str();//UTF-8编码的多字节 转 宽字符编码(Windows系统上是UTF-16) 的宽字符
std::wstring wsWide_fromUTF8 = s_convert_to_ws(ch_utf8);//UTF-8编码的多字节 转 UTF-16编码的宽字符
const char16_t* pCh16_fromUTF8 = (const char16_t*)wsWide_fromUTF8.c_str();

测试结果:

19

总结

本文详细介绍了宽窄字符和字符编码之间的关系,同时归纳了两种宽窄字符转换的情况:
一种是保持原有字符编码不变,仅进行宽窄字符类型的转换
另一种是即进行宽窄字符类型的转换,也进行字符编码的转换

并给出了对应的测试示例及测试结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/594555.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023 年博客总结

当无所事事&#xff0c;没有多少收获的时候&#xff0c;时间过得格外的快… 当充实有为&#xff0c;经常有收获有进步的时候&#xff0c;才觉得时间没有浪费… 2023年收获不多… 以前说孩子小&#xff0c;需要照顾所以没时间&#xff0c;我以为孩子大点就有时间了&#xff0c;…

图片处理相关网站(图片分辨率、尺寸修改、AI扩图等)

分享一些免费的可进行图片的各种处理的网站&#xff0c;包括图片分辨率、尺寸修改、AI扩图等&#xff0c;持续增加中。。。 1.photokit.com 可进行图片分辨率、尺寸、压缩等修改。 免费在线图片编辑器 - 在线抠图、改图、修图、美图 - PhotoKit.comPhotoKit是一款免费的…

【RocketMQ每日一问】RocketMQ SQL92过滤用法以及原理?

1.生产端 public class SQLProducer {public static int count 10;public static String topic "xiao-zou-topic";public static void main(String[] args) {DefaultMQProducer producer MQUtils.createLocalProducer();IntStream.range(0, count).forEach(i -&g…

自动化测试面试题及答案大全(上)

selenium中如何判断元素是否存在&#xff1f; 没有提供原生的方法判断元素是否存在&#xff0c;一般我们可以通过定位元素异常捕获的方式判断selenium中hidden或者是display &#xff1d; none的元素是否可以定位到&#xff1f; 不可以&#xff0c;想点击的话&#xff0c;可以用…

[ASIS 2019]Unicorn shop

点入题目看见四个可购买的东西&#xff0c;但是都购买不了&#xff0c;最后一个价格大的脱俗&#xff0c;猜测成功买到后会得到flag&#xff0c;但是购买时提示操作失败只允许一个字符。查看源码发现在utf-8后面特意标注提示 涉及到了字符编码和字符集的概念&#xff1a; UTF-…

2024 年度 AAAI Fellows 揭晓!清华大学朱军教授入选!

今日&#xff0c;国际人工智能领域最权威的学术组织 AAAI 揭晓 2024 年度 Fellows 评选结果&#xff0c;新增 12 位 Fellow。 其中&#xff0c;清华大学计算机系教授朱军因「在机器学习理论与实践方面做出的重大贡献」而成功入选&#xff0c;成为本年度入选的唯一华人学者&…

2023最大技术潮:大模型冲击下的智能汽车

作者 |德新 编辑 |王博 过去这年最大的技术潮&#xff0c;非大模型莫属。 2023年初&#xff0c;由ChatGPT掀起的浪花&#xff0c;迅速地演变成了席卷全球的AI科技浪潮。汽车行业在其中也不可避免。各大车企纷纷投入与大模型相关的布局。 长城官宣成立了AI Lab&#xff0c;到…

尝试解决 H5无法播放mp3录音问题

1.问题描述 将H5传递的录音 media_id 通过微信获取临时素材接口获取到录音文件流之后&#xff0c;转为mp3文件上传到服务器。但是H5却没办法播放这个mp3文件。这个文件只能在播放器上播放&#xff0c;比如网易云什么的。不能直接在微信&#xff0c;浏览器上播放 2.原因分析 通…

山西电力市场日前价格预测【2024-01-04】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-01-04&#xff09;山西电力市场全天平均日前电价为197.93元/MWh。其中&#xff0c;最高日前电价为376.04元/MWh&#xff0c;预计出现在18:15。最低日前电价为0.00元/MWh&#xff0c;预计出…

Go语言基础简单了解

文章目录 前言关于Go学习流程 基础语法注释变量常量数据类型运算符fmt库 流程控制if、switch、selectfor、break、continue遍历String 函数值传递和引用传递deferinit匿名、回调、闭包函数 数组和切片Map结构体自定义数据类型接口协程和channel线程锁异常处理泛型文件读取文件写…

数据库中的几种锁

数据库锁 1.数据库锁的种类 以 mysql innoDB 为例&#xff0c;数据库的锁有 排他锁&#xff0c;共享锁&#xff0c;意向锁&#xff0c;自增锁&#xff0c;间隙锁&#xff0c;锁的范围有包括&#xff0c;行锁&#xff0c;表锁 &#xff0c;区间锁。 从应用研发的视角&#xff…

C++:第十一讲DFS深搜

Everyday English Your optimal career is simply this: Share the real you with physical world through th e process of creative self-expression. 你的最佳职业很简单&#xff0c;就是这样&#xff1a;通过创造性自我表达的途径和世界分享真实的你。 前言 今天带着大家…

一元函数微分学——刷题(14

目录 1.题目&#xff1a;2.解题思路和步骤&#xff1a;3.总结&#xff1a;小结&#xff1a; 1.题目&#xff1a; 2.解题思路和步骤&#xff1a; 只要知道反函数求导等于原函数的导数的倒数&#xff0c;那就很好求解了&#xff0c;这不过要先处理好x和y的对应关系&#xff0c;知…

本地套接字通信

本地套接字&#xff08;Unix域套接字&#xff09;是用于在同一台机器上的进程之间进行通信的一种方式。它不涉及网络协议&#xff0c;而是直接在文件系统中创建一个套接字文件&#xff0c;进程可以通过该文件进行通信。 本地套接字的作用&#xff1a;本地的进程间通信 有关系的…

语音AI小夜灯项目

一、项目简介 使用ESP32-S3N8R8模块作为主控芯片&#xff0c;S3内核增加了用于加速神经网络计算和信号处理等的指令&#xff0c;这使得我们可以使用它来快速解析训练好的语音模型进行语音识别的功能。 二、原理解析 本项目由四个部分组成&#xff0c;电源部分、LED照明部分、…

uniapp二维码有效期倒计时三分钟的效果是实现

需求&#xff1a; 会员码时效只有3分钟有效期&#xff0c;需要在页面倒计时3分钟&#xff0c;没有长按保存的效果实现 效果&#xff1a; 代码&#xff1a; <templete> <view> <uni-list> <view class"custom-list-item" click"onCode(c…

java代码规范(适合写程序之前先了解有助于开发协同)

目录 一、类定义 二、方法定义 三、接口定义 四、变量定义 1、命名规范&#xff1a; 2、类型规范&#xff1a; 3、常量规范&#xff1a; 五、static关键字 1、静态变量&#xff08;类变量&#xff09;&#xff1a; 2、静态方法&#xff08;类方法&#xff09;&#x…

AUTOSAR软件手册文档缩写描述,AUTOSAR_TR_PredefinedNames

由于AUTOSAR文档中的缩写太多&#xff0c;入门者看起开不方便。例如TR、SWS、SRS、EXP模块。 下载链接&#xff1a;https://www.autosar.org/fileadmin/standards/R21-11/FO/AUTOSAR_TR_PredefinedNames.pdf

android——自定义TextView

效果展示&#xff1a; 代码解析&#xff1a; 1、首先设置自定义属性&#xff08;res/values下新建一个attrs.xml文件&#xff09; <?xml version"1.0" encoding"utf-8"?> <resources><!-- name 自定义view的名字 CustomTextView--&…

Spring Bean的生命周期(钩子函数)

借鉴&#xff1a;https://www.cnblogs.com/liweimingbk/p/17843970.html https://blog.csdn.net/lxz352907839/article/details/128634404 一、Spring Bean生命周期 如果Spring配置文件中所定义的Bean类实现了ApplicationContextAware 接口&#xff0c;那么在加载Spring配置文…