- 系統:windows 64
- 編譯器:gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)
- 文本編輯器:notepad
- 控制台:Cmder
- 編程語言:C、Python
首先,要想打印漢字,必須考慮到編碼問題。在windows下,由於系統使用GBK編碼,而GCC解析時使用UTF-8而會導致以下代碼運行時出現亂碼:
#include <stdio.h> int main() { char *str = "你好,世界!"; printf("%s\n", str); return 0; }
解決方法為:使用“-fexec-charset=gbk”命令
解決了編碼問題,我們還需要了解幾點:
- char類型本質上是數字,占據一個字節(即八位),可以通過%d打印編碼,通過%c打印字符
- 在C語言中,一個漢字占據兩個char類型
- 漢字的兩個char類型為負數
- 在打印漢字時,它的兩個char必須緊跟着
根據這幾點,我們可以打印出漢字以及它們的編碼:
#include <stdio.h> #include <string.h> int main() { // str為字符指針,指向一個字符字面量,這個字符字面量由'\0'結尾 char *str = "你好,世界!Hello, world!"; // chr為字符指針,指向str所指向的字符字面量的第一個字符的地址,即'你'字符的兩個char中的第一個 char *chr = str; printf("%zu %s\n", strlen(str), str); // 如果遇到'\0',說明字符串結束了 while (*chr != '\0') { // 如果chr的編碼為負數,則說明遇到了一個漢字 if (*chr < 0) { // 打印漢字及漢字的編碼 // 注意兩個char必須緊緊跟着打印(%c%c),否則會打印出 ?? printf("%c%c: %d%d\n", *chr, *(chr+1), *(chr), *(chr+1)); // chr自增兩個字節(因為每個漢字都由兩個char組成) chr += 2; } else { // 打印英文字符 printf("%c: %d\n", *chr, *chr); // chr自增一個字節 ++chr; } } return 0; }
從上圖,我們可以看出,這個字符串占據了25個字節,4個漢字加2個全角符號占據了12個字節,再加上23個英文字符,總共25個字節。我們可以從下圖更清晰地看出str的構造:
但是,根據我們在網上查詢的結果,漢字‘你’的GBK編碼應為:C4E3,但是在這里,卻打印出了:-60-29,這是為什么呢?
這里涉及到進制的問題,可能-60-29是十六進制數C4E3的十進制數?
首先,我們先通過Python看看C4E3的二進制數以及十進制數。這好像跟-60-29根本不沾邊。
我們先看看下面的代碼,導入<limits.h>頭文件,看看char類型的取值范圍為多少:
#include <stdio.h> #include <limits.h> int main() { printf("[%d ~ %d]\n", CHAR_MIN, CHAR_MAX); printf("%c%c\n", 0xC4, 0xE3);return 0; }
我們可以看到:char類型的取值范圍為[-128 ~ 127],但是我們卻可以打印出漢字”你“。這是為什么呢?明明char的取值范圍最多127,而漢字“你”的兩個字符分別為:196和227,都超過了這個值。其實這是因為,C語言將這兩個數字的二進制數作為負數處理。C中的char類型有1個字節,占8位,而它的最高位為符號位,當它為0時為正,1時則為負。C通過對正數做補碼操作得到負數。補碼,即對一個二進制數取反,然后再加1。比如,0xC4的二進制數為0b11000100,我們可以看到最高位1,在C中這個數就是負數。我們可以通過對這個二進制數做補碼操作,得到0b00111100,即60。所以0b11000100在C中表示為-60。
從以上,我們可以發現,GBK編碼中,一個漢字占兩字節。因為C中char類型只占一個字節,所以需要使用兩個char類型來表示漢字。因為C中char為有符號類型,char的表示范圍為[-128 ~ 127],所以在遇到大於127的數字時,會被char表示為負數。
其實,我們還可以使用unsigned char來實現。char默認是有符號的,取值范圍為:-128 ~127。而unsigned char的取值范圍則為:0~255,那么漢字“你”的編碼就會被顯示為:196和227。
#include <stdio.h> #include <string.h> int main() { // str為字符指針,指向一個字符字面量,這個字符字面量由'\0'結尾 unsigned char *str = (unsigned char *)"你好,世界!Hello, world!"; // chr為字符指針,指向str所指向的字符字面量的第一個字符的地址,即'你'字符的兩個char中的第一個 unsigned char *chr = str; printf("%zu %s\n", strlen(str), str); // 如果遇到'\0',說明字符串結束了 while (*chr != '\0') { // 如果chr的編碼大於127,則說明遇到了一個漢字 if (*chr > 127) { // 打印漢字及漢字的編碼 // 注意兩個char必須緊緊跟着打印(%c%c),否則會打印出 ?? printf("%c%c: %d %d\n", *chr, *(chr+1), *(chr), *(chr+1)); // chr自增兩個字節(因為每個漢字都由兩個char組成) chr += 2; } else { // 打印英文字符 printf("%c: %d\n", *chr, *chr); // chr自增一個字節 ++chr; } } return 0; }