如何在C语言中比较多字节字符

10

我尝试解析文本并查找其中的某些字符。我使用以下代码。它能处理像abcdef这样的普通字符,但无法处理öçşğüı。GCC会给出编译警告。我该怎么做才能处理öçşğüı

代码:

#include <stdio.h>
#include <ctype.h>
#include <string.h>

int main()
{
    char * text = "öçşğü";
    int i=0;

    text = strdup(text);

    while (text[i])
    {       
        if(text[i] == 'ö')
        {
            printf("ö \n");
        }

        i++;
    }

    return 0;
}

警告:

warning: multi-character character constant [-Wmultichar]
warning: comparison is always false due to limited range of data type [-Wtype-limits]

当我在while循环中打印char地址时,会有10个地址

printf("%d : %p \n", i, text[i]);

输出:

0 : 0xffffffc3 
1 : 0xffffffb6 
2 : 0xffffffc3 
3 : 0xffffffa7 
4 : 0xffffffc5 
5 : 0xffffff9f 
6 : 0xffffffc4 
7 : 0xffffff9f 
8 : 0xffffffc3 
9 : 0xffffffbc 

并且 strlen 的长度是10。

但是如果我使用 abcde

0 : 0x61 
1 : 0x62 
2 : 0x63 
3 : 0x64 
4 : 0x65 

并且strlen的长度为5。


如果我使用wchar_t进行文本输出,则

0 : 0xa7c3b6c3 
1 : 0x9fc49fc5 
2 : 0xbcc3 

strlen 是 10,wcslen 是 3。


1
我认为这是因为umlauts被视为另一个字符。您可能需要检查实际字符串而不是字符,或者将oe作为输入代替ö - Arc676
1
使用 wchar_t 替代 char。在所有字符串字面量和字符字面量上使用 L 后缀。使用 wcslen 替代 strlen - Lundin
@MikeCAT应该使用wscmp来进行比较。 - ameyCU
1
阅读这篇文章http://www.joelonsoftware.com/articles/Unicode.html,然后再试一次。 - n. m.
@M.M 除了 c 字符串之外。但这更像是一个编辑器问题:是否会/不会显示/操作(例如,字体设置是否已加载)。在编译器中,“<asc><utf1-utf3><asc2>…”将被解析为我认为它只查找0x00或\"。编译器需要特殊逻辑来处理它——只是为了追求严谨。在注释中也应该可以正常工作(包括 K&R 和 ANSI)。OP 无法编译,因为他使用单引号而不是双引号(例如,一个 utf8 代码点是 1-4 个字符)。根据诊断,编译器理解 utf8 很好。OP 有错误的代码,他的自我回答也没有改善多少。 - Craig Estey
显示剩余11条评论
4个回答

2
为了遍历字符串中的每个字符,您可以使用 mblen。您还需要设置正确的区域设置(由多字节字符串表示的编码),以便 mblen 可以正确解析多字节字符串。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>

int main()
{
    char * text = "öçşğü";
    int i=0, char_len;

    setlocale(LC_CTYPE, "en_US.utf8");

    while ((char_len = mblen(&text[i], MB_CUR_MAX)) > 0)
    {
        /* &text[i] contains multibyte character of length char_len */
        if(memcmp(&text[i], "ö", char_len) == 0)
        {
            printf("ö \n");
        }

        i += char_len;
    }

    return 0;
}

有两种字符串表示方式,一种是使用多字节(8位字节),另一种是使用宽字节(大小取决于平台)。多字节表示的优点是可以使用char *(通常在代码中使用的c字符串),但缺点是多个字节表示一个字符。宽字符串使用wchar_t *表示。 wchar_t的优点是一个wchar_t就是一个字符(然而正如@anatolyg指出的,在wchar_t无法表示所有可能的字符的平台上,这种假设仍然可能失败)。
你是否用十六进制编辑器查看过源代码?字符串"öçşğü"实际上是由多字节字符串c3 b6 c3 a7 c5 9f c4 9f c3 bc(UTF-8编码)在内存中表示的,当然还有零终止符。您看到5个字符只是因为该字符串由您的UTF-8感知查看器/浏览器正确呈现。很容易意识到,对于此,strlen(text)返回10,而上面的代码仅循环5次。
如果使用宽字节字符串,则可以按@WillBriggs所述进行操作。

