C言語の基本|C言語の文字列比較の基本

見た目ではなく、中身を1文字ずつ確かめよう。strcmpとstrncmpを使えば、C言語の文字列を正しく比較できる

C言語で文字列を扱っていると、2つの文字列が同じ内容かどうかを調べたい場面がたくさんあります。

例えば、入力されたユーザー名を登録済みの名前と比較する、メニューで入力された文字列を判定する、特定の文字列から始まっているか確認するといった処理です。

整数や1文字の値は、等価演算子==を使って比較できます。

if (number1 == number2) {
    /* 2つの数値が等しい場合 */
}

しかし、C言語の文字列はchar型の配列として表現されます。2つの文字配列を==で比較しても、配列に格納されている文字を先頭から比較する処理にはなりません。

文字列の内容を比較するには、strcmpやstrncmpを使います。

strcmpは、2つの文字列全体を先頭から比較します。strncmpは、先頭から最大n文字までを比較します。

これらの関数を使うと、次のような判定ができます。

  • 文字列全体が一致しているか
  • 文字列全体が異なっているか
  • 先頭の数文字だけが一致しているか
  • 最初に異なる文字の位置で、どちらが大きいか
  • 入力文字列が特定の接頭辞から始まっているか

strcmpとstrncmpは、等しい場合に0を返します。等しい場合に1を返す関数ではないため、戻り値の意味を正しく理解することが大切です。

ここでは、文字列を==で比較できない理由、strcmpの比較方法、戻り値の意味、strncmpによる部分比較、if文での判定方法について順番に確認していきましょう。

文字列を比較するとは

文字列を比較するとは、2つの文字列を先頭から順番に見比べ、内容が同じか、どの位置で異なるかを調べることです。

例えば、次のような比較を考えます。

文字列1文字列2比較結果
catcat全体が等しい
catcar3文字目が異なる
appleapply5文字目が異なる
ABCDABCC4文字目が異なる
stationstatus先頭3文字は等しい
everyeveryoneeveryまで等しいが全体は異なる

strcmpとstrncmpは、先頭から文字を順番に比較します。

比較している文字が同じであれば、次の位置へ進みます。異なる文字が見つかった場合は、その位置の文字の値を基に大小関係を決めます。

文字列の最後まで違いが見つからなければ、2つの文字列は等しいと判断されます。

文字列比較に必要なヘッダ

strcmpとstrncmpは、string.hで宣言されています。

#include <string.h>

プログラムでstrcmpやstrncmpを使う場合は、ソースファイルの先頭付近でstring.hをインクルードします。

関数名主な役割必要なヘッダ
strcmp2つの文字列全体を比較するstring.h
strncmp2つの文字列を先頭から最大n文字比較するstring.h

比較する文字列は、どちらもナル文字で正しく終わっている必要があります。

なぜ==で文字列の内容を比較できないのか

C言語では、文字列はchar型の配列として表現されます。

例えば、次の2つの配列を用意します。

char text1[] = "abc";
char text2[] = "abc";

text1とtext2は、どちらも同じabcを格納しています。しかし、2つは別々に用意された独立した配列です。

text1
'a'  'b'  'c'  '\0'

text2
'a'  'b'  'c'  '\0'

配列名は、多くの式の中で配列の先頭要素を指すポインタへ変換されます。

そのため、次の比較は文字列の中身を比較する処理ではありません。

if (text1 == text2) {
    /* 文字列の内容を比較しているわけではない */
}

この比較では、text1の先頭を表すアドレスと、text2の先頭を表すアドレスを比較します。

text1とtext2は別々の配列なので、それぞれ異なる場所に置かれています。格納されている文字が同じでも、先頭アドレスは通常異なります。

==が比較するもの

配列名を==で比較した場合と、strcmpで比較した場合の違いを整理すると、次のようになります。

比較方法比較するもの
text1 == text2配列の先頭を表すアドレス
strcmp(text1, text2) == 0配列に格納された文字列の内容

文字列の内容が同じかどうかを調べる場合は、strcmpを使います。

if (strcmp(text1, text2) == 0) {
    /* 文字列の内容が等しい */
}

図1:==によるアドレス比較とstrcmpによる内容比較

この図から分かること

text1とtext2には同じabcが格納されていますが、それぞれ別の配列です。そのため、先頭アドレスも異なります。

text1 == text2では、格納されている文字ではなく、2つの配列の先頭アドレスを比較します。

strcmpは、配列の先頭から文字を順番に比較します。すべての文字が同じで、同じ位置にナル文字が見つかれば0を返します。

