LoginSignup
6
9

More than 5 years have passed since last update.

標準のマルチバイト関数を使って UTF-8 の文字列から1文字ずつ取り出す

Last updated at Posted at 2015-01-19

char 型でヌル終端の文字列でかつ不正なバイト列が含まれておらず、かつロケールを決め打ちにしてよいのであれば、1文字ずつ取り出すのに mblenmbrlenmbtowcmbrtowcmbrtoc32 を使うことができます。これらの関数の戻り値は次の文字のバイト数です。

mblenmbrlen の使いわけは状態変数を自分で管理する必要があるかどうかです。mbrlen の第3引数に内部の状態をあらわす変数を指定します。

mbtowcmblen の違いはコードポイントが必要であるかどうかです。mbtowc の第1引数にコードポイントが参照渡しされます。第1引数の型は wchar_t なので、OS によって結果が変わります。筆者は wchar_t のサイズが32ビットである Linux と Mac OS X で確認していますが、16ビットの Windows では未検証です。

mbrtowcmbtowc の使いわけは状態変数を自分で管理する必要があるかどうかです。mbrtowc の第4引数で内部の状態をあらわす変数を指定します。

mbrtoc32 は C11 で追加された関数で、mbrtowc と比べて wchar_t の代わりに char32_t が使われます。2015年1月の時点では GCC で利用可能です。

mbrtoc16 も同時に C11 で導入されましたが、U+10000 から U+10FFFF の範囲の文字に遭遇すると負の値を返すので、使い道はかぎられます。

複数の種類の OS で利用することを想定するのであれば ICU を検討する必要があるでしょう。こちらの記事をご参照ください。

テストケース

次の文字列を試すことにします。「?」(U+20BB7) は基本多言語面外の文字 (U+10000 から U+10FFFF) に対応しているかどうかを確かめるために使います。

#include <stdio.h>
#include <locale.h>
#include <stdlib.h>

void print_each_char(const char*);

int main(void)
{
    //const char* str = "\U00020BB7\u91CE\u5BB6";
    const char* str = "?野家";
    const char* str2 = "?野\x80家";

    setlocale(LC_CTYPE, "ja_JP.UTF-8");
    print_each_char(str);
    puts("");
    print_each_char(str2);

    return 0;
}

mblen

void print_each_char(const char* str)
{
    char size;
    mblen(NULL, 0);

    while (*str) {
        size = mblen(str, MB_CUR_MAX);

        if (size < 1) {
            puts("不正なバイト列に遭遇したので、処理を中断しました。");
            break;
        }

        printf("%.*s\n", size, str);
        str += size;
    }
}

mbrlen

mbstate_t を使うために wchar.h のインクルードが必要になります。

#include <wchar.h>

void print_each_char(const char* str)
{
    char size;
    mbstate_t state;

    while (*str) {
        size = mbrlen(str, MB_CUR_MAX, &state);

        if (size < 1) {
            puts("不正なバイト列に遭遇したので、処理を中断しました。");
            break;
        }

        printf("%.*s\n", size, str);
        str += size;
    }
}

mbtowc

#include <wchar.h>

void print_each_char(const char* str)
{
    char size;
    wchar_t cp;
    mbtowc(&cp, NULL, 0);

    while (*str) {
        size = mbtowc(&cp, str, MB_CUR_MAX);

        if (size < 1) {
            puts("不正なバイト列に遭遇したので、処理を中断しました。");
            break;
        }

        printf("U+%X %.*s\n", cp, size, str);
        str += size;
    }
}

mbrtowc

#include <wchar.h>

void print_each_char(const char* str)
{
    char size;
    wchar_t cp;
    mbstate_t status;

    mbsinit(&status);

    while (*str) {
        size = mbrtowc(&cp, str, MB_CUR_MAX, &status);

        if (size < 1) {
            puts("不正なバイト列に遭遇したので、処理を中断しました。");
            break;
        }

        printf("U+%X %.*s\n", cp, size, str);
        str += size;
    }
}

mbrtoc32

C11 で導入された mbrtoc32 は GCC で利用可能である。

#include <wchar.h>
#include <uchar.h>

void print_each_char(const char* str)
{
    char size;
    char32_t cp;
    mbstate_t status;

    mbsinit(&status);

    while (*str) {
        size = mbrtoc32(&cp, str, MB_CUR_MAX, &status);

        if (size < 1) {
            puts("不正なバイト列に遭遇したので、処理を中断しました。");
            break;
        }

        printf("U+%X %.*s\n", cp, size, str);
        str += size;
    }
}

文字数を求める

文字数を求めるだけであれば mbstowcs を使うことができる。ただし mbstowcs は OS ごとの wchar_t の違いを考慮しなければならない。

mbstowcs

#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <stdlib.h>

int main(void)
{
    const char* str = "?野家";
    size_t size = strlen(str);
    wchar_t wchar[1000];

    setlocale(LC_CTYPE, "ja_JP.UTF-8");
    printf("%zu\n", mbstowcs(wchar, str, size));

    return 0;
}
6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9