在计算机中文字的编码和存储
ASCII编码方式
ASCII是基本的文字编码方式,它的方式是用一个字节Byte来表示一个符号,比如说0x30代表'0',而0x41代表'A',这个链接有详细的ASCII每个数字对应的字符。
以下的C代码打印出你所需要的ASCII符号 'A':
printf("ASCII: %c\r\n", 0x41);
// 或者
printf("ASCII: %c\r\n", 'A');
Code Page
但大家可能会有疑问,一个字节一共有256个值,明明可以使用256个字符,为什么ASCII只有128个?如果是printf("Code Page: %c\r\n", 0xA0);
会打印出什么结果?
答案是另外128个字符预留给了不同的Code Page(码页)。为了打印出各个国家的符号,我们定义了多个码页,一般来说每个码页第0-127 是一样的,即拉丁数字和26个英文字母等常用符号,而第128-255是根据每个码页而不同的。比如说Visual Studio的系统预设(default)的码页是Code Page 437。因此上面的代码会打印出:
printf("Code Page: %c\r\n", 0xA0);
//结果
Code Page: á
我们可以切换不同的码页,比如说Code Page 864 有阿拉伯文字所需的符号。Code Page 932 有日文符号。
// Code Page 932 Japanese
SetConsoleOutputCP(932); // Set to Code Page 932
printf("Code Page: %c\r\n", 0xC0);
// 结果
Code Page: タ
这个链接 是目前windows 支持的码页ID(SetConsoleOutputCP
支持的参数)。
Unicode
接下来大家可能会想到这个问题,我们的中文一共可不止128个字符,经常使用的中文字符大约有5000个,是无法使用码表来表示的。
因此我们用Unicode来包含更多的字符,我们只需要使用Unicode就可以表示不同国家的文字,无需在不同文字系统之间系统切换。Unicode的想法非常直接,就是用多个字节Byte来表示文字符号。
在Unicode中,常用的编码方式用两种,一个是UTF-8,一个是UTF-16。
- UTF-8
UTF-8是最为常用的编码方式,这种方式最大的优点是它和ASCII码表是通用的。这种编码方式中,我们用1-4的字节来表示一个文字符号。网上有不少查询UTF-8/16的工具,链接。如果我们想知道“大家好”的UTF-8的值,一次查询每一个文字字符,我们得到:
大:0xE5 0xA4 0xA7
家:0xE5 0xAE 0xB6
好:0xE5 0xA5 0xBD
我们可以在Windows系统中用gcc编译下面的UTF-8测试文件并运行
#include
#include
int main( int argc, char **argv )
{
char PrintData[] = { 0xE5, 0xA4, 0xA7, 0xE5, 0xAE, 0xB6, 0xE5, 0xA5, 0xBD, 0x00}; //0x00 is for NUL termination
SetConsoleOutputCP(65001);
printf( "%s\n", PrintData);
return 0;
}
// 输出
大家好
- UTF-16
这种编码方式中,我们固定用两个字节来表示一个文字符号。这种编码的优势在于因为长度是固定的,所以利于存储。我们可以方便地建立一个uint16_t的数组来存储UTF-16。如果我们想知道“大家好”的UTF-16的值,一次查询每一个文字字符,我们得到:
大:0x5927
家:0x5BB6
好:0x597D
我们也可以在Visual Studio中打印出UTF-16编码的“大家好”(参考链接1, 参考链接2):
// Windows System
#include
#include
#include
#include
int main ()
{
wchar_t SampleData[] = {0x5927, 0x5BB6, 0x597D, 0x0};
_setmode(_fileno(stdout), _O_U16TEXT);
wprintf(L"%s\n", SampleData);
return 0;
}
// Linux System
#include
#include
#include
#include
int main() {
setlocale(LC_ALL,"");
wchar_t SampleData[] = {0x5927, 0x5BB6, 0x597D, 0x0};
wprintf(L"%ls\n", SampleData);
}
- 因此我们需要在UTF-8和UTF-16之间进行转换。系统会接受外部传入的UTF-8编码的字符串(比如网络通信中,HTTP协议一般用UTF-8),然后再将其转换成UTF-16编码的字符串,找到对应的字形(Glyph),然后将字形(Glyph)发到显示器上显示。下面的C程序实现UTF-8和UTF-16的相互转换。
int8_t Utf8To16Converter(const uint8_t * Utf8Val, uint16_t * Utf16Result, uint16_t NumOfUtf8Byte)
{
int8_t Status = 0; // -1 for process fail
uint16_t Utf16Val = 0;
uint8_t TrailingNumOfBytes = 0; // The number of bytes of UTF8 is 1 - 4
uint16_t i = 0;
const uint8_t * InputTraveler;
InputTraveler = Utf8Val;
while (i < NumOfUtf8Byte)
{
if (*InputTraveler <= 0x7F)
{
Utf16Val = *InputTraveler;
TrailingNumOfBytes = 0;
}
else if (*InputTraveler <= 0xBF)
{
Status = -1;
}
else if (*InputTraveler <= 0xDF)
{
Utf16Val = (*InputTraveler) & 0x1F;
TrailingNumOfBytes = 1;
}
else if (*InputTraveler <= 0xEF)
{
Utf16Val = (*InputTraveler) & 0x0F;
TrailingNumOfBytes = 2;
}
else if (*InputTraveler <= 0xF7)
{
Utf16Val = (*InputTraveler) & 0x07;
TrailingNumOfBytes = 3;
}
else
{
Status = -1;
}
if (-1 == Status)
{
break;
}
else
{
uint8_t j;
for (j = 0; j < TrailingNumOfBytes; j++)
{
InputTraveler ++;
if (*InputTraveler == 0x0)
{
Status = -1;
break;
}
else
{
Utf16Val <<= 6;
Utf16Val += (*InputTraveler) & 0x3F;
}
i ++;
}
if (-1 == Status)
{
break;
}
else
{
*Utf16Result = Utf16Val;
Utf16Result ++;
InputTraveler ++;
i ++;
Utf16Val = 0;
TrailingNumOfBytes = 0;
}
}
}
return Status;
}
int8_t Utf16ToUtf8Converter(const uint16_t * Utf16Val, uint8_t * Utf8Result, uint16_t NumOfUtf16Vals)
{
int Status = 0;
uint8_t Utf8Val;
uint16_t i = 0;
for (i = 0; i < NumOfUtf16Vals; i++)
{
uint16_t Utf16TempVal = Utf16Val[i];
if (Utf16TempVal <= 0x7F)
{
*(Utf8Result) = (uint8_t)Utf16TempVal;
Utf8Result ++;
}
else if (Utf16TempVal <= 0x7FF)
{
*(Utf8Result) = 0xC0 | (Utf16TempVal >> 6); /* 110xxxxx */
Utf8Result ++;
*(Utf8Result) = 0x80 | (Utf16TempVal & 0x3F); /* 10xxxxxx */
Utf8Result ++;
}
else if (Utf16TempVal <= 0xFFFF)
{
*(Utf8Result) = 0xE0 | (Utf16TempVal>> 12); /* 1110xxxx */
Utf8Result ++;
*(Utf8Result) = 0x80 | ((Utf16TempVal >> 6) & 0x3F); /* 10xxxxxx */
Utf8Result ++;
*(Utf8Result) = 0x80 | (Utf16TempVal & 0x3F); /* 10xxxxxx */
Utf8Result ++;
}
else if (Utf16TempVal <= 0x10FFFF) {
*(Utf8Result) = 0xF0 | (Utf16TempVal >> 18); /* 11110xxx */
Utf8Result ++;
*(Utf8Result) = 0x80 | ((Utf16TempVal >> 12) & 0x3F); /* 10xxxxxx */
Utf8Result ++;
*(Utf8Result) = 0x80 | ((Utf16TempVal >> 6) & 0x3F); /* 10xxxxxx */
Utf8Result ++;
*(Utf8Result) = 0x80 | (Utf16TempVal & 0x3F); /* 10xxxxxx */
Utf8Result ++;
}
else
{
Status = -1;
}
if (-1 == Status)
{
break;
}
}
return Status;
}