web-dev-qa-db-ja.com

CのUnicode文字列の文字を数える方法

文字列があるとしましょう:

char theString[] = "你们好āa";

私のエンコーディングがutf-8であるとすると、この文字列は12バイトの長さです(3つのhanzi文字はそれぞれ3バイトで、macronnを含むラテン文字は2バイトで、 'a'は1バイトです)。

strlen(theString) == 12

文字数を数えるにはどうすればよいですか?下付き文字と同等のことをどのように行うことができますか?

theString[3] == "好"

このような文字列をスライスしてキャットするにはどうすればよいですか?

56
jsj

上位2ビットが10に設定されていない文字のみをカウントします(つまり、0x80未満または0xbfより大きいすべて)。

これは、上位2ビットが10に設定されているすべての文字がUTF-8継続バイトであるためです。

エンコーディングの説明とstrlenがUTF-8文字列でどのように機能するかについては here を参照してください。

UTF-8文字列のスライスとダイシングについては、基本的に同じルールに従う必要があります。 0ビットまたは11シーケンスで始まるバイトは、UTF-8コードポイントの始まりであり、それ以外はすべて継続文字です。

サードパーティのライブラリを使用したくない場合の最善の策は、次のような機能を提供することです。

utf8left (char *destbuff, char *srcbuff, size_t sz);
utf8mid  (char *destbuff, char *srcbuff, size_t pos, size_t sz);
utf8rest (char *destbuff, char *srcbuff, size_t pos;

それぞれ取得する:

  • 文字列の左sz UTF-8バイト。
  • szから始まる文字列のpos UTF-8バイト。
  • posで始まる、文字列の残りのUTF-8バイト。

これは、目的に合わせて文字列を十分に操作できる適切な構成要素になります。

29
paxdiablo

最も簡単な方法は [〜#〜] icu [〜#〜] のようなライブラリを使用することです

17
Mark

サイズのためにこれを試してください:

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// returns the number of utf8 code points in the buffer at s
size_t utf8len(char *s)
{
    size_t len = 0;
    for (; *s; ++s) if ((*s & 0xC0) != 0x80) ++len;
    return len;
}

// returns a pointer to the beginning of the pos'th utf8 codepoint
// in the buffer at s
char *utf8index(char *s, size_t pos)
{    
    ++pos;
    for (; *s; ++s) {
        if ((*s & 0xC0) != 0x80) --pos;
        if (pos == 0) return s;
    }
    return NULL;
}

// converts codepoint indexes start and end to byte offsets in the buffer at s
void utf8slice(char *s, ssize_t *start, ssize_t *end)
{
    char *p = utf8index(s, *start);
    *start = p ? p - s : -1;
    p = utf8index(s, *end);
    *end = p ? p - s : -1;
}

// appends the utf8 string at src to dest
char *utf8cat(char *dest, char *src)
{
    return strcat(dest, src);
}

// test program
int main(int argc, char **argv)
{
    // Slurp all of stdin to p, with length len
    char *p = malloc(0);
    size_t len = 0;
    while (true) {
        p = realloc(p, len + 0x10000);
        ssize_t cnt = read(STDIN_FILENO, p + len, 0x10000);
        if (cnt == -1) {
            perror("read");
            abort();
        } else if (cnt == 0) {
            break;
        } else {
            len += cnt;
        }
    }

    // do some demo operations
    printf("utf8len=%zu\n", utf8len(p));
    ssize_t start = 2, end = 3;
    utf8slice(p, &start, &end);
    printf("utf8slice[2:3]=%.*s\n", end - start, p + start);
    start = 3; end = 4;
    utf8slice(p, &start, &end);
    printf("utf8slice[3:4]=%.*s\n", end - start, p + start);
    return 0;
}

サンプルの実行:

matt@stanley:~/Desktop$ echo -n 你们好āa | ./utf8ops 
utf8len=5
utf8slice[2:3]=好
utf8slice[3:4]=ā

あなたの例には、1つのエラーによるオフがあることに注意してください。 theString[2] == "好"

14
Matt Joiner

「キャラクター」の概念に応じて、この質問は多少複雑になります。

最初に、バイト文字列をUnicodeコードポイントの文字列に変換する必要があります。 ICUのiconv()を使用してこれを実行できますが、これが唯一の方法である場合、iconv()の方がはるかに簡単であり、POSIXの一部です。

Unicodeコードポイントの文字列は、nullで終了する_uint32_t[]_のようなものか、C1xがある場合は_char32_t_の配列になります。その配列のサイズ(つまり、バイト数ではなく要素の数)は、コードポイント(およびターミネーター)の数であり、非常に適切な開始点になります。

ただし、「印刷可能な文字」の概念はかなり複雑であり、コードポイントではなくgraphemesをカウントすることをお勧めします。たとえば、aにアクセント_^_は、2つのUnicodeコードポイント、またはレガシーコードポイントの組み合わせ_â_として表すことができます。どちらも有効であり、Unicode標準では同等に処理する必要があります。文字列を明確なバージョンに変換する「正規化」と呼ばれるプロセスがありますが、単一のコードポイントとして表現できない多くの書記素があり、一般にこれを理解して書記素をカウントする適切なライブラリを回避する方法はありません。

とはいえ、スクリプトの複雑さや、スクリプトをどの程度徹底的に処理するかを決めるのはあなた次第です。 Unicodeコードポイントへの変換は必須であり、それを超えるものはすべてあなたの裁量に任されています。

ICU必要な場合は、遠慮なく質問してください。ただし、最初は非常に単純なiconv()を自由に試してください。

8
Kerrek SB

現実の世界では、theString[3]=foo;は意味のある操作ではありません。文字列の特定の位置にある文字を別の文字に置き換える必要があるのはなぜですか?この操作が意味を持つ自然言語テキスト処理タスクは確かにありません。

文字を数えることも意味がありそうにありません。 「á」にはいくつの文字(「文字」の概念)がありますか? 「あ́」はいかがですか? 「གི」はいかがですか?何らかのテキスト編集を実装するためにこの情報が必要な場合は、これらの難しい質問に対処するか、既存のライブラリ/ guiツールキットを使用する必要があります。世界のスクリプトと言語の専門家で、もっと上手にできると思わない限り、後者をお勧めします。

他のすべての目的のために、strlenは、実際に役立つ情報を正確に伝えます。つまり、文字列がどれだけのストレージ容量を必要とするかです。これは、文字列の結合と分離に必要なものです。文字列を組み合わせるか、特定の区切り文字でそれらを分離するだけの場合は、snprintf(または、主張する場合はstrcat)とstrstrで十分です。

大文字、改行などのより高いレベルの自然言語テキスト操作、または複数形化、時制の変更などのより高いレベルの操作を実行する場合は、ICUまたはそれぞれ、はるかに高レベルで言語学的に対応している(そして、使用している言語に固有の)何か。

繰り返しになりますが、ほとんどのプログラムはこの種のことをまったく使用せず、自然言語を考慮せずにテキストをアセンブルおよび解析するだけで済みます。

2
R..
while (s[i]) {
    if ((s[i] & 0xC0) != 0x80)
        j++;
    i++;
}
return (j);

これは、UTF-8文字列の文字をカウントします...(この記事で見つかりました: さらに高速なUTF-8文字カウント

しかし、私はまだスライスと連結に困っていますか?!?

1
jsj

一般に、Unicode文字には別のデータ型を使用する必要があります。

たとえば、ワイド文字データ型を使用できます

wchar_t theString[] = L"你们好āa";

文字列がワイド文字で構成されていることを示すL修飾子に注意してください。

その文字列の長さは、wcslenのように動作するstrlen関数を使用して計算できます。

1
abahgat

上記の答えから明確ではないことの1つは、なぜそれが単純ではないかということです。各文字は何らかの方法でエンコードされます。たとえば、UTF-8である必要はありません。各文字には、アクセントの組み合わせを処理するさまざまな方法など、複数のエンコードが含まれる場合があります。ルールは非常に複雑で、エンコーディングによって異なります(たとえば、utf-8とutf-16)。

この質問にはセキュリティに関する大きな懸念があるため、これを正しく行うことが不可欠です。 OS提供のライブラリまたは有名なサードパーティライブラリを使用して、Unicode文字列を操作します。自分で転がしてはいけません。

1
Steve Dispensa

私は何年も前に同様の実装を行いました。しかし、私にはコードがありません。

各ユニコード文字の最初のバイトは、ユニコード文字を構成するためにそれに続くバイト数を示します。最初のバイトに基づいて、各Unicode文字の長さを決定できます。

私はそれが良いUTF8ライブラリだと思います。 ここにリンクの説明を入力

0
Senthil