C言語の文字列比較の基本
C言語で文字列を扱う場合、他の言語とは少し違った特徴があります。
例えば、JavaやPythonでは文字列同士を==で比較できますが、C言語ではそれができません。なぜなら、C言語の文字列は特殊な仕組みで管理されているからです。
ここでは、文字列比較を正しく行うために知っておくべき基礎知識を学びましょう。
C言語における文字列の仕組み
C言語の文字列は、文字が並んだ配列として扱われます。
そして、文字列の最後には必ず\0(null文字)という特別な文字が付いています。この\0があることで、プログラムは「ここで文字列が終わり」だと判断できるのです。
文字列を比較する際も、この\0が見つかるまで1文字ずつ順番にチェックしていく仕組みになっています。
実際のコードで、文字列の構造を確認してみましょう。
出力結果
文字列: ネコ
1文字目: ネ
2文字目: コこの例では、animalという配列に「ネコ」という文字列を格納しています。
printfで文字列全体を表示した後、各文字を個別に表示しています。
文字列比較が必要となる典型的な場面
プログラムを作っていると、文字列を比較したい場面が頻繁に出てきます。
例えば、「ユーザーの入力値に応じて、違う処理を実行したい」という場合です。
「イヌ」と入力されたら犬の情報を表示し、「ネコ」と入力されたら猫の情報を表示する、といったイメージです。
また、データベースから取得した情報を検索したり、設定ファイルの内容を読み取ったりする際にも文字列比較が必要になります。
条件分岐で文字列を比較する例を見てみましょう。
出力結果
犬が選択されましたこの例では、strcmp関数を使ってinputの内容が「イヌ」と一致するかを確認しています。
一致する場合はstrcmpは0を返すため、if文の条件が成立して「犬が選択されました」と表示されます。
strcmp関数の概要と戻り値の意味
strcmp関数は、C言語で文字列を比較する関数です。
この関数は2つの文字列を受け取り、それらを先頭から1文字ずつ比較していきます。そして、比較した結果を整数値で返します。
戻り値の意味は、次の通りです。
- 0:2つの文字列が完全に一致
- 正の値:第1引数の文字列が辞書順で後ろ
- 負の値:第1引数の文字列が辞書順で前
辞書順とは、辞書に載っている単語の並び順のことです。例えば「あ」は「い」より前で、「cat」は「dog」より前になります。
strcmp関数の戻り値を確認してみましょう。
出力結果
同じ文字列の比較結果: 0
異なる文字列の比較結果: 1最初の比較では、両方とも「ウサギ」なので戻り値は0です。2つ目の比較では、「ウサギ」と「イヌ」を比較した結果、1(正の値)が返されています。これは「ウサギ」の方が辞書順で前だからです。
strcmpによる文字列比較
strcmp関数は、C言語における文字列比較の基本となる関数です。「2つの文字列が同じかどうか知りたい」「どちらが辞書順で前か調べたい」といった場面で使います。
ここでは、実際にstrcmpを使う際の書き方や、比較結果の読み取り方、そして初心者が間違えやすいポイントについてくわしく見ていきましょう。
strcmpの基本構文
strcmp関数の基本的な書き方は、strcmp(文字列1, 文字列2)という形です。
この関数は、2つの文字列を先頭から1文字ずつ比較していきます。違いが見つかった時点で、その場で結果を返します。もし最後まで同じだった場合は、全ての比較が終わってから0を返します。
出力結果
比較結果: -1このプログラムでは、「トラ」と「ライオン」を比較しています。戻り値が負の値(-1)なので、「トラ」の方が辞書順で前だとわかります。
比較結果の使い分け
strcmp関数の戻り値は、用途に応じて2つの使い方があります。
1つ目は「2つの文字列が同じかどうかを判定する」場合です。この場合は、戻り値が0かどうかをチェックします。
2つ目は「文字列を辞書順に並べ替える(ソートする)」場合です。この場合は、戻り値が正か負かを使って、どちらが前に来るべきかを判断します。
出力結果
ゾウとゾウは同じです
キリンはゾウより辞書順で前です最初のif文では、animals[0](ゾウ)とanimals[2](ゾウ)を比較して、戻り値が0なので「同じです」と表示されています。
2つ目のif文では、strcmpの戻り値が負の値なので、キリンの方が辞書順で前だと判定されています。
strcmp使用時のよくあるミス
strcmpを使う際、初心者がよく間違えるポイントがいくつかあります。代表的なミスを3つほど挙げてみます。
1つ目は、代入演算子(=)と比較演算子(==)を混同してしまうことです。条件判定では必ず==を使う必要があります。
2つ目は、配列とポインタの違いを理解せずに使ってしまうことです。特に、ポインタが正しくメモリを指しているかを確認せずに使うと、プログラムが予期しない動作をします。
3つ目は、NULLポインタ(何も指していないポインタ)をstrcmpに渡してしまうことです。これをすると、プログラムが異常終了してしまいます。そのため、比較する前に必ずNULLチェックを行う必要があります。
よくあるミスとその対策例を見てみましょう。
出力結果
比較できませんこの例では、animal2がNULLで、strcmpを実行する前にNULLチェックをするような構造になっています。animal2 != NULLという条件が偽(false)になるため、strcmpは実行されず、安全に「比較できません」と表示されます。
ちなみに、もし下記のように
と書いてしまうと、先にstrcmpが実行されてしまい、NULLを読み取ろうとしてプログラムが異常終了してしまいます。
「NULLチェックは必ず先に書く」 というのが、鉄則です。
大文字小文字を区別しない比較
strcmp関数は、大文字と小文字を別の文字として扱います。
例えば、「CAT」と「cat」は同じ単語でも、strcmpでは異なる文字列と判定されます。
大文字小文字を無視して比較したい場合は、比較する前にtolower関数で全て小文字に統一するか、環境に応じてstrcasecmp関数を使用します。
tolower関数を使った大文字小文字を区別しない比較の例です。
出力結果
大文字小文字を無視すると同じですtolower関数の使い方は簡単で、tolower(‘A’)のように括弧の中に変換したい文字を入れるだけです。戻り値として小文字が返ってくるので、それを変数に代入したり、そのまま使ったりできます。
注意点として、tolower関数は1文字ずつしか処理できません。文字列全体を小文字にしたい場合は、ループを使って1文字ずつ処理する必要があります。
このプログラムでは、tolower関数を使って両方の文字列を小文字に変換してからstrcmpで比較しています。これにより、「CAT」と「cat」が同じだと判定されます。
ちなみに、tolower関数を使うには、ctype.hというヘッダーファイルをインクルードする必要があります。
実際、冒頭にinclude <ctype.h>の記述が確認できるはずです。
このヘッダーファイルには、文字の種類を判定したり変換したりする関数がまとめられています。
部分一致や応用的な文字列比較処理
ここまでは「2つの文字列が完全に一致するか」を調べる方法を学びました。
しかし実際のプログラミングでは、「文字列の一部が含まれているか」や「先頭だけが一致しているか」といった、より柔軟な比較が必要になることがあります。
ここでは、そういった応用的な文字列比較の方法を学びましょう。
部分一致判定の基本手法
部分一致を調べる最も基本的な方法は、strstr関数を使うことです。
strstr関数は、「ある文字列の中に、別の文字列が含まれているか」を検索してくれます。見つかった場合は、その位置を指すポインタを返し、見つからなかった場合はNULLを返します。
戻り値がNULLかどうかをチェックすることで、部分一致しているかどうかを判定できます。
strstr関数を使った部分一致の例を見てみましょう。
出力結果
「大きなクマが森にいます」に「クマ」が含まれていますstrstr関数は、
という形で使います。
このプログラムでは、「大きなクマが森にいます」という文字列の中に「クマ」が含まれているため、strstrがNULLではない値を返します。その結果、if文の条件が成立して「含まれています」と表示されます。
ちなみに、テキストを変更すると「見つかりませんでした」と表示されるはずです。ぜひ、実際に確認してみてください。
先頭一致と末尾一致の実装方法
先頭一致を調べたい場合は、strncmp関数を使います。
strncmpはstrcmpと似ていますが、「指定した文字数だけ比較する」という点で違います。末尾一致を調べる場合は、文字列の長さを取得してから、後ろから指定した文字数分を比較します。
この手法を使うと、ファイル名の拡張子をチェックしたり、URLがどのプロトコルで始まっているかを判定したりできます。
出力結果
「シロ」で始まっています
「クマ」で終わっていますstrncmp関数は、
という形で使います。ここではstrlen(prefix)で「シロ」の文字数を取得し、その文字数分だけ比較しています。
末尾一致の判定では、まず文字列全体の長さと、調べたい文字列の長さを取得します。
そしてanimal + len - suf_lenという式で、末尾部分を指すポインタを作り、strcmpで比較しています。
ポインタに数値を足すと、その分だけ後ろの位置を指すことができます。ここでは文字列の末尾から「クマ」の長さ分だけ戻った位置を指定しているというわけです。
ワイルドカード文字を使った簡易的なマッチング
アスタリスク(*)のようなワイルドカード文字を使った、簡易的なパターンマッチングも自作できます。
ワイルドカードとは、「どんな文字列にも一致する」特別な記号のことです。例えば「ハム*」というパターンは、「ハムスター」や「ハムサンド」など、「ハム」で始まる全ての文字列に一致します。
基本的な考え方は、ワイルドカード文字が出現した時点で、残りの文字列に対して再帰的にマッチング処理を行うことです。
簡単なワイルドカードマッチング関数の例を見てみましょう。
出力結果
パターンにマッチしましたこの関数は再帰関数と呼ばれるもので、自分自身を呼び出しながら処理を進めます。
*patternは現在見ている文字を指します。
一番のポイントは、パターンの中に*が現れたときです。 プログラムは「ここで*を終わらせるパターン」と「まだ*を続けて次の文字も読み込むパターン」の両方のルートを同時に探索します。
どちらか一方のルートでも文字列の終わりまで正しくたどり着ければ、「マッチした」という結果が返ってくる仕組みです。
この例では「ハム*」というパターンで「ハムスター」を検索しているため、マッチが成功します。
これは、初心者の方にとってはかなり難易度が高い「再帰」というテクニックのため、今は完璧に理解できなくても問題ありません。
文字列比較におけるポインタ・配列の理解
C言語で文字列比較を安全かつ効率的に行うには、ポインタと配列の違いをしっかり理解することが大切です。
「ポインタ」と「配列」は似ているようで、実はメモリ上での扱い方が全く違います。この違いを理解していないと、プログラムが予期せず動作したり、最悪の場合はクラッシュしてしまったりします。
ここでは、メモリ上で文字列がどのように扱われているかと、それが比較処理にどう影響するかを学びましょう。
char配列とcharポインタの違い
char配列は、固定サイズのメモリ領域を確保して、そこに文字列を格納します。
一方、charポインタは、文字列が格納されているメモリのアドレス(住所)を指すだけです。
配列の場合は内容を自由に変更できますが、文字列リテラル(プログラムに直接書いた文字列)を指すポインタの内容は変更できません。変更しようとするとエラーになります。
出力結果
配列の比較: 0
ポインタの比較: 0
変更後の配列: moalastrcmpでの比較結果はどちらも0なので、配列でもポインタでも正しく比較できています。
しかし、配列の場合はarray_animal[0] = 'm'のように内容を変更できます。一方、ポインタが指す文字列リテラルは読み取り専用なので、変更しようとするとエラーになります。
ちなみに、配列はchar array_animal[] = "koala"と宣言すると、メモリ上に文字列のコピーが作られます。
ポインタはchar* pointer_animal = "koala"と宣言すると、プログラム内の固定された場所にある文字列を指すだけです。
ポインタを使った文字列参照と比較の流れ
ポインタを使って文字列を操作する場合、strcmp関数は内部でポインタが指すアドレスから1文字ずつ読み取りながら比較を行います。
このとき、ポインタが有効なメモリ領域を指していることと、文字列が\0で正しく終端されていることが前提となります。
この条件が満たされていないと、プログラムが予期しない動作をしたり、クラッシュしたりします。
出力結果
ptr1が指す文字列: ウマ ヒツジ
ptr2が指す文字列: ヒツジ
比較結果: -118ptr1は配列の先頭を指しているので、「ウマ ヒツジ」全体を指します。
ptr2はanimals + 7で、配列の4文字目から始まる位置を指しています。ポインタに数値を足すと、その分だけ後ろの位置を指すようになります。そのためptr2は「ヒツジ」の部分を指します。
strcmp(ptr1, ptr2)で比較すると、「ウマ ヒツジ」と「ヒツジ」を比較することになるため、戻り値は-118(負の値)になります。これは「ウマ ヒツジ」の方が辞書順で前だからです。
ちなみに、C言語において、animals + 7 の「7」は「7文字目」ではなく、「7バイト目」を意味します。
現在の多くの実行環境(UTF-8)では、日本語の全角文字は1文字=3バイトで保存されています。
- 0〜2バイト目: 「ウ」
- 3〜5バイト目: 「マ」
- 6バイト目: 「 」(半角スペースは1バイト)
- 7〜9バイト目: 「ヒ」
のようになるため、「7」で指定しているというわけです。
nullポインタと未初期化ポインタに注意した比較処理
NULLポインタは、「何も指していない」ことを明示的に表すポインタです。
一方、未初期化ポインタは、宣言しただけで値を設定していないポインタで、不定な値を持っています。
どちらもstrcmp関数に渡すと、プログラムが異常終了してしまいます。そのため、文字列を比較する前に、必ずポインタが有効な値を持っているかをチェックする必要があります。
出力結果
安全な比較結果: 1safe_strcmp関数は、strcmpを呼び出す前にNULLチェックを行います。
まず、両方がNULLなら同じとみなして0を返します。片方だけがNULLの場合は、NULLの方を「小さい」とみなして適切な値を返します。両方とも有効なポインタの場合だけ、strcmpを呼び出します。
この例ではanimal2がNULLなので、strcmpを実行せずに1を返しています。これにより、プログラムが異常終了するのを防いでいます。
NULLチェックは面倒に感じるかもしれませんが、安全なプログラムを作る上で欠かせない処理です。また、無駄な処理を避けて効率的なプログラムを組むという点でも大事なので覚えておきましょう。
動的メモリ確保と文字列比較の安全性
malloc関数で動的にメモリを確保して作った文字列を比較する場合、いくつか注意点があります。
まず、メモリが正しく確保できているかを確認する必要があります。mallocは失敗するとNULLを返すため、この確認を怠るとプログラムがクラッシュします。次に、文字列が\0で正しく終端されているかも確認しましょう。そして最後に、使い終わったメモリは必ずfree関数で解放する必要があります。解放しないと、メモリリークと呼ばれる問題が発生します。
動的メモリを使った安全な文字列比較の例を見てみましょう。
出力結果
動的に確保した文字列が一致しましたmalloc(20)で20バイトのメモリを確保しています。
mallocは、
という形で書きます。
確保に成功したかをif (dynamic_animal != NULL)でチェックしています。成功した場合のみ、strcpyで文字列をコピーし、strcmpで比較しています。
そして最も重要なのが、最後のfree(dynamic_animal)です。これを忘れると、確保したメモリが解放されずに残り続けてしまいます。
動的メモリの確保と解放は、必ずセットで行うことを覚えておきましょう。
実践的な文字列比較テクニック
実際にプログラムを作る際には、
「たくさんの文字列を効率よく比較したい」
「処理速度を上げたい」
「安全性を確保したい」
といった、より実践的な課題に直面します。
ここでは、実際の開発現場でよく使われる、一歩進んだ文字列比較のテクニックを学びましょう。
複数文字列を扱う際の効率的な比較方法
たくさんの文字列を比較する場合、ループ処理を使って効率的に処理することが大切です。
例えば、動物のリストの中から特定の動物を探したい場合、1つずつ順番に比較していきます。
このとき、事前に文字列の長さをチェックしたり、最初の文字で振り分けたりすることで、無駄な比較を減らすことができます。
特に大量のデータを扱う場合は、このような工夫が処理速度の向上につながります。
出力結果
ウサギを3番目で見つけましたこのプログラムでは、animalsという配列に5つの動物名が入っています。
sizeof(animals) / sizeof(animals[0])という部分で、配列に何個の要素が入っているかを計算しています。sizeof(animals)は配列全体のサイズ、sizeof(animals[0])は1つの要素のサイズなので、割り算すると要素数が求められるというわけです。
forループを使って1つずつstrcmpで比較し、見つかったらbreakでループを抜けています。これにより、見つかった時点で残りの比較をスキップできるので、無駄な処理をせずに済み、効率的です。
用途に応じた関数の選び方
文字列比較の目的に応じて、適切な関数を選ぶことが大切です。
完全一致を調べたいならstrcmp、指定した文字数だけ比較したいならstrncmp、部分一致を調べたいならstrstrを使いましょう。
それぞれの関数には得意な処理があるので、目的に合った関数を選ぶことで、処理速度と正確性を両立できます。
出力結果
テキストファイルです
セキュアな接続です1つ目の例では、ファイル名に「.txt」が含まれているかをstrstrで調べています。strstrは文字列の中に指定した文字列が含まれていれば、その値のメモリ上の住所(ポインタ)を返します。含まれていなければNULLを返します。
2つ目の例では、URLが「https://」で始まっているかをstrncmpで調べています。strncmp(url, "https://", 8)は、最初の8文字だけを比較します。8は「https://」の文字数です。
このように、目的に合わせて関数を使い分けることで、わかりやすく効率的なプログラムが書けます。
ケースインセンシティブ比較の実装方法
ケースインセンシティブ比較とは、大文字小文字を区別しない比較のことです。
例えば、ユーザーが「PANDA」と入力しても「panda」と入力しても、同じものとして扱いたい場合があります。このような比較を実現するには、比較する前に両方の文字列を小文字(または大文字)に統一する方法が一般的です。
あるいは、大文字小文字を区別せずに比較する専用の関数を自作することもできます。
出力結果
大文字小文字を無視すると同じですcase_insensitive_strcmp関数は、比較しながら同時に小文字に変換しています。
while (*str1 && *str2)は、どちらの文字列もまだ終わっていない間、ループを続けるという意味です。
ループの中で、tolower関数を使って現在の文字を小文字に変換し、それを比較しています。文字が異なれば、その差を返します。文字が同じなら、次の文字に進みます(str1++とstr2++)。
最後まで同じだった場合は、return tolower(*str1) - tolower(*str2)で、両方とも\0なので0が返されます。
この関数を使えば、「PANDA」と「panda」を同じものとして扱えます。
比較前の入力値検証と安全対策
プログラムの安全性を確保するには、文字列を比較する前に入力値をチェックすることが大切です。
具体的には、ポインタがNULLでないか、文字列の長さが想定内か、想定外の文字が含まれていないかなどを確認します。
このようなチェックを行うことで、エラーやセキュリティ上の問題を未然に防ぐことができます。
出力結果
安全に比較が完了しましたこのvalidate_and_compare関数は、比較する前に2つのチェックを行っています。
1つ目は、NULLチェックです。どちらかの文字列がNULLなら、エラーメッセージを表示して-999という特別な値を返します。
2つ目は、文字列の長さチェックです。strlenで文字列の長さを調べ、指定された最大長(max_len)を超えていたら、エラーメッセージを表示します。
両方のチェックをパスした場合のみ、strcmpで比較を行います。
このように、事前チェックを行うことで、想定外の入力によるトラブルを防ぐことができます。
エラー処理は面倒に感じるかもしれませんが、安全なプログラムを作る上で非常に重要です。
よくある質問(Q&A)
Q: strcmpの戻り値が0以外の時の具体的な値は何を意味しますか?
A: 戻り値の正負は文字コードの大小関係を表します。正の値は第1引数が辞書順で後ろ、負の値は前になることを示しており、ソート処理などで活用できます。
Q: 文字列リテラルとchar配列の比較で注意点はありますか?
A: 文字列リテラルは読み取り専用のメモリ領域に格納されるため、内容を変更しようとするとエラーになります。比較自体は問題ありませんが、後で内容を変更する可能性がある場合はchar配列を使用してください。
Q: 日本語の文字列比較で文字化けが起こる原因は何ですか?
A: 文字エンコーディングの不一致が主な原因です。文字エンコーディングとは、文字をコンピュータ内部で扱うための変換方式のことです。UTF-8やShift_JISなど、いくつかの種類があります。プログラム全体で同じエンコーディングを使うことで、文字化けを防ぐことができます。
Q: strlen関数とstrcmpの処理速度の違いはありますか?
A: strlen関数は文字列の終端まで読む必要がありますが、strcmpは違いが見つかった時点で処理を終了します。そのため、異なる文字列の比較ではstrcmpの方が高速になる場合があります。
Q: 配列の要素数を超えた比較を防ぐ方法はありますか?
A: strncmp関数を使用して比較する文字数を制限したり、事前にstrlen関数で文字列長をチェックしたりすることで、バッファオーバーフローを防げます。
まとめ
この記事では、C言語における文字列比較の基礎から応用まで、幅広く解説してきました。
strcmp関数を活用したいのは、次のような場面です。
strcmp関数が活躍する場面
- ユーザーが入力した文字列が特定の値と一致するか判定したいとき
- データベースや配列の中から特定の文字列を検索したいとき
- 文字列を辞書順(五十音順)に並べ替えたいとき
- パスワードや設定値の照合を行いたいとき
文字列比較について、押さえておきたいポイントは次の通りです。
重要なポイント
- strcmp関数の戻り値の意味
- NULLポインタチェックを行い、プログラムの異常終了を防ぐ
- 完全一致にはstrcmp、指定文字数の比較にはstrncmp、部分一致にはstrstrが適する
- ポインタと配列の違い
- 大文字小文字を区別しない比較にはtolower関数との組み合わせが有効
- 動的メモリを使った場合は、必ずメモリを解放する
この記事の内容は、初心者にとっては難しい内容も多く含まれていたかと思います。
例えば、ワイルドカードを使った再帰的なマッチング処理や、ポインタと配列の違いのコード例は、C言語を学び始めたばかりの方にはハードルが高く感じられたかもしれません。
しかし、もしわからなかったとしても焦る必要はありません。
まずは、strcmp関数の基本的な使い方と、戻り値が0なら一致するという点をしっかり押さえましょう。
それだけでも、ユーザー入力の判定やデータ検索といった実用的なプログラムが作れるようになります。
応用的な内容は、基本をマスターしてから少しずつ挑戦していけば大丈夫です。最初は完全一致の判定から始めて、慣れてきたら部分一致や大文字小文字を区別しない比較に挑戦してみてください。
さらに余裕ができたら、ポインタや動的メモリの扱いを学ぶ、というように段階的にステップアップしていきましょう。
プログラミングは、小さな成功体験を積み重ねながら上達していくものです。この記事で学んだ内容を、実際に手を動かしてみながら少しずつ成長していきましょう。