01 文字列操作(char 配列・終端文字・string.h)

C言語には「文字列型」が存在しない。文字列は char 型の配列として扱われ、必ず末尾に終端文字 '¥0'(ヌル文字)が付く。2級ではこの仕組みと string.h の関数、メモリ上の並びが問われる。

✅ この章のゴール:① 文字列=char配列+'¥0' を説明できる ② strlen/strcpy/strncpy/strcmp の動作と戻り値が分かる ③ 文字列の配列 char s[][N] の各要素にアクセスできる。

01-1 配列の宣言と初期化

配列は、同じ型のデータを連続したメモリ領域に並べて格納するデータ構造である。

int  arr[5];    /* 整数型を5要素ぶん確保 */
char str[10];   /* 文字型を10要素ぶん確保 */

宣言と同時に初期値を与えることもできる。

int num[10] = {1,2,3,4,5,6,7,8,9,0};  /* 各要素に値を格納 */
int num[]   = {1,2,3,4,5,6,7,8,9,0};  /* 要素数は初期化式から決まる(=10) */

01-2 文字列の宣言と終端文字

文字列は char 配列として宣言する。文字列リテラルで初期化すると、末尾に終端文字 '¥0' が自動的に付く

char str[6] = "Hello";  /* H e l l o ¥0 の6文字ぶん */
char str[]  = "Hello";  /* 要素数は初期化式から決まる(=6) */

メモリマップ

宣言 char str[6] = "Hello"; はメモリ上に次のように並ぶ。

  +---+---+---+---+---+----+
  | H | e | l | l | o | ¥0 |
  +---+---+---+---+---+----+
    0   1   2   3   4   5
⚠ 「Hello」は5文字だが、確保されるのは 6バイト(5文字 + 終端文字 '¥0')。
strlenstrcpy などの文字列関数は '¥0' を見つけるまで処理を続ける。'¥0' が無いと、メモリの不正アクセス(暴走)が起こりうる。

01-3 string.h の主な関数

文字列を操作するには標準ライブラリ <string.h> を使う。

関数機能使用例
strlen文字列の長さを返す('¥0' は数えない)size_t len = strlen(str);
strcpy文字列をコピーstrcpy(dest, src);
strcat文字列を末尾に連結strcat(dest, src);
strcmp2つの文字列を比較(等しいと 0int c = strcmp(s1, s2);
strncpy最大 n 文字だけコピーstrncpy(dest, src, n);
strchr指定文字が最初に現れる位置(アドレス)を返すchar *p = strchr(str, 'e');
strcmp の戻り値は「等しい→0/s1が大きい→正/s1が小さい→負」。「等しいと0」がよく問われる(0以外=違う、と覚える)。

01-4 文字列の配列

複数の文字列をまとめて持つには、文字列の配列(2次元 char 配列)を使う。

char str[][10] = { "Hello", "World", "C" };

メモリマップ

  +---+---+---+---+---+----+---+---+---+---+
  | H | e | l | l | o | ¥0 |   |   |   |   |  ← str[0]
  +---+---+---+---+---+----+---+---+---+---+
  | W | o | r | l | d | ¥0 |   |   |   |   |  ← str[1]
  +---+---+---+---+---+----+---+---+---+---+
  | C | ¥0|   |   |   |    |   |   |   |   |  ← str[2]
  +---+---+---+---+---+----+---+---+---+---+
    0   1   2   3   4   5    6   7   8   9

要素へのアクセス

printf("%s¥n", str[0]);     /* "Hello" を表示(str[0] は文字列の先頭アドレス) */
printf("%s¥n", str[1]);     /* "World" を表示 */
printf("%s¥n", str[2]);     /* "C" を表示 */

printf("%c¥n", str[0][1]);  /* 'e' を表示(str[0] の1番目の "1文字") */
printf("%c¥n", str[1][2]);  /* 'r' を表示 */
よくある間違いstr[0][1]1文字(char'e'であって文字列ではない。
printf("%s", str[0][1]) のように %s に1文字を渡すと暴走する(未定義動作)。
e から後ろ("ello")」を文字列として表示したいなら、アドレスを渡す
printf("%s¥n", str[0] + 1); または printf("%s¥n", &str[0][1]);"ello"

01-5 strncpy の応用(途中にコピー)

strncpy(コピー先, コピー元, 文字数)。コピー先・コピー元はアドレスなので、dest + 2 のように「途中の位置」から始められる。

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

int main(void)
{
    char src[]     = "Hello, World!";
    char dest[20]  = "XXXXXXXXXXXXXXX";   /* X が15個 */

    strncpy(dest + 2, src, 5);  /* dest の index 2 から "Hello" を5文字コピー */
    dest[7] = '¥0';             /* 終端文字を手動で付ける */

    printf("Result: %s¥n", dest);
    return 0;
}

コピー後のメモリ

  +---+---+---+---+---+---+---+----+---+---+
  | X | X | H | e | l | l | o | ¥0 | X | X |
  +---+---+---+---+---+---+---+----+---+---+
    0   1   2   3   4   5   6   7    8   9

%s は先頭から '¥0' まで表示するので、実行結果は:

Result: XXHello

01-6 📝 過去問で確認

過去問(文字列操作)— まず自分で解いてみよう
文字列操作の過去問 問題①
問題 ①
文字列操作の過去問 問題②
問題 ②
解説を表示
文字列操作の過去問 解説①
解説 ①
文字列操作の過去問 解説②
解説 ②

01-7 まとめ


01-8 理解度チェック

Q1. strlen("Hello") の戻り値はどれか。

解説を表示

正解:イ(5)

strlen は終端文字 '¥0' を数えない。"Hello" は5文字なので 5。なお char s[]="Hello";配列サイズsizeof)は '¥0' を含めて 6 になる点と区別すること。

Q2. char str[6] = "Hello"; で確保される配列のバイト数はどれか。

解説を表示

正解:ウ(6)

5文字 + 終端文字 '¥0' = 6バイト。

Q3. 2つの文字列が等しいとき、strcmp(s1, s2) が返す値はどれか。

解説を表示

正解:ア(0)

strcmp は等しいと 0、s1 が大きいと正、s1 が小さいと負を返す。「等しい=0」「0以外=違う」と覚える。

Q4. char s[][10] = {"Hello","World","C"}; のとき、s[1][2] が表すものはどれか。

解説を表示

正解:ウ('r')

s[1] は2番目の文字列 "World"。その [2] は3文字目(index 2)なので 'r'(1文字)。文字列 "rld" として扱いたいなら s[1] + 2 のようにアドレスを渡す。

Q5. 次のコードの実行結果はどれか。

char src[]    = "Hello, World!";
char dest[20] = "XXXXXXXXXXXXXXX";
strncpy(dest + 2, src, 5);
dest[7] = '¥0';
printf("%s¥n", dest);
解説を表示

正解:エ(XXHello)

dest の先頭2文字 "XX" はそのまま、index 2 から "Hello" を5文字コピーし、index 7 に '¥0' を置く。%s は先頭から '¥0' まで表示するので "XXHello"。

Q6. 文字列の「終わり」を示すものはどれか。

解説を表示

正解:イ('¥0')

紛らわしい3つを区別:'¥0'(ヌル文字)=文字列の終わり / EOFファイル(入力)の終わり / NULLどこも指さないポインタ