C++获取UTF8编码下字符串中的中英混合时的字符个数

问题需求

  有关字符串长度的获取,我们经常会遇到,最常规的处理方式,就是调用 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)??????

不同编码集汉字所占字节