处理平台:linux
1. 中文编码
中文字符常见的编码方式有:gbk, gb2312, gb18030和utf-8。这些都是内码,即字符存储在计算机中的编码方式。
gb2312编码由国家标准总局于1980制定,共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄西里尔字母在内的682个字符。在gb2312编码下,汉字占2个字节,英文字母占一个字节。(参考:http://zh.wikipedia.org/wiki/GB_2312)
gbk为gb2312的扩展,来源于微软的cp936编码(两者可视为等同),于1995年被国家确定为汉字编码规范,共收录有21886个符号,完全兼容gb2312的所有字符。但与gb2312相比,收录了更多的简体字,并支持繁体字。在gbk编码下,汉字占2个字节,英文字母占一个字节。(参考:http://zh.wikipedia.org/wiki/GBK)
gb18030为国家于2005年制定的中文编码标准,与gb2312完全兼容,与gbk基本兼容,支持Unicode的全部统一汉字,共收录汉字70244个。采用多字节编码,每个字可以由1个、2个或4个字节组成。(参考:http://zh.wikipedia.org/wiki/GB_18030)
utf-8为unicode编码的实现方式之一(其他的实现方式还有utf-16, utf-32)。utf-8是一种变长的编码,占1-4个字节,但中文下最多只用到3个字节,其中汉字占3个字节。虽然gb18030是最新的中文编码标准,但是现在貌似用的并不多。而gb2312由于支持的字符太少,现在已较少被使用。所以,中文编码一般选用utf-8和gbk,而utf-8通常是首选。
利用相关命令,可以直接查看字符编码,如在编码方式为utf-8的文件1.txt中存储一行文字:“专业”
通过命令hexdump可以看到:汉字”专“的utf-8编码为e4b893,而”业“的utf-8编码为e4b89a(0a为换行符编码)$ hexdump -C 1.txt 00000000 e4 b8 93 e4 b8 9a 0a |.......|将文件1.txt转为gbk编码存于文件2.txt:
$ iconv -f utf-8 -t gbk 1.txt > 2.txt再次通过hexdump命令可以看到:汉字”专“的gbk编码为d7a8,而”业“的gbk编码为d2b5
$ hexdump -C 2.txt 00000000 d7 a8 d2 b5 0a |.....|在linux下,利用iconv命令或者调用头文件iconv.h中的函数都可实现不同编码之间的转换,所以其他编码都可以转为utf-8编码再进行处理。下面假定字符编码均为utf-8。
2. utf-8的编码原理
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
如上表所示,编码规则有两条:
1.对于单字节字符,该字节最高位为0,后面的位由该字符unicode码填充
2.对于n字节字符,第一字节前n位为1,第n+1位为0,后面每个字节头两位均为10,xxx的部分由unicode码填充
例如:“专”的unicode编码为4e13(01001110 00010011),查上表处于0000 0800-0000 FFFF 范围内,需要三个字节,将该汉字的unicode编码从低位到高位依次填入“1110xxxx 10xxxxxx 10xxxxxx”的x中(填入顺序也是从低位到高位,不足的补零),则可以得到“专”的utf-8编码为e4b893(11100100 10111000 10010011)
关于utf-8编码更多的介绍可参考:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
字符编码的许多关键概念的介绍可参考:http://www.pconline.com.cn/pcedu/empolder/gj/other/0505/616631.html
3. C++下中文的处理
有两种方式:
一种需要将utf-8编码的字符转换为unicode进行处理,好处是可以使用许多字符处理的库函数,坏处是在linux下比较浪费空间。
另一种方式是直接根据utf-8的编码原理解析出一个个字符进行处理。
3.1 将utf-8编码的字符串转换为unicode
首先,可以通过c语言库函数mbstowcs()和wcstombs()来实现
C++下unicode其实是宽字符数组,即wchar_t数组,在linux下一个wchar_t字符占4个字节(在windows下wchar_t字符只占2个字节)。不难看出,比较浪费空间。C++下的string其实就是模板类型basic_string<char>, 相应的wstring就是模板类型basic_string<wchar_t>,这两个类都带有许多相同的方法,如c_str(), size()...
在C++中,不能混合使用输出流cout和wcout,为了输出的统一和方便,将所有输入字符串(string)转化为unicode(wstring)进行中间字符串层面上的算法处理,最后再将处理后的结果转换为普通的string输出。
如下代码中,函数convert_to_string()和convert_to_wstring()实现了wstring和string的相互转换(需要包含头文件stdlib.h):
int convert_to_string(const wchar_t * wstr, string &out_str){ if (!wstr) return 1; char* str = NULL; int size = 0; string loc = setlocale(LC_ALL,NULL); setlocale(LC_ALL, "zh_CN.utf8"); size = wcstombs( NULL, wstr, 0); str = new char[size + 1]; wcstombs(str, wstr, size); str[size] = '\0'; out_str = str; setlocale(LC_ALL, loc.c_str()); return 0; } int convert_to_wstring(const char* str, wstring &out_wstr){ if (!str) return 1; wchar_t* wcs = NULL; int size = 0; string loc = setlocale(LC_ALL,NULL); setlocale(LC_ALL, "zh_CN.utf8"); size = mbstowcs(NULL,str,0); wcs = new wchar_t[size+1]; size = mbstowcs(wcs, str, size+1); wcs[size] = 0; out_wstr = wcs; delete[] wcs; setlocale(LC_ALL, loc.c_str()); return 0; }
3.2 不将utf-8编码的字符转换为unicode的处理方式
在某些情况下,并不需要将utf-8编码下每一个字符解析出来存于数组,这时,可以利用utf-8编码的原理遍历字符串,提取出一个个字符进行处理。
如下代码中,函数get_char_lenght()获取str指向的字符串首个字符的长度,函数get_string_size()获取str指向的字符串的size:
int get_char_length(const char* str) { if (!str) return 0; unsigned char mask = 0x80; if (!(str[0] & mask)) //ASCII return 1; int len = 0; while (str[0] & mask) { len++; mask = mask >> 1; } return len; //return 1, 2 or 3 } int get_string_size(const char* str) { if (!str) return 0; int size = 0; int i = 0; int len = 0; while(*(str+i)) { len = get_char_length(str+i); i += len; size++; } return size; }
上述两段代码的测试主程序如下:
int main(int argc, char* argv[]) { string s("cs专业。"); cout << s << "(size:" << get_string_size(s.c_str()) << ")" << endl;; wstring wstr; convert_to_wstring(s.c_str(), wstr); cout <<"wstring size:" << wstr.size() << endl; for(size_t i = 0; i < wstr.size(); ++i) cout << hex << wstr[i] << ' '; cout << dec << endl; string new_str; convert_to_string(wstr.c_str(), new_str); cout << new_str << "(size:" << new_str.size() << ")" << endl;; return 0; }输出为:
cs专业。(size:5) wstring size:5 63 73 4e13 4e1a 3002 cs专业。(size:11)3.3 其他情况
在linux下,如果想转换为unicode,又想节省空间,还可根据上面所讲的utf-8和unicode转换原理自己编写转换函数,将utf-8字符串的每个字符转换为两个字节的unicode存于容器中。
可参考C++版本jieba分词的函数utf8ToUnicode()和unicodeToUtf8(),地址为:
https://github.com/aszxqw/cppjieba/blob/master/src/Limonp/StringUtil.hpp