はじめに
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
をインクルードすることで実現できます。個別の関数について説明している内容を//処理の中身
に記載するかたちになります。
#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の使用例を示します。
これらの関数を使うことで、文字列の中に特定の文字列が含まれるかを判定し、その先頭ポインタを得ることができます。
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の使用例を示します。
これらの関数を使うことで、文字列の先頭または末尾に特定の文字列が含まれるかを判定することができます。
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をすることで、文字列を追記することができます。
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
の長さを指定することでメモリ安全なコピーを行います。
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
の長さを指定することでメモリ安全な追記を行います。
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);
このコードの実行結果は下記になります。
dest2
はdest2[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の使用例を示します。
この関数を使うことで、フォーマット指定子を用いて文字列を生成することができます。新たにメモリ確保した文字列のポインタが戻り値となるため、使用後は開放する必要があります。
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の使用例を示します。
これらの関数を使うことで、指定した文字列の前後、前のみ、後ろのみから不要な文字列をストリップすることができます。
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の使用例を示します。
この関数を使うことで、指定した文字列に含まれる複数種の区切り文字を特定の区切り文字に置換することができます。第二引数で置換したい区切り文字を列挙し、第三引数に置換後の文字を指定します。
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の使用例を示します。
この関数を使うことで、指定した文字列に含まれる制御文字をエスケープすることができます。
第二引数でこの処理で除外したい制御文字を指定することもできます。
この関数は新しくメモリ確保した文字列のポインタを戻り値として返すので、使用後は開放処理を行う必要があります。
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の使用例を示します。
この関数を使うことで、指定した文字列に含まれるエスケープされた制御文字を制御文字にすることができます。
この関数は新しくメモリ確保した文字列のポインタを戻り値として返すので、使用後は開放処理を行う必要があります。
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使用例を示します。
この関数を使うことで、指定した文字列に含まれる予め指定した文字種を除く文字列を、全て指定した文字列に置換することができます。
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の使用例を示します。
この関数を使うことで、指定した文字列を特定の文字列で分割することができます。
分割された文字列の配列の先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。
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型バッファに、区切り文字とする文字種を列挙します。
分割された文字列の配列の先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。
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の使用例を示します。
この関数を使うことで、指定した文字列を反転させることができます。
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を指定する必要があります。連結された文字列は新しくメモリ確保された領域に生成され、その先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。
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を指定する必要があります。連結された文字列は新しくメモリ確保された領域に生成され、その先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。
//第二引数以降に指定する複数文字列の最後要素は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でもよいようです。
変換された文字列は新しくメモリ確保された領域に生成され、その先頭アドレスが関数の戻り値として得られるので、使用後は開放処理を行う必要があります。
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は大文字小文字を区別しません。
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の使用例を示します。
この関数を使うことで、文字列の配列に指定した文字列に一致するものが含まれるかどうか判定することができます。
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文字の大文字/小文字を相互に変換することができます。
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を返します。
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は第三引数にて進数を指定することができます。
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は第三引数に変換時のフォーマットを指定することができます。
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を活用することで、本質ではない部分に割く時間を削減し、本当に重要なことに注力できると思います。