C言語で文字列を扱う際に覚えておくと便利なのが文字列操作関数です。
このページでは、よく利用する文字列操作関数と、その使用方法について解説していきます。
私も毎回ググってるよ…
まあ、なので、覚えなくても良いようにこんなまとめページを作ったんだ!
私も文字列操作関数は毎回調べながら使用しています。
おそらくそういう方も多いと思うので、よく使う文字列操作関数をこのページにまとめました!
是非文字列操作関数やその使い方に迷ったらこのページを参考にしてください。
Contents
文字列の長さを取得する
文字列の長さ(文字数)を取得する時に使用する関数は「strlen
関数」です。
strlen
関数
strlen
関数は文字列の長さを取得する関数になります。
strlen
関数を実行することで、例えば文字列 "aiueo"
の文字列の長さ 5
を取得することができます。
strlen
関数の定義ファイル、関数定義は下記の通りです。
#include <string.h>
size_t strlen(const char* str);
strlen
関数の引数
strlen
関数の引数には str
、文字列へのアドレスを指定します。より具体的には、文字列が格納された配列やメモリのアドレスを指定します。
strlen
関数の返却値
strlen
関数の返却値は引数 str
の文字列の長さになります。
この文字列の長さにはヌル文字(\0
)の分はカウントされません。
strlen
関数の詳細
では、この strlen
関数が返却する「文字列の長さ」とは具体的にはどのような値になるでしょうか?
これは、引数で指定するアドレス str
からアドレスを1バイトずつ増やしていき、次にヌル文字(\0
)が見つかるまでのバイト数になります。
バイト数ですので、日本語などマルチバイト文字を格納した文字列に対して strlen
関数を実行すると文字数と一致しないので注意しましょう。
ただ、特にC言語習いたての方は1バイト文字(英数字)を利用することが多いので、まずは「strlen
関数は文字数を取得する関数」と覚えておいて差し支えないと思います。
また、ヌル文字(\0
)が存在することを期待して動作しますので、ヌル文字(\0
)が含まれないデータへのアドレスを strlen
関数に渡してしまうと動作がおかしくなります。
\0
)が含まれないとどうなるの?おそらくだけど、配列のサイズに関係なく、次にヌル文字(\0
)があるアドレスまでヌル文字の探索が行われるよ
この場合、配列のサイズを超えてデータの参照が行われるのでバッファオーバーフローになる可能性もある
スポンサーリンク
strlen
関数の使用例
strlen
関数を使用して文字列の長さを取得するプログラムの例は下記になります。
#include <stdio.h>
#include <string.h>
int main(void){
char *str1 = "hitotsume";
size_t len1;
/* 文字列の長さ取得 */
len1 = strlen(str1);
/* 表示 */
printf("str1 = %s, len1 = %lu\n", str1, len1);
return 0;
}
実行結果は下のようになります。
str1 = hitotsume, len1 = 9
文字列をコピーする
文字列をコピーする時に利用する関数は「strcpy
関数」です。
strcpy
関数
strcpy
関数は文字列をコピーする関数です。
strcpy
関数の定義ファイル、関数定義は下記の通りです。
#include <string.h>
char* strcpy(char* str1, const char* str2);
strcpy
関数の引数
第1引数の str1
には文字列をコピーする “先” の配列やメモリのアドレスを指定し、第2引数の str2
には文字列をコピーする “元” の配列やメモリのアドレスを指定します。
strcpy
関数の返却値
返却値は文字列をコピーした “先” の先頭アドレスになります。つまり、str1
と一致します。
strcpy
関数の詳細
strcpy
を実行することで、str2
のアドレスからヌル文字(\0
)までの文字列が str1
のアドレスの配列やメモリにコピーされます。
\0
)もコピーされるの?ヌル文字(\0
)も含めてコピーされると考えて良いです。
ただし、コピー先のアドレスである str1
の指す配列やメモリは、ヌル文字(\0
)を含めたサイズにしておく必要があります。
スポンサーリンク
strcpy
関数の使用例
strcpy
関数を使用して文字列をコピーするプログラムの例は下記になります。
#include <stdio.h>
#include <string.h>
#define MAX_CHAR_NUM 10
int main(void){
char str1[MAX_CHAR_NUM];
char str2[MAX_CHAR_NUM];
/* 文字列コピー */
strcpy(str1, "hitotsume");
printf("str1 = %s\n", str1);
printf("\n");
strcpy(str2, str1);
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf("\n");
strcpy(str2, "futatsume");
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf("\n");
return 0;
}
実行結果は下のようになります。ポイントは "hitotsume"
などのような「文字列自体」を strcpy
関数の第2引数にも指定可能という点だと思います。
この場合もしっかりヌル文字(\0
)が含まれる形で文字列のコピーが行われます。
str1 = hitotsume str1 = hitotsume str2 = hitotsume str1 = hitotsume str2 = futatsume
文字列を比較する
文字列の比較する時に利用する関数は「strcmp
関数」です。
strcmp
関数
strcmp
関数は2つの文字列を比較する関数です。
strcmp
関数の定義ファイル、関数定義は下記の通りです。
#include <string.h>
int strcmp(const char* str1, const char* str2);
strcmp
関数の引数
strcmp
関数の第1引数 str1
・第2引数 str2
に比較したい2つの文字列が格納された配列やメモリの先頭アドレスを指定します。
単に同じかどうかを比較したい場合は、第1引数 str1
と第2引数 str2
の順序に特に意味はありません。
ただし、大小関係を調べたい場合は第1引数 str1
と第2引数 str2
の指定順で返却値が異なりますので注意してください。
strcmp
関数の返却値
引数 str1
と str2
で指定したアドレスの先の2つの文字列が同じである場合、strcmp
関数は 0
を返却します。
同じでない場合は 0
以外の値を返却します。
より具体的には、”文字コード的に” 第1引数の方が大きい場合は “正” の値、第2引数の方が大きい場合は “負” の値を返却します。
正確に言うと「文字に割り当てられた数字」に大きいや小さいがあるんだ
各文字には文字コードという数字が割り当てられてて、その数字の大小関係に応じて値が返却される感じだね
strcmp
関数の詳細
strcmp
関数は引数 str1
と str2
で指定された2つの文字列の比較を行います。
比較は先頭の文字から1文字ずつ行われます。
そして、両方の文字列の文字がヌル文字(\0
)である場合もしくは文字が異なる場合に比較を終了します。
この時、両方文字列の文字がヌル文字(\0
)である場合は、2つの文字列の先頭から文字列の最後(ヌル文字)まで同じであると判断できるので、文字列が同じであることを示す 0
が返却されます。
一方、文字が異なる場合は、2つの文字列が異なると判断できるので、文字列が異なることを示す 0
以外の値が返却されます。
ここからは環境によって異なるかもしれませんが、私の環境では異なった文字の文字コードの差が返却されました。
具体的には、引数 str1
と引数 str2
の第 n
文字目が異なる場合は、下記の値が返却されるようでした。
str1[n] - str2[n];
例えば str1 = "abc"
、str2 = "abx"
の場合、'c' - 'x' = -21
が返却されました。
この辺りの細かい返却値については環境依存で結果が異なる可能性がありますが、str1
の方が文字コード的に大きい場合は正の値、str2
の方が文字コード的に大きい場合は負の値が返却される点はどの環境でも同じではないかと思います。
こういった文字コードの大小関係を strcmp
関数の返却値で取得することもできますので、2分探索を行う場合にも便利な関数です。
スポンサーリンク
strcmp
関数の使用例
strcmp
関数を使用して文字列を比較するプログラムの例は下記になります。
#include <stdio.h>
#include <string.h>
int main(void){
char str[] = "abc";
char str1[] = "abc";
char str2[] = "abcd";
char str3[] = "abd";
char str4[] = "abb";
char str5[] = "Acd";
printf("strcmp(%s, %s) : %d\n", str, str1, strcmp(str, str1));
printf("strcmp(%s, %s) : %d\n", str, str2, strcmp(str, str2));
printf("strcmp(%s, %s) : %d\n", str, str3, strcmp(str, str3));
printf("strcmp(%s, %s) : %d\n", str, str4, strcmp(str, str4));
printf("strcmp(%s, %s) : %d\n", str, str5, strcmp(str, str5));
return 0;
}
実行結果は下のようになります。
strcmp(abc, abc) : 0 strcmp(abc, abcd) : -100 strcmp(abc, abd) : -1 strcmp(abc, abb) : 1 strcmp(abc, Acd) : 32
文字列を連結(結合)する
文字列を連結することができるのは strcat
関数です。
strcat
関数
strcat
関数は2つの文字列を連結する関数です。
例えば "ai"
という文字列と "ueo"
という文字列を連結して "aiueo"
という文字列を生成すすることができます。
strcat
関数の定義ファイル、関数定義は下記の通りです。
#include <string.h>
char* strcat(char* str1, const char* str2);
strcat
関数の引数
strcat
関数の第1引数 str1
には連結 “元” になる文字列が格納された配列やメモリのアドレスを、第2引数 str2
には第1引数 str1
に連結したい文字列が格納された配列やメモリのアドレスを指定します。
strcat
関数の返却値
2つの文字列を連結した結果が格納される配列やメモリの先頭アドレスが返却されます。
といっても、strcat
関数では後述するように第1引数で指定した文字列の後ろ側に第2引数で指定した文字列が連結されるため、結局第1引数で指定したアドレスと同じものが返却されることになります。
strcat
関数の詳細
strcat
関数は引数で指定した文字列 str1
の後ろに文字列 str2
を連結します。
より具体的に言うと、str1
の終端を表すヌル文字(\0
)部分から後ろ側に文字列 str2
が連結されます。
str1
の終端を表すヌル文字(\0
)は str2
に上書きされて無くなりますが、連結後も str2
側のヌル文字(\0
)は残ります。
strcat
関数の注意点
strcat
関数には注意点が2つあります。
1点目は、第1引数で指定した str1
の指す配列やメモリの内容が下記変わってしまうことです。具体的には、strcat
関数を実行することで、str1
の指す配列やメモリには str1
に str2
を連結した文字列が格納されることになります。
2点目は第1引数で指定した str1
の指す配列やメモリのサイズです。
strcat
関数を実行すると、str1
の後ろ側に str2
の文字列が連結されることになりますので、str1
の指す配列やメモリのサイズは str2
連結後の文字列が十分に格納できるだけのものでなければなりません。
連結後の文字列が配列やメモリに入りきらない場合、メモリの不正アクセスになってしまうので注意してください。
スポンサーリンク
strcat
関数の使用例
下記は strcat
関数を用いて文字列の連結を行うプログラムの例です。
#include <stdio.h>
#include <string.h>
int main(void) {
char str1[10] = "ai";
char str2[] = "ueo";
strcat(str1, str2);
printf("str1 = %s\n", str1);
}
実行すると下記のように表示され、str1
に str2
が連結されていることが確認できると思います。
aiueo
下記はダメな例です。
#include <stdio.h>
#include <string.h>
int main(void) {
char str1[] = "ai";
char str2[] = "ueo";
strcat(str1, str2);
printf("str1 = %s\n", str1);
}
str1
には str2
が連結されるので、str2
の文字列の長さも考慮して str1
の配列のサイズを設定する必要があります。
下記だと str1
の配列のサイズが "ai"
の文字列分しか確保されていないため、strcat
関数で str1
に str2
に連結する時にバッファオーバーフローが発生します。
書式を指定して文字列を生成する
書式を指定して文字列を生成するのは sprintf
関数です。
sprintf
関数
sprintf
関数を書式を指定して文字列を生成する関数です。
うーん、まずはあまり深く考えなくて良いかな
要は printf
関数の文字列出力バージョンだよ
おそらく最初に使用した関数が printf
という方も多いと思います。馴染み深い関数だと思います。
printf
関数は標準出力(例えばコンソール・ターミナルなど)に文字列を出力する関数ですよね!
変換指定子(%d
や %s
など)を指定し、そこに変数の値を埋め込んで文字列を出力できるところがポイントです。
sprintf
関数は、標準出力ではなく、配列やメモリに文字列を出力することができる関数です。
sprintf
関数数の定義ファイル、関数定義は下記の通りです。
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
sprintf
関数の引数
sprintf
関数の第1引数 str
は文字列の出力先のアドレスを指定します。
第2引数 format
には書式を指定します。
第3引数以降は可変長引数になっており、format
に含まれる変換指定子の数分の引数を指定します。
sprintf
関数の返却値
成功時は str
に格納された文字列の長さ(ヌル文字は含まない)を返却します。
失敗時は負の値を返却します。
sprintf
関数の詳細
書式や変換指定子など難しい言葉が使われていたり、関数の引数が可変長引数になっていたりで難しそうに感じるかもしれませんが、基本的に printf
関数と同じ感じで使用すれば良いです。
例えば第2引数 format
に "I am %s\n"
を指定し、第3引数に "cat"
を指定して sprintf
関数を実行すれば、第1引数 str
の指すアドレスに "I am cat\n"
が格納されることになります。
前述で strcat
関数を紹介しましたが、sprintf
関数でも文字列の連結を簡単に行うことができます。下記は str1
と str2
と str3
を連結した文字列を str
の指すアドレスに格納する sprintf
関数の使用例になります。
sprintf(str, "%s%s%s", str1, str2, str3);
printf
同様に、変換指定子 に %d
や %f
を指定すれば、数値などを format
に埋め込んだ結果を取得することもできます。
sprintf
関数の注意点
第1引数 str
の指すアドレスの配列やメモリのサイズに注意が必要です。
第1引数 str
には、第2引数 format
に対して第3引数以降の引数を埋め込んだ結果が格納されます。
したがって、その埋め込み後の文字列の長さ以上のサイズの配列やメモリのアドレスを第1引数 str
に指定する必要があります。
サイズが足りない場合はバッファオーバーフローが発生します。
スポンサーリンク
sprintf
関数の使用例
下記は sprintf
関数を使用して文字列を生成するプログラムの例です。
#include <stdio.h>
int main(void) {
char str[100];
char name[] = "hanako";
float height = 165.5;
sprintf(str, "%s's height is %.1f\n", name, height);
printf("%s", str);
return 0;
}
文字列を探索する
文字列を探索する時に使用するのは strstr
関数です。
strstr
関数
strstr
関数は文字列の中から指定した文字列を探索する関数です。
strstr
関数の定義ファイル、書式は下記の通りです。
#include <string.h>
char* strstr(const char* str1, const char* str2);
strstr
関数の引数
strstr
関数の第1引数 str1
には探索 “先” になる文字列が格納された配列やメモリのアドレスを、第2引数 str2
には第1引数 str1
で指定した文字列から探索したい文字列が格納された配列やメモリのアドレスを指定します。
strstr
関数の返却値
文字列 str1
の中から文字列 str2
が見つかった位置(アドレス)を返却します。
見つからなかった場合は NULL
を返却します。
strstr
関数の詳細
strstr
関数では str1
の文字列(str1
の指すアドレスから、str1
の終端を表すヌル文字('\0'
)までの間のデータ)の中から str2
の指す文字列の探索が行われます。
str1
の文字列の中から str2
が見つかった場合、str2
が見つかった位置のアドレスを返却します。
str1
の文字列に str2
の文字列が複数存在する場合は、先頭に近い方のアドレスが返却されます。
スポンサーリンク
strstr
関数の使用例
下記は strstr
関数を用いて文字列の探索を行うプログラムの例です。
#include <string.h>
#include <stdio.h>
#define MAX_CHAR_NUM 10
#define MAX_LINE 5
int main(void) {
char str[MAX_LINE][MAX_CHAR_NUM] = {
"I am cat",
"scan",
"melon",
"cat can",
"cccaaaa"
};
char search[] = "ca";
int i;
char *ret;
for (i = 0; i < MAX_LINE; i++) {
ret = strstr(str[i], search);
if (ret != NULL) {
printf("%s:%s\n", str[i], ret);
} else {
printf("%s:Notfound\n", str[i]);
}
}
return 0;
}
実行結果は下記のようになります。strstr
関数の返却値は、str2
で指定した文字列が見つかった位置ですので、返却値を表示すると str1
の途中に str2
が存在する場合はその位置から文字列が表示されることになります。
I am cat:cat scan:can melon:Notfound cat can:cat can cccaaaa:caaaa
文字列を分離する(切り離す)
文字列を分離(切り離し)したい時に使用するのは strtok
関数です。
strtok
関数
strtok
関数を指定した文字を区切りにして文字列を分離する関数です。
例えば "I am a cat."
と言う文字列を ' '
で区切って(スペースで区切って)下記の文字列に分離するようなことができます。
I
am
a
cat.
strtok
関数数の定義ファイル、関数定義は下記の通りです。
#include <string.h>
char* strtok(char* str1, const char* str2);
strtok
関数の引数
strtok
関数の第1引数 str1
には分離を行いたい文字列が格納された配列やメモリのアドレスを指定します。
複数回同じ文字列に対して strtok
関数を実行する場合は、2回目以降は str1
に NULL
を指定します。
え?
2回目も同じアドレスを指定しちゃダメなんだ…
そうなんだ…
ややこしい関数なんだよね…
同じアドレスを指定すると、後述するように意図しない結果になるから注意が必要だよ!
第2引数 str2
には第1引数 str1
で指定した文字列をどの文字で分離するかを指定します。複数文字指定可能で、”文字列” の形式で指定します。
例えば a
を区切りにして文字列を分離したいのであれば、第2引数には "a"
を指定します('a'
ではないことに注意)。また a
と b
と c
を区切りにして文字列を分離したいのであれば、"abc"
を指定します。
strtok
関数の返却値
分離後の文字列を指すアドレスが返却されます。
分離できない場合(つまり第2引数で指定した文字がない場合)は、第1引数 str1
に文字列が格納された配列やメモリのアドレスを指定しているとその文字列自体が、NULL
を 指定しているとNULL
が返却されます。
多くの解説ページでは、分離できない場合は NULL
が返却されると説明されています
が、私の環境 MacOSX では上記の通り第1引数によって返却値が異なりました
上記の解説は、私の環境で動作確認した時の結果に基づいたものになります
環境によって異なる可能性があるので注意してください
strtok
関数の詳細
strtok
関数の動きを説明し、そこを踏まえて注意点について解説していきたいと思います。
strtok
関数は基本的に第1引数の str1
の文字列の “先頭” から str2
に含まれる文字を探索し、見つかった場合にその文字をヌル文字('\0'
)に置き換える動きをする関数です。
この時、1文字分だけ置き換えると関数は終了します。
下の図は str1
に "I-am-a-cat."
の文字列が格納された配列のアドレスを、str2
に "-"
を指定した時の strtok
関数によって文字が置き換えられる様子を表しています。
ここで str1
の文字列に注目してみましょう!str1
の文字列は strtok
関数によって str2
に含まれる文字がヌル文字('\0'
)に置き換えられているので、そのヌル文字までが str1
の文字列として扱われることになります。
つまり str1
の文字列が str2
に含まれる文字を区切りとして分離されたことになります。
一方で、一度に置き換えられるのが1文字分のみなので、区切り文字が複数ある場合は一度の実行では分離は完了することにはなりません。
この場合に str1
の文字列をさらに分離したい場合は、再度 strtok
関数を実行することになります。
で、この時に再度 strtok
関数の第1引数に str1
を指定したとしましょう。str1
は str2
に含まれる文字を区切りとして既に分離されているため、2回目以降の実行では絶対に分離されないことになります。
なので、複数回分離したい場合は、本当は strtok
関数の第1引数としては、先ほど strtok
関数でヌル文字に置き換えられた文字の1文字後ろ位置のアドレスを指定する必要があります。
でも、わざわざこのアドレスを指定するのは面倒ですよね?
なので、strtok
関数では第1引数で NULL
を指定すれば、直前にヌル文字に置き換えた位置の1文字後ろの位置のアドレスから分離を開始してくれるようになっています。
この際に strtok
関数は、次に見つけた str2
に含まれる文字をヌル文字に置き換えて、分離を開始した位置のアドレスを返却します。
そして分離できない場合は strtok
関数は NULL
を返却します。
つまり、str1
の文字列を何度も str2
に含まれる文字を区切りとして分離したい場合は、下記のようにして strtok
関数をループで実行すれば良いです。
strtok(str1, str2)
を実行strtok(NULL, str2)
を実行- 返却値が
NULL
でない場合は 2. に戻り、返却値がNULL
の場合は終了
で、ここまでの話を踏まえて注意点について整理しておきたいと思います。
strtok
関数の注意点
注意点は下記の3つになります。
- 第1引数で指定した
str1
の文字列はstrtok
関数の中で書き換えられてしまうstr1
の文字列自体を後から使いたい場合は、事前に他の配列等に退避しておく必要あり
- 文字列に対して分離が行われるのは1回だけ
- 複数回分離を行うためには複数回
strtok
関数を実行する必要あり
- 複数回分離を行うためには複数回
- 同じ文字列に対して複数回の分離を行う場合、実行するタイミングによって第1引数を変更する必要がある
- 1回目の実行時には文字列が格納されたアドレスを指定
- 2回目以降は
NULL
を指定
strtok
関数は、はっきり言ってC言語の標準関数の中でもトップレベルに癖のある関数だと思います。正直自分で文字列を分離する関数を作ってしまった方が早いような…。
スポンサーリンク
strtok
関数の使用例
下記は strtok
関数を用いて文字列の分離を行うプログラムの例になります。
#include <stdio.h>
#include <string.h>
int main(void) {
char str1[] = "I am a cat.";
char str2[] = " ";
char *ret;
size_t len;
size_t i;
len = strlen(str1);
/* 1度目のstrtokの実行 */
ret = strtok(str1, str2);
while(ret){
printf("%s\n", ret);
/* 2度目以降のstrtokの実行 */
ret = strtok(NULL, str2);
}
/* str1をバイト単位に16進数で表示 */
for (i = 0; i < len; i++) {
printf("%02x", str1[i]);
}
printf("\n");
return 0;
}
実行すると下記のように表示されます。"I am a cat."
が ' '
を区切りとして分離されて表示されている様子が確認できると思います。
最後の数字は最終的な str1
の文字列を1文字ずつ文字コード(16 進数)で表示したものになります。00
がヌル文字('\0'
)の文字コードで、所々ヌル文字に置き換えられていることが確認できると思います。
I am a cat. 4900616d0061006361742e
まとめ
このページでは下記の文字列を操作する関数について解説しました!
strlen
strcpy
strcmp
strcat
strstr
strtok
C言語でも文字列の操作を行う関数が数多く用意されています。
とにかくこれらの関数では、文字列の最後にヌル文字('\0'
)が存在することを期待して動作します。なので、ヌル文字('\0'
)が含まれない文字列を引数に指定すると基本的にバッファオーバーフローが起こります。ここが文字列操作関数における1番の注意点だと思います。
また、便利なものが多いですが、strtok
のようにクセの強い関数も結構多い印象です!
使い方に困った時などは是非コメントなどで質問いただければと思いますのでよろしくお願いいたします。