你的代码问“为什么需要这个?”,这似乎很奇怪。你是从某个地方复制了它和这个误导性的注释,还是你自己想知道为什么需要strdup?我认为在这里绝对没有使用strdup的理由(顺便说一下,strdup必须与free配对使用,而你的代码中缺少了这个)。 - anatolyg
我只是想问一下原帖作者,为什么他使用了strdup。当然,这并不是必需的,只是为了解析。我会编辑我的回答。感谢指出。 - user1969104
@user1969104 感谢您的回答。我在Linux中尝试了您的代码,但从未将 ö 打印到控制台。您可以在 http://ideone.com/2arnRE 上查看。但是它可以使用 abcde - utarid
请在循环后打印char_len。如果它是“-1”,那么可能是mblen失败了。这可能是由于区域设置不正确造成的。请查看命令locale -a的输出,以查看是否在输出中存在en_US.utf8。如果没有,请使用输出中存在的任何utf8字符串,例如C.UTF-8。 - user1969104

0

关于在源文件中直接嵌入非 ASCII 字符,目前不存在标准。

相反,C11 标准规定您可以使用 Unicode 码点:

wchar_t text[] = L"\u00f6\u00e7\u015f\u0131\u011f";

// Print whole string
wprintf(L"%s\n", text);

// Test individual characters
for (size_t i = 0; text[i]; ++i)
{
    if ( text[i] == u'\u00f6' )
        // whatever...
}

如果你正在使用 Windows,那么你会面临一个额外的问题,即 Windows 控制台默认不能打印 Unicode 字符。你需要执行以下步骤:

  • 更改控制台使用 TrueType 等宽字体,该字体包括你尝试打印的字符的字形。(我在本例中使用了 "DejaVu Sans Mono")
  • 在源代码中调用函数_setmode(1, _O_WTEXT);,这将需要#include <fcntl.h>

要恢复正常文本,可以使用_setmode(1, _O_TEXT);

当然,如果你将输出到文件或 Win32 API 函数,则不需要执行这些步骤。


感谢 @M.M. 的回答。但是我遇到了这个错误 error: ‘u’ undeclared (first use in this function)。如果我将 u 改为 L,代码就可以运行了。 - utarid
好的。你的编译器可能不支持C11(如果支持,你也可能没有以C11模式调用它)。 - M.M

0

请参阅维基百科:https://en.wikipedia.org/wiki/UTF-8,其中有一个位模式表。

以下是将 utf-8 字符串扫描/转换为 codepoint 的另一种方法[仅供参考,请参考维基百科]:

// utf8scan -- convert utf8 to codepoints (example)

char inpbuf[1000];
char uni[8];

typedef union {
    char utf8[4];
    unsigned int code;
} codepoint_t;

codepoint_t outbuf[1000];

// unidecode -- decode utf8 char into codepoint
// RETURNS: updated rhs pointer
char *
unidecode(codepoint_t *lhs,char *rhs)
{
    int idx;
    int chr;

    idx = 0;
    lhs->utf8[idx++] = *rhs++;

    for (;  ;  ++rhs, ++idx) {
        chr = *rhs;

        // end of string
        if (chr == 0)
            break;

        // start of new ascii char
        if ((chr & 0x80) == 0)
            break;

        // start of new unicode char
        if (chr & 0x40)
            break;

        lhs->utf8[idx] = chr;
    }

    return rhs;
}

// main -- main program
int
main(void)
{
    char *rhs;
    codepoint_t *lhs;

    rhs = inpbuf;
    lhs = outbuf;

    for (;  *rhs != 0;  ++lhs) {
        lhs->code = 0;

        // ascii char
        if ((*rhs & 0x80) == 0)
            lhs->utf8[0] = *rhs++;

        // get/skip unicode char
        else
            rhs = unidecode(lhs,rhs);
    }

    // add EOS
    lhs->code = 0;

    return 0;
}

-2

处理宽字符的最佳方式就是使用宽字符。

wchar_t myWord[] = L"Something";

这样做就可以了:
#include <stdio.h>
#include <ctype.h>
#include <string.h>

int main()
{
    wchar_t * text = L"öçşğü";
    int i = 0;

    while (text[i])
    {
        if (text[i] == L'ö')
        {
            wprintf(L"ö \n");
        }

        i++;
    }

    return 0;
}

如果你和我一样使用 Visual Studio,就会记得控制台窗口不太支持 Unicode。你可以将输出重定向到文件中查看,然后就能看到 ö 了。

1
在我看来,使用 wchar_t 是最糟糕的方式,因为在 Visual Studio 中它们是 16 位,并且需要 UTF-16 编码。因此忽略编码的代码(如 i++)将会隐藏其错误 - 您只会在大概 0.1% 的字符串中看到错误,而不是可能有的 10%。 - anatolyg

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