【C言語】文字列操作関数(strcat・strtokなど)まとめ【目的から逆引き】

文字列操作関数まとめページのアイキャッチ

C言語で文字列を扱う際に覚えておくと便利なのが文字列操作関数です。

このページでは、よく利用する文字列操作関数と、その使用方法について解説していきます。

文字列関連の関数って結構使うんだけど使い方なんかは全然覚えてないや

私も毎回ググってるよ…

まあ、なので、覚えなくても良いようにこんなまとめページを作ったんだ!

私も文字列操作関数は毎回調べながら使用しています。

おそらくそういう方も多いと思うので、よく使う文字列操作関数をこのページにまとめました!

是非文字列操作関数やその使い方に迷ったらこのページを参考にしてください。

文字列の長さを取得する

文字列の長さ(文字数)を取得する時に使用する関数は「strlen 関数」です。

strlen 関数

strlen 関数は文字列の長さを取得する関数になります。

strlen 関数を実行することで、例えば文字列 "aiueo" の文字列の長さ 5 を取得することができます。

strlen 関数の定義ファイル、関数定義は下記の通りです。

strlen関数
#include <string.h>
size_t strlen(const char* str);

strlen 関数の引数

strlen 関数の引数には str、文字列へのアドレスを指定します。より具体的には、文字列が格納された配列やメモリのアドレスを指定します。

strlen 関数の返却値

strlen 関数の返却値は引数 str の文字列の長さになります。 

この文字列の長さにはヌル文字(\0)の分はカウントされません。

strlen 関数の詳細

では、この strlen 関数が返却する「文字列の長さ」とは具体的にはどのような値になるでしょうか?

これは、引数で指定するアドレス str からアドレスを1バイトずつ増やしていき、次にヌル文字(\0)が見つかるまでのバイト数になります。

strlenの動作

バイト数ですので、日本語などマルチバイト文字を格納した文字列に対して strlen 関数を実行すると文字数と一致しないので注意しましょう。

ただ、特にC言語習いたての方は1バイト文字(英数字)を利用することが多いので、まずは「strlen 関数は文字数を取得する関数」と覚えておいて差し支えないと思います。

また、ヌル文字(\0)が存在することを期待して動作しますので、ヌル文字(\0)が含まれないデータへのアドレスを strlen 関数に渡してしまうと動作がおかしくなります。

ヌル文字(\0)が含まれないとどうなるの?

おそらくだけど、配列のサイズに関係なく、次にヌル文字(\0)があるアドレスまでヌル文字の探索が行われるよ

この場合、配列のサイズを超えてデータの参照が行われるのでバッファオーバーフローになる可能性もある

スポンサーリンク

strlen 関数の使用例

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 関数の定義ファイル、関数定義は下記の通りです。

strcpy関数
#include <string.h>
char* strcpy(char* str1, const char* str2);

strcpy 関数の引数

第1引数の str1 には文字列をコピーする “先” の配列やメモリのアドレスを指定し、第2引数の str2 には文字列をコピーする “元” の配列やメモリのアドレスを指定します。

strcpy 関数の返却値

返却値は文字列をコピーした “先” の先頭アドレスになります。つまり、str1 と一致します。

strcpy 関数の詳細

strcpy を実行することで、str2 のアドレスからヌル文字(\0)までの文字列が str1 のアドレスの配列やメモリにコピーされます。

strcpyの動作

ヌル文字(\0)もコピーされるの?

ヌル文字(\0)も含めてコピーされると考えて良いです。

ただし、コピー先のアドレスである str1 の指す配列やメモリは、ヌル文字(\0)を含めたサイズにしておく必要があります。

スポンサーリンク

strcpy 関数の使用例

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 関数の定義ファイル、関数定義は下記の通りです。

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 関数の返却値

引数 str1str2 で指定したアドレスの先の2つの文字列が同じである場合、strcmp 関数は 0 を返却します。

同じでない場合は 0 以外の値を返却します。

より具体的には、”文字コード的に” 第1引数の方が大きい場合は “正” の値、第2引数の方が大きい場合は “負” の値を返却します。

文字に大きいや小さいがあるの?

正確に言うと「文字に割り当てられた数字」に大きいや小さいがあるんだ

各文字には文字コードという数字が割り当てられてて、その数字の大小関係に応じて値が返却される感じだね

