Unicode 码点(Code Point) 与 它的 UTF-8编码, UTF-16编码的换算程序

熟悉Unicode标准的朋友都知道,统一码的码点(又称码值),在使用往往不是直接使用,而是使用UTF-8或UTF-16或UTF-32编码方法来进行编码后再用。 UTF32编码就是直接用四字节来表示Unicode编码,不足四字节的补0,这样对英文等字符集很小的文字来讲,一个英文字母,在ASCII字符集里只要1个字节就能表达,在UTF32下要四个字节,甚是浪费,因此使用并不多。主要我们能碰到的大都是UTF-8,少部分是UTF-16编码后的Unicode字符。
UTF-16是比较早出现的编码方式,早期用2个字节来表达Unicode编码,它只适用于目前Unicode平面的中的基本平面,也就是很早期Unicode收字比较少的时候。后来标准收集的字符多了,必须得扩容,才发展出用代理对来表达BMP之外的更多的字符。因此,对于BMP部分的Unicode字符,UTF-16编码就是它自身的码点,不足2字节的高位加0来补齐,适用于0xFFFF以下的字符。对于BMP部分之外的5位hex值的字符码点(目前标准还没有6位hex的码值),以四个字节来表达,高位2字节用D800~DBFF,低位两字节用DC00~DFFF组合来表达。为了表达成这样,数字需要经过一定的转换,转换的规则有兴趣的可以网上搜索一下,这里不赘述,在下面的代码中体现。
UTF8根据字符码点大小的不同,分成不同字节数来表示。在0x7F以下的用一个字节,也就是它自身的编码来表示。 0x80~0x7FF之间的用两个字节来表示,0x800~0xFFFF之间 的用三个字节来表示,大部分常用汉字处于0x4e00~0x9FFF之间,因此大部分常用汉字的UTF8编码,都是三个字节长度; 更大一点,0x10000~0x1FFFFF这个区间的字符,其UTF8编码就需要用四个字节来表示,比如𰻝𰻝面的𰻝字,罕用的姓氏“𲰹“。
UTF-8 或UTF-16编码后的字符,在存放到文件中时,还有一个大端序和小端序的问题,其实就是字节在内存空间排列的顺序选择。比如UTF-8编码后的“丁“字是E4B881,这是一个3字节的十六进制数。E4这一侧称为数字的高位,81称为这一数字的低位。这个数字存放在内存(或文件中)如果按地址从小到大,依次是 E4 B8 81,这种排列与人们阅读时从高到低的习惯相符,称为大端序(big Endian,缩写BE)。 如果存放时低位在低地址,高位在高地址,依次是81 B8 E4,这种排列顺序就是小端序(Little Endian,缩写LE)。BE与LE的排列顺序正好是相反的。
大端序常用于网络通信(网络字节序)和某些文件格式,因为它更符合人类的阅读习惯。但现代处理器中更多的是小端序,如x86、ARM等芯片,因为它在处理器设计中效率更高。

为了练习一下位的操作并巩固下Unicode编码之UTF-8,UTF-16的计算方式,写了个简易的小程序,注意这里面用的都是Unicode码点(16进制数字)而非字符本身。代码如下

点击查看代码
//----------头文件----------
//
// Created by Administrator on 2026/1/23.
//

#ifndef CP_UTF_CONVERTER_CBASECONV_H
#define CP_UTF_CONVERTER_CBASECONV_H

using namespace std;

class cBaseConv {
public:
    cBaseConv() : input{0}, Res{0} {
    };

    ~cBaseConv() {
    };

    int promptChoose();

    void parse();

    virtual void SetInputValue(unsigned int v); //供外部直接设置参数使用
    virtual unsigned int GetResValue();
protected:
    unsigned int input; //store the input number
    unsigned int Res; //store the convert result
    virtual void getInput() {
    };

    virtual void Convert() {
    };

    virtual void FormatResult() {
    };
};


class cCPtoUTF8 : public cBaseConv {
public:
    void getInput();

    void Convert();

    void FormatResult();
};

class cCPtoUTF16 : public cBaseConv {
public:
    void getInput();

    void Convert();

    void FormatResult();
};

class cUTF8toCP : public cBaseConv {
public:
    void getInput();

    void Convert();

    void FormatResult();
};

class cUTF16toCP : public cBaseConv {
protected:
    unsigned int SecondInput;

public:
    void getInput();

    void Convert();

    void FormatResult();
};

#endif //CP_UTF_CONVERTER_CBASECONV_H


//----------定义文件----------
//
// Created by Administrator on 2026/1/23.
//
#include <iostream>
#include "cBaseConv.h"
using namespace std;
//-----------class cBaseConv
int cBaseConv::promptChoose() {
    std::cout <<
            "please choose(input 1~4):\n 1. CP to UTF-8\n 2. CP to UTF-16\n 3. UTF-8 to CP\n 4. UTF-16 to CP\n 0. Exit\n"
            << endl;
    int c{0};
    cin >> c;
    return c; //for start different converter.
}