文字列の内容を比較したい場合は、==ではなくstrcmpを利用します。

文字列リテラル同士を==で比較してはいけない理由

次のように、文字列リテラル同士を==で比較する書き方も、内容比較には使えません。

if ("abc" == "abc") {
    /* 内容比較としては不適切 */
}

コンパイラは、同じ内容の文字列リテラルを同じ場所へまとめることもあれば、別々の場所へ置くこともあります。

そのため、この比較結果を文字列内容の一致判定として利用することはできません。

文字列リテラルの内容を比較する場合も、strcmpを使います。

if (strcmp("abc", "abc") == 0) {
    /* 文字列の内容が等しい */
}

strcmp関数とは

strcmpは、2つの文字列全体を比較する関数です。

関数宣言は次のとおりです。

int strcmp(const char *s1, const char *s2);

strcmpの引数と戻り値を整理すると、次のようになります。

項目内容
関数名strcmp
第1引数s1比較する1つ目の文字列
第2引数s2比較する2つ目の文字列
戻り値の型int型
必要なヘッダstring.h

strcmpは、s1とs2が指す文字列を先頭から比較します。

strcmpの戻り値

strcmpは、比較結果をint型の値で返します。

比較結果戻り値
s1とs2が等しい0
s1がs2より大きい正の値
s1がs2より小さい負の値

ここで重要なのは、正の値や負の値の具体的な数値ではなく、符号です。

strcmpが返す値は、必ず1、0、-1になるとは限りません。

次のように判定します。

int result = strcmp(s1, s2);

if (result == 0) {
    /* 等しい */
} else if (result > 0) {
    /* s1のほうが大きい */
} else {
    /* s1のほうが小さい */
}

等しい場合に0を返す理由

strcmpが0を返すのは、2つの文字列に差が見つからなかった場合です。

差がないため0と考えると分かりやすいでしょう。

戻り値考え方
0文字列の差がない
正の値s1側の最初に異なる文字が大きい
負の値s1側の最初に異なる文字が小さい

真偽を直接返す関数ではないため、等しい場合に1が返るわけではありません。

strcmpの比較の流れ

appleとapplyを比較してみましょう。

比較位置s1の文字s2の文字判定
1文字目aa同じ
2文字目pp同じ
3文字目pp同じ
4文字目ll同じ
5文字目ey異なる

1文字目から4文字目までは同じです。5文字目でeとyが異なるため、strcmpはこの位置で大小関係を決めます。

一般的なASCII互換の文字コードでは、eの値はyより小さいため、strcmpは負の値を返します。

5文字目で結果が決まるので、それ以降の比較は必要ありません。

図2:strcmpが最初に異なる文字を見つける流れ

この図から分かること

strcmpは、2つの文字列を先頭から1文字ずつ比較します。

appleとapplyでは、先頭のapplまでは同じです。5文字目でeとyが異なるため、その位置の比較によって結果が決まります。

strcmpは、文字列全体を最後まで無条件に調べるわけではありません。最初に異なる文字が見つかった時点で、大小関係を判断できます。

一方の文字列が先に終わる場合

一方の文字列がもう一方の先頭部分と一致し、先にナル文字へ到達することがあります。

例えば、everyとeveryoneを比較します。

every\0
everyone\0

先頭のeveryまでは同じですが、every側は次にナル文字へ到達します。everyone側には、まだoが続いています。

この場合、短いeveryのほうが小さいと判定されます。

s1s2一般的な比較結果
everyeveryones1が小さい
testtestings1が小さい
codecode等しい

文字列が等しいためには、同じ位置まで同じ文字が続き、同じ位置でナル文字へ到達する必要があります。

strcmpは文字コード値を基に比較する

strcmpの比較は、一般的な辞書の並べ替えと似ていますが、国語辞典のような言語規則で比較しているわけではありません。

strcmpは、各文字をunsigned char型の値として比較します。

一般的なASCII互換環境では、次のような関係があります。

文字1文字2比較
ABAのほうが小さい
BABのほうが大きい
AaAのほうが小さい
abaのほうが小さい

大文字と小文字は異なる文字コードを持つため、Appleとappleは等しい文字列ではありません。

strcmp("Apple", "apple")

この結果は0になりません。

strcmpは大文字と小文字を自動的に同じものとして扱わないため、比較前に表記をそろえる必要がある場面もあります。

strcmpの基本的な使用例

2つの文字列が完全に一致しているか確認する場合は、次のように書きます。

