- 1. 因为要写个函数:实现截取指定字节长度的中英混合字符串,当截取位置为半个汉字字符时,舍去当前字节.并要求写入对话框应用程序中.
-
对话框有很多的坑啊,并且起初我是不知道vs与vc++6.0之间的字符集问题的,最初通过查看了相关的资料.
我们知道
因为C++支持两种字符串,即常规的ANSI编码(使用""包裹)和Unicode编码(使用L""包裹),这样对应的就有了两套字符串处理函数,比如:strlen和wcslen,分别用于处理两种字符串.
在以前VC++6.0中默认的字符集是多字节字符集(MBCS:Multi-Byte Character Set),而VS2005及以后默认的字符集是Unicode,这样导致以前在VC6.0中非常简单实用的各类字符操作和函数在VS2010环境下运行时会报各种各样的错误。
字符集可以通过工程属性修改:“工程-属性-字符集”。
CString在Unicode和多字节字符集下的区别:CString 是基于 TCHAR 数据类型的。如果为程序的生成定义了符号 _UNICODE,则会将 TCHAR 定义为 wchar_t 类型(一个 16 位的字符编码类型);否则,会将它定义为 char(普通的 8 位字符编码)。于是,在 Unicode 下,CString 由 16 位字符组成。如果没有 Unicode,它们则由 char 类型的字符组成(来自MSDN)。
多字节字符集(DBCS :double-byte character set)即以前的窄字节,是基于ASCll码的基础之上扩展而来的,采用多字节(两个字节)
来对各国复杂的字符进行编码.(不同国家有各种不同的编码标准,汉字编码的国家标准有,gbk,gb2132-80,GB18030)
一开始的错误解决思路是:
想着只要通过循环判断字符串每个字节是否属于双字节字符,当其最后一个字节位属于双字节字符的时候,就去掉该字节.(这个思路是错误的,通过简单的判断最后一个字节位是否为双字节字符位是不可行的,汉字时双字节字符,它的两个字节位都满足这个条件,这意味着,当截取字节位恰好属于一个汉字的高字节位时,舍掉它是错误的,当然一开始我并没有意识到这个错误,)
所以我起初将一切思考的核心放在了如何判断当前字节位属于双字节字符(判断某个字符是否为汉字)这个问题上.
于是了解到了两种解决方式:
- 一种普遍的说法是:0xa0是汉字编码开始的地方.所以只要判断指定位是否大于0xa0就能判断中英文.(当然,我基于这种判断写了一版函数,测试后发现有问题).了解到,0xa0(160)位置是特殊字符不间断空格.0xa1才是汉字开始的位置(啊),所以判断的时候当然不能大于等于零.
(当然,这种思路用来判断某个字符是否是双字节字符确实可行,但还是不能解决汉字高低位的问题,且0xa1只是低位字节开始的地方,汉字编码的方式决定了没办法通过二进制编码等方式来区分汉字的高低位)
CString inceptorCS(CString Cstr,int bitnum){
CString restr;
int nlen = Cstr.GetLength();
if(bitnum > Cstr.GetLength()){
AfxMessageBox(_T("无法截取,截取长度大于字符串长度!"));
}else if(bitnum == Cstr.GetLength()){
restr = Cstr.Left(bitnum);
}
else{
// 声明一个无法号char变量来存储截取字节处的字符.
unsigned char cha1;
int nl = bitnum;
cha1 = Cstr[bitnum];
// 判断截取字节位置的字符大小来分别中文与英文
if(cha1 > 0xa0){
restr = Cstr.Left(nl);
}else{
restr = Cstr.Left(nl);
}
}
return restr;
}
- 另一种判断字符的思路是取一个有符号的int型变量,如果它小于零.则证明该字节位是一个双字节字符.原理是什么?不论对于多字节字符集(窄字节),还是unicode(宽字节).都是在ASCll码的延伸,其0-127,都正好是西文以及西文符号的编码位.而我们知道0-127的二进制位(0000 0000-0111 1111),它们的有符号整形数都是正的,其所有高位正好都为0,所以双字节字符,最高位必定为1,这在有符号整形中代表负号.当然这种方式确实能判断出当前字节位是否是属于一个双字节字符位.但是还是无法满足截取指定字节长度的混合字符串.
(当然我基于这种判断机制也写了一版函数,结果当然还是不行,因为更本的原因还是在于汉字编码的高低位无法确定).
// 截取指定长度的字符串
string interceptString(string str,int n) {
string restr;
if (n > str.length()){
cout << "无法截取,截取长度大于字符串长度";
}
else {
char a = str[n];
signed int val = a;
if (val >= 0){
restr = str.substr(0,n);
}
else
{
restr = str.substr(0, n+1);
}
}
return restr;
}
正确的解决思路和参考解决方式:
至此,经过一些列的思考和挖坑,入坑.最后才将思路转换到了如何判断双子节汉字的高低位上,最终悲哀的发现汉字双子节高低位并没有明显的界限划分.也没有发现有效的系统函数,或库函数(本来就是要自己实现,就没有去找).最后想着,只能通过循环控制,来人工切分,记录整个混合字符串的状态,从而找到高低位字节.
(经过反复测试,最终写的一版函数,达到了我想要的效果)
void CMFCCStingDlg::OnBnClickedButton1()
{
CString restr;
// int nlen = m_string.GetLength();
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
if (m_num > m_string.GetLength()) {
AfxMessageBox("截取长度过长");
}
else {
int i = 0;
// 取到要截取位置的字节下标.
int byteval = m_num - 1;
// 通过while循环来遍历整个字符串.
while (i <byteval){
// 判断其是否属于双字节字符.是则跳过其一字节.否则自增1.
if (m_string[i]<0){
i += 2;
}
else {
i += 1;
}
}
if (i>byteval) { // 满足该条件说明,当前字节位为双字节字符的低字节位.
// CString.Left截取方法与传统方法不同,其下标是以1为起始,按字节截取字符串,当然如果你使用unicode编码的话,就直接按字符截取了.
restr = m_string.Left(m_num);
}
else if(m_string[i]<0){ // 满足该条件,证明该字节位置为一个双字节字符的高字节位.
restr = m_string.Left(m_num-1);
}
else { // 剩余的都是西文.0-127范围内.
restr = m_string.Left(m_num);
}
}
AfxMessageBox(restr);
}
需要注意的一点是,对话框程序,edit 编辑框显示数据会进行自动处理掉半个双字节字符(这也是很坑的一点,更本没必要自己实现,一开始还以为自己写的函数没问题了),通过messagebox弹出框函数可以完整弹出截取的字符串内容,即便是半个双字节字符.
2019.12.21.
21:54