首页 > 代码库 > c++下的中文处理:编码与转码

c++下的中文处理:编码与转码

处理平台: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

c++下的中文处理:编码与转码