1 string --> CString
在使用MFC时,遇到了CString与string转换的问题,特此记录下来。其实CString与string的转换方式有挺多种的,但也并不是每一种都适用,可能需要一些稍微的改动才能正常运行。
std::string str3 = "你好呀! hello 123 ";
CString CStr = str3.c_str();
比如网上常见的一种转换方法(如果你的能直接转换也是没问题滴):
发现转不了,就很气。提示错误翻译一下大概就是说:没有合适的构造函数来进行那个类型的转换。
提示错误为:
no suitable constructor exists to convert from “const char *” to “ATL::CStringT<wchar_t, StrTraitMFC_DLL<wchar_t,
ATL::ChTraitsCRT<wchar_t>>>” ,
严重性 代码 说明 项目 文件 行 禁止显示状态
错误 C2440 “初始化”: 无法从“const _Elem *”转换为“ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>”
MFCApplication1 D:\Projects\MFC\MFCApplication1\MFCApplication1\MFCApplication1Dlg.cpp 610
严重性 代码 说明 项目 文件 行 禁止显示状态
错误(活动) E0415 不存在从 "const char *" 转换到 "ATL::CStringT<wchar_t, StrTraitMFC_DLL<wchar_t, ATL::ChTraitsCRT<wchar_t>>>"
的适当构造函数 MFCApplication1 D:\Projects\MFC\MFCApplication1\MFCApplication1\MFCApplication1Dlg.cpp 610
为什么呢?原因就是CString c_name = name.c_str();其实是需要调用拷贝构造函数的,但是类型不匹配,所以找不到合适的构造函数。
1.1 Unicode编码下
1.1.1 方法1:ATL字符串转换宏
该方法是比较通用的,通过使用ATL字符串转换宏。
std::string str1 = "hello !";
std::string str2 = "你好!";
std::string str3 = "你好呀! hello 123 ";
CString CStr,cstr1, cstr2, cstr3;
cstr1 = CA2T(str1.c_str()); //所以CA2T也就是CA2W就是将多字符集转换为宽字符UNICODE,也可写成CA2W。
cstr2 = CA2T(str2.c_str());
cstr3 = CA2W(str3.c_str());//写成CA2W,试试看
CStr = cstr1 + "\r\n" + cstr2 + "\r\n" + cstr3;
AfxMessageBox(CStr);
cstr1 = CA2T(str1.c_str()); //所以CA2T也就是CA2W
就是将多字符集转换为宽字符UNICODE,也可写成CA2W
。
1.1.2 方法2:调用“赋值运算符重载” (区别于直接赋值)
修改编码可能导致程序中其它地方出现错误(我的就是- -!)。所以可以调用“赋值运算符重载”的方式来完成转换,很简单也很实用。不用修改编码方式。
std::string str3 = "你好呀! hello 123 ";
CString CStr;
CStr = str3.c_str();//注意这里使用的是 =运算符的重载,注意与直接赋值的区别
AfxMessageBox(CStr);
注意二者区别,定义时,直接赋值,是不行的。
混合字符串
std::string str1 = "hello !";
std::string str2 = "你好!";
std::string str3 = "你好呀! hello 123 ";
CString CStr,cstr1, cstr2, cstr3;
cstr1 = str1.c_str();
cstr2 = str2.c_str();
cstr3 = str3.c_str();
CStr = cstr1 + "\r\n" + cstr2 + "\r\n" + cstr3;
AfxMessageBox(CStr);
这种方式在 Unicode编码、多字节编码
下通用
1.1.3 自己封装函数 Str2Cstr(string str)
也可以直接写个函数用:
CString Str2Cstr(string str)
{
return CString(str.c_str());
}
调用
void CMFCtestDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
std::string str3 = "你好呀! hello 123 ";
CString CStr;
CStr = Str2Cstr(str3.c_str());
AfxMessageBox(CStr);
}
也可以这样写
std::string str3 = "你好呀! hello 123 ";
CString CStr = Str2Cstr(str3.c_str());
AfxMessageBox(CStr);
还可以 强制转换
std::string str3 = "你好呀! hello 123 ";
AfxMessageBox(CString(str3.c_str()));
1.2 “多字节”编码方式
1.2.1 方法1:改为 “多字节”编码方式
通过修改编码方式,可以解决。我的是在Unicode编码方式下不能使用,修改为“多字节”编码方式即可。
右键项目->“属性”->选择“多字节”。
注意要在 x64模式下,x86不行
1.2.2 方法2: “多字节”编码下 格式化转换
右键项目->“属性”->选择“多字节”。
std::string str1 = " hello 123 ";
std::string str3 = "你好呀! hello 123 ";
CString CStr;
CStr.Format("%s", str3.c_str());
AfxMessageBox(CStr);
注意要在 x64模式下,x86不行
1.3 string --> CString/CStringW / wstring
//std::string --> CString/CStringW / std::wstring
使用Unicode字符集时,CString等价于CStringW;
std::string str1 = "你好呀! hello 123 ";
//使用Unicode字符集时,CString等价于CStringW;
//CString CStr(CStringA(str1.c_str()));
CStringW CStr(CStringA(str1.c_str()));
std::wstring strw(CStr);
AfxMessageBox(CStr);
GetDlgItem(IDC_STATIC_STR)->SetWindowTextW(strw.c_str());
2 CString --> string
转换完之后,最好将 CString释放掉
CStr1.ReleaseBuffer();
CStr2.ReleaseBuffer();
2.1 多字节编码下
右键项目->“属性”->选择“多字节”。
2.1.1 CStr.GetBuffer()
CString CStr1 = _T("hello 123");
CString CStr2 = _T("你好呀! hello 123");
//// GetString()比较新的VS有,旧可以用GetBuffer(),我这里VS2019,两个都可以用
std::string str1 = CStr1.GetBuffer();
std::string str2 = CStr2.GetString();
//MFC中输出 cout
AllocConsole(); //AllocConsole函数的功能是为当前的窗口程序申请一个Console窗口
freopen("CONOUT$", "w", stdout); // freopen函数用来替换一个流,或者说重新分配文件指针,以实现重定向。
std::cout << str1 << "\r\n" << str2 << std::endl;
赋值运算符 ,或者 =运算符的重载,都是可以的
CString CStr1 = _T("hello 123");
CString CStr2 = _T("你好呀! hello 123");
std::string str1;
std::string str2 ;
str1 = CStr1.GetBuffer();
str2 = CStr2.GetBuffer();
2.1.2 string(CStringA(CStr))
CString CStr1 = _T("你好呀! hello 123");
CStringA cstra(CStr1);
std::string str1(cstra);
//或
std::string(CStringA(CStr1));
//MFC中输出 cout
AllocConsole(); //AllocConsole函数的功能是为当前的窗口程序申请一个Console窗口
freopen("CONOUT$", "w", stdout); // freopen函数用来替换一个流,或者说重新分配文件指针,以实现重定向。
std::cout << str1 << "\r\n" << std::string(CStringA(CStr1))<<std::endl;
2.2 Unicode编码下
2.2.1 CT2A /CW2A(CStr.GetString())
//CT2A含义
//C:convert,转换的意思
//T:中间类型,如果定义了_UNICODE,则T表示W;如果定义了_MBCS,则T表示A
//W:宽字符串,也就是UNICODE
//A:ANSI字符串,也就是Muti-Byte。
CString CStr1 = _T("hello 123");
CString CStr2 = _T("你好呀! hello 123");
//所以CT2A其实就是CW2A就是将Unicode转换为多字符集ASCII,也可写成CW2A
//// GetString()比较新的VS有,旧可以用GetBuffer(),我这里VS2019,两个都可以用
std::string str1 = CT2A(CStr1.GetString());
std::string str2 = CT2A(CStr2.GetBuffer());
//MFC中输出 cout
AllocConsole(); //AllocConsole函数的功能是为当前的窗口程序申请一个Console窗口
freopen("CONOUT$", "w", stdout); // freopen函数用来替换一个流,或者说重新分配文件指针,以实现重定向。
std::cout << str1 << "\r\n" << str2 << std::endl;
2.2.2 CT2A /CW2A(CStr)
//CT2A含义
//C:convert,转换的意思
//T:中间类型,如果定义了_UNICODE,则T表示W;如果定义了_MBCS,则T表示A
//W:宽字符串,也就是UNICODE
//A:ANSI字符串,也就是Muti-Byte。
CString CStr2 = _T("你好呀! hello 123");
//所以CT2A其实就是CW2A就是将Unicode转换为多字符集ASCII,也可写成CW2A
std::string str1 = CW2A(CStr2);
std::string str2 = CT2A(CStr2);
//MFC中输出 cout
AllocConsole(); //AllocConsole函数的功能是为当前的窗口程序申请一个Console窗口
freopen("CONOUT$", "w", stdout); // freopen函数用来替换一个流,或者说重新分配文件指针,以实现重定向。
std::cout << str1 <<"\n" << str2 <<std::endl;
2.2.3 W2A(CStr)
CString CStr1 = L"你好呀! hello 123";
CString CStr2 = _T("你好呀! hello 123");
USES_CONVERSION;
std::string str1 = W2A(CStr1);
std::string str2 = W2A(CStr2);
//首先str--》const wchar_t* ,然后W2A将const wchar_t*--》const char*,
//最后用const char*初始化s
//MFC中输出 cout
AllocConsole(); //AllocConsole函数的功能是为当前的窗口程序申请一个Console窗口
freopen("CONOUT$", "w", stdout); // freopen函数用来替换一个流,或者说重新分配文件指针,以实现重定向。
std::cout << str1 << "\r\n" << str2 << std::endl;
3 相关函数 字母含义 解释
//CT2A含义
//C:convert,转换的意思
//T:中间类型,如果定义了_UNICODE,则T表示W;如果定义了_MBCS,则T表示A
//W:宽字符串,也就是UNICODE
//A:ANSI字符串,也就是Muti-Byte。
string转CString
:CA2T,将多字符集转换为宽字符UNICODE
,也可写成CA2W。
CString转string
:CT2A,将Unicode转换为多字符集ASCII
, 也可写成CW2A。
c_str()就是将C++的string转化为C的字符串数组,c_str()
生成一个const char *
指针,指向字符串的首地址
- 使用Unicode字符集时,CString等价于CStringW;
- 使用多字节字符集时,CString相对于CStringA;
1)TCHAR 转换为const wchar_t *,直接强制转换,在TCHAR前面加上(*const wchar_t)
2)BSTR:是一个OLECHAR*类型的Unicode字符串,是一个COM字符串,带长度前缀,与VB有关,没怎么用到过。
LPSTR:即 char *,指向以'/0'结尾的8位(单字节)ANSI字符数组指针
LPWSTR:即wchar_t *,指向'/0'结尾的16位(双字节)Unicode字符数组指针
LPCSTR:即const char *
LPCWSTR:即const wchar_t *
LPTSTR:LPSTR、LPWSTR两者二选一,取决于是否宏定义了UNICODE或ANSI
LPCTSTR: LPCSTR、LPCWSTR两者二选一,取决于是否宏定义了UNICODE或ANSI,
如下是从MFC库中拷来的:
#ifdef UNICODE
typedef LPWSTR LPTSTR;
typedef LPCWSTR LPCTSTR;
#else
typedef LPSTR LPTSTR;
typedef LPCSTR LPCTSTR;
#endif
相互转换方法:
LPWSTR->LPTSTR: W2T();
LPTSTR->LPWSTR: T2W();
LPCWSTR->LPCSTR: W2CT();
LPCSTR->LPCWSTR: T2CW();
ANSI->UNICODE: A2W();
UNICODE->ANSI: W2A();
在头文件<atlconv.h>中定义了ATL提供的所有转换宏,如:
A2CW (LPCSTR) -> (LPCWSTR)
A2W (LPCSTR) -> (LPWSTR)
W2CA (LPCWSTR) -> (LPCSTR)
W2A (LPCWSTR) -> (LPSTR)
所有的宏如下表所示:
上表中的宏函数,非常的有规律,每个字母都有确切的含义如下:
一文搞懂C++和MFC中的字符串,CString和string如何转换
4 C++和MFC中的字符串,CString和string转换 原理
在C的时代,还没有字符串类型,我们通过字符数组,或者char*
来处理 “字符串”。
通过在字符串的最后加上一个‘\0’, 来表示字符串的结束。所以没有字符串类型之前,我们总是在处理完字符串之后加个‘\0’,或者数字0(因为‘\0’对应的ASCII码值是0,注意字符‘0’对应的ASCII码值是0x30)
到了C++有了类型string,到了MFC 有了 CString, 但是本质还是 char*。
4.1 char* 、 const char* 、 CString 的关系
所以我们在转换的时候,就是通过char*进行中转,按照这个思路,我们就能很好理解这个转换的过程。
在此之前,我们还得理解一个概念:
- 什么是指向常量的指针?其实就是指针指向的一个常量,指针指向的地址中的值不可变。
因为字符串本身规定就是不可变的,所以大多情况下,我们都是用 一个指向常量的指针管理字符串。如:const char* a1; 这就是一个指向常量的指针,对应MFC中的LPCSTR,其中的这个C就是const的意思,这里我们可以看下MFC中的定义,下次就不怕,也不蒙B了:
好的,言归正传,看看标准C++中的字符串:
cout << "----------string转const char *----------------" << endl;
const char* a1;
string s1 = "asdf";
a1 = s1.c_str(); //应为字符串是不可变的,所以需要一个指向常量的指针,c_str:c中的字符串。
cout << a1 << endl;
cout << "-------------string转char *---------------" << endl;
//--这里相当于对了一个步骤,将const char * 转成 char *
//--于是就请了const_cast这个玩意帮忙,是个模板,看来想去掉const就找他就行!
char *a2 = const_cast<char *>(s1.c_str()) ;
cout << a2 << endl;
cout << "----------char*转string----------------" << endl;
string s2 = a2; //直接赋值即可!(高端的直接兼容低端的)
cout << s2 << endl;
cout << "----------char*与const char * 的互换----------------" << endl;
char* aa = "asdf";
const char* cc = aa; // 会报一个警告
aa = const_cast<char *>(cc);
cout << cc << " " << aa << endl;
以上这段代码,介绍了string到char*的相互转换,以及如何去掉const。
接下来,再看看MFC中的CString:
首先,想将char* 或者 const char* 转成 CString,是直接可以赋值的
(和转成string是一样的)
因为所有的字符串底层逻辑都是char*,它可以直接变成其他“字符串类型”。
既然如此,那么string转CString不就是找 char* 打个桥就可以了?
- string -> CString
(这种方式在Unicode模式和多字节模式下通用)
string str = "123123123123";
CString mfcstr;
mfcstr = CString(str.c_str());
首先将string变成const char* 然后,通过const char*构造CString,就完成可这次转换。
- CString -> string
CString转string的时候需要考虑一个问题,就是MFC允许两种编码格式的编程,一种是多字节一种是Unicode,Unicode自己搞了个宽字符TCHAR,意图是兼容多国语言。所以如果你用Unicode char* 就不再是底层逻辑了,底层变成TCHAR*了。所以,这也是MFC搞死人的地方。
现在建议直接选多字节,有了UTF8的出现,多字节也能支持中文了。
4.2 字符串的历史
这里简单的讲一下,字符串的历史,帮助大家加深理解:
由于计算机是美国人发明的,因此最早只有127个字母被编码到计算机中,也就是大小写英文字母、数字和一些符号,这个编码表称为ASCII编码。
例如:大写字母A的编码是65,小写字母z的编码时122。
要处理中文,显然一个字节是不够的,至少需要两个字节,且不能和ASCII编码冲突,所以我国制定了GB2312编码,用于把中文编进去。
可以想象,全世界上有上百种语言,日本把日文编写到Shift_JIS里,韩国把韩文编写到Euc-kr里,各国有各国的标准,就不可避免出现冲突,结果就是,在多语言混合的文本中就会显示乱码。
在此背景下,Unicode应运而生,Unicode把所有语言都统一到一套编码里,这样就不会有乱码问题了。
Unicode标准在不断发展,最常用的是用两个字节表示一个字符(如果要用到非常生僻的字符,就需要4个字节)。现代操作系统和大多数编程语言都直接支持Unicode。
下面看看ASCII编码和Unicode编码的区别:ASCII编码时1个字节,二Unicode编码通常是两个字节。
那么新的问题就出现了:如果统一成Unicode编码,乱码问题从此消失了,但是写的文本基本上全部是英文时,用Unicode编码比ASCII编码多一倍存储空间,在存储和传输上十分不划算。
本着节约的精神,又出现了把Unicode编码转化成为“可变长编码”的UTF-8编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1~6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4~6个字节。如果你要传输的文本包含大量英文字母,用UTF-8编码就会节省空间。
各编码方式的比较
由上表可知,UTF-8编码有一个额外的好处,就是ASCII编码实际上可看成是UTF-8编码的一部分,所以只支持ASCII编码的大量历史遗留软件可在UTF-8编码下继续使用。
GetBuffer和GetString目前(VS2022下测试)我发现不管在Unicode还是多字节环境下都只能返回TCHAR了,没法返回char了。
但是我又发现了一种,兼容Unicode和多字节的转换方式: CW2A。
CString mfcstr = "456487894564啊士大夫十大";
str = CW2A(mfcstr);
4.3 MFC的字符串转换小结
那么MFC的字符串转换小结就是:
str = CW2A(mfcstr); // CString -> string
mfcstr = CString(str.c_str()); // string -> CString