char command[] = "start";
char input[] = "start";

if (strcmp(command, input) == 0) {
    printf("文字列は一致しています。\n");
} else {
    printf("文字列は一致していません。\n");
}

strcmpの結果が0であれば、2つの文字列全体が一致しています。

正の値と負の値も利用できる

strcmpは、一致判定だけでなく、文字列の大小関係を調べるためにも使えます。

int result = strcmp(word1, word2);

if (result < 0) {
    printf("word1のほうが前です。\n");
} else if (result > 0) {
    printf("word2のほうが前です。\n");
} else {
    printf("2つの文字列は同じです。\n");
}

この比較結果を利用すると、文字列を一定の順序に並べる処理などへ応用できます。

ただし、strcmpの順序は文字コード値に基づくため、日本語の辞書順や人名の読み順を直接表すものではありません。

strncmp関数とは

strncmpは、2つの文字列を先頭から最大n文字まで比較する関数です。

関数宣言は次のとおりです。

int strncmp(const char *s1,
            const char *s2,
            size_t n);

strncmpの引数と戻り値を整理すると、次のようになります。

項目内容
関数名strncmp
第1引数s1比較する1つ目の文字列
第2引数s2比較する2つ目の文字列
第3引数n比較する最大文字数
戻り値の型int型
必要なヘッダstring.h

戻り値の意味はstrcmpと同じです。

比較結果戻り値
比較範囲が等しい0
s1側が大きい正の値
s1側が小さい負の値

strncmpの基本的な使い方

stationとstatusを先頭から3文字比較する場合は、次のように書きます。

char word1[] = "station";
char word2[] = "status";

int result = strncmp(word1, word2, 3);

先頭3文字は、どちらもstaです。

station
sta

status
sta

指定した3文字の範囲では違いがないため、resultには0が入ります。

4文字まで比較すると、4文字目のtとtも同じです。5文字まで比較すると、word1のiとword2のuが異なるため、0以外の値になります。

strncmpの比較範囲

stationとstatusを比較する範囲を変えると、結果も変わります。

1sとs等しい
2stとst等しい
3staとsta等しい
4statとstat等しい
5statiとstatu異なる
7stationとstatus異なる

strncmpは、指定した範囲までしか比較しません。

全体では異なる文字列でも、指定範囲が同じであれば0を返します。

図3:strncmpで先頭部分だけを比較する流れ

この図から分かること

stationとstatusは、文字列全体では異なります。しかし、先頭3文字はどちらもstaです。

strncmpでnに3を指定すると、先頭3文字だけを比較します。その範囲に違いがないため、戻り値は0になります。

strncmpは、文字列全体の一致ではなく、接頭辞や先頭部分の一致を確認したい場合に役立ちます。

strncmpが途中でナル文字へ到達した場合

strncmpは最大n文字を比較しますが、n文字に到達する前にナル文字が見つかった場合は、そこで文字列の終わりを考慮して結果を決めます。

例えば、carとcarpetを5文字まで比較します。

strncmp("car", "carpet", 5)

先頭3文字のcarは同じです。しかし、1つ目の文字列は次にナル文字へ到達し、2つ目の文字列にはpが続いています。

そのため、結果は0になりません。

strncmpは、短い文字列をナル文字の後ろまで無理に読み続けるわけではありません。

nが0の場合

strncmpでnに0を指定した場合は、1文字も比較しません。

strncmp("apple", "orange", 0)

比較する文字がないため、戻り値は0になります。

n動作
01文字も比較せず0を返す
1先頭1文字を比較する
3先頭から最大3文字を比較する

nが0のときに0が返っても、2つの文字列全体が等しいという意味ではありません。比較範囲が0文字だったという意味です。

strncmpが便利な場面

strncmpは、文字列全体ではなく、先頭部分だけを調べたい場面で役立ちます。

活用場面
接頭辞の確認文字列がhttpから始まるか確認する
コマンドの分類入力がgetから始まるか確認する
日付文字列の比較年を表す先頭4文字を比較する
製品コードの分類先頭の分類コードを比較する
共通部分の確認stationとstatusの先頭部分を比較する

文字列が特定の接頭辞から始まっているか調べる場合は、接頭辞の長さをnへ指定します。

char text[] = "network-device";

if (strncmp(text, "network", 7) == 0) {
    printf("networkから始まっています。\n");
}

先頭7文字がnetworkと一致すれば0が返ります。

strncmpのnは日本語の文字数とは限らない

