1. 程式人生 > 其它 >C語言處理中文字元

C語言處理中文字元

大部分C語言教材對中文字元的處理諱莫如深,甚至隻字不提,導致很多初學者認為C語言只能處理英文,而不支援中文。其實C語言是一門全球化的程式語言,它支援世界上任何一個國家的語言文化,包括中文、日語、韓語等。

中文字元的儲存

正確地儲存中文字元需要解決兩個問題。

1) 足夠長的資料型別

char 只能處理 ASCII 編碼中的英文字元,是因為 char 型別太短,只有一個位元組,容納不下我大中華幾萬個漢字,要想處理中文字元,必須得使用更長的資料型別。

一個字元在儲存之前會轉換成它在字符集中的編號,而這樣的編號是一個整數,所以我們可以用整數型別來儲存一個字元,比如 unsigned short、unsigned int、unsigned long 等。

2) 選擇包含中文的字符集

C語言規定,對於漢語、日語、韓語等 ASCII 編碼之外的單個字元,也就是專門的字元型別,要使用寬字元的編碼方式。常見的寬字元編碼有 UTF-16 和 UTF-32,它們都是基於 Unicode 字符集的,能夠支援全球的語言文化。

在真正實現時,微軟編譯器(內嵌於 Visual Studio 或者 Visual C++ 中)採用 UTF-16 編碼,使用 2 個位元組儲存一個字元,用 unsigned short 型別就可以容納。GCC、LLVM/Clang(內嵌於 Xcode 中)採用 UTF-32 編碼,使用 4 個位元組儲存字元,用 unsigned int 型別就可以容納。

對於編號較小的字元,UTF-16 採用兩個位元組儲存;對於編號較大的字元,UTF-16 使用四個位元組儲存。但是,全球常用的字元也就幾萬個,使用兩個位元組儲存足以,只有極其罕見,或者非常古老的字元才會用到四個位元組。

微軟編譯器使用兩個位元組來儲存 UTF-16 編碼的字元,雖然不能囊括所有的 Unicode 字元,但是也足以容納全球的常見字元了,基本滿足了軟體開發的需求。使用兩個位元組儲存的另外一個好處是可以節省記憶體,而使用四個位元組會浪費 50% 以上的記憶體。

你看,不同的編譯器可以使用不同的整數型別。如果我們的程式碼使用 unsigned int 來儲存寬字元,那麼在微軟編譯器下就是一種浪費;如果我們的程式碼使用 unsigned short 來儲存寬字元,那麼在 GCC、LLVM/Clang 下就不夠。

為了解決這個問題,C語言推出了一種新的型別,叫做 wchar_t。w 是 wide 的首字母,t 是 type 的首字元,wchar_t 的意思就是寬字元型別。wchar_t 的長度由編譯器決定:

  • 在微軟編譯器下,它的長度是 2,等價於 unsigned short;
  • 在GCC、LLVM/Clang 下,它的長度是 4,等價於 unsigned int。
wchar_t 其實是用 typedef 關鍵字定義的一個別名,我們會在《C語言typedef:給型別起一個別名》一節中深入講解,大家暫時只需要記住,wchar_t 在不同的編譯器下長度不一樣。

wchar_t 型別位於 <wchar.h> 標頭檔案中,它使得程式碼在具有良好移植性的同時,也節省了不少記憶體,以後我們就用它來儲存寬字元。

上節我們講到,單獨的字元由單引號' '包圍,例如'B''@''9'等;但是,這樣的字元只能使用 ASCII 編碼,要想使用寬字元的編碼方式,就得加上L字首,例如L'A'L'9'L'中'L'國'L'。'。

注意,加上L字首後,所有的字元都將成為寬字元,佔用 2 個位元組或者 4 個位元組的記憶體,包括 ASCII 中的英文字元。

下面的例子演示瞭如何儲存寬字元(注意引入 <wchar.h> 標頭檔案):

  • wchar_t a = L'A'; //英文字元(基本拉丁字元)
  • wchar_t b = L'9'; //英文數字(阿拉伯數字)
  • wchar_t c = L'中'; //中文漢字
  • wchar_t d = L'國'; //中文漢字
  • wchar_t e = L'。'; //中文標點
  • wchar_t f = L'ヅ'; //日文片假名
  • wchar_t g = L'♥'; //特殊符號
  • wchar_t h = L'༄'; //藏文

