UTF-8 是变长编码,不同字符的字节为 1 个到 4 个字节大小。

1 个字节

以字符 A 为例,其 ASCII 码为 65(二进制:0100 0001,十六进制:0x41)。 ASCII码表示的字符个数为 128 个,即 0~127,故只需要 7 位二进制即可。

故其 Unicode 编码为:0100 0001,码点为:U+0041

2 个字节

以带重音字母e(é) 为例,其 Unicode 码点为:U+00E9 换成二进制位:1110 1001

对于 2 个字节的编码,第一个字节中前 3 个 bit 为 110,表示该字符用 2 个字节存储(110是为了与后面的隔开,如果只使用 2 位 11,第 3 位如果是 1,总的前 3 位为 111,就分不清是 2 个字节还是 3 个字节了。),之后每个字节以 10 开头,用来和其他字节区分,10开头的字节称为后续字节。 故这个字符的格式为:110xxxxx 10xxxxxx,只有 8 个 bit,但是要填 11 个字节,故补前导 0。即填的是 00011101001, 最后的字节为:1100 0011 1010 1001,对应的编码单元为\xC3\xA9

3 个字节

以中文汉字 为例,其 Unicode 码点为:U+4E2D 换成二进制位:0100 1110 0010 1101

对于 3 个字节的编码,第一个字节的前 4 个 bit 为 1110,表示该字符用 3 个字节存储,之后每个字节以 10 开头。 故这个字符的格式为:1110xxxx 10xxxxxx 10xxxxxx,有 16 个 bit ,恰好填 16 个 bit,故填的是 11100100 10111000 10101101

最后的字节为:11100100 10111000 10101101,对应的编码单元为:\xE4\xB8\xAD

4 个字节

以 emoji 😀 为例,其 Unicode 码点为:U+1F600 换成二进制位:0001 1111 0110 0000 0000

对于 4 个字节的编码,第一个字节前 5 个 bit 为 11110,表示该字符用 4 个字节存储,之后每个字节以 10 开头。 故这个字符的格式为:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx,只有 20 个 bit ,但是要填 21 个 bit,故补前导 0。故填的是 0 0001 1111 0110 0000 0000

最后的字节为:11110000 10011111 10011000 10000000,对应的编码单元为:\xF0\x9F\x98\x80

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// main.cpp
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>

void output_char2bit(char* s) {
    std::vector<unsigned char> vec;
    int n = strlen(s);
    printf("%s, len = %d\n", s, n);
    printf("0x");
    for (int i = 0; i < n; ++i) {
        unsigned char* p = (unsigned char*)&s[i];
        printf("%02x", *p);
        vec.push_back(*p);
    }
    printf("\n");

    if (n < 1) return ;

    // 判断其是几个字节,拿首字节去做判断,从大到小判断。
    std::vector<int> bit;
    int len = 0;
    if ((0xf0 & vec[0]) == 0xf0) {
        len = 4;
    } else if ((0xe0 & vec[0]) == 0xe0) {
        len = 3;
    } else if ((0xc0 & vec[0]) == 0xc0){
        len = 2;
    } else {
        len = 1;
    }

    // 第一个字节
    for (int j = (len == 1) ? len : len + 1; j < 8; ++j) {
        bit.push_back(vec[0] >> (8 - j - 1) & 1);
    }

    // 后续字节
    for (int i = 1; i < n; ++i)
        for (int j = 2; j < 8; ++j)
            bit.push_back(vec[i] >> (8 - j - 1) & 1);

    // 补前导0
    std::reverse(bit.begin(), bit.end());
    while (bit.size() % 4 != 0) {
        bit.push_back(0);
    }
    std::reverse(bit.begin(), bit.end());

    // 输出 Unicode 码点
    printf("U+");
    for (int i = 0, lead = 1; i < bit.size(); i += 4) {
        int val = 0;
        for (int j = i; j < i + 4; ++j)
            val = val * 2 + bit[j];
        if (val != 0) {
            lead = 0;
        } 
        if (!lead) {
            printf("%X", *(unsigned char*)&val);
        }
    }
    printf("\n");

    puts("------------------------------------");

}

int main()
{

    char one[] = u8"A";
    char two[] = u8"é";
    char three[] = u8"";
    char four[] = u8"😀";
    output_char2bit(one);
    output_char2bit(two);
    output_char2bit(three);
    output_char2bit(four);

    return 0;
}

执行 g++ main.cpp -o main && ./main

输出结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
A, len = 1
0x41
U+41
------------------------------------
é, len = 2
0xc3a9
U+E9
------------------------------------
中, len = 3
0xe4b8ad
U+4E2D
------------------------------------
😀, len = 4
0xf09f9880
U+1F600
------------------------------------