0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【C言語】glib2.0を使ってみた〜char配列に対する文字列処理〜

Last updated at Posted at 2022-04-04

はじめに

C言語でアプリケーションを作成するとき、入出力の基本的な処理からコーディングするのは面倒ですし、バグの温床のなりえます。
良い解決策がないか調べたところ、glib2.0というライブラリが

  • インストール・利用の容易さ
  • 多機能
  • ライセンス

という観点で、良さそうであると思いました。
一方、ネット上にあまり情報がなく、公式ドキュメントもわかりにくいということもあり、本記事にて基本的な使い方を紹介したいと思います。

今回は、char配列に対する文字列処理について記載します。

動作環境

本記事は、下記の環境で動作を確認しています。

  • Ubuntu18.04
  • gcc

インストール手順

下記コマンドでglib2.0一式をインストールすることができます。
Ubuntu18.04の場合、バージョン2.56.4が入るようです。

sudo apt install libglib2.0-dev

機能一覧

glibに含まれる文字列処理を、処理の内容に応じて私なりに分類し、一覧化すると下記のようになります。

[文字列検索]

関数名 説明
g_strrstr 入力文字列に対し、指定した文字列が含まれるか検索する。
g_strrstr_len g_strrstrに検索範囲の指定が追加されたもの。
g_strstr_len g_strrstr_lenと比べ、対象文字列および検索文字列がNULL終端されている必要がない。
g_str_has_prefix 入力文字列の先頭が、指定した文字列と一致するか判定する。
g_str_has_suffix 入力文字列の末尾が、指定した文字列と一致するか判定する。

[文字列複製]

関数名 説明
g_stpcpy 文字列を指定したコピー先にコピーする。
g_strlcpy 標準のstrlcpyと同機能。
NULL終端された文字列を指定したコピー先にコピーする。
g_strlcat 標準のstrlcatと同機能。
NULL終端された文字列を指定したコピー先の末尾に連結する。
g_strdup_printf printf形式で文字列を読み込んで新しい文字列を生成する。
g_strdup_vprintf g_strdup_printfと同機能だが、va_listを受けることができる。

[文字列変換]

関数名 説明
g_strstrip 入力文字列の先頭と末尾のwhitespaceを削除する。
g_strchug 入力文字列の先頭のwhitespaceを削除する。
g_strchomp 入力文字列の末尾のwhitespaceを削除する。
g_strdelimit 入力文字列に含まれる指定した区切り文字を、新たに指定した区切り文字に置換する。
g_strescape 入力文字列に含まれる制御文字\b, \f, \n ,\r, \t, "をエスケープする。
g_strcompress 入力文字列に含まれるエスケープされた制御文字を通常の制御文字に置換する。
g_strcanon 入力文字列に含まれる指定した文字以外を、特定の文字列に置換する。
g_strsplit 入力文字列を指定した区切り文字列で分割する。
g_strsplit_set 入力文字列を指定した複数種の区切り文字で分割する。
g_strreverse 入力文字列を反転させ、末尾から先頭に文字列が並ぶように変換する。
g_strconcat 複数個の文字列を連結する。
g_strjoin 複数個の文字列を指定した区切り文字で連結する。
g_strvjoin g_strjoinと同機能だが、va_listを入力とする。
g_ascii_strup 入力文字列に含まれる英字をすべて大文字にする。
g_ascii_strdown 入力文字列に含まれる英字をすべて小文字にする。

[文字列比較]

関数名 説明
g_strcmp0 2つの文字列を比較する。
g_ascii_strcasecmp 2つの文字列を、大文字小文字の区別なく比較する。
g_ascii_strncasecmp g_ascii_strcasecmpと同機能だが、比較する長さ制限を指定する。
g_strv_contains 文字列の配列の中に、指定した文字列と一致する文字列が存在するか判定する。

[1文字処理]

関数名 説明
g_ascii_tolower 英字のchar文字を小文字に変換する。
g_ascii_toupper 英字のchar文字を大文字に変換する。
g_ascii_digit_value 数字のchar文字を数値に変換する。
g_ascii_xdigit_value 16進数値のchar文字を数値に変換する。

