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");
}
}
是为自己备忘。

浙公网安备 33010602011771号