void cBaseConv::parse() {
    getInput();
    Convert();
    FormatResult();
}

void cBaseConv::SetInputValue(unsigned int v) {
    input = v;
}

unsigned int cBaseConv::GetResValue() {
    return Res;
}

// ----------end of class cBaseConv-------------

// ----------class cCPtoUTF8-------------
void cCPtoUTF8::getInput() {
    cout << "CodePoint to UTF8:" << endl;
    cout << "please input codePoint,No 0x prefix,no space, Enter to confirm." << endl;
    cin >> hex >> input;
}

//return -1 means failure
void cCPtoUTF8::Convert() {
    unsigned int result;
    if (input <= 0x7F) {
        //       Cat = u8Cat::u8_ASCII;
        result = input;
    } else if (input <= 0x7FF) {
        //       Cat = u8Cat::u8_2bytes;
        result = (input & 0x3F) + 0b1000'0000; //low first
        result += (((input >> 6) & 0x3F) + 0b1100'0000) << 8; //low second
    } else if (input <= 0xFFFF) {
        //       Cat = u8Cat::u8_3bytes;
        result = (input & 0x3F) + 0b1000'0000; //low first
        result += (((input >> 6) & 0x3F) + 0b1000'0000) << 8; //low second
        result += (((input >> 12) & 0xF) + 0b1110'0000) << 16; //low third
    } else if (input <= 0x1FFFFF) {
        //       Cat=u8Cat::u8_4bytes;
        result = (input & 0x3F) + 0b1000'0000; //low first
        result += (((input >> 6) & 0x3F) + 0b1000'0000) << 8; //low second
        result += (((input >> 12) & 0x3F) + 0b1000'0000) << 16; //low third
        result += (((input >> 18) & 0x7) + 0b1111'0000) << 24; //low four
    } else result = 0;
    Res = result;
}

void cCPtoUTF8::FormatResult() {
    if (Res == 0) { cout << "illegal code? " << endl; } else { cout << "UTF8: " << hex << Res << endl; }
}

//--------class cCPtoUTF16------------
void cCPtoUTF16::getInput() {
    cout << "CodePoint to UTF16:" << endl;
    cout << "please input codePoint,No 0x prefix,no space, Enter to confirm." << endl;
    cin >> hex >> input;
}

void cCPtoUTF16::Convert() {
    unsigned int hi{0}, lo{0};
    //   u8Cat Cat = u8Cat::u8_ASCII;
    if (input <= 0xFFFF) {
        //       BMP平面,在0~FFFF之间,除了代理对占用的区间无定义字符之外,直接双字节结果。
        if (input >= 0xD800 && input <= 0xDFFF) {
            cout << "Range for surogate pair,no char defined.please check input coode point." << endl;
            Res = 0;
        }
        //UTF-16的存贮单元是2字节, 对0x4E01, 这两个字节大端序按4E 01 存放, 小端序按01 4E存放。
        Res = input;
    } else if (input > 0x10FFFF) {
        cout << "codepoint out of range, over 0x10FFFF. please input cp of a single char.";
        Res = 0;
    } else {
        //码点大于0xFFFF,但小于等于0x10FFFF,要使用代理对来表达,为四个字节。
        unsigned int tmp{0};
        tmp = input - 0x10000;
        lo = tmp & 0x3FF; //取右边12位,作为低位
        hi = tmp >> 10;
        lo += 0xDC00;
        hi += 0xD800;
        Res = (hi << 16) + lo;
    }
}

void cCPtoUTF16::FormatResult() {
    if (Res == 0) { cout << "illegal code? " << endl; } else { cout << "UTF-16BE: " << hex << Res << endl; }
}

//---------class cUTF8toCP------------
void cUTF8toCP::getInput() {
    cout << "UTF8 to CodePoint:" << endl;
    cout << "please input UTF8 string,No 0x prefix, no space Enter to confirm." << endl;
    cin >> hex >> input;
}

void cUTF8toCP::Convert() {
    int m1{0}, n1{0}, m2{0}, n2{0}, m3{0}, n3{0}, m4{0}, n4{0};

    if (input <= 0x7F) {
        //7f以下不用变换。
        Res = input;
        return;
    };

    if (input <= 0xDFBF) {
        //对应7ff
        // Cat = u8Cat::u8_2bytes;
        //110xxxxx 10xxxxxx
        n1 = (input & 0x3F); //取低6位,与0b111111与
        m1 = (input >> 6) & 0b11; //取7~8位的10掩码(if有)
        n2 = (input >> 8 & 0x1F); //取110xxxxx中的xxxxx
        m2 = (input >> 13) & 0b111; //取110xxxxx中的110
        if (m1 == 0b10 && m2 == 0b110) {
            //校验2字节的格式是否正确
            Res = (n2 << 6) + n1; // 合成一个数字
            return;
        } else {
            cout << "Wrong UTF-8 Code" << endl;
            Res = 0;
            return;
        }
    };
    if (input <= 0xEFBFBF) {
        //对应ffff
        //       Cat = u8Cat::u8_3bytes;
        // 1110xxxx 10xxxxxx 10xxxxxx

        n1 = (input & 0x3F); //取倒数第1个的低6位,与0b111111与
        m1 = (input >> 6) & 0b11; //取倒数第1个的7~8位的10掩码(if有)
        n2 = (input >> 8 & 0x3F); //取倒数第2个10xxxxx中的xxxxxx
        m2 = (input >> 14) & 0b11; //取倒数第2个10xxxxxx中的10
        n3 = (input >> 16) & 0xF; //取1110xxxx中的xxxx
        m3 = (input >> 20) & 0xf; //取1110xxxx中的1110

        if (m1 == 0b10 && m2 == 0b10 && m3 == 0b1110) {
            Res = (n3 << 12) + (n2 << 6) + n1;
            return;
        } else {
            cout << "Wrong UTF-8 Code" << endl;
            Res = 0;
            return;
        }
    };

    if (input <= 0xF7BFBFBF) {
        //对应1FFFFF
        //       Cat=u8Cat::u8_4bytes;
        // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

        n1 = (input & 0x3F); //取倒数第1个的低6位,与0b111111与
        m1 = (input >> 6) & 0b11; //取倒数第1个的7~8位的10掩码(if有)
        n2 = (input >> 8 & 0x3F); //取倒数第2个10xxxxx中的xxxxxx
        m2 = (input >> 14) & 0b11; //取倒数第2个10xxxxxx中的10
        n3 = (input >> 16) & 0x3F; //取倒数第3个10xxxxxx中的xxxxxx
        m3 = (input >> 22) & 0b11; //取倒数第3个10xxxxxx中的10
        n4 = (input >> 24) & 0x7; //取11110xxx中的xxx
        m4 = (input >> 27) & 0x1F; //取11110xxx中的11110
        if (m1 == 0b10 && m2 == 0b10 && m3 == 0b10 && m4 == 0b11110) {
            Res = (n4 << 18) + (n3 << 12) + (n2 << 6) + n1;
            return;
        } else {
            cout << "Wrong UTF-8 Code" << endl;
            Res = 0;
            return;
        }
    } else {
        //     Cat=u8Cat::u8_error;
        cout << "number beyond limits(0xF7BFBFBF), Maybe you input more than 1 byte?" << endl;
        Res = 0;
        return;
    }
}

void cUTF8toCP::FormatResult() {
    if (Res != 0) { cout << "Code Point: " << hex << Res << endl; }
}

//----------class of cUTFtoCP----------
void cUTF16toCP::getInput() {
    cout << "UTF16 surogate pair to CodePoint:\n"
            "please input UTF16 surogate pair separated by space, format:  D800~DBFF DC00~DFFF\n"
            "No 0x prefix, Hi surogate first. Enter to confirm." << endl;
    cin >> hex >> input >> SecondInput;
}

void cUTF16toCP::Convert() {
    if ((input > 0xDBFF) || (input < 0xD800)) {
        cout << "UTF-16LE代理对,高位值 应该在0xD800~0xDBFF之间。" << input << endl;
        Res = 0;
        return;
    }
    if ((SecondInput > 0xDFFF) || (SecondInput < 0xDC00)) {
        cout << "UTF-16LE代理对,低位值 应该在0xDC00~0xDFFF之间。" << SecondInput << endl;
        Res = 0;
        return;
    }
    //   cout << "输入UTF-16LE:" << showbase << hex << ((input << 16) + SecondInput) << endl;
    //step1. 分别减去两个常数
    input -= 0xD800;
    SecondInput -= 0xDC00;
    //step 2, 先算高位,再算低位
    input = (input << 2) + (SecondInput >> 8); //前10位
    SecondInput &= 255; //后10位
    //step,再加上10000h 得到码点
    Res = (input << 8) + SecondInput + 0x10000;
}

void cUTF16toCP::FormatResult() {
    if (Res != 0) { cout << "Code Point: " << Res << endl; };
}

// --------------主程序----------
#include <algorithm>
#include <iostream>
#include "cBaseConv.h"

int main() {
    cBaseConv* obj{};
    int i;
    while (true) {
        i=obj->promptChoose();
        switch (i) {
            case 1: {obj=new cCPtoUTF8; break;}  //让父类指针指向一个子类对象
            case 2: {obj=new cCPtoUTF16;break;}
            case 3: {obj=new cUTF8toCP; break;}
            case 4: {obj=new cUTF16toCP; break;}
            case 0:
            default: {delete obj;return 0;}
        }
        obj->parse();
        system("pause");
    }
}

EXE文件

是为自己备忘。

posted @ 2026-01-25 22:44  dingxianghuan  阅读(53)  评论(0)    收藏  举报