[数値文字列変換]

関数名 説明
g_ascii_strtoll 文字列をint64に変換する。
g_ascii_strtoull 文字列をuint64に変換する。
g_ascii_strtod 文字列をdoubleに変換する。
g_ascii_dtostr doubleを文字列に変換する。
g_ascii_formatd g_ascii_dtostrと比べ、フォーマット指定が可能。

使用例

共通

glibを用いた文字列処理は、下記のようにglib.hをインクルードすることで実現できます。個別の関数について説明している内容を//処理の中身に記載するかたちになります。

main.c
#include <glib.h>

int main(int argc, char* argv[])
{
    //処理の中身

    return 0;
}

下記のコマンドでコンパイルを行うことができます。

gcc main.c -o main `pkg-config --cflags --libs glib-2.0`

[文字列検索]

g_strrstr, g_strrstr_len, (g_strstr_len)

g_strrstr, g_strrstr_lenの使用例を示します。
これらの関数を使うことで、文字列の中に特定の文字列が含まれるかを判定し、その先頭ポインタを得ることができます。

main.c
    gchar *p;
    gchar *test_str1 = "Tokyo, Shinagawa, Shin-Yokohama,";

    //検索範囲指定なし
    //test_strの中から'Shin'を検索する
    p = g_strrstr(test_str1, "Shin");

    if (NULL != p) {
        g_print("target str found. %s\n", p);
    } 

    //検索範囲指定あり
    p = g_strrstr_len(test_str1, strlen(test_str1), "Shinagawa");

    if (NULL != p) {
        g_print("target str found. %s\n", p);
    }

このコードの実行結果は下記です。

./main
target str found. Shin-Yokohama,
target str found. Shinagawa, Shin-Yokohama,

g_str_has_prefix, g_str_has_suffix

g_str_has_prefix, g_str_has_suffixの使用例を示します。
これらの関数を使うことで、文字列の先頭または末尾に特定の文字列が含まれるかを判定することができます。

main.c
    gchar *test_str2 = "test_20220401_090000.csv";

    if (g_str_has_prefix(test_str2, "test")) {
        g_print("test_str2 has 'test' prefix.\n");
    }

    if (g_str_has_suffix(test_str2, ".csv")) {
        g_print("test_str2 has '.csv' suffix.\n");
    }

このコードの実行結果は下記です。

 ./main
test_str2 has 'test' prefix.
test_str2 has '.csv' suffix.

[文字列複製]

g_stpcpy

g_stpcpyの使用例を示します。
この関数を使うことで、char型バッファに指定した文字列をコピーすることができます。コピー後はNULL終端されます。g_spcpyの戻り値はこのNULL終端のアドレスになるため、pに対し繰り返しg_stpcpyをすることで、文字列を追記することができます。

main.c
    gchar *p;
    char out_buf[256];

    //戻り値pはコピー後のNULL終端点のアドレスなので、
    //pに対し繰り返しg_stpcpyをすることで、
    //連結のような動作をすることができる
    p = g_stpcpy(out_buf, "value1,");
    p = g_stpcpy(p, "value2,");
    p = g_stpcpy(p, "value3,");

    g_print("out_buf = %s\n", out_buf);

このコードの実行結果は下記になります。

./main
out_buf = value1,value2,value3,

g_strlcpy

g_strlcpyの使用例を示します。
この関数を使うことで、char型バッファに指定した文字列をコピーすることができます。コピー後のdestの長さを指定することでメモリ安全なコピーを行います。

main.c
    gchar *src = "123456789";
    char dest1[256];
    char dest2[5];
    gsize size;

    size = g_strlcpy(dest1, src, sizeof(dest1));
    g_print("dest1=%s, size=%lu\n", dest1, size);

    size = g_strlcpy(dest2, src, sizeof(dest2));
    g_print("dest2=%s, size=%lu\n", dest2, size);

このコードの実行結果は下記になります。
dest2は5byteのchar型バッファに123456789を書き込もうとしますが、5byte目はNULL終端文字となるため、1234が書き込まれます。なお、g_strlcpyの戻り値は第三引数の長さによらずsrcに指定した文字列の長さになるようです。

