最近两天在研究基于位运算的字符串模糊匹配时,在匹配过程中,因为中文的原因,出现了很多问题。最终发现了C++中有一个类型叫做wchar_t,宽字符,用于表示Unicode字符集,很好的解决了string和char在中文表示上的问题。以前没有引起足够的重视,现在在出现了很多问题后才不得不面对这个问题。
在我们常用的类型string和char中,对中文支持并不是很好。在这两种类型中,中文都是两个字节,也就是说中文的汉字要占用两个位置,举个简单的例子,一个“汉”字用一个char是无法表示的,即 char c = '汉' ; 是错误的,必须用 char c[3] = "汉". 汉字占用两个字节,还有一个结尾符“0/”。string s = "汉" ; s.length()的值是2.
通过以上描述,我们会发现,我们在进行包含中文字符串处理的过程中就会遇到以下问题:在包含数字,字母,汉字的字符串处理中我们应该怎样应付各个字符占多少个字节?总不能在处理之前先转换成ASCII码判断它属于哪种字符在进行处理吧?中文取两个,字母和数字取一个。在进行判断相等与否时也遇到了问题。
1. string s = “中国人”; 想知道第二个字是不是“国”字,不能够直接使用 "s[2] == "国"",这样的s[2]是指“国”字的前一个字节代表的数字,是个负数,两个根本无法相等。必须使用 strncmp(&s[2], "国", 2); 这个函数来进行匹配。这个函数的含义是对字符串进行比较,将从第一个参数位置开始的第三个参数个字符与第二个参数进行比较,如果相等则返回0,不相等就返回1.具体到本例中s[2]开始的2个字符与“国”字进行比较。在这用情况下,不仅增加了表达的复杂度,而且增加了算法的复杂度。
2. 想进行匹配或者其他处理必须先判断类型,也就是要维护一个几乎和原字符串等长的类型链表,用于标记原字符串各个位置上的类型。在进行操作时,先判断类型,数字和字母等往后挪一位,汉字等往后挪两位,这样即麻烦又浪费存储空间。
3. 不知者在进行匹配时,用过使用 s[i] == p[j] 时会产生意想不到的结果。我就是问题出在这里。用字母进行匹配时一点问题也没有,简单的几个汉字也没有问题,但是当我使用几万字的两个文本进行匹配时就产生了很大的问题。每个汉字会被拆成两个负整数来表示,假设一个汉字被拆成了-3444,-1235,另外一个被拆成了-6433,-1235,那么本来毫无关系的两个汉字就会错误的被认为成第二个是相等的,最终的结果就会产生错误。
经过研究,发现在c++中还存在另外一个字符集体系,我之前一直没有注意到过,直到现在才明白怎么回事。
wchar_t 和 wstring的出现很好的解决了这些问题。
wchar_t c = ‘汉’; 不会出现任何问题。对应的字符串则为wstring。在这两个类型中,默认的情况下字母和数字就占用1个字节,而汉字汉用2个字节,能够很好的解决上面提到的三个问题。如:wstring s = "中国人abc" ; s.length() 的值是6,在string类型下值是9,这基本就是wstring和string的差别。
针对wstring有对应的fopen方法。对于Unicode的文件,使用下面方式读取文件,与默认的不太一样。
[color=brown]FILE* fp;
wchar_t utf[20000], *p = utf;
fp = _wfopen(name.c_str(), L"rb");
while(!feof(fp))
fread(p++, 1, 2, fp);
*--p = L'\0';
fclose(fp);[/color]
在宽字符指针p中就存储了文本中的内容。但是记住,一定要是Unicode的编码,如果是其他编码的文本,则会出现乱码。
string和char对应的读取字符串的方法如下:
[color=brown]FILE* fp = fopen(tStr.c_str(),rb);
if (fp == NULL)
{
cout<<"failed to read";
getchar();
return a;
}
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
cout<<len<<endl;
rewind(fp);
char* pBuffer = new char[len+1];
memset(pBuffer, 0, len+1);
fread(pBuffer, 1, len, fp);
fclose(fp);[/color]
这样对应文本中的内容就会存在pBuffer中了。另外不能够用方法二只是稍微改动一下,即将char改为wchar_t,string改为wstring,fopen改为_wfopen,就去处理宽字符串,这样是不行的,会在正确文本的结尾产生大量乱码,估计是由于计算 len 的过程中中文按照两个长度算的,导致最后多了很多字符,成为乱码。
最后,记录一下宽字符和字符之间的转换。宽字符和字符实际上是统一的,两个之间只是所使用的字符集不同而已,以下两个函数能够比较理想的实现两者的转化:
将普通字符串转化成宽字符:
[color=brown]const char *pFilePathName = "c:\\aa.dll";
int nLen = strlen(pFilePathName) + 1;
int nwLen = MultiByteToWideChar(CP_ACP, 0, pFilePathName, nLen, NULL, 0);
wchar_t lpszFile[256];
MultiByteToWideChar(CP_ACP, 0, pFilePathName, nLen, lpszFile, nwLen);
[/color]
最终的宽字符就存储在lpszFile里
将宽字符转化成普通字符:
[color=brown]wchar_t utf[30], *p = utf;
char ansi[60];
p = "宽字符转化成普通字符";
WideCharToMultiByte(CP_ACP, 0, utf + 1, -1, ansi, sizeof(ansi), NULL, NULL);
[/color]
最终的普通字符就存储在ansi里。
在我们常用的类型string和char中,对中文支持并不是很好。在这两种类型中,中文都是两个字节,也就是说中文的汉字要占用两个位置,举个简单的例子,一个“汉”字用一个char是无法表示的,即 char c = '汉' ; 是错误的,必须用 char c[3] = "汉". 汉字占用两个字节,还有一个结尾符“0/”。string s = "汉" ; s.length()的值是2.
通过以上描述,我们会发现,我们在进行包含中文字符串处理的过程中就会遇到以下问题:在包含数字,字母,汉字的字符串处理中我们应该怎样应付各个字符占多少个字节?总不能在处理之前先转换成ASCII码判断它属于哪种字符在进行处理吧?中文取两个,字母和数字取一个。在进行判断相等与否时也遇到了问题。
1. string s = “中国人”; 想知道第二个字是不是“国”字,不能够直接使用 "s[2] == "国"",这样的s[2]是指“国”字的前一个字节代表的数字,是个负数,两个根本无法相等。必须使用 strncmp(&s[2], "国", 2); 这个函数来进行匹配。这个函数的含义是对字符串进行比较,将从第一个参数位置开始的第三个参数个字符与第二个参数进行比较,如果相等则返回0,不相等就返回1.具体到本例中s[2]开始的2个字符与“国”字进行比较。在这用情况下,不仅增加了表达的复杂度,而且增加了算法的复杂度。
2. 想进行匹配或者其他处理必须先判断类型,也就是要维护一个几乎和原字符串等长的类型链表,用于标记原字符串各个位置上的类型。在进行操作时,先判断类型,数字和字母等往后挪一位,汉字等往后挪两位,这样即麻烦又浪费存储空间。
3. 不知者在进行匹配时,用过使用 s[i] == p[j] 时会产生意想不到的结果。我就是问题出在这里。用字母进行匹配时一点问题也没有,简单的几个汉字也没有问题,但是当我使用几万字的两个文本进行匹配时就产生了很大的问题。每个汉字会被拆成两个负整数来表示,假设一个汉字被拆成了-3444,-1235,另外一个被拆成了-6433,-1235,那么本来毫无关系的两个汉字就会错误的被认为成第二个是相等的,最终的结果就会产生错误。
经过研究,发现在c++中还存在另外一个字符集体系,我之前一直没有注意到过,直到现在才明白怎么回事。
wchar_t 和 wstring的出现很好的解决了这些问题。
wchar_t c = ‘汉’; 不会出现任何问题。对应的字符串则为wstring。在这两个类型中,默认的情况下字母和数字就占用1个字节,而汉字汉用2个字节,能够很好的解决上面提到的三个问题。如:wstring s = "中国人abc" ; s.length() 的值是6,在string类型下值是9,这基本就是wstring和string的差别。
针对wstring有对应的fopen方法。对于Unicode的文件,使用下面方式读取文件,与默认的不太一样。
[color=brown]FILE* fp;
wchar_t utf[20000], *p = utf;
fp = _wfopen(name.c_str(), L"rb");
while(!feof(fp))
fread(p++, 1, 2, fp);
*--p = L'\0';
fclose(fp);[/color]
在宽字符指针p中就存储了文本中的内容。但是记住,一定要是Unicode的编码,如果是其他编码的文本,则会出现乱码。
string和char对应的读取字符串的方法如下:
[color=brown]FILE* fp = fopen(tStr.c_str(),rb);
if (fp == NULL)
{
cout<<"failed to read";
getchar();
return a;
}
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
cout<<len<<endl;
rewind(fp);
char* pBuffer = new char[len+1];
memset(pBuffer, 0, len+1);
fread(pBuffer, 1, len, fp);
fclose(fp);[/color]
这样对应文本中的内容就会存在pBuffer中了。另外不能够用方法二只是稍微改动一下,即将char改为wchar_t,string改为wstring,fopen改为_wfopen,就去处理宽字符串,这样是不行的,会在正确文本的结尾产生大量乱码,估计是由于计算 len 的过程中中文按照两个长度算的,导致最后多了很多字符,成为乱码。
最后,记录一下宽字符和字符之间的转换。宽字符和字符实际上是统一的,两个之间只是所使用的字符集不同而已,以下两个函数能够比较理想的实现两者的转化:
将普通字符串转化成宽字符:
[color=brown]const char *pFilePathName = "c:\\aa.dll";
int nLen = strlen(pFilePathName) + 1;
int nwLen = MultiByteToWideChar(CP_ACP, 0, pFilePathName, nLen, NULL, 0);
wchar_t lpszFile[256];
MultiByteToWideChar(CP_ACP, 0, pFilePathName, nLen, lpszFile, nwLen);
[/color]
最终的宽字符就存储在lpszFile里
将宽字符转化成普通字符:
[color=brown]wchar_t utf[30], *p = utf;
char ansi[60];
p = "宽字符转化成普通字符";
WideCharToMultiByte(CP_ACP, 0, utf + 1, -1, ansi, sizeof(ansi), NULL, NULL);
[/color]
最终的普通字符就存储在ansi里。