
C言語基礎|11章のまとめ
文字列は“配列”でも“ポインタ”でも書ける!違いをつかめば、Cの文字列操作が一気に読みやすくなる。
第11章では、文字列とポインタの“距離の近さ”を、かなり実感できたと思います。
配列で作った文字列も、ポインタで指す文字列も、見た目は同じように printf で表示できます。でも、中身(メモリの持ち方・書き換えの可否・サイズの意味)はけっこう違います。
さらに、文字列の配列(2次元配列版とポインタ配列版)も登場して、**「どこに文字が置かれて、何が可変で、何が固定なのか」**を意識できるようになったはずです。
最後に、標準ライブラリ(string.h / stdlib.h)の代表的な関数を使って、実務での基本動作も押さえました。ここで一度、要点を“整理して定着”させましょう。

文字列の2つの表現(配列の文字列 / ポインタの文字列)
まずは違いを表で整理
| 観点 | 配列による文字列 | ポインタによる文字列 |
|---|---|---|
| 宣言例 | char a[] = "CIA"; | char *p = "FBI"; |
| 実体 | char の配列そのもの | 先頭文字を指すポインタ |
| 書き換え | a[0] などは書き換え可能 | 文字列リテラル側は書き換え保証なし |
| 代入(付け替え) | a = "DEF" は不可 | p = "XYZ" は可 |
| sizeof の意味 | 配列の全バイト数 | ポインタのバイト数 |
図でイメージ(簡略)
配列: a[] = "CIA"
a: [ 'C' ][ 'I' ][ 'A' ][ '\0' ] ← 配列が実体
ポインタ: p = "FBI"
p: [ 先頭アドレス ] → [ 'F' ][ 'B' ][ 'I' ][ '\0' ] ← リテラルは別の場所
文字列リテラルとポインタの関係(重要ポイント)
文字列リテラルは「先頭文字へのポインタ」として扱える
- "FBI" を式として評価すると、先頭文字 'F' の位置(アドレス)を指すポインタとして扱われます。
- だから char *p = "FBI"; で、p は 'F' を指すように初期化されます。
「両方が記憶域を占有する」の意味
| 何がメモリを使う? | 例 | 使われる領域 |
|---|---|---|
| ポインタ変数そのもの | p | sizeof(char *) バイト |
| 文字列リテラルの実体 | "FBI" | 文字数+1 バイト(\0 含む) |
ポインタに文字列リテラルを代入できる理由
代入すると「文字列をコピー」するのではなく「指す先が変わる」
p = "FBI"; → p は 'F' を指す
p = "CIA"; → p は 'C' を指す(指す先の付け替え)
この動きができるのは、p が「ポインタ(アドレスを入れる箱)」だからです。
一方、配列は「箱そのもの」なので、箱を別の箱に入れ替えるような代入はできません。
文字列の配列(2通り:2次元配列 / ポインタ配列)
2つの実現方法を表で比較
| 観点 | 配列による文字列の配列(2次元配列) | ポインタによる文字列の配列(ポインタ配列) |
|---|---|---|
| 宣言例 | char a2[][5] = {"LISP","C","Ada"}; | char *p2[] = {"PAUL","X","MAC"}; |
| 実体 | char[5] が行数分並んだもの | char * が要素数分並んだもの |
| 文字の配置 | すべて連続領域 | 連続する保証なし |
| サイズ(配列部分) | sizeof(a2) = 行×列 | sizeof(p2) = sizeof(char*)×要素数 |
| 追加で必要な領域 | なし(文字も全部中にある) | 各文字列リテラル分の領域も必要 |
図でイメージ
a2(2次元配列):
[ L I S P \0 ][ C \0 ? ? ? ][ A d a \0 ? ] ← 全体が連続
p2(ポインタ配列):
p2: [ →"PAUL" ][ →"X" ][ →"MAC" ] ← 先の文字列は別々でもOK
ポインタのインクリメント / デクリメント(文字列操作の基本)
規則をひとことで
- ポインタ p が配列の要素を指しているとき
p++ は次の要素を指す
p-- は前の要素を指す
例(文字列走査の発想)
const char *s = "CAT";
s が 'C' を指す
s++ で 'A' を指す
s++ で 'T' を指す
s++ で '\0' を指す
この考え方が、strlen っぽい処理や、コピー処理の基礎になります。
文字列リテラルへ書き込みは危険(やってはいけない)
なぜ危険?
- 書き換え可能な場所に置かれている保証がない(処理系依存)
- たとえ書けたとしても、長い文字列を書き込めば領域を越えて破壊する可能性がある
結論
文字列リテラルや、その周辺に書き込むコードは作らない。
「ポインタを返す関数」は使い回しが効く
strcpy や strcat が便利なのは、コピー先(連結先)の先頭ポインタを返すからです。
返り値をそのまま次の処理に渡せます。
例の発想(イメージ):
- strcpy(dst, src) の結果(dst)を printf に渡せる。
- 連続コピー、連続連結、表示と合成ができる。
文字列関連ライブラリの全体像(string.h / stdlib.h)
代表関数まとめ(超要点)
| 分類 | 関数 | 何をする? |
|---|---|---|
| 長さ | strlen | \0 を除いた文字数 |
| コピー | strcpy / strncpy | 文字列をコピー(後者は制限あり) |
| 連結 | strcat / strncat | 後ろに連結(後者は制限あり) |
| 比較 | strcmp / strncmp | 辞書順っぽい比較(文字コード依存) |
| 変換 | atoi / atol / atoll / atof | 文字列を数値へ |
各関数の書式と役割(最低限ここを押さえる)
strlen
- ヘッダ:#include <string.h>
- 形式:size_t strlen(const char *s);
- すること:\0 を含まない長さを返す
strcpy / strncpy
- strcpy 形式:char *strcpy(char * restrict s1, const char * restrict s2);
- strncpy 形式:char *strncpy(char * restrict s1, const char * restrict s2, size_t n);
- すること:s2 を s1 へコピー(ただし重なりは未定義)
- 注意:strncpy は必ずしも \0 を付けてくれないケースがある
strcat / strncat
- strcat 形式:char *strcat(char * restrict s1, const char * restrict s2);
- strncat 形式:char *strncat(char * restrict s1, const char * restrict s2, size_t n);
- すること:s1 の末尾へ s2 を連結
- 注意:s1 側のバッファに十分な空きが必要
strcmp / strncmp
- strcmp 形式:int strcmp(const char *s1, const char *s2);
- strncmp 形式:int strncmp(const char *s1, const char *s2, size_t n);
- すること:大小比較(等しいなら0、s1>s2なら正、s1<s2なら負)
- 注意:比較基準は文字コード依存
atoi / atof など
- ヘッダ:#include <stdlib.h>
- 形式:int atoi(const char *nptr); / double atof(const char *nptr);
- すること:数字として読める部分を数値化
- 注意:失敗しても 0 になりやすい、範囲外の動作が保証されない
サンプルプログラム
プロジェクト名:chap11-13-1 ソースファイル名:chap11-13-1.c
// 11章まとめ:文字列の持ち方と標準関数の基本
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 文字列を [ ] で囲んで表示する
void put_bracket(const char *s)
{
putchar('[');
while (*s) putchar(*s++);
putchar(']');
putchar('\n');
}
int main(void)
{
// 配列による文字列(中身を持つ)
char a[] = "DOG";
// ポインタによる文字列(指す)
const char *p = "CAT";
// 文字列の配列:2次元配列版(連続)
char a2[][6] = {"RED", "BLUE", "GREEN"};
// 文字列の配列:ポインタ配列版(指す)
const char *p2[] = {"TOKYO", "OSAKA", "NAGOYA"};
puts("配列の文字列 a と ポインタの文字列 p を表示します。");
printf("a = %s\n", a);
printf("p = %s\n", p);
puts("\nstrlen で長さを確認します。");
printf("strlen(a) = %zu\n", strlen(a));
printf("strlen(p) = %zu\n", strlen(p));
puts("\n2種類の「文字列の配列」を表示します。");
for (int i = 0; i < (int)(sizeof(a2) / sizeof(a2[0])); i++) {
printf("a2[%d] = ", i);
put_bracket(a2[i]);
}
for (int i = 0; i < (int)(sizeof(p2) / sizeof(p2[0])); i++) {
printf("p2[%d] = ", i);
put_bracket(p2[i]);
}
puts("\natoi の簡単な例:数字文字列を数にします。");
printf("文字列 \"42\" を atoi すると %d です。\n", atoi("42"));
return 0;
}このプログラム内で登場する命令(関数)の役割
| 名前 | 何をする? | ここでの役割 |
|---|---|---|
| puts | 文字列+改行を表示 | 説明文を表示 |
| printf | 書式付き表示 | 値や結果を整形表示 |
| putchar | 1文字出力 | [ ] の装飾表示に使用 |
| strlen | 文字列長(\0除く) | a と p の長さ比較 |
| atoi | 文字列→int 変換 | "42" を数値化 |
11章の要点を一気に復習(まとめ表)
| テーマ | 覚えておくポイント |
|---|---|
| 配列の文字列 | 実体を持つ、sizeof は全体サイズ、配列自体の代入は不可 |
| ポインタの文字列 | 指すだけ、代入で指す先変更OK、リテラル書き換えは危険 |
| 文字列の配列(2次元) | 文字が連続、固定幅ゆえ未使用領域が出ることがある |
| 文字列の配列(ポインタ配列) | 柔軟、配置は保証なし、配列部分+リテラル分の領域 |
| ポインタ演算 | p++ で次要素へ(文字列走査の基本) |
| 標準関数 | string.h と stdlib.h の基本関数を用途別に使う |
