在 C 程式中處理 UTF-8 文字

NO IMAGE

如果你對 UTF-8 編碼不是非常瞭解,就不要試圖在 C 程式中徒手處理 UTF-8 文字。如果你對 UTF-8 非常瞭解,就更沒必要這樣做。找一個提供了 UTF-8 文字處理功能並且可以跨平臺執行的 C 庫來做這件事吧!

GLib 就是這樣的庫。

從問題出發

下面的這段文字是 UTF-8 編碼的(我之所以如此確定,是因為我用的是 Linux 系統,系統預設的文字編碼是 UTF-8):

我的 C81 每天都在口袋裡
@

我需要在 C 程式中讀入這些文字。在讀到 '@'

注:在 GLib 中,gchar

結果是 38

注:glong

此時 tail

藉助 g_utf8_prev_char

GLib 還提供了一個 g_utf8_next_char

此時,雖然可以將 viewer

注:g_print

這樣顯然太繁瑣了。不過,這意味著我們應該寫一個函式專門做這件事。這個函式可取名為 get_utf8_char

藉助這個函式,就可以實現從 demo_text

注:g_memdup

注:gboolean

解決問題

現在萬事俱備,只欠東風,我們應該著手解決問題了。如果讀到此處已經忘記了問題是什麼,那麼請回顧第一節。

儘管下面這段程式碼看上去挺醜,但是它能夠解決問題。

gboolean is_right_at_sign = TRUE;
glong offset = g_utf8_strlen(demo_text, -1);
gchar *viewer = g_utf8_offset_to_pointer(demo_text, offset - 1);
while (viewer != demo_text) {
viewer = g_utf8_prev_char(viewer);
gchar *utf8_char = get_utf8_char(viewer);
if (!is_space(utf8_char)) {
if (!is_line_break(utf8_char)) {
is_right_at_sign = FALSE;
g_free(utf8_char);
break;
} else {
g_free(utf8_char);
break;
}
}
g_free(utf8_char);
}
if (is_right_at_sign) g_print("Right @ !\n");

對上述程式碼略做簡化,可得:

gboolean is_right_at_sign = TRUE;
glong offset = g_utf8_strlen(demo_text, -1);
gchar *viewer = g_utf8_offset_to_pointer(demo_text, offset - 1);
while (viewer != demo_text) {
viewer = g_utf8_prev_char(viewer);
gchar *utf8_char = get_utf8_char(viewer);
if (!is_space(utf8_char)) {
if (!is_line_break(utf8_char)) is_right_at_sign = FALSE;
g_free(utf8_char);
break;
}
g_free(utf8_char);
}
if (is_right_at_sign) g_print("Right @ !\n");

其實,如果將 UTF-8 字元的提取與記憶體釋放過程置入 is_space

可以得到進一步的簡化結果:

gboolean is_right_at_sign = TRUE;
glong offset = g_utf8_strlen(demo_text, -1);
gchar *viewer = g_utf8_offset_to_pointer(demo_text, offset - 1);
while (viewer != demo_text) {
viewer = g_utf8_prev_char(viewer);
if (!is_space(viewer)) {
if (!is_line_break(viewer)) is_right_at_sign = FALSE;
break;
}
}
if (is_right_at_sign) g_print("Right @ !\n");

附:完整的程式碼

#include <string.h>
#include <glib.h>
gchar *demo_text =
"我的 C81 每天都在口袋裡\n"
"                      @";
static gchar * get_utf8_char(const gchar *base) {
gchar *new_base = g_utf8_next_char(base);
gsize n = new_base - base;
gchar *utf8_char = g_memdup(base, (n   1));
utf8_char[n] = '\0';
return utf8_char;
}
static gboolean is_space(const gchar *c) {
gboolean ret = FALSE;
gchar *utf8_char = get_utf8_char(c);
char *space_chars_set[] = {" ", "\t", " "};
size_t n = sizeof(space_chars_set) / sizeof(space_chars_set[0]);
for (size_t i = 0; i < n; i  ) {
if (!strcmp(utf8_char, space_chars_set[i])) {
ret = TRUE;
break;
}
}
g_free(utf8_char);
return ret;
}
static gboolean is_line_break(const gchar *c) {
gboolean ret = FALSE;
gchar *utf8_char = get_utf8_char(c);
if (!strcmp(utf8_char, "\n")) ret = TRUE;
g_free(utf8_char);
return ret;
}
int main(void) {
gboolean is_right_at_sign = TRUE;
glong offset = g_utf8_strlen(demo_text, -1);
gchar *viewer = g_utf8_offset_to_pointer(demo_text, offset - 1);
while (viewer != demo_text) {
viewer = g_utf8_prev_char(viewer);
if (!is_space(viewer)) {
if (!is_line_break(viewer)) is_right_at_sign = FALSE;
break;
}
}
if (is_right_at_sign) g_print("Right @ !\n");
return 0;
}

若是在 Bash 中使用 gcc 編譯這份程式碼,可使用以下命令:

$ gcc `pkg-config --cflags --libs glib-2.0` utf8-demo.c -o utf8-demo