C言語の文字列操作、なんだか難しそう…って思っていませんか?
文字の配列? NULL終端文字? strcpyって何? そんな疑問が頭の中をぐるぐる…
わかります、その気持ち! C言語の文字列は、ちょっとクセがあるんですよね。
この記事では、C言語の文字列操作の「いろは」から、よく使う関数 `strcpy`, `strcat`, `strcmp` などの使い方、そして「うっかり」やりがちなミスまで、図解(のイメージ)やサンプルコードをたっぷり使って、どこよりも分かりやすく解説していきます。
さあ、肩の力を抜いて、一緒に文字列操作の世界を探検しましょう!読み終わる頃には、きっと「なるほど!」ってスッキリしているはずですよ。
この記事を読むと、こんなことができるようになります!
- C言語の文字列がどういうものか分かる
- 文字列の宣言や初期化が自分でできる
- strcpy関数で安全に文字列をコピーする方法が分かる
- strlen関数で文字列の長さを取得できる
- strcat関数で安全に文字列を連結する方法が分かる
- strcmp関数で文字列同士を比較できるようになる
- 文字列操作で注意すべき点が分かる
C言語における文字列とは?基本を理解しよう
まず、C言語の世界で「文字列」がどう扱われているか、その基本から見ていきましょう。
コンピューターは基本的に数字しか扱えませんが、文字を扱うために「文字コード」というルールがあります。例えば、'A'という文字には65という番号が割り当てられています(ASCIIコードの場合)。
C言語では、この文字(char型データ)を複数個並べたものを「文字列」として扱います。つまり、文字列の実体は「char型の配列」なのです。
例えば、"hello" という文字列は、メモリ上では 'h', 'e', 'l', 'l', 'o' という5つの文字が順番に並んでいます。
+---+---+---+---+---+ | h | e | l | l | o | +---+---+---+---+---+でも、これだけだとコンピューターはどこまでが文字列なのか分かりません。「hello」の次にもし別のデータがあったら、どこで区切ればいいのでしょう?
そこで登場するのが「NULL終端文字(ヌルしゅうたんもじ)」、記号で書くと `\0` です。
これは「文字列はここで終わりですよー!」という特別な目印の文字なんです。文字コードの値は0です。
C言語では、文字列の最後には必ずこの `\0` を付けるというルールがあります。これがないと、文字列として正しく認識されません。
なので、"hello" という文字列は、実際にはメモリ上でこうなっています。
+---+---+---+---+---+----+ | h | e | l | l | o | \0 | +---+---+---+---+---+----+このNULL終端文字 `\0` を意識することが、C言語の文字列操作をマスターする上でめちゃくちゃ大事なポイントです!忘れないでくださいね。
C言語の文字列操作の第一歩:宣言と初期化
文字列がchar型の配列で、最後に `\0` が付くルールだと分かりましたね。
では、実際にプログラムで文字列を使うために、まずは文字列を入れるための「箱」(配列)を用意する「宣言」と、その箱に最初から文字を入れておく「初期化」の方法を見ていきましょう。
文字列リテラルによる初期化
一番簡単でよく使われるのが、ダブルクォーテーション `"` で囲んだ文字(これを文字列リテラルと呼びます)を使って初期化する方法です。
こんな風に書きます。
#include <stdio.h> int main(void) { // "hello" という文字列で配列 s1 を初期化 char s1[] = "hello"; // 配列のサイズを明示的に指定することも可能 (文字数5 + NULL文字1 = 6) char s2[6] = "hello"; printf("s1: %s\n", s1); printf("s2: %s\n", s2); return 0; }実行結果:
s1: hello s2: helloこの書き方の良い点は、文字列リテラルを使うと、コンパイラが自動的に最後に `\0` を付けてくれることです。楽ちんですね!
配列要素ごとの初期化
もう一つの方法として、配列の要素を一つずつ文字で指定して初期化する方法もあります。
#include <stdio.h> int main(void) { // 一文字ずつ指定して初期化 char s3[] = {'h', 'e', 'l', 'l', 'o', '\0'}; // ← 自分で \0 を入れる! // サイズを指定する場合 (文字数5 + NULL文字1 = 6) char s4[6] = {'h', 'e', 'l', 'l', 'o', '\0'}; printf("s3: %s\n", s3); printf("s4: %s\n", s4); return 0; }実行結果:
s3: hello s4: helloこの方法だと、文字リテラルと違って、自分で最後に `\0` を入れないと、ただの文字の集まりになってしまい、文字列として扱えません。めちゃくちゃ注意が必要です!
例えば、`char s5[] = {'h', 'e', 'l', 'l', 'o'};` と `\0` を忘れると、`printf` などで表示しようとしたときに、どこまで表示すればいいか分からず、予期せぬ動作(メモリ上のゴミデータを延々と表示しようとしたり、プログラムが停止したり)を引き起こす可能性があります。怖いですね。
C言語の文字列操作の基本:代入とコピー (strcpy, strncpy)
配列を宣言した後で、その配列に文字列を入れたい(コピーしたい)場面はよくあります。
例えば、こんな感じです。
char destination[20]; // 文字列を入れるための配列を用意 // ここに "hello world" という文字列を入れたい!他の変数みたいに `=` を使って `destination = "hello world";` と書きたくなりますが、残念ながらC言語の配列ではこれはできません。
なぜなら、`destination` という配列名は、配列の先頭のアドレス(メモリ上の場所)を示しているからです。配列そのものに直接何かを代入する、という操作はできないルールになっています。
そこで登場するのが、文字列をコピーするための標準ライブラリ関数 `strcpy` です!
これを使うには、まずプログラムの先頭で `string.h` というヘッダファイルをインクルードする必要があります。おまじないみたいなものですね。
strcpy関数の使い方と注意点
`strcpy` (エスティーアールコピーと読みます) は、文字列を丸ごとコピーする関数です。
`strcpy(コピー先の配列名, コピー元の文字列);` のように使います。
使い方(サンプルプログラムの紹介と実行結果、サンプルプログラムの紹介)
#include <stdio.h> #include <string.h> // strcpy を使うために必要 int main(void) { char source[] = "hello world"; // コピー元の文字列 char destination[20]; // コピー先の配列 (十分なサイズを用意) // source の内容を destination にコピー strcpy(destination, source); printf("コピー元 (source): %s\n", source); printf("コピー先 (destination): %s\n", destination); return 0; }実行結果:
コピー元 (source): hello world コピー先 (destination): hello world
サンプルプログラムの解説
`strcpy(destination, source);` によって、`source` の内容("hello world" と最後の `\0`)が `destination` 配列にコピーされました。
ただし、`strcpy` には非常に危険な落とし穴があります。それは、コピー先の配列サイズをチェックしてくれないことです!
もし、コピー先の配列 `destination` のサイズが、コピー元の文字列 `"hello world"`(NULL文字含めて12文字)よりも小さかったらどうなるでしょう?
char destination[5]; // サイズが5しかない! strcpy(destination, "hello world"); // ← 危険!配列の範囲を超えて書き込んでしまう!この場合、`strcpy` はお構いなしに `destination` 配列の範囲を超えてメモリにデータを書き込んでしまいます。これを「バッファオーバーフロー」と呼び、プログラムが異常終了したり、セキュリティ上の問題を引き起こしたりする深刻な原因になります。
strncpy関数の使い方と安全なコピー
`strcpy` のバッファオーバーフロー問題を回避するために用意されたのが `strncpy` (エスティーアールエヌコピー) です。
`strncpy` は、コピーする最大文字数を指定できます。
`strncpy(コピー先の配列名, コピー元の文字列, 最大文字数);` のように使います。
使い方(サンプルプログラムの紹介と実行結果、サンプルプログラムの紹介)
#include <stdio.h> #include <string.h> // strncpy を使うために必要 int main(void) { char source[] = "hello world"; char destination[10]; // サイズは10 // source から destination へ、最大 9文字までコピーする // 配列サイズより1小さい値を指定するのがコツ strncpy(destination, source, 9); // ★重要★ strncpy はNULL終端しないことがあるので、手動で付ける! destination[9] = '\0'; printf("コピー先 (destination): %s\n", destination); return 0; }実行結果:
コピー先 (destination): hello wor
サンプルプログラムの解説
`strncpy(destination, source, 9);` で、`source` から最大9文字("hello wor")が `destination` にコピーされました。配列サイズ(10)を超えて書き込まれる心配はありません。
ただし、`strncpy` にも注意点があります。それは、指定した最大文字数分コピーした場合、最後にNULL文字 `\0` を自動で付けてくれないことがある点です。
上の例では、9文字コピーしたので、`destination` 配列の最後の要素(インデックス9)にNULL文字がありません。そのまま使うと文字列として正しく扱えない可能性があります。
そのため、`strncpy` を使った後は、必ず配列の最後に手動で `destination[配列サイズ - 1] = '\0';` のようにNULL文字を書き込む習慣をつけましょう。(この例では `destination[9] = '\0';`)
少し手間ですが、安全のためにはこの一手間が欠かせません。
C言語の文字列操作:文字列の長さを知る (strlen)
文字列を扱っていると、「この文字列、何文字あるんだろう?」と長さを知りたくなることがあります。
そんな時に使うのが `strlen` (エスティーアールレン) 関数です。これも `string.h` をインクルードすると使えます。
使い方(サンプルプログラムの紹介と実行結果、サンプルプログラムの紹介)
#include <stdio.h> #include <string.h> // strlen を使うために必要 int main(void) { char str[] = "hello"; size_t len; // 長さを格納する変数の型は size_t が一般的 // 文字列 str の長さを取得 len = strlen(str); printf("文字列 \"%s\" の長さは %zu です。\n", str, len); // 配列全体のサイズも見てみよう printf("配列 str のサイズは %zu バイトです。\n", sizeof(str)); char empty_str[] = ""; // 空文字列 len = strlen(empty_str); printf("文字列 \"%s\" の長さは %zu です。\n", empty_str, len); return 0; }実行結果:
文字列 "hello" の長さは 5 です。 配列 str のサイズは 6 バイトです。 文字列 "" の長さは 0 です。
サンプルプログラムの解説
`strlen(str)` は、文字列 `str` の文字数を返します。 `"hello"` は5文字なので、結果は 5 になりましたね。
ここで注意したいのは、`strlen` が返す長さには、最後のNULL文字 `\0` は含まれないということです。
一方、`sizeof(str)` は、配列 `str` がメモリ上で確保している全体のサイズ(バイト数)を返します。`"hello"` は文字が5つと `\0` が1つで、合計6バイトなので、`sizeof(str)` の結果は 6 になります。
`strlen` はNULL文字を含まない文字数、`sizeof` はNULL文字を含む配列全体のサイズ、この違いをしっかり区別しましょう!
また、空文字列 `""` の長さは 0 になります。
C言語の文字列操作:文字列を連結する (strcat, strncat)
文字列同士をくっつけて、新しい一つの文字列にしたい場合もありますね。例えば、姓と名をくっつけて氏名を作りたい、とか。
そんな時には、文字列連結(結合)関数を使います。`string.h` に含まれています。
strcat関数の使い方と注意点
`strcat` (エスティーアールキャット) は、ある文字列の後ろに別の文字列を連結する関数です。
`strcat(連結先の配列名, 連結したい文字列);` のように使います。
使い方(サンプルプログラムの紹介と実行結果、サンプルプログラムの紹介)
#include <stdio.h> #include <string.h> // strcat を使うために必要 int main(void) { char str1[20] = "hello"; // 連結先 (十分なサイズが必要!) char str2[] = " world"; // 連結したい文字列 // str1 の後ろに str2 を連結 strcat(str1, str2); printf("連結後の文字列: %s\n", str1); return 0; }実行結果:
連結後の文字列: hello world
サンプルプログラムの解説
`strcat(str1, str2);` によって、`str1` の末尾(元々あった `\0` の場所)から `str2` の内容がコピーされ、最後に新しい `\0` が付けられます。
`strcat` を使う上での最大の注意点は、`strcpy` と同じくバッファオーバーフローの危険性があることです。
連結先の配列 `str1` には、元の文字列 `"hello"` (NULL込み6文字) と、連結する文字列 `" world"` (NULL込み7文字) の両方を合わせた長さ(+最後のNULL文字1つ)を格納できるだけの十分なサイズが必要です。
この例では `char str1[20]` と十分なサイズを用意しましたが、もし `char str1[10]` のようにサイズが足りないと、`strcat` は配列の範囲を超えて書き込みを行い、バッファオーバーフローを引き起こします。
`strcat` を使う前には、連結後の文字列長が連結先の配列サイズを超えないか、必ず確認しましょう!
strncat関数の使い方と安全な連結
`strcat` のバッファオーバーフロー対策として `strncat` (エスティーアールエヌキャット) があります。
`strncat` は、連結する最大文字数を指定できます。
`strncat(連結先の配列名, 連結したい文字列, 最大文字数);` のように使います。
使い方(サンプルプログラムの紹介と実行結果、サンプルプログラムの紹介)
#include <stdio.h> #include <string.h> // strncat を使うために必要 #include <stdlib.h> // size_t を使うため (環境による) int main(void) { char str1[15] = "hello"; // 連結先 (サイズ15) char str2[] = " beautiful world"; size_t capacity = sizeof(str1); // 配列の容量 (15) size_t current_len = strlen(str1); // 現在の文字数 (5) size_t remaining; // 連結できる残りの文字数 // 連結できる残りの文字数を計算 (NULL文字の分1を引く) remaining = capacity - current_len - 1; if (remaining > 0) { // str1 の後ろに str2 を、最大 remaining 文字まで連結 strncat(str1, str2, remaining); } // strncat は常にNULL終端してくれるので、手動追加は不要なことが多い // (ただし、仕様をよく確認すること) printf("連結後の文字列: %s\n", str1); return 0; }実行結果:
連結後の文字列: hello beautifu
サンプルプログラムの解説
まず、連結先の配列 `str1` の容量 `capacity` (15) と、現在の文字数 `current_len` (5) を取得します。
連結できる残りの文字数 `remaining` は `15 - 5 - 1 = 9` 文字となります(最後のNULL文字の分を引くのを忘れずに)。
`strncat(str1, str2, remaining);` で、`str2` の先頭から最大9文字 (`" beautifu"`) が `str1` の後ろに連結されます。
`strncat` は `strncpy` と少し異なり、指定した文字数連結した後、必ず最後にNULL文字 `\0` を付けてくれる仕様になっています(通常は)。そのため、`strncpy` のように常に手動でNULL文字を追加する必要性は低いですが、念のため注意は必要です。
バッファオーバーフローを防ぐためには、このように連結前に残りの容量を計算し、その範囲内で連結するのが安全な使い方です。
C言語の文字列操作:文字列を比較する (strcmp, strncmp)
プログラムでは、2つの文字列が同じかどうか、あるいは辞書(アルファベット順)でどちらが先に来るかを比較したい場面がよくあります。
例えば、ユーザーが入力したパスワードが正しいかチェックする、などですね。
これも `=` や `==` では比較できません。`==` で文字列(配列名)を比較すると、文字列の内容ではなく、配列がメモリ上のどこにあるか(アドレス)を比較してしまい、意図した結果になりません。
文字列の内容を比較するには、標準ライブラリ関数 `strcmp` (エスティーアールコンプ) を使います。これも `string.h` が必要です。
使い方(サンプルプログラムの紹介と実行結果、サンプルプログラムの紹介)
#include <stdio.h> #include <string.h> // strcmp, strncmp を使うために必要 int main(void) { char str1[] = "apple"; char str2[] = "orange"; char str3[] = "apple"; char str4[] = "apples"; int result; // str1 と str2 を比較 result = strcmp(str1, str2); printf("strcmp(\"%s\", \"%s\") = %d ", str1, str2, result); if (result < 0) { printf("(str1 は str2 より小さい)\n"); } else if (result > 0) { printf("(str1 は str2 より大きい)\n"); } else { printf("(str1 と str2 は等しい)\n"); } // str1 と str3 を比較 result = strcmp(str1, str3); printf("strcmp(\"%s\", \"%s\") = %d ", str1, str3, result); if (result == 0) { printf("(str1 と str3 は等しい)\n"); } else { printf("(str1 と str3 は等しくない)\n"); } // str1 と str4 を比較 result = strcmp(str1, str4); printf("strcmp(\"%s\", \"%s\") = %d ", str1, str4, result); if (result < 0) { printf("(str1 は str4 より小さい)\n"); } else { printf("(str1 は str4 より大きい)\n"); } // 先頭 N 文字だけ比較する strncmp // str1 ("apple") と str4 ("apples") の先頭 5文字を比較 result = strncmp(str1, str4, 5); printf("strncmp(\"%s\", \"%s\", 5) = %d ", str1, str4, result); if (result == 0) { printf("(先頭5文字は等しい)\n"); } else { printf("(先頭5文字は等しくない)\n"); } return 0; }実行結果:
strcmp("apple", "orange") = -14 (str1 は str2 より小さい) strcmp("apple", "apple") = 0 (str1 と str3 は等しい) strcmp("apple", "apples") = -1 (str1 は str4 より小さい) strncmp("apple", "apples", 5) = 0 (先頭5文字は等しい)
サンプルプログラムの解説
`strcmp(文字列1, 文字列2)` は、2つの文字列を辞書順(文字コード順)に比較します。
戻り値の意味は少し特殊なので、しっかり覚えましょう。
- 戻り値が 0 なら → 2つの文字列は完全に等しい。
- 戻り値が負の値(例: -1, -14)なら → 文字列1 は 文字列2 より辞書順で小さい(先に来る)。
- 戻り値が正の値(例: 1, 14)なら → 文字列1 は 文字列2 より辞書順で大きい(後に来る)。
【まとめ】C言語の文字列操作をマスターするために
お疲れ様でした!C言語の文字列操作の基本について、たくさんの関数を見てきましたね。
最後に、今回学んだことのポイントを振り返っておきましょう。
- C言語の文字列は char型の配列 で、最後に必ず NULL終端文字 `\0` が付く。
- 文字列の初期化は、`char s[] = "text";` の形が簡単で安全。
- 文字列のコピーには `strcpy` や `strncpy` を使う。`=` ではコピーできない。
- 文字列の連結には `strcat` や `strncat` を使う。
- 文字列の長さを知るには `strlen` を使う (`\0` は数えない)。
- 文字列の比較には `strcmp` や `strncmp` を使う (`==` では比較できない)。
- `strcpy` や `strcat` を使う際は、バッファオーバーフローに最大限注意し、コピー先・連結先の配列サイズが十分か確認する。
- 安全性を高めるには `strncpy` や `strncat` が有効だが、NULL終端の扱いに注意が必要。
特に、NULL終端文字 `\0` の存在と、バッファオーバーフローの危険性は、C言語で文字列を扱う上で絶対に忘れてはいけないポイントです。
今回紹介した関数は、C言語での文字列操作のほんの一部ですが、これらをしっかり理解し、安全に使えるようになれば、あなたのC言語プログラミングスキルは格段に向上するはずです!
最初は難しく感じるかもしれませんが、実際にコードを書いて、動かして、時にはエラーを経験しながら、少しずつ慣れていきましょう。
【関連記事】
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。