C言語編 逆引き 大文字・小文字の違いを無視して文字列を比較する

先頭へ戻る

目的

アルファベットの大文字と小文字の違いを無視して、文字列比較を行いたいとします。

標準ライブラリの strcmp関数と同じ感覚で使える方が便利でしょう。引数や戻り値の仕様を同じにして、独自の strcmp_ignorecase関数を作ります。例えば、以下の printf関数の呼び出しのそれぞれが、コメントに示した結果を出力することを目指します。

printf("%d\n", strcmp_ignorecase("abc", "abc"));  /* 0 */
printf("%d\n", strcmp_ignorecase("abc", "ABC"));  /* 0 */
printf("%d\n", strcmp_ignorecase("abc", "Abc"));  /* 0 */
printf("%d\n", strcmp_ignorecase("abc", "ABc"));  /* 0 */

printf("%d\n", strcmp_ignorecase("abc", "axC"));  /* 0 より小さい */
printf("%d\n", strcmp_ignorecase("axc", "abC"));  /* 0 より大きい */

printf("%d\n", strcmp_ignorecase("abcd", "ABc")); /* 0 より大きい */
printf("%d\n", strcmp_ignorecase("abc", "ABcd")); /* 0 より小さい */
printf("%d\n", strcmp_ignorecase("", "ABc"));     /* 0 より小さい */
printf("%d\n", strcmp_ignorecase("abc", ""));     /* 0 より大きい */
printf("%d\n", strcmp_ignorecase("", ""));        /* 0 */

方法①(大文字や小文字に統一して比較する)[C89~]

残念ながら、標準ライブラリ関数に一発で目的を達する関数はありません。そのため、自力で実装することが多いです。

方法としては、それぞれの文字列を1文字ずつ、小文字(あるいは大文字)のどちらかに合わせてから比較することを繰り返します。ここでは小文字に合わせることにします。

