在Linux环境下,c/c++默认的中文编码为UTF-8,中文的unicode编码是双字节的编码。有趣的是,windows环境下中文utf-8的编码为两个字节,而linux环境下UTF-8的为三个字节,而匹配中文使用正则表达式为
[\u4e00-\u9fa5]
无论你是怎么搜索,搜索出来匹配中文的正则基本都是上述的表达式。因此linux环境下3字节的UTF-8也用这个正则来匹配显然是不正确的。
Linux环境下的正则匹配规则中
\un,其中的 n 是一个用四个十六进制数字表示的 Unicode字符。并不支持三个字节的utf-8
\xn,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如, '\x41'匹配 "A"。'\x041' 则等价于 '\x04' & "1"。正则表达式中可以使用 ASCII编码。
因此得考虑将三个字节转变成2个字节,再进行中文的匹配。
先看双字节的unicode是如何转化成三字节的utf-8
查表可知,
\u4e00-\u9fa5
处于n=3的编码处,例如4E00,那么将它先转化成16进制(不足16位的在前面补0)
0000 1110 0000 0000
根据第三行的格式,也就是将上述16个比特位重新断句
0000 111000 000000
从左至右分别放置到以下的xx处
1110xxxx 10xxxxxx 10xxxxxx
得到三字节的UTF-8编码
11100000 10111000 10000000
也就是0xE0 B8 80
经计算可知,无法只用一个范围来确定三字节的中文要怎么表示,所以还是将其转化成双字节来匹配中文。
将上述推理反推,就能知道三字节的UTF-8如何转变成双字节的unicode编码。既然知道原理,就没有必要再用正则来判断字符是否为中文了。
#include <iostream>
#include <string>
using namespace std;
bool is_chinese(const string& str)
{
unsigned char utf[4] = {0};
unsigned char unicode[3] = {0};
bool res = false;
for (int i = 0; i < str.length(); i++) {
if ((str[i] & 0x80) == 0) { //ascii begin with 0
res = false;
}
else /*if ((str[i] & 0x80) == 1) */{
utf[0] = str[i];
utf[1] = str[i + 1];
utf[2] = str[i + 2];
i++;
i++;
unicode[0] = ((utf[0] & 0x0F) << 4) | ((utf[1] & 0x3C) >>2);
unicode[1] = ((utf[1] & 0x03) << 6) | (utf[2] & 0x3F);
//printf("%x,%x\n",unicode[0],unicode[1]);
//printf("aaaa %x,%x,%x\n",utf[0],utf[1],utf[2]);
if(unicode[0] >= 0x4e && unicode[0] <=0x9f && unicode[1] <=0xa5)
res = true;
else
res = false;
}
}
return res;
}
int main(){
string str="中国";
string str1="asdasfdsfd";
string str2="中国sdsds";
cout << is_chinese(str) <<endl;
cout << is_chinese(str1) <<endl;
cout << is_chinese(str2) <<endl;
return 0;
}