1、std::wstring--string
方法一:
string CwxPropPageAuthenticationSheet::WstingToChar(std::wstring& wstr)
{
string result;
//获取缓冲区大小,并申请空间,缓冲区大小事按字节计算的
int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), NULL, 0, NULL, NULL);
char* buffer = new char[len + 1];
//宽字节编码转换成多字节编码
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), buffer, len, NULL, NULL);
buffer[len] = '\0';
//删除缓冲区并返回值
result.append(buffer);
delete[] buffer;
return result;
}
#include
//将string转换成wstring
wstring string2wstring(string str)
{
wstring result;
//获取缓冲区大小,并申请空间,缓冲区大小按字符计算
int len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), NULL, 0);
TCHAR* buffer = new TCHAR[len + 1];
//多字节编码转换成宽字节编码
MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), buffer, len);
buffer[len] = '\0'; //添加字符串结尾
//删除缓冲区并返回值
result.append(buffer);
delete[] buffer;
return result;
}
//将wstring转换成string
string wstring2string(wstring wstr)
{
string result;
//获取缓冲区大小,并申请空间,缓冲区大小事按字节计算的
int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), NULL, 0, NULL, NULL);
char* buffer = new char[len + 1];
//宽字节编码转换成多字节编码
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), buffer, len, NULL, NULL);
buffer[len] = '\0';
//删除缓冲区并返回值
result.append(buffer);
delete[] buffer;
return result;
}
方法二:
#include
#pragma comment(lib, "comsuppw.lib")
string ws2s(const wstring& ws)
{
_bstr_t t = ws.c_str();
char* pchar = (char*)t;
string result = pchar;
return result;
}
wstring s2ws(const string& s)
{
_bstr_t t = s.c_str();
wchar_t* pwchar = (wchar_t*)t;
wstring result = pwchar;
return result;
}
C++中常会看到如char*、wchar_t*、LPSTR、LPCSTR、LPTSTR、LPCTSTR这样的字符串类型,也有CString、CStringA、CStringW、std::string、std::wstring这样的C++类,纷繁复杂难以理解,它们之间的互相转换也如一个个谜团一般。
下面就一起来剥茧抽丝,解开这些谜团。
这是大家最常见的类型了,它指向一个字符串的首地址。
根据C/C++的约定,它也能用来表示整个字符串,串的结尾符为'\0'(内存中数值为0)。
char* pString = "字符串内容"; |
字符数组变量既包含了字符串的首地址信息,在某些场景下可等同于字符指针,所以它能这样:
char szString[] = "字符串内容"; char* pString = szString; //获取字符数组首地址 |
同时它又包含了整个字符数组的长度信息,所以字符数组也能这样:
size_t nSize = sizeof(szString); // 获取字符串长度 |
若一个字符串内容不希望被随意修改,我们应当为其加上const属性,特别是在传入参数时。
对于Unicode(双字节字符集)字符串,在Windows中使用wchar_t*来表示。在早期的Visual Studio(2005以前)中,wchar_t实际上是unsigned short的typedef。在后期版本中已经称为一种独立的内置类型。
CHAR实际就是char,WCHAR就是wchar_t。
而TCHAR则使用了宏定义技术来检测项目的字符集配置,根据这个配置来自动适应——当项目是多字节(或ANSI,下同)时,它就是char;项目是宽字节(Unicode,下同)时,它就是wchar_t。
#ifdef UNICODE typedef WCHAR TCHAR; #else typedef CHAR TCHAR; #endif |
Windows API设计得很精巧,但常常也把简单的东西变得更复杂,下面几种类型其实就是char*、wchar_t*类型的typedef而已。
typedef char* LPSTR; |
typedef const char* LPCSTR; |
typedef wchar_t* LPWSTR; |
typedef const wchar_t* LPCWSTR; |
typedef TCHAR* LPTSTR; |
typedef const TCHAR* LPCTSTR; |
该类型来自于STL库,其实所有STL容器都是使用泛型技术的类模版实现,string也不例外,它是类模版basic_string的实例化。本质上跟vector没啥区别,只是为了字符串操作便利性做了许多优化设计而已。
typedef basic_string<char, …> string; |
专门针对ANSI字符串的相关操作。
typedef basic_string<wchar_t, …> wstring; |
专门针对Unicode字符串的相关操作。
typedef LPWSTR BSTR; |
CString也是类模版CStringT的实例化。客观来讲,CString设计上比std::string要更为精巧,也更加实用。
typedef CStringT |
CString会根据TCHAR的实际类型自动适应项目字符集配置。
bstr_t是COM中的字符串封装类,只是单纯的类,而不是类模版,它在一个类中同时实现了对char*和wchar_t*的支持。
不同的字符串类型设计思路和背景各不相同,互不兼容,但它们一定都兼容const char*、const wchar_t*。所以我们的思路一般是将const char*(Unicode的话就是const wchar_t*)作为桥梁,按源类型àconst char*à目标类型来完成转换。
由于char*本身就是LPSTR,所以没有区别,不用转换。
char* pString = "字符串内容"; LPSTR lpStr = pString;
|
而非const对象也可以直接转换到const对象,所以也能隐式转换到LPCSTR类型。
char* pString = "字符串内容"; LPCSTR lpcStr = pString;
|
只是需要注意const char*无法直接赋值到LPSTR类型,最安全的方法是将const char*拷贝到一个字符数组,然后再将字符数组赋值给LPSTR类型。
const char* pszString = "字符串内容"; size_t nBufferSize = ::strlen(pszString) + 1; char* pBuffer = new char[nBufferSize]; ::memset(pBuffer, 0, nBufferSize); LPSTR pStr = pBuffer;
|
当然我们也能强制转换,但并非安全的做法。
pStr = (char*)pszString; // C风格 pStr = const_cast
|
std::string有针对const char*类型的构造函数和赋值函数,所以char*/const char*能很轻松转换到std::string类型。
// 拷贝构造 char* pString = "字符串内容"; std::string str = pString; // 赋值 std::string str1; str1 = pString;
|
注意当char*的值是NULL时,直接传递到std::string会报告异常,这点是std::string设计得不好的地方。解决方法是使用前先行判断char*的值,若为NULL则不要赋值,简洁的写法如下:
std::string s = lpString != NULL ? lpString : ""; |
由于BSTR是wchar_t*的变体,所以这实际上是ANSI到Unicode的转换。这方面内容请参考第3章。后续内容也不在赘述。
char*转换到CString也类似于std::string。
更强的是,CString内部实现了ANSI/Unicode的相互转换,所以char*和wchar_t*类型赋值到CString可以不用考虑字符集的转换。
char* pString = "字符串内容"; wchar_t* pWstring = L"字符串内容"; CString str = pString; str = pWstring;
|
bstr_t重载了针对const char*的
初学者常见的错误是将char*类型给强制转换到wchar_t*类型,或反过来干。字符编码不同,这样的强制转换是没有意义的,最后只能得到一堆乱码。
// 错误!得到乱码结果!
|
标准做法是使用MultiByteToWideChar / mbstowcs 从char*转换到wchar_t*,使用WideCharToMultiByte / wcstombs从wchar_t*转换到char*。具体内容可参考第3章。
由于LPSTR系列本来就是char*的变体,所以转换方式可参考char*的转换。以后章节也不在针对LPSTR赘述。
std::string不建议直接转换到char*,但是我们有函数可以直接转换到const char*。
std::string str; const char* pstr = str.c_str();
|
先转换到const char*,然后再将const char*转换到CString,完事儿。
string str; CString cstr = str.c_str();
|
CString比较强悍的是可以同时处理ANSI和Unicode两种字符集,所以下面这样也完全没问题。
wstring wstr; CString cstr = wstr.c_str();
|
转换思路跟上一节相同。
string str; bstr_t bstr = str.c_str();
|
CString实现了operator LPCTSTR(),该类型随项目字符集设置而变化,在多字节环境下是const char*,而在宽字节环境下又变成了const wchar_t*。
所以多字节时,我们像这样转换:
CString str; const char* pstr = str; pstr = str.GetString(); // 显式调用成员函数 char* pstr1 = str.GetBuffer(); // 该成员函数返回 char*
|
而宽字节环境得显式地利用CStringA:
CString str; CStringA strA(str); const char* pstr = strA; char* pstr1 = strA.GetBuffer();
|
同样需要区分多字节还是宽字节环境。
多字节环境下,可以直接这样写:
CString cstr = _T("字符串内容"); string str = cstr.GetString(); str = cstr; // 也同样合法 void Func(std::string ); // 错误!引发C2664
// 正确!帮助编译器识别对象类型 Func((const char*)cstr); Func(cstr.GetString());
|
宽字节环境下,同样得利用CStringA做一层转换:
CString cstr = _T("字符串内容"); CString cstrA(str); string str = cstrA.GetString(); void Func(std::string ); // 错误!引发C2664
// 正确!帮助编译器识别类型 Func(cstrA.GetString());
|
在上面两个例子中可以发现一个共性——CString àconst char*àstd::string这层隐式转换关系虽然看起来很明显,而且大多数时候都行得通。但到了函数参数传递时往往就卡壳了。编译器显得很傻,不能顺畅地直接转换过去,需要我们显式地强制转换,或调用对象成员函数,将对象先转换成更基本的const char*类型,从而帮助编译器做好类型识别。
因为bstr_t能同时接受ANSI、Unicode字符串,所以CString到bstr_t的转换又相对简单了:
CString cstr; bstr_t bstr = cstr; void Func(bstr_t ); // 错误!C2664
// 正确!函数参数传递时注意帮助编译器决断对象类型 Func(cstr.GetString());
|
bstr_t不仅实现了支持const char*、const wchar_t*的构造函数、赋值函数,还实现了operator const char*()、operator const wchar_t*()、operator char*()和operator wchar_t*()。
这意味着它可以与char*、wchar_t*、const char*和const wchar_t*之间完全无障碍地进行转换。我们一般就无视那些转换规则了。
但下面这种情况,会报告一个C2668错误:
void Func(char*); void Func(wchar_t*); bstr_t b; // 引发C2668错误
|
该错误比之前弱智的C2664错误更容易理解一些。由于Func同时重载了两种类型,编译器对于要转换到哪种类型无从抉择。这时的标准处理方法是写成
Func((wchar_t*)b)); |
加了一个强制转换符,帮助编译器做出明确的选择。
为什么是(wchar_t*)而不是(char*)呢?因为Windows NT API内部就是使用Unicode实现的,如果我们强制转换到ANSI,Windows内部又得转换到Unicode,影响了效率。
一般情况下bstr_t也能无障碍转换到std::string。但下面这种情况可能会报告一个C2664错误:
void Func(const std::string& ); bstr_t b; // 引发C2664错误
|
解决办法跟上一节相同。
又是个让编译器做选择的问题,于是编译器又不干了,这次是涉及到模版推断的C2440错误。解决办法依然跟上两节一样,加一个(wchar_t*)强制转换。
bstr_t bstr; CString str = (wchar_t*)bstr; |
Windows API提供了MultiByteToWideChar和WideCharToMultiByte这两个函数来实现多字节、宽字节之间的转换。
后续所有转换方式,内部实现都是基于Windows API的,所以在Windows下这种方法是首选。
使用Windows API中的MultiByteToWideChar函数可实现多字节到宽字节的转换,代码如下:
string s = "字符串内容"; int nSize = ::MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, NULL, NULL); wchar_t* lpBuffer = new WCHAR[nSize]; ::memset(lpBuffer, 0, nSize * sizeof(lpBuffer[0])); ::MultiByteToWideChar(CP_ACP, 0, s.c_str(), -1, lpBuffer, nSize); wstring ws = lpBuffer; delete[] lpBuffer; // delete之后重置为NULL,避免悬挂指针! lpBuffer = NULL;
|
第一次调用返回转换后wchar_t数组的长度,第二次才填充转换后的字符串内容。
宽字节到多字节的转换使用另外一个函数,方法类似。
wstring ws = L"字符串内容"; int nSize = ::WideCharToMultiByte(CP_ACP, 0, ws.c_str(), -1, NULL, 0, NULL, NULL); char* lpBuffer = new CHAR[nSize]; ::memset(lpBuffer, 0, nSize * sizeof(lpBuffer[0])); ::WideCharToMultiByte(CP_ACP, 0, ws.c_str(), -1, lpBuffer, nSize, 0, 0); string s = lpBuffer; delete[] lpBuffer; lpBuffer = NULL;
|
C函数库中也提供了mbstowcs和wcstombs两个函数来实现字符集转换。Visual Studio中暴露了两者的源代码,可看到这两个函数内部是调用Windows API来实现转换的。
注意在使用这两个函数前,需要先使用setlocale设置全局locale,使用完之后再设置回来。在多线程环境下可能需要注意加锁保护。
string s = "字符串内容"; string sCurLocale = ::setlocale(LC_ALL, NULL); ::setlocale(LC_ALL, "chs"); int nSize = :: mbstowcs(NULL, s.c_tr(), 0); wchar_t* lpBuffer = new WCHAR[nSize + 1]; ::memset(lpBuffer, 0, (nSize + 1) * sizeof(lpBuffer[0])); :: mbstowcs(lpBuffer, s.c_tr(), nSize + 1); ::setlocale(LC_ALL, sCurLocale.c_str()); wstring ws = lpBuffer; delete[] lpBuffer; lpBuffer = NULL; |
wstring ws = L"字符串内容"; string sCurLocale = ::setlocale(LC_ALL, NULL); ::setlocale(LC_ALL, "chs"); int nSize = :: wcstombs(NULL, ws.c_tr(), 0); char* lpBuffer = new char[nSize + 1]; ::memset(lpBuffer, 0, (nSize + 1) * sizeof(lpBuffer[0])); :: wcstombs (lpBuffer, ws.c_tr(), nSize + 1); ::setlocale(LC_ALL, sCurLocale.c_str()); string s = lpBuffer; delete[] lpBuffer; lpBuffer = NULL; |
std::string s = "字符串内容"; CStringW cstrW = s.c_str(); std::wstring ws = cstrW.GetString(); |
std::wstring ws = L"字符串内容"; CStringA cstrA = ws.c_str(); std::string s = cstrA.GetString(); |
前面提到了bstr_t实现了对ANSI和Unicode的同时支持,所以通过它可以轻松进行字符集的转换。
string s = "字符串内容"; bstr_t bs = s.c_str(); wstring ws = bs; |
wstring ws = L"字符串内容"; bstr_t bs = ws.c_str(); string s = bs; |
主要利用COM的两个函数_com_util::ConvertStringToBSTR和_com_util::ConvertBSTRToString。
但注意这两个函数都返回了堆数组指针,用完之后需要调用方释放!
std::string s = "字符串内容"; BSTR pws = _com_util::ConvertStringToBSTR(s.c_str()); std::wstring ws = pws; // 函数分配了一个堆数组,用完之后注意释放! delete[] pws; pws = NULL; |
std::wstring ws = L"字符串内容"; char* pstr = _com_util::ConvertBSTRToString(ws.c_str()); std::string s = pstr; // 用完之后注意释放! delete[] pstr; pstr = NULL; |
const属性指明了变量或对象不能被修改,这在C++中是一种约定,也是一种保护性措施。而强制去除const的转换,破坏了这一约定,让代码变得难读、难维护,也带来安全隐患。
这一条的意义跟3.1条一样。另外一点,std::string只能返回const char*类型,所以也更方便在代码中大规模使用标准C++代码。
TCHAR相关类型除TCHAR*、const TCHAR*外还包括LPTSTR、LPCTSTR、CString等
由于TCHAR是根据项目字符集配置来的,若DLL库配置为ANSI,而DLL调用方是Unicode,则会产生调用异常;反之亦然。
更关键的是从头文件、lib文件、dll文件无法获得库文件的项目字符集配置。