strcmp 関数の詳細

strcmp 関数は引数 str1str2 で指定された2つの文字列の比較を行います。

比較は先頭の文字から1文字ずつ行われます。

strcmpの動作

そして、両方の文字列の文字がヌル文字(\0)である場合もしくは文字が異なる場合に比較を終了します。

この時、両方文字列の文字がヌル文字(\0)である場合は、2つの文字列の先頭から文字列の最後(ヌル文字)まで同じであると判断できるので、文字列が同じであることを示す 0 が返却されます。

一方、文字が異なる場合は、2つの文字列が異なると判断できるので、文字列が異なることを示す 0 以外の値が返却されます。

ここからは環境によって異なるかもしれませんが、私の環境では異なった文字の文字コードの差が返却されました。

具体的には、引数 str1 と引数 str2 の第 n 文字目が異なる場合は、下記の値が返却されるようでした。

strcmpの返却値
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 関数の定義ファイル、関数定義は下記の通りです。

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 が連結されます。

strcatの動作

str1 の終端を表すヌル文字(\0)は str2 に上書きされて無くなりますが、連結後も str2 側のヌル文字(\0)は残ります。

strcat 関数の注意点

strcat 関数には注意点が2つあります。

1点目は、第1引数で指定した str1 の指す配列やメモリの内容が下記変わってしまうことです。具体的には、strcat 関数を実行することで、str1 の指す配列やメモリには str1str2 を連結した文字列が格納されることになります。

2点目は第1引数で指定した str1 の指す配列やメモリのサイズです。

strcat 関数を実行すると、str1 の後ろ側に str2 の文字列が連結されることになりますので、str1 の指す配列やメモリのサイズは str2 連結後の文字列が十分に格納できるだけのものでなければなりません。

連結後の文字列が配列やメモリに入りきらない場合、メモリの不正アクセスになってしまうので注意してください。

スポンサーリンク

strcat 関数の使用例

下記は 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

下記はダメな例です。

strcatの使用例(ダメな例)
#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 関数で str1str2 に連結する時にバッファオーバーフローが発生します。

書式を指定して文字列を生成する

書式を指定して文字列を生成するのは sprintf 関数です。

sprintf 関数

sprintf 関数を書式を指定して文字列を生成する関数です。

書式とは…?

うーん、まずはあまり深く考えなくて良いかな

要は printf 関数の文字列出力バージョンだよ

おそらく最初に使用した関数が printf という方も多いと思います。馴染み深い関数だと思います。

printf 関数は標準出力(例えばコンソール・ターミナルなど)に文字列を出力する関数ですよね!

変換指定子(%d%s など)を指定し、そこに変数の値を埋め込んで文字列を出力できるところがポイントです。

sprintf 関数は、標準出力ではなく、配列やメモリに文字列を出力することができる関数です。

sprintf 関数数の定義ファイル、関数定義は下記の通りです。

strtok関数
#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 関数でも文字列の連結を簡単に行うことができます。下記は str1str2str3 を連結した文字列を str の指すアドレスに格納する sprintf 関数の使用例になります。

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 関数を使用して文字列を生成するプログラムの例です。

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 関数の定義ファイル、書式は下記の通りです。

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 が見つかった位置のアドレスを返却します。

strstrの動作

str1 の文字列に str2 の文字列が複数存在する場合は、先頭に近い方のアドレスが返却されます。

スポンサーリンク

strstr 関数の使用例

下記は 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 関数数の定義ファイル、関数定義は下記の通りです。

strtok関数
#include <string.h>
char* strtok(char* str1, const char* str2);

strtok 関数の引数

strtok 関数の第1引数 str1 には分離を行いたい文字列が格納された配列やメモリのアドレスを指定します。

複数回同じ文字列に対して strtok 関数を実行する場合は、2回目以降は str1NULL を指定します。

え?

2回目も同じアドレスを指定しちゃダメなんだ…

そうなんだ…

ややこしい関数なんだよね…

同じアドレスを指定すると、後述するように意図しない結果になるから注意が必要だよ!

第2引数 str2 には第1引数 str1 で指定した文字列をどの文字で分離するかを指定します。複数文字指定可能で、”文字列” の形式で指定します。

例えば a を区切りにして文字列を分離したいのであれば、第2引数には "a" を指定します('a' ではないことに注意)。また abc を区切りにして文字列を分離したいのであれば、"abc" を指定します。

strtok 関数の返却値

分離後の文字列を指すアドレスが返却されます。

分離できない場合(つまり第2引数で指定した文字がない場合)は、第1引数 str1 に文字列が格納された配列やメモリのアドレスを指定しているとその文字列自体が、NULL を 指定しているとNULL が返却されます。

MEMO

多くの解説ページでは、分離できない場合は NULL が返却されると説明されています

が、私の環境 MacOSX では上記の通り第1引数によって返却値が異なりました

上記の解説は、私の環境で動作確認した時の結果に基づいたものになります

環境によって異なる可能性があるので注意してください

strtok 関数の詳細

strtok 関数の動きを説明し、そこを踏まえて注意点について解説していきたいと思います。

strtok 関数は基本的に第1引数の str1 の文字列の “先頭” から str2 に含まれる文字を探索し、見つかった場合にその文字をヌル文字('\0')に置き換える動きをする関数です。

この時、1文字分だけ置き換えると関数は終了します。

下の図は str1"I-am-a-cat." の文字列が格納された配列のアドレスを、str2"-" を指定した時の strtok 関数によって文字が置き換えられる様子を表しています。

strtokの動作1

ここで str1 の文字列に注目してみましょう!str1 の文字列は strtok 関数によって str2 に含まれる文字がヌル文字('\0')に置き換えられているので、そのヌル文字までが str1 の文字列として扱われることになります。

つまり str1 の文字列が str2 に含まれる文字を区切りとして分離されたことになります。

strtokの動作2

一方で、一度に置き換えられるのが1文字分のみなので、区切り文字が複数ある場合は一度の実行では分離は完了することにはなりません。

strtokの動作3

この場合に str1 の文字列をさらに分離したい場合は、再度 strtok 関数を実行することになります。

で、この時に再度 strtok 関数の第1引数に str1 を指定したとしましょう。str1str2 に含まれる文字を区切りとして既に分離されているため、2回目以降の実行では絶対に分離されないことになります。

strtokの動作4

なので、複数回分離したい場合は、本当は strtok 関数の第1引数としては、先ほど strtok 関数でヌル文字に置き換えられた文字の1文字後ろ位置のアドレスを指定する必要があります。

strtokの動作5

でも、わざわざこのアドレスを指定するのは面倒ですよね?

なので、strtok 関数では第1引数で NULL を指定すれば、直前にヌル文字に置き換えた位置の1文字後ろの位置のアドレスから分離を開始してくれるようになっています。

strtokの動作6

この際に strtok 関数は、次に見つけた str2 に含まれる文字をヌル文字に置き換えて、分離を開始した位置のアドレスを返却します。

strtokの動作7

そして分離できない場合は strtok 関数は NULL を返却します。

つまり、str1 の文字列を何度も str2 に含まれる文字を区切りとして分離したい場合は、下記のようにして strtok 関数をループで実行すれば良いです。

  1. strtok(str1, str2) を実行
  2. strtok(NULL, str2) を実行
  3. 返却値が NULL でない場合は 2. に戻り、返却値が NULL の場合は終了

で、ここまでの話を踏まえて注意点について整理しておきたいと思います。

strtok 関数の注意点

注意点は下記の3つになります。

  • 第1引数で指定した str1 の文字列は strtok 関数の中で書き換えられてしまう
    • str1 の文字列自体を後から使いたい場合は、事前に他の配列等に退避しておく必要あり
  • 文字列に対して分離が行われるのは1回だけ
    • 複数回分離を行うためには複数回 strtok 関数を実行する必要あり
  • 同じ文字列に対して複数回の分離を行う場合、実行するタイミングによって第1引数を変更する必要がある
    • 1回目の実行時には文字列が格納されたアドレスを指定
    • 2回目以降は NULL を指定

strtok 関数は、はっきり言ってC言語の標準関数の中でもトップレベルに癖のある関数だと思います。正直自分で文字列を分離する関数を作ってしまった方が早いような…。

スポンサーリンク

strtok 関数の使用例

下記は 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 のようにクセの強い関数も結構多い印象です!

使い方に困った時などは是非コメントなどで質問いただければと思いますのでよろしくお願いいたします。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です