1. 程式人生 > 其它 >C語言到底使用什麼編碼?誰說C語言使用ASCII碼,真是荒謬!

C語言到底使用什麼編碼?誰說C語言使用ASCII碼,真是荒謬!

C語言是 70 年代的產物,那個時候只有 ASCII,各個國家的字元編碼都還未成熟,所以C語言不可能從底層支援 GB2312、GBK、Big5、Shift-JIS 等國家編碼,也不可能支援 Unicode 字符集。

稍微有點C語言基本功的讀者可能認為C語言使用 ASCII 編碼,字元在儲存時會轉換成對應的 ASCII 碼值,這也是錯誤的,你被大學老師和教材誤導了!在C語言中,只有 char 型別的窄字元才使用 ASCII 編碼,char 型別的窄字串、wchar_t 型別的寬字元和寬字串都不使用 ASCII 編碼!

wchar_t 型別的寬字元和寬字串使用 UTF-16 或者 UTF-32 編碼,這個在上節已經講到了,現在只剩下 char 型別的窄字串(下面稱為窄字串)沒有講了,這就是本節的重點。

對於窄字串,C語言並沒有規定使用哪一種特定的編碼,只要選用的編碼能夠適應當前的環境即可,所以,窄字串的編碼與作業系統和編譯器有關。

但是,可以肯定的說,在現代計算機中,窄字串已經不再使用 ASCII 編碼了,因為 ASCII 編碼只能顯示字母、數字等英文字元,對漢語、日語、韓語等其它地區的字元無能為力。

討論窄字串的編碼要從以下兩個方面下手。

原始檔使用什麼編碼

原始檔用來儲存我們編寫的程式碼,它最終會被儲存到本地硬碟,或者遠端伺服器,這個時候就要儘量壓縮檔案體積,以節省硬碟空間或者網路流量,而程式碼中大部分的字元都是 ASCII 編碼中的字元,用一個位元組足以容納,所以 UTF-8 編碼是一個不錯的選擇。

UTF-8 相容 ASCII,程式碼中的大部分字元可以用一個位元組儲存;另外 UTF-8 基於 Unicode,支援全世界的字元,我們編寫的程式碼可以給全球的程式設計師使用,真正做到技術無國界。

常見的 IDE 或者編輯器,例如 Xcode、Sublime Text、Gedit、Vim 等,在建立原始檔時一般也預設使用 UTF-8 編碼。但是 Visual Studio 是個奇葩,它預設使用本地編碼來建立原始檔。

所謂本地編碼,就是像 GBK、Big5、Shift-JIS 等這樣的國家編碼(地區編碼);針對不同國家發行的作業系統,預設的本地編碼一般不同。簡體中文字的 Windows 預設的本地編碼是 GBK。
對於編譯器來說,它往往支援多種編碼格式的原始檔。微軟編譯器、GCC、LLVM/Clang(內嵌於 Xcode 中)都支援 UTF-8 和本地編碼的原始檔,不過微軟編譯器還支援 UTF-16 編碼的原始檔。如果考慮到原始檔的通用性,就只能使用 UTF-8 和本地編碼了。

窄字串使用什麼編碼

前面講到,用 puts 或者 printf 可以輸出窄字串,程式碼如下:
  • #include <stdio.h>
  • int main()
  • {
  • puts("城東書院");
  • printf("http://www.cdsy.xyz");
  • return 0;
  • }

"城東書院""http://www.cdsy.xyz"就是需要被處理的窄字串,程式執行後,它們會被載入到記憶體中。你看,這裡面還包含了中文,肯定不能使用 ASCII 編碼了。

1) 微軟編譯器使用本地編碼來儲存這些字元。不同地區的 Windows 版本預設的本地編碼不一樣,所以,同樣的窄字串在不同的 Windows 版本下使用的編碼也不一樣。對於簡體中文版的 Windows,使用的是 GBK 編碼。

2) GCC、LLVM/Clang 編譯器使用和原始檔相同的編碼來儲存這些字元:如果原始檔使用的是 UTF-8 編碼,那麼這些字元也使用 UTF-8 編碼;如果原始檔使用的是 GBK 編碼,那麼這些字元也使用 GBK 編碼。

你看,對於程式碼中需要被處理的窄字串,不同的編譯器差別還是挺大的。不過可以肯定的是,這些字元始終都使用窄字元(多位元組字元)編碼。

正是由於這些字元使用 UTF-8、GBK 等編碼,而不是使用 ASCII 編碼,所以它們才能包含中文。

那麼,為什麼很多初學者會誤認為C語言使用 ASCII 編碼呢?

不管是在課堂跟著老師學習,還是通過網際網路自學,初學者都是從處理英文開始的,對於英文來說,使用 GBK、UTF-8、ASCII 都是一樣的,GBK、UTF-8 都相容 ASCII,初學者根本察覺不出用了哪種編碼。

另外,很多大學老師和書籍作者也經常會念叨,字元在儲存時會被轉換成對應的 ASCII 碼,在讀取時又會從 ASCII 碼轉換成對應的字元實體,大家需要熟悉 ASCII 編碼,它是C語言處理字元的基礎,這從很大程度上給初學者造成一種錯誤印象:C語言和 ASCII 編碼是繫結的,C語言使用 ASCII 編碼。

總結

對於 char 型別的窄字元,始終使用 ASCII 編碼。

對於 wchar_t 型別的寬字元和寬字串,使用 UTF-16 或者 UTF-32 編碼,它們都是基於 Unicode 字符集的。

對於 char 型別的窄字串,微軟編譯器使用本地編碼,GCC、LLVM/Clang 使用和原始檔編碼相同的編碼。

另外,處理窄字元和處理寬字元使用的函式也不一樣:

  • <stdio.h> 標頭檔案中的 putchar、puts、printf 函式只能用來處理窄字元;
  • <wchar.h> 標頭檔案中的 putwchar、wprintf 函式只能用來處理寬字元。

你看,僅僅是字元的處理,C語言就能玩出這麼多花樣,讓人捉摸不透,不容易學習。這是因為,C語言是一種較為底層和古老的語言,既有歷史遺留問題,又有貼近計算機底層的特性。不過,一旦搞明白這些繁雜的底層問題,你的程式設計內功將精進一個層次,這也許就是學習C語言的樂趣。

【拓展】編碼字符集和執行字符集

站在專業的角度講,原始檔使用的字符集被稱為編碼字符集,也就是寫程式碼的時候使用的字符集;程式中的字元或者字串使用的字符集被稱為執行字符集,也就是程式執行後使用的字符集。

原始檔需要儲存到硬碟,或者在網路上傳輸,使用的編碼要儘量節省儲存空間,同時要方便跨國交流,所以一般使用 UTF-8,這就是選擇編碼字符集的標準。

程式中的字元或者字串,在程式執行後必須被載入到記憶體,才能進行後續的處理,對於這些字元來說,要儘量選用能夠提高處理速度的編碼,例如 UTF-16 和 UTF-32 編碼就能夠快速定位(查詢)字元。

編碼字符集是站在儲存和傳輸的角度,執行字符集是站在處理或者操作的角度,所以它們並不一定相同。