strncmpの第3引数nは、char型の要素数を表します。一般的な環境では、比較する最大バイト数として考えられます。

半角英数字は1文字を1バイトで表すことが多いため、nと見た目の文字数が一致しやすくなります。

UTF-8の日本語では、一般的に1文字が複数バイトです。そのため、nに3を指定しても、必ず日本語3文字を比較するわけではありません。

データnの考え方
半角英数字見た目の文字数と一致しやすい
UTF-8の日本語見た目の文字数とは一致しないことがある
マルチバイト文字文字を構成する途中のバイトまで比較する可能性がある

基本動作を確認するプログラムでは、半角英数字を使用すると結果を理解しやすくなります。

strcmpとstrncmpの違い

2つの関数を比較すると、次のようになります。

比較項目strcmpstrncmp
比較範囲文字列全体先頭から最大n文字
主な用途完全一致の確認先頭部分や接頭辞の確認
等しい場合0を返す比較範囲が等しければ0を返す
異なる場合正または負の値を返す正または負の値を返す
戻り値の型int型int型
必要なヘッダstring.hstring.h

使い分けの基本は次のとおりです。

  • 文字列全体を比較する場合はstrcmpを使う
  • 先頭部分だけを比較する場合はstrncmpを使う
  • 等しいか確認するときは戻り値が0か調べる
  • 大小関係を調べるときは戻り値の正負を調べる

strcmpとstrncmpを使ったプログラム

次のプログラムでは、2つの英単語を入力し、strcmpで全体を比較します。その後、strncmpで先頭から指定した文字数を比較します。

2つの英単語を全体と先頭部分で比較するプログラム

ファイル名:12_7_1.c

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

int main(void)
{
    char word1[100];
    char word2[100];
    size_t count;

    printf("1つ目の英単語を入力してください > ");
    if (scanf("%99s", word1) != 1) {
        puts("1つ目の英単語を読み取れませんでした。");
        return 1;
    }

    printf("2つ目の英単語を入力してください > ");
    if (scanf("%99s", word2) != 1) {
        puts("2つ目の英単語を読み取れませんでした。");
        return 1;
    }

    /* strcmpで文字列全体を比較する */
    if (strcmp(word1, word2) == 0) {
        puts("2つの英単語は全体が一致しています。");
    } else {
        puts("2つの英単語は全体が異なります。");
    }

    printf("先頭から比較する文字数を入力してください > ");
    if (scanf("%zu", &count) != 1) {
        puts("比較する文字数を読み取れませんでした。");
        return 1;
    }

    /* strncmpで先頭から指定した文字数を比較する */
    if (strncmp(word1, word2, count) == 0) {
        printf("先頭から%zu文字の範囲は一致しています。\n", count);
    } else {
        printf("先頭から%zu文字の範囲は異なります。\n", count);
    }

    return 0;
}
実行結果の例

stationとstatusを入力し、先頭3文字を比較した場合の例です。

1つ目の英単語を入力してください > station
2つ目の英単語を入力してください > status
2つの英単語は全体が異なります。
先頭から比較する文字数を入力してください > 3
先頭から3文字の範囲は一致しています。

2つの英単語を比較するプログラムの解説

必要なヘッダをインクルードする部分

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

stdio.hはprintf、scanf、putsを使うために必要です。

string.hはstrcmpとstrncmpを使うために必要です。

文字列を格納する配列

char word1[100];
char word2[100];

入力された2つの英単語を格納するために、100要素のchar型配列を2つ用意しています。

word1とword2は独立した配列です。

比較する文字数を格納する変数

size_t count;

strncmpの第3引数はsize_t型です。そのため、比較する文字数を格納するcountもsize_t型で宣言しています。

size_t型の値をscanfで入力するときは%zuを使います。

1つ目の英単語を入力する部分

if (scanf("%99s", word1) != 1) {
    puts("1つ目の英単語を読み取れませんでした。");
    return 1;
}

scanfと%sで英単語を読み取ります。

%99sと指定することで、最大99バイトまでをword1へ格納し、最後にナル文字を付けられるようにしています。

scanfの戻り値が1でなければ、文字列を正常に読み取れなかったと判断してプログラムを終了します。

2つ目の英単語を入力する部分

if (scanf("%99s", word2) != 1) {
    puts("2つ目の英単語を読み取れませんでした。");
    return 1;
}

2つ目の英単語をword2へ読み込みます。

word1とword2に同じ文字が入っていても、2つは別々の配列です。文字列の内容を比較するため、後でstrcmpを使います。

strcmpで全体を比較する部分