ただし、ASCIIコードにおいては、A~Z と a~z の間に([, \, ], ^, _, `)があるため、これらの文字が含まれている場合には、大文字と小文字のどちらに統一するかによって、結果に違いが生まれてきます。つまり、'a' と '^' の比較が、'a' と '^' として行われれば '^' の方が小さいですし、'A' と '^' として行われれば '^' の方が大きいことになります。大文字の方が、数値(文字コード)としては小さいので、このような結果になります。「一致するかしないか」にしか興味が無いケースが多く、その場合は特に問題ありませんが、本当に大小関係を知る必要がある場合には注意が必要です。

アルファベットを小文字化するには、標準ライブラリ関数の tolower関数を使用します。「逆引き 文字列を大文字化・小文字化する」の方法を使って、文字列全体を大文字や小文字に統一することはできますが、これは、元の文字列を壊してしまうため好ましくありません。

#include <ctype.h>
#include <stdio.h>

/*
    文字列を大文字・小文字の違いを無視して比較する

    s1:       比較する文字列
    s2:       比較する文字列
    戻り値:   s1 の方が小さい場合は 0 より小さい値、
              s1 の方が大きい場合は 0 より大きい値、
              s1 と s2 が一致する場合は 0 を返す。
*/
static int strcmp_ignorecase(const char* s1, const char* s2)
{
    /* 1文字ずつ、小文字に統一して比較する。
       この比較が真になり続けた場合、2つの文字列は一致する。
    */
    while (tolower(*s1) == tolower(*s2)) {

        /* s1とs2 が同時に '\0' に到達したのなら、2つの文字列は一致している。
           一方が先に '\0' に到達するケースでは、while文の条件式を満たさなくなるため、
           while文から脱出している。
        */
        if (*s1 == '\0') {
            return 0;
        }

        ++s1;
        ++s2;
    }

    /* 一致しなくなった文字同士で比較して、
       s1 の側が小さいなら 0 より小さい値を、s1 の側が大きいなら 0 より大きい値を返す。
    */
    return tolower(*s1) - tolower(*s2);
}

int main(void)
{
    printf("%d\n", strcmp_ignorecase("abc", "abc"));
    printf("%d\n", strcmp_ignorecase("abc", "ABC"));
    printf("%d\n", strcmp_ignorecase("abc", "Abc"));
    printf("%d\n", strcmp_ignorecase("abc", "ABc"));

    printf("%d\n", strcmp_ignorecase("abc", "axC"));
    printf("%d\n", strcmp_ignorecase("axc", "abC"));

    printf("%d\n", strcmp_ignorecase("abcd", "ABc"));
    printf("%d\n", strcmp_ignorecase("abc", "ABcd"));
    printf("%d\n", strcmp_ignorecase("", "ABc"));
    printf("%d\n", strcmp_ignorecase("abc", ""));
    printf("%d\n", strcmp_ignorecase("", ""));
    
    return 0;
}

実行結果:

0
0
0
0
-22
22
68
-68
-65
65
0

比較する文字列の長さが同じであるとは限らないため、意外と実装は難しくなります。

2つの文字列を先頭から1文字ずつ、小文字化して比較することを繰り返します。両者が '\0' に到達するまでの間、ずっと一致し続けた場合は、両者は一致しますから 0 を返せば良いです。

途中で 'b' と 'x' のように、一致しない文字が現れた場合、どちらが小さいか判断して戻り値を返します。このとき、if文を使って大小比較をしてもいいですし、減算で実装してもいいです。strcmp関数でもそうですが、-1、0、1 のいずれかを返すという仕様ではなく、0より小さい,0,0より大きい値を返すという仕様ですから、減算での実装もあり得ます。

もし、他方の文字列が短い場合には、いずれ、文字列に含まれているある文字と、'\0' を比較するときが来ます。これは絶対に一致しませんから、その時点で処理を終了させます。'\0' は、数値でいえば 0 なので、'b' と 'x' のような比較のときと同じ方法で大小関係を行えば、目的通りの結果を得られます。

方法②(_stricmp関数を使う)[VisualStudio]

非標準の _stricmp関数(→参考)を使うと、目的を達せられます。

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

/*
    文字列を大文字・小文字の違いを無視して比較する

    s1:       比較する文字列
    s2:       比較する文字列
    戻り値:   s1 の方が小さい場合は 0 より小さい値、
              s1 の方が大きい場合は 0 より大きい値、
              s1 と s2 が一致する場合は 0 を返す。
*/
static int strcmp_ignorecase(const char* s1, const char* s2)
{
    return _stricmp(s1, s2);
}

int main(void)
{
    printf("%d\n", strcmp_ignorecase("abc", "abc"));
    printf("%d\n", strcmp_ignorecase("abc", "ABC"));
    printf("%d\n", strcmp_ignorecase("abc", "Abc"));
    printf("%d\n", strcmp_ignorecase("abc", "ABc"));

    printf("%d\n", strcmp_ignorecase("abc", "axC"));
    printf("%d\n", strcmp_ignorecase("axc", "abC"));

    printf("%d\n", strcmp_ignorecase("abcd", "ABc"));
    printf("%d\n", strcmp_ignorecase("abc", "ABcd"));
    printf("%d\n", strcmp_ignorecase("", "ABc"));
    printf("%d\n", strcmp_ignorecase("abc", ""));
    printf("%d\n", strcmp_ignorecase("", ""));
    
    return 0;
}

実行結果:

0
0
0
0
-22
22
100
-100
-97
97
0

_stricmp関数を使うには、string.h をインクルードします。引数と戻り値の仕様は、strcmp関数と同様です。

_stricmp関数は、内部処理的には、小文字に統一して比較を行います。


参考リンク

更新履歴

'2018/5/1 全体的に文章を見直して修正。内容的な変更は無し。

'2018/4/2 「VisualC++」という表現を「VisualStudio」に統一。

'2018/2/18 新規作成。





逆引きのトップページへ

C言語編のトップページへ

Programming Place Plus のトップページへ


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS