如何在C语言中正确处理非ASCII字符串?

5
我的想法是用C语言编写一个类似“猜词游戏”的程序。我希望它能够使用带有变音符号的德语单词(例如:äüö),还可以使用希腊语单词(完全是非ASCII字符)。
我的编译器和终端都能很好地处理Unicode。字符串的显示效果也很好。
但是,我应该如何对这些字符串进行操作呢?对于德语,我或许可以通过在函数中处理这些情况来处理6个大小写带重音字符。但是考虑到希腊语,这似乎是不可能的。
我编写了这段测试代码。它输出字符串、字符串长度(当然是错误的,因为UTF-8序列占据了两个字符的位置),以及字符串中各个字符的纯文本和十六进制值。
#include <stdio.h>
#include <string.h>

int main() {
    printf("123456789\n");
    char aTestString[] = "cheese";
    printf("%s ist %d Zeichen lang\n", aTestString, strlen(aTestString));
        
    for (int i = 0; i < strlen(aTestString); i++) {
        printf("( %c )", aTestString[i]);   // char als char
        printf("[ %02X ]", aTestString[i]); // char in hexadezimal
    }

    printf("\n123456789\n");
    char aTestString2[] = "Käse";
    printf("%s has %d characters\n", aTestString2, strlen(aTestString2));
        
    for (int i = 0; i < strlen(aTestString2); i++) {
        printf("( %c )", aTestString2[i]);  // char als char
        printf("[ %02X ]", aTestString2[i]); // char in hexadezimal
    }
    
    printf("\n123456789\n");    
    char aTestString3[] = "λόγος";
    printf("%s has %d characters\n", aTestString3, strlen(aTestString3));

    for (int i = 0; i < strlen(aTestString3); i++) {
        printf("( %c )", aTestString3[i]);  // char als char
        printf("[ %02X ]", aTestString3[i]); // char in hexadezimal
    }
}

例如,有什么推荐的方法来计算Unicode字符的数量,或者查看特定的Unicode字符(即代码点)是否在字符串中?我相信一定有一些简单的解决方案,因为这样的字符经常用于密码等场景。
以下是测试程序的输出:
123456789
cheese has 6 character
( c )[ 63 ]( h )[ 68 ]( e )[ 65 ]( e )[ 65 ]( s )[ 73 ]( e )[ 65 ]
123456789
Käse has 5 characters
( K )[ 4B ](  )[ FFFFFFC3 ](  )[ FFFFFFA4 ]( s )[ 73 ]( e )[ 65 ]
123456789
λόγος has 10 characters
(  )[ FFFFFFCE ](  )[ FFFFFFBB ](  )[ FFFFFFCF ](  )[ FFFFFF8C ](  )[ FFFFFFCE ](  )[ FFFFFFB3 ](  )[ FFFFFFCE ](  )[ FFFFFFBF ](  )[ FFFFFFCF ](  )[ FFFFFF82 ]

所以你建议我使用scanf和fgets定期进行输入,但编写一些函数来搜索字符并根据字节对字符串进行长度计算? - ᛉᛉᛉ ᛉᛉᛉ
1
@ᛉᛉᛉᛉᛉᛉ 不需要。使用wchar_t和处理宽字符串的函数,只需计算wchar_t即可。在处理特殊字符和罕见的特殊符号之前,你不需要更复杂的东西。对于德文和希腊文来说,这已经足够了。 - n. m. will see y&#39;all on Reddit
1
不要使用wchar_t和处理宽字符串的函数,只需计算wchar_t即可。除非你开始处理特殊字符和罕见字符,否则不需要更复杂的东西。对于德语和希腊语来说,这已经足够了。 - n. m. could be an AI
关于C++的相关问题:如何在C++中将UTF-8字符转换为大写/小写? - Andreas Wenzel
@tchrist "你将不得不应对" 可能不需要。使用德国键盘的普通用户不会输入分解字符。理论上他们可以输入组合的分音符,但谁在乎呢。只需告诉他们不要这样做。 - n. m. will see y&#39;all on Reddit
显示剩余19条评论
2个回答

5
C的多字节字符串工具在这种情况下非常有用。例如,使用mbrlen,一种找到字符串中字符数量的方法(尽管可能是一个非常天真的方法,我刚刚拼凑在一起)是这样的:
#include <stdio.h>
#include <wchar.h>
#include <locale.h>

size_t string_size(const char *s)
{
    mbstate_t state = {0};
    size_t len = 0;
    for (; *s != '\0'; ++len)
    {
        unsigned c_len;
        for (c_len = 1; mbrlen(s+c_len-1, 1, &state) == -2; ++c_len) {}
        s += c_len;
    }
    return len;
}

int main(void)
{
    setlocale(LC_ALL, "en_US.utf8");
    const char *s = "zß水🍌";
    printf("%zu\n", string_size(s));
}

// Output: 4

使用相同的函数 mbrlen,您还可以通过找到它们的长度来提取单个字符。如果您想使用宽字符进行操作,还有一些函数可以在多字节字符和宽字符之间进行转换。

2
为了正确处理Unicode字符,您需要使用宽字符和宽字符串函数,而不是常规字符和字符串函数。宽字符和字符串函数以为前缀(例如,wprintf,wcslen,wcsstr),旨在正确处理多字节字符。
以下是打印带有Unicode字符的短语的示例:
#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main() {
    setlocale(LC_ALL, ""); // Set the locale to handle wide characters

    wchar_t phrase[] = L"Hello, 世界!"; // Unicode phrase

    wprintf(L"%ls\n", phrase); // Print the Unicode phrase

    return 0;
}

4
wchar_t的问题在于它并不真正具备可移植性。例如,在Linux系统上,wchar_t是一个32位类型,而在使用MSVC编译器的Windows系统上,它是16位的。Windows还使用UTF-16,这仍然是一种可变长度的编码方案(会导致与OP已经遇到的完全相同的问题)。其他系统,甚至其他编译器可能会使用其他大小或编码方式。 - Some programmer dude
4
wchar_t的问题在于它并不真正具备可移植性。例如,在Linux系统上,wchar_t是一个32位类型,而在使用MSVC编译器的Windows系统上,它是16位的。Windows还使用UTF-16,这仍然是一种可变长度的编码方案(会导致与OP已经遇到的完全相同的问题)。其他系统,甚至其他编译器可能会使用其他大小或编码方式。 - Some programmer dude
1
@Someprogrammerdude 问题提出者指定了德语。无需处理替代字符。wchar_t 足够应对这个需求。 - n. m. will see y&#39;all on Reddit
1
@Someprogrammerdude OP已经指定了德语。不需要处理代理字符。对于这个任务来说,wchar_t已经足够了。 - n. m. could be an AI
@n.m.willseey'onReddit 嘿,是的,我确实使用了ChatGPT。我测试了代码并确保它能正常工作。这是我在这个网站上的第二个回答,我不知道这是不允许的。现在我知道了,并且不会再使用它。我的其他(一个)回答都没有使用它,这完全是我的错。 - Daniel
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接