if (strcmp(word1, word2) == 0) {
    puts("2つの英単語は全体が一致しています。");
} else {
    puts("2つの英単語は全体が異なります。");
}

strcmpでword1とword2の文字列全体を比較します。

戻り値が0であれば、2つの文字列は完全に一致しています。0以外であれば、どこかに違いがあります。

stationとstatusは5文字目から異なるため、全体比較の結果は0以外になります。

比較する文字数を入力する部分

if (scanf("%zu", &count) != 1) {
    puts("比較する文字数を読み取れませんでした。");
    return 1;
}

strncmpで比較する最大文字数をsize_t型のcountへ入力しています。

scanfの戻り値が1でなければ入力失敗として処理を終了します。

strncmpで先頭部分を比較する部分

if (strncmp(word1, word2, count) == 0) {
    printf("先頭から%zu文字の範囲は一致しています。\n", count);
} else {
    printf("先頭から%zu文字の範囲は異なります。\n", count);
}

strncmpで、word1とword2を先頭から最大count文字比較します。

countに3を入力した場合は、stationとstatusの先頭3文字を比較します。どちらもstaなので、strncmpは0を返します。

if文で等しいことを判定する書き方

strcmpやstrncmpで等しいことを判定する場合は、戻り値が0かどうかを確認します。

最も意味が分かりやすい書き方は次の形です。

if (strcmp(word1, word2) == 0) {
    /* 等しい場合 */
}

論理否定演算子!を使って、次のように書くこともできます。

if (!strcmp(word1, word2)) {
    /* 等しい場合 */
}

2つの書き方は、どちらも文字列が等しい場合にif文の中を実行します。

書き方意味
strcmp(word1, word2) == 0戻り値が0なら等しい
!strcmp(word1, word2)戻り値0を論理否定して真にする

!strcmpの動き

C言語では、条件式の値を次のように扱います。

条件式での扱い
0
0以外

strcmpは、文字列が等しいときに0を返します。そのままif文の条件にすると、等しい場合が偽になります。

論理否定演算子!を付けると、真偽が反転します。

strcmpの結果!を付けた結果
01
0以外0

そのため、!strcmpは文字列が等しい場合に真になります。

ただし、初めのうちはstrcmpの戻り値が0であることを明示する書き方のほうが読みやすいでしょう。

if (strcmp(word1, word2) == 0) {

0以外はすべて異なる

文字列が異なる場合、strcmpとstrncmpは正の値または負の値を返します。

一致しているかどうかだけを確認する場合、戻り値が正か負かを区別する必要はありません。

if (strcmp(word1, word2) != 0) {
    /* 文字列が異なる */
}

大小関係も調べたい場合は、0より大きいか小さいかを確認します。

int result = strcmp(word1, word2);

if (result < 0) {
    puts("word1のほうが小さいです。");
} else if (result > 0) {
    puts("word1のほうが大きいです。");
} else {
    puts("2つの文字列は等しいです。");
}

文字列比較を使う場面

strcmpとstrncmpは、入力内容に応じて処理を分ける場面で役立ちます。

活用場面比較の例
ユーザー名の確認入力された名前と登録名をstrcmpで比較する
メニュー入力startやexitと完全一致するか確認する
コマンド判定コマンド全体をstrcmpで比較する
接頭辞の確認httpから始まるかstrncmpで確認する
製品コードの分類先頭の分類部分だけをstrncmpで比較する
日付の分類先頭の年だけをstrncmpで比較する

完全一致が必要な場面ではstrcmpを使います。

先頭部分だけで分類したい場合や、特定の接頭辞を確認したい場合はstrncmpが適しています。

文字列比較で確認したいこと

strcmpとstrncmpを使うときは、次の点を確認しましょう。

  • string.hをインクルードしているか
  • 比較する文字列がナル文字で終わっているか
  • 文字列内容の比較に==を使っていないか
  • 等しい場合の戻り値が0であることを理解しているか
  • 正の値と負の値の具体的な数値に依存していないか
  • 大文字と小文字を別の文字として扱っているか
  • strncmpの比較範囲nが適切か
  • nが0の場合は0が返ることを理解しているか
  • 日本語ではnと見た目の文字数が一致しない場合を考えているか

strcmpとstrncmpは、文字列を使った条件分岐の基本となる関数です。

==は文字列が置かれている場所を比較し、strcmpとstrncmpは文字列に格納されている内容を比較します。この違いを理解し、完全一致と先頭部分の比較を目的に応じて使い分けましょう。