在以後的程式設計中,我們將不加L字首的字元稱為窄字元,將加上L字首的字元稱為寬字元。窄字元使用 ASCII 編碼,寬字元使用 UTF-16 或者 UTF-32 編碼。

寬字元的輸出

putchar、printf 只能輸出不加L字首的窄字元,對加了L字首的寬字元無能為力,我們必須使用 <wchar.h> 標頭檔案中的寬字元輸出函式,它們分別是 putwchar 和 wprintf:

  • putwchar 函式專門用來輸出一個寬字元,它和 putchar 的用法類似;
  • wprintf 是通用的、格式化的寬字元輸出函式,它除了可以輸出單個寬字元,還可以輸出寬字串(稍後講解)。寬字元對應的格式控制符為%lc

另外,在輸出寬字元之前還要使用 setlocale 函式進行本地化設定,告訴程式如何才能正確地處理各個國家的語言文化。由於大家基礎還不夠,關於本地化設定的內容我們不再展開講解,請大家先記住這種寫法。

如果希望設定為中文簡體環境,在 Windows 下請寫作:

setlocale(LC_ALL, "zh-CN");

在 Linux 和 Mac OS 下請寫作:

setlocale(LC_ALL, "zh_CN");

setlocale 函式位於 <locale.h> 標頭檔案中,我們必須引入它。下面的程式碼完整地演示了寬字元的輸出:

  • #include <wchar.h>
  • #include <locale.h>
  • int main(){
  • wchar_t a = L'A'; //英文字元(基本拉丁字元)
  • wchar_t b = L'9'; //英文數字(阿拉伯數字)
  • wchar_t c = L'中'; //中文漢字
  • wchar_t d = L'國'; //中文漢字
  • wchar_t e = L'。'; //中文標點
  • wchar_t f = L'ヅ'; //日文片假名
  • wchar_t g = L'♥'; //特殊符號
  • wchar_t h = L'༄'; //藏文
  • //將本地環境設定為簡體中文
  • setlocale(LC_ALL, "zh_CN");
  • //使用專門的 putwchar 輸出寬字元
  • putwchar(a); putwchar(b); putwchar(c); putwchar(d);
  • putwchar(e); putwchar(f); putwchar(g); putwchar(h);
  • putwchar(L'\n'); //只能使用寬字元
  • //使用通用的 wprintf 輸出寬字元
  • wprintf(
  • L"Wide chars: %lc %lc %lc %lc %lc %lc %lc %lc\n", //必須使用寬字串
  • a, b, c, d, e, f, g, h
  • );
  • return 0;
  • }

執行結果:

A9中國。ヅ♥༄
Wide chars: A 9 中 國 。 ヅ ♥ ༄

寬字串

給字串加上L字首就變成了寬字串,它包含的每個字元都是寬字元,一律採用 UTF-16 或者 UTF-32 編碼。輸出寬字串可以使用 <wchar.h> 標頭檔案中的 wprintf 函式,對應的格式控制符是%ls

下面的程式碼演示瞭如何使用寬字串:

  • #include <wchar.h>
  • #include <locale.h>
  • int main(){
  • wchar_t web_url[] = L"http://www.cdsy.xyz";
  • wchar_t *web_name = L"城東書院";
  • //將本地環境設定為簡體中文
  • setlocale(LC_ALL, "zh_CN");
  • //使用通用的 wprintf 輸出寬字元
  • wprintf(L"web_url: %ls \nweb_name: %ls\n", web_url, web_name);
  • return 0;
  • }

執行結果:

web_url: http://www.cdsy.xyz
web_name: 城東書院

其實,不加L字首的窄字串也可以處理中文,它和加上了L字首的寬字串有什麼區別呢?我們將在下節《C語言到底使用什麼編碼?誰說C語言使用ASCII碼,真是荒謬!》中詳細講解。