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

トップページ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

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

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

方法としては、それぞれの文字列を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 を返す。
*/
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("", ""));
}

実行結果:

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関数を使う)[Windows]

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

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

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

    s1:       比較する文字列
    s2:       比較する文字列
    戻り値:   s1 の方が小さい場合は 0 より小さい値、
              s1 の方が大きい場合は 0 より大きい値、
              s1 と s2 が一致する場合は 0 を返す。
*/
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("", ""));
}

実行結果:

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 のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る