问题需求
有关字符串长度的获取,我们经常会遇到,最常规的处理方式,就是调用 std::string::size() 或者 std::string::length() 或 strlen 函数。其实,这有时候并不是我们所需的,或者说不准确的。
比如,前台界面输入框提示的剩余字符输入个数是10,而在普通用户的观念中,一个英文字母是1个长度,一个中文汉字也是1个长度,一个中文或英文标点符号(句号、问号、叹号、逗号、顿号、分号和冒号等),更是1个长度!所以此时调用上面的函数,那是绝对错误的。
汉字所占字节数
如何解决?网上有很多“字符串中英混合字数统计”的代码,但是大多数中,只提及了一个 0x80 , 或者 GBK,其他没说。对吗?也不一定,因为在不同的编码环境中,相同的汉字所占用的字节长度,是不同的!正确的字节长度列表如下:
编码方式 | GB2312 | GBK | GB18030 | ISO-8859-1 | UTF-8 | UTF-16 | UTF-16BE | UTF-16LE |
英文字母 | 1 | 1 | 1 | 1 | 1 | 4 | 2 | 2 |
中文汉字 | 2 | 2 | 2 | 1 | 3 | 4 | 2 | 2 |
由于大多的C++服务编码方式都采用UTF8编码,所以本文这里,仅以UTF8编码下的字符串为例,来展示如何获取中英混合字符串中字符的个数。
UTF-8编码规则
但首先,我们要知道的是,UTF-8是一种变长字节编码方式,它有两条规则:
-
规则1:对于单字节的符号(n=1个字节),字节的最高位为0,后面的7位为这个符号的Unicode编码。
-
规则2:对于多字节的符号(n>1个字节),第一个字节的前n位都是1,第n+1位为0;后面字节的前两位一律为10;剩余没有提及的二进制位,均为这个符号的Unicode编码。
例如:
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
对应的取值范围如下:
printf("0xxxxxxx: %02X-%02X ",0b00000000,0b01111111); // 0x00-0x7F printf("10xxxxxx: %02X-%02X ",0b10000000,0b10111111); // 0x80-0xBF printf("110xxxxx: %02X-%02X ",0b11000000,0b11011111); // 0xC0-0xDF printf("1110xxxx: %02X-%02X ",0b11100000,0b11101111); // 0xE0-0xEF printf("11110xxx: %02X-%02X ",0b11110000,0b11110111); // 0xF0-0xF7 printf("111110xx: %02X-%02X ",0b11111000,0b11111011); // 0xF8-0xFB printf("1111110x: %02X-%02X ",0b11111100,0b11111101); // 0xFC-0xFD
因此,UTF-8最多可用到6个字节,可以用来表示字符编码的实际位数最多有31位,即上表中x所表示的位。除去那些控制位(每字节开头的10等),这些x表示的位与UNICODE编码是一一对应的,位高低顺序也相同。
实现代码
以上,知道UTF8的编码原理后,我们也就可以轻轻松地解决【如何获取UTF-8编码下的中英混合时的字符个数】了。代码如下:
// 获取UTF8编码下字符串中的字符个数; 返回-true: 完整的UTF8字符串; 返回-false-非完整的UTF8字符串 bool GetUtf8CharCount(const string& str, int& count){ count=0; for(int str_idx=0,str_size=str.size(); str_idx<str_size; ++str_idx,++count){ unsigned char str_char = str[str_idx]; if(str_char<0x80){ // 常规的单字节 //printf("0x%02X str_size=%d, str_idx=%d, count=%d -normal ",str_char,str_size,str_idx,count); continue; } int skip_idx=0; if(str_char<0xE0){ // 2个字节 skip_idx=1; } else if(str_char<0xF0){ // 3个字节 skip_idx=2; } else if(str_char<0xF8){ // 4个字节 skip_idx=3; } else if(str_char<0xFC){ // 5个字节 skip_idx=4; } else { // 6个字节 skip_idx=5; } //printf("0x%02X str_size=%d, str_idx=%d, count=%d, skip_idx=%d ",str_char,str_size,str_idx,count,skip_idx); if((str_idx+skip_idx) >= str_size){ // 正常字符的编码都是完整的,但不排除被截断的可能 return false; } do{ ++str_idx; str_char = str[str_idx]; if( (str_char<0x80) || (str_char>0xBF) ){ // 中间的编码必须满足 10xxxxxx 范围 return false; } --skip_idx; }while(skip_idx>0); } return true; } // 获取UTF8编码下字符串中的字符个数-保证输入的字符串是UTF8格式的,不然返回值不准确 int GetUtf8CharCount(const string& str){ int count=0; GetUtf8CharCount(str,count); return count; }
测试代码
string str; while(true){ cout << "输入:" ; cin >> str; int count=0; bool bOK=GetUtf8CharCount(str,count); printf("std::string::size=%ld, GetUtf8CharCount=%d, utf8-%s ",str.size(),count,bOK?"true":"false"); }
运行结果
输入:123中文45
std::string::size=11, GetUtf8CharCount=7, utf8-true
输入:12345!@#$%^&*(龙行龘龘前程朤朤asdfgh
std::string::size=44, GetUtf8CharCount=28, utf8-true
参考网页
c/c++字符串中英混合字数统计??????
UTF8编码规则及用C++语言的识别
C++中文字符编码(GBK/Unicode/UTF8)介绍以及转换(1/2)??????
不同编码集汉字所占字节