./main
dest1=123456789, size=9
dest2=1234, size=9

g_strlcat

g_strlcpyの使用例を示します。
この関数を使うことで、char型バッファに指定した文字列を追記することができます。コピー後のdestの長さを指定することでメモリ安全な追記を行います。

main.c
    gchar *src = "123456789";
    char dest1[256];
    char dest2[5];
    gsize size;

    //dest1, dest2は1文字目に'a'を代入して初期化しておく。
    g_stpcpy(dest1, "a");
    g_stpcpy(dest2, "a");

    size = g_strlcat(dest1, src, sizeof(dest1));
    g_print("dest1=%s, size=%lu\n", dest1, size);

    size = g_strlcat(dest2, src, sizeof(dest2));
    g_print("dest2=%s, size=%lu\n", dest2, size);

このコードの実行結果は下記になります。
dest2dest2[0]='a'の状態の5byteのchar型バッファに123456789を追記しますが、5byte目はNULL終端文字となるため、123が追記され、結果的にはa123となります。なお、g_strlcatの戻り値は公式ドキュメントにはMIN (dest_size, strlen (original dest)) + strlen (src)とありますが、実行結果を見るとstrlen (original dest)) + strlen (src)になっています。

./main
dest1=a123456789, size=10
dest2=a123, size=10

g_strdup_printf, (g_strdup_vprintf)

g_strdup_printfの使用例を示します。
この関数を使うことで、フォーマット指定子を用いて文字列を生成することができます。新たにメモリ確保した文字列のポインタが戻り値となるため、使用後は開放する必要があります。

main.c
    gchar *out;

    out = g_strdup_printf("%s, %f, %d", "abc", 3.14, 100);
    g_print("out=%s\n", out);

    //開放処理
    g_free(out);

このコードの実行結果は下記になります。

./main
out=abc, 3.140000, 100

[文字列変換]

g_strstrip, g_strchug, g_strchomp

g_strstrip, g_strchug, g_strchompの使用例を示します。
これらの関数を使うことで、指定した文字列の前後、前のみ、後ろのみから不要な文字列をストリップすることができます。

main.c
    char test1[256];
    gchar *out;

    g_stpcpy(test1, "  \t  test 123  \t  \n");
    out = g_strstrip(test1);
    g_print("test1=%s(%p)\n", test1, test1);
    g_print("out  =%s(%p)\n", out, out);

    g_stpcpy(test1, "  \t  test 123  \t  \n");
    out = g_strchug(test1);
    g_print("test1=%s(%p)\n", test1, test1);
    g_print("out  =%s(%p)\n", out, out);

    g_stpcpy(test1, "  \t  test 123  \t  \n");
    out = g_strchomp(test1);
    g_print("test1=%s(%p)\n", test1, test1);
    g_print("out  =%s(%p)\n", out, out);

このコードの実行結果は下記になります。これらの関数の戻り値としてchar型のポインタが得られますが、引数に与えたchar型バッファの先頭ポインタと同じになるようです。

./main
test1=test 123(0x7ffdaf83e5d0)
out  =test 123(0x7ffdaf83e5d0)
test1=test 123            
(0x7ffdaf83e5d0)
out  =test 123            
(0x7ffdaf83e5d0)
test1=            test 123(0x7ffdaf83e5d0)
out  =            test 123(0x7ffdaf83e5d0)

g_strdelimit

g_strdelimitの使用例を示します。
この関数を使うことで、指定した文字列に含まれる複数種の区切り文字を特定の区切り文字に置換することができます。第二引数で置換したい区切り文字を列挙し、第三引数に置換後の文字を指定します。

main.c
    char test1[256];
    gchar *out;

    g_stpcpy(test1, "value1,value2 value3\tvalue4-value5");
    out = g_strdelimit(test1, ", \t-", ':');
    g_print("test1=%s(%p)\n", test1, test1);
    g_print("out  =%s(%p)\n", out, out);

このコードの実行結果は下記になります。g_strdelimitの戻り値としてchar型のポインタが得られますが、第一引数に与えたchar型バッファの先頭ポインタと同じになるようです。

./main
test1=value1:value2:value3:value4:value5(0x7ffd4e7254c0)
out  =value1:value2:value3:value4:value5(0x7ffd4e7254c0)

g_strescape

g_strescapeの使用例を示します。
この関数を使うことで、指定した文字列に含まれる制御文字をエスケープすることができます。
第二引数でこの処理で除外したい制御文字を指定することもできます。
この関数は新しくメモリ確保した文字列のポインタを戻り値として返すので、使用後は開放処理を行う必要があります。

main.c
    gchar *src = "value1\tvalue2\n\"value3\"";
    gchar *out;

    out = g_strescape(src, NULL);
    g_print("out=%s\n", out);

    //開放処理
    g_free(out);

このコードの実行結果は下記になります。

./main
out=value1\tvalue2\n\"value3\"

g_strcompress

g_strcompressの使用例を示します。
この関数を使うことで、指定した文字列に含まれるエスケープされた制御文字を制御文字にすることができます。
この関数は新しくメモリ確保した文字列のポインタを戻り値として返すので、使用後は開放処理を行う必要があります。

main.c
    gchar *src = "value1\\tvalue2\\nvalue3";
    gchar *out;

    out = g_strcompress(src);
    g_print("out=%s\n", out);

    //開放処理
    g_free(out);

このコードの実行結果は下記になります。

./main
out=value1      value2
value3

g_strcanon

g_strcanon使用例を示します。
この関数を使うことで、指定した文字列に含まれる予め指定した文字種を除く文字列を、全て指定した文字列に置換することができます。

main.c
    gchar test1[256];
    gchar *out;

    g_stpcpy(test1, ";sdfkj:aa11aa:fddw\tf");

    out = g_strcanon(test1, "a1", '_');
    g_print("test1=%s(%p)\n", test1, test1);
    g_print("out  =%s(%p)\n", out, out);

このコードの実行結果は下記になります。g_strcanonの戻り値としてchar型のポインタが得られますが、第一引数に与えたchar型バッファの先頭ポインタと同じになるようです。

./main
test1=_______aa11aa_______(0x7ffdfe4a62b0)
out  =_______aa11aa_______(0x7ffdfe4a62b0)

g_strsplit

g_strsplitの使用例を示します。
この関数を使うことで、指定した文字列を特定の文字列で分割することができます。
分割された文字列の配列の先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。

main.c
    gchar *src = "value1<->value2<-><->value3";
    gchar **array;
    int i;

    array = g_strsplit(src, "<->", 0);

    for (i = 0; i < g_strv_length(array); i++) {
        g_print("column[%d]=%s\n", i, array[i]);
    }

    //開放処理
    g_strfreev(array);

このコードの実行結果は下記になります。
区切り文字列が連続で並んだ場合は、空の要素が存在したとみなされます。したがって、column[2]には空の文字列が割り当てられています。

./main
column[0]=value1
column[1]=value2
column[2]=
column[3]=value3

g_strsplit_set

g_strsplit_setの使用例を示します。
この関数を使うことで、指定した文字列を複数の特定文字で分割することができます。
第二引数のchar型バッファに、区切り文字とする文字種を列挙します。
分割された文字列の配列の先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。

main.c
    gchar *src = "value1 value2\tvalue3,";
    gchar **array;
    int i;

    array = g_strsplit_set(src, ",\t ", 0);

    for (i = 0; i < g_strv_length(array); i++) {
        g_print("column[%d]=%s\n", i, array[i]);
    }

    //開放処理
    g_strfreev(array);

このコードの実行結果は下記になります。
区切り文字列が連続で並んだ場合は、空の要素が存在したとみなされます。また、最後が区切り文字で終わった場合は空の要素が存在したとみなされます。したがって、column[3]には空の文字列が割り当てられています。

./main
column[0]=value1
column[1]=value2
column[2]=value3
column[3]=

g_strreverse

g_strreverseの使用例を示します。
この関数を使うことで、指定した文字列を反転させることができます。

main.c
    char test1[256];
    gchar *out;

    g_stpcpy(test1, "abcdef");

    out = g_strreverse(test1);
    g_print("test1=%s(%p)\n", test1, test1);
    g_print("out  =%s(%p)\n", out, out);

このコードの実行結果は下記になります。g_strreverseの戻り値としてchar型のポインタが得られますが、第一引数に与えたchar型バッファの先頭ポインタと同じになるようです。

./main
test1=fedcba(0x7fffd3dd8630)
out  =fedcba(0x7fffd3dd8630)

g_strconcat

g_strconcatの使用例を示します。
この関数を使うことで、複数の文字列を連結させることができます。引数の一番最後にはNULLを指定する必要があります。連結された文字列は新しくメモリ確保された領域に生成され、その先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。

main.c
    gchar *out;

    //引数に指定する複数文字列の最後要素はNULLが必要
    out = g_strconcat("value1", "value2", "value3", NULL);
    g_print("out=%s\n", out);

    //開放処理
    g_free(out);

このコードの実行結果は下記になります。

./main
out=value1value2value3

g_strjoin, (g_strvjoin)

g_strjoinの使用例を示します。
この関数を使うことで、複数の文字列を指定した区切り文字を挟んで連結させることができます。第二引数以降の引数の一番最後にはNULLを指定する必要があります。連結された文字列は新しくメモリ確保された領域に生成され、その先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。

main.c
    //第二引数以降に指定する複数文字列の最後要素はNULLが必要
    out = g_strjoin("<->", "value1", "value2", "value3", NULL);
    g_print("out=%s\n", out);

    //開放処理
    g_free(out);

このコードの実行結果は下記になります。

./main
out=value1<->value2<->value3

g_ascii_strup, g_ascii_strdown

g_ascii_strup, g_ascii_strdownの使用例を示します。
これらの関数を使うことで、指定した文字列を大文字/小文字に変換することができます。
第二引数は文字列の長さを指定します。第一引数に与えた文字列がNULL終端されていれば、長さは-1でもよいようです。
変換された文字列は新しくメモリ確保された領域に生成され、その先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。

main.c
    gchar *src = "abcDEF123";
    gchar *out;

    //第二引数は、第一引数で指定した文字列の長さを指定
    //第一引数がNULL終端されていれば、-1でOK
    out = g_ascii_strup(src, -1);
    g_print("out=%s\n", out);

    //開放処理
    g_free(out);

    //第二引数は、第一引数で指定した文字列の長さを指定
    //第一引数がNULL終端されていれば、-1でOK
    out = g_ascii_strdown(src, -1);
    g_print("out=%s\n", out);

    //開放処理
    g_free(out);

このコードの実行結果は下記になります。

./main
out=ABCDEF123
out=abcdef123

[文字列比較]

g_strcmp0, g_ascii_strcasecmp, g_ascii_strncasecmp

g_strcmp0, g_ascii_strcasecmp, g_ascii_strncasecmpの使用例を示します。
これらの関数を使うことで、指定した2つの文字列同士を比較することができます。g_strcmp0は大文字小文字を区別しますが、g_ascii_strcasecmp, g_ascii_strncasecmpは大文字小文字を区別しません。

main.c
    gchar *src1 = "abcDEF123";
    gchar *src2 = "ABCdef123";

    if (0 == g_strcmp0(src1, src2)) {
        g_print("src1 and src2 is same.\n");
    } else {
        g_print("src1 and src2 is different.\n");
    }

    if (0 == g_ascii_strcasecmp(src1, src2)) {
        g_print("src1 and src2 is same.\n");
    } else {
        g_print("src1 and src2 is different.\n");
    }

    if (0 == g_ascii_strncasecmp(src1, src2, 6)) {
        g_print("src1 and src2 is same.\n");
    } else {
        g_print("src1 and src2 is different.\n");
    }

このコードの実行結果は下記になります。

./main
src1 and src2 is different.
src1 and src2 is same.
src1 and src2 is same.

g_strv_contains

g_strv_containsの使用例を示します。
この関数を使うことで、文字列の配列に指定した文字列に一致するものが含まれるかどうか判定することができます。

main.c
    gchar *src1 = "Tokyo";
    gchar *src2 = "Hakata";
    const gchar *const list[] = {
        "Shin-Osaka",
        "Shin-Kobe",
        "Okayama",
        "Hiroshima",
        "Kokura",
        "Hakata",
        NULL
    };

    if (g_strv_contains(list, src1)) {
        g_print("list contains src1.\n");
    }
    if (g_strv_contains(list, src2)) {
        g_print("list contains src2.\n");
    }

このコードの実行結果は下記になります。

./main
list contains src2.

[1文字処理]

g_ascii_tolower, g_ascii_toupper

g_ascii_tolower, g_ascii_toupperの使用例を示します。
これらの関数を使うことで、char文字の大文字/小文字を相互に変換することができます。

main.c
    gchar test1 = g_ascii_tolower('A');
    gchar test2 = g_ascii_toupper('b');

    g_print("test1=%c, test2=%c\n", test1, test2);

このコードの実行結果は下記になります。

./main
test1=a, test2=B

g_ascii_digit_value, g_ascii_xdigit_value

g_ascii_digit_value, g_ascii_xdigit_valueの使用例を示します。
これらの関数を使うことで、char文字からchar文字が示す数値を得ることができます。なお、指定されたchar文字が変換できなかった場合は-1を返します。

main.c
    int num1, num2, num3, num4, num5;

    num1 = g_ascii_digit_value('9');
    num2 = g_ascii_digit_value('f');
    num3 = g_ascii_xdigit_value('9');
    num4 = g_ascii_xdigit_value('f');
    num5 = g_ascii_xdigit_value('k');

    g_print("num1=%d, num2=%d, num3=%d, num4=%d, num5=%d\n", num1, num2, num3, num4, num5);

このコードの実行結果は下記になります。

./main
num1=9, num2=-1, num3=9, num4=15, num5=-1

[数値文字列変換]

g_ascii_strtoll, g_ascii_strtoull, g_ascii_strtod

g_ascii_strtoll, g_ascii_strtoull, g_ascii_strtodの使用例を示します。
これらの関数を使うことで、文字列の先頭に数値として解釈可能な文字列がある場合にその数値を得ることができます。また、g_ascii_strtoll, g_ascii_strtoullは第三引数にて進数を指定することができます。

main.c
    gint64 value1;
    guint64 value2;
    gdouble value3;

    value1 = g_ascii_strtoll("-10 u", NULL, 10);
    value2 = g_ascii_strtoull("FF y", NULL, 16);
    value3 = g_ascii_strtod("3.14", NULL);

    g_print("value1=%ld, value2=%lu, value3=%f\n", value1, value2, value3);

このコードの実行結果は下記になります。

./main
value1=-10, value2=255, value3=3.140000

g_ascii_dtostr, g_ascii_formatd

g_ascii_dtostr, g_ascii_formatdの使用例を示します。
これらの関数を使うことで、小数値を文字列に変換することができます。g_ascii_formatdは第三引数に変換時のフォーマットを指定することができます。

main.c
    char buf1[256];
    char buf2[256];
    gchar *out1, *out2;

    out1 = g_ascii_dtostr(buf1, 256, 3.14);
    out2 = g_ascii_formatd(buf2, 256, "%.01f", 3.14);

    g_print("buf1=%s(%p)\n", buf1, buf1);
    g_print("out1=%s(%p)\n", out1, out1);
    g_print("buf2=%s(%p)\n", buf2, buf2);
    g_print("out2=%s(%p)\n", out2, out2);

このコードの実行結果は下記になります。これらの関数の戻り値としてchar型のポインタが得られますが、第一引数に与えたchar型バッファの先頭ポインタと同じになるようです。

./main
buf1=3.1400000000000001(0x7ffe84380b50)
out1=3.1400000000000001(0x7ffe84380b50)
buf2=3.1(0x7ffe84380c50)
out2=3.1(0x7ffe84380c50)

あとがき

C言語は文字列処理のコーディングに向いておらず、入出力の基本的な処理を作るのは骨が折れます。glib2.0を活用することで、本質ではない部分に割く時間を削減し、本当に重要なことに注力できると思います。

参考文献

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?