C言語基礎|10章のまとめ

アドレスとポインタが分かると、配列・関数・ソートまで一気に“つながる”。第10章の総仕上げ!

第10章は、C言語の山場であるポインタを「怖いもの」から「便利な道具」へ変える章でした。
アドレスは“場所”、ポインタは“場所を持つ値”、* は“その場所の中身”、& は“場所を取り出す”――この4つが腹落ちすると、関数や配列が急に読みやすくなります。

このまとめでは、章で出てきた重要事項を、表と図で「一枚の地図」にして整理します。
最後に、ポインタと配列、関数が全部出てくる小さな実用プログラムで、総復習もしちゃいましょう。

まずは全体地図:第10章で身につけた“3つの視点”

10章を貫く視点

視点ひとことで代表キーワード
場所の視点メモリ上のどこ?アドレス、番地
住所メモの視点その場所を持つ値ポインタ、Type *
参照の視点指している先を触る間接演算子 *、参照外し

この表は「今どの話をしてるのか」を迷子にしないための整理です。

アドレス:オブジェクトの“場所(番地)”

アドレスは、記憶域上におけるオブジェクトの場所を示す番地です。

図:バラバラの箱 → メモリ空間の一部

この図が言いたいのは「変数は空間のどこかに置かれている」という当たり前を、ポインタのためにハッキリ意識しよう、ということです。

アドレス演算子 &:ポインタを生成する

Type 型オブジェクト x に & を適用した &x は、x を指すポインタを生成します。

アドレス演算子 & の整理

意味結果の型結果の値
&xx へのポインタを作るType *x のアドレス

ここで大事なのは「& はアドレスを返す」というより、ポインタ(Type *)を生成すると捉えることです。

「p は x を指す」:ポインタの値が x のアドレス

Type * 型ポインタ p の値が x のアドレスなら、p は x を指すと表現します。

図:指す関係

間接演算子 *:指しているオブジェクトそのもの(エイリアス)

Type * 型ポインタ p に * を適用した *p は、p が指す Type 型オブジェクトそのものを表します。
p が x を指すとき、*p は x のエイリアス(別名)です。

間接演算子 * の整理

意味ポイント
*pp が指すオブジェクトそのもの*p は“別名”になる

別名(エイリアス)のイメージ

参照外し:ポインタ経由で読む・書く

ポインタに * を適用して間接的にアクセスすることを 参照外し と呼びます。

参照外しで起きること

操作何が起きる?
読むvalue = *p指す先の値を読む
書く*p = 99指す先に 99 を書く

注意点はひとつ。何も指していないポインタを参照外しすると危険です(未定義動作につながりやすいです)。

ポインタの型:Type * は Type を指す(型違いは原則NG)

Type * 型ポインタは、「番地」だけではなく、その番地を先頭にある Type 型オブジェクトを指すものです。
だから、型が違うものを指させるのは原則避けます。

型が合わないと何が困る?

何が起きる?なぜ危険?
scanf で int に %lf予定より大きく書き込む可能性必要バイト数が違う
int * で char を交換交換サイズが大きくなる可能性sizeof(int) 分動く

この表は「ポインタの型=アクセス単位を決めるもの」と理解するためのものです。

配列名は先頭要素へのポインタとして扱われやすい

一部の例外を除き、配列名 a は &a[0] として解釈されます。

配列名がポインタとして扱われる代表例

場面a の意味
int *p = a&a[0]
関数呼び出し f(a)&a[0]

例外(この章で出た重要な2つ)も忘れずに。

  • sizeof(a) は配列全体のサイズ
  • &a は配列全体へのポインタ(先頭要素ではない)

ポインタ算術:p+i は i 要素ぶん進む

配列内の要素を指すポインタ p に整数 i を足し引きすると、i 要素ぶん前後の要素を指します。

図:要素単位で進む

*(p+i) と p[i] は同じ(添字は省略記法)

*(p+i) は「その要素そのもの」なので、p[i] と等価です。
さらに順序の入れ替えも成り立つので、i[p] も動きます(でも読みにくいので通常は使いません)。

同じ意味になる代表セット

要素アクセス要素へのポインタ
a[i] / *(a+i) / p[i] / *(p+i)&a[i] / (a+i) / (p+i)

配列は代入の左にできない

配列名を代入演算子 = の左オペランドにすることはできません。
配列は“動かない実体”なので、まるごと付け替える代入はできない、という感覚が大切です。

関数で配列を渡すとき:渡しているのは先頭要素へのポインタ

関数間での配列の受渡しは、配列そのものではなく 先頭要素へのポインタとして行われます。
だから要素数は別引数で渡す必要があります。

図:配列受け渡しのカラクリ

空ポインタ NULL:何も指さないことが保証された特別な値

いかなるオブジェクトも関数も指さないポインタが空ポインタです。
それを表す空ポインタ定数が NULL で、<stddef.h> で定義されています(他の標準ヘッダを含めても利用できることが多いです)。

NULL の役割

もの意味
空ポインタ何も指さないことが保証されたポインタ
NULL空ポインタを表すための定数(マクロ)

スカラ型:算術型+ポインタ型

算術型(int や double など)とポインタ型を合わせた総称が スカラ型です。
「数のように扱える単一の値」というイメージでOKです。

まとめプログラム

配列を関数に渡して、並べ替えて、結果を表示するプログラム例です。

プロジェクト名:chap10-14-1 ソースファイル名:chap10-14-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define COUNT 5

void swap(int *x, int *y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}

void bsort(int a[], int n)
{
    for (int i = 0; i < n - 1; i++)
        for (int j = n - 1; j > i; j--)
            if (a[j - 1] > a[j])
                swap(&a[j], &a[j - 1]);
}

int main(void)
{
    int height[COUNT];

    printf("%d 人の身長を入力してください。\n", COUNT);
    for (int i = 0; i < COUNT; i++) {
        printf("%d 人目(cm):", i + 1);
        scanf("%d", &height[i]);
    }

    bsort(height, COUNT);

    puts("小さい順に並べました。");
    for (int i = 0; i < COUNT; i++)
        printf("%d 人目:%d cm\n", i + 1, height[i]);

    return 0;
}

このプログラムで10章の何が復習できる?

  • scanf は &height[i] を渡す(書き込み先の場所を渡す)
  • bsort は a[] で受け取るけど、実体は先頭要素へのポインタ
  • swap はポインタを受け取って *x と *y を交換(参照外し)
  • &a[j] のように要素のアドレスを作って渡す。

登場する命令(関数)と書式・何をする命令なのか

scanf

  • 書式:scanf(書式文字列, 変数へのポインタ, ...);
  • 何をする?:入力を読み取り、指定されたアドレス先の変数へ格納する

printf

  • 書式:printf(書式文字列, 引数1, 引数2, ...);
  • 何をする?:書式に従って表示する

puts

  • 書式:puts(文字列);
  • 何をする?:文字列を表示して改行する

使った表や図の説明(まとめ)

  • 「3つの視点」の表は、10章の話題を迷わず整理するための地図です。
  • & と * の表は、演算子が何を生成し、何を表すかを型まで含めて固定するためのものです。
  • 配列受け渡しの図は、配列が“渡っていないのに書き換えられる”理由を視覚化しています。
  • ポインタ算術の図は、バイトではなく要素単位で進む感覚をつかむためのものです。