1. 概述
unicode 碼又稱爲“萬國碼”,顧名思義,世界上所有語言對應的字符都可以在 unicode 碼錶中找到對應的編號。而我們常說的 utf-8,utf-16, utf-32 等則是編碼方式,是將碼錶中的編碼轉化成計算機字節的方法。
unicode 是一種碼錶,像這樣的還有 US-ASCII,GBK,UCS-2,UCS-4 等。許多人搞不清楚 unicode 和 utf-16 之間的區別,將他們混爲一談。這裏做個簡單澄清,希望對字符編碼“小白”有些幫助。
編碼方式將任意一種碼錶中的每個編號用一種通用方式存儲在計算機中,比如下文中要提到的 utf-8 編碼。
2. utf-8
utf-8 是一種變長編碼,根據維基百科的描述,它的編碼方式如下:
Bits for code point | First code point | Last code point | Byte 1 | Byte 2 | Byte 3 | Byte 4 | |
---|---|---|---|---|---|---|---|
1 | 7 | U+0000 | U+007F | 0xxxxxxx | |||
2 | 11 | U+0080 | U+07FF | 110xxxxx | 10xxxxxx | ||
3 | 16 | U+0800 | U+FFFF | 1110xxxx | 10xxxxxx | 10xxxxxx | |
4 | 21 | U+10000 | U+10FFFF | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
稍微解釋一下:
U+0000 - U+007F 對應的 US-ASCII 碼錶,它在 utf-8 編碼中只佔用一個字節。也就是將 ASCII 碼值直接用二進制表示
U+0080 - U+10FFFF 則是擴展的碼錶,其中包含了 GB2312 碼錶(U+A1A1 - U+FEFE)。
C 語言代碼如下:
頭文件
#pragma once enum EncodeType { u8, // utf-8 編碼 u16, // short-wchar 編碼,就是在使用 wchar_t 時候,添加編譯選項 -fshort-wchar u32, // wchar_t (linux 下的 wchar_t 是 4 個字節存儲的) other }; /* @brief 將目標字符串轉換爲 utf-8 編碼 @param [in] src 源串 @param [out] dest 存儲轉換後的結果 @param [in] maxBytes 最多轉換的字節數,以防止越界。如果轉換的字符串超過 maxBytes,則會做截斷處理, 並保證截斷的結果是合法的 utf-8 字符串。 @param [in] type 源串的編碼方式,一般而言: linux char 是 u8 編碼;wchar_t 則是 u32 編碼;如果 在編譯的時候使用 -fshort-wchar 選項,則 wchar_t 是 u16 編碼 @return 轉換後的串長度 */ int convert2Utf8(const unsigned char* src, char* dest, int maxBytes, EncodeType type);
源文件
#include "utf8.h" #include <wchar.h> #include <locale.h> #include <string.h> #include <stdio.h> static char g_utf8[10]; static void _convertCharacter(unsigned character, int& bytes) { /* 注意:以下算法獲取的結果需要進行逆序才正確 */ if (character < 128) { bytes = 1; g_utf8[0] = (char)character; return; } bytes = 0; unsigned bits = 0; unsigned afterShift = character >> 6; while (afterShift > 0) { bits = character & 63; /* 截取 6 位 */ bits |= 128; /* 添加 10 */ g_utf8[bytes++] = bits; character = afterShift; afterShift = character >> 6; } g_utf8[bytes] = character; for (int i = 0; i < bytes + 1; i++) { g_utf8[bytes] |= 1 << (7 - i); } bytes++; } int convert2Utf8(const unsigned char* src, char* dest, int maxBytes, EncodeType type) { switch (type) { case u8: { int i; maxBytes--; /* 需要保證能存儲字符串結束符,因此有效字符個數應該不超過 maxBytes-1 */ for (i = 0; i < maxBytes && src[i] != '\0'; i++) { dest[i] = src[i]; } dest[i] = '\0'; /* 添加串結束符 */ return i; } case u32: { int len = 0; int bytes; wchar_t* target = (wchar_t*)src; for (int i = 0; target[i] != 0; i++) { /* 轉換一個字符 */ _convertCharacter(target[i], bytes); if (len + bytes >= maxBytes) return len; for (int i = bytes-1; i > -1; i--) { dest[len++] = g_utf8[i]; } dest[len] = '\0'; /* 不可少 */ } return len; } case u16: { int len = 0; int bytes; unsigned short* target = (unsigned short*)src; for (int i = 0; target[i] != 0; i++) { /* 轉換一個字符 */ _convertCharacter(target[i], bytes); if (len + bytes >= maxBytes) return len; for (int i = bytes-1; i > -1; i--) { dest[len++] = g_utf8[i]; } dest[len] = '\0'; /* 不可少 */ } return len; } default: { printf("Unknown encoding!!\n"); return 0; } } } int main(void) { const wchar_t* str = L"中國"; /* 獲取系統環境變量設置的本地配置,用於正常顯示中文 */ setlocale(LC_ALL, ""); char strU8[20]; int n = convert2Utf8((unsigned char*)str, strU8, 20, u16); printf("%s, length: %d\n", strU8, n); return 0; }
3. utf-16/utf-32
utf-16 編碼就是將超出 127 碼值的 unicode 編號用兩個字節存儲。utf-32 是 utf-16 的擴展,目前只是簡單地將 utf-16 編碼結果的高位字補 0,以湊齊 4 個字節。
utf-8 轉化爲 utf-16/utf-32 的代碼如下。
#include <locale.h> #include <wchar.h> #include <stdio.h> #include <stdlib.h> #define safe_add(ch) if (c < maxBytes) dest[c++] = (wchar_t)ch; else break int convert2Unicode(const char* src, wchar_t* dest, int maxBytes) { int i = 0; int c = 0; maxBytes--; while (src[i]) { if (src[i] > 0) { safe_add(src[i]); i++; } else { /* 計算多少個字節 */ int bytes = 0; unsigned char t = src[i]; for (int shift = 7; shift > 0; shift--) { if ((t & (1<<shift)) == 0) break; bytes++; } /* 檢查是否合法 utf-8 格式 */ for (int j = 1; j < bytes; j++) { t = src[i+j]; if ((t >> 6) != 2) { printf("INVALID FORMAT!!\n"); exit(1); } } /* 轉換 */ unsigned tranform = 0; t = src[i]; tranform += t & ((1<< (7-bytes)) - 1); for (int j = 1; j < bytes; j++) { t = src[i+j]; tranform <<= 6; tranform += t & 63; } safe_add(tranform); i += bytes; } } dest[c] = 0; return c; } int main(void) { setlocale(LC_ALL, ""); char* str = "中國人, *,shsu,"; wchar_t wstr[20]; convert2Unicode(str, wstr, 20); wprintf(L"%ls\n", wstr); return 0; }