在计算机中文字的编码和存储

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 <stdio.h>
#include <Windows.h>

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 <stdio.h>
#include <windows.h>
#include <fcntl.h>
#include <io.h>

int main () 
{
  wchar_t SampleData[] = {0x5927, 0x5BB6, 0x597D, 0x0};
  _setmode(_fileno(stdout), _O_U16TEXT);
  wprintf(L"%s\n", SampleData);
  return 0;
}

// Linux System
#include <stdio.h>
#include <wchar.h>
#include <stdlib.h>
#include <locale.h>

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;
}

参考链接1, 参考链接2


RectCream
1 声望5 粉丝

引用和评论

0 条评论