【6日でできるC言語入門】ポインタ変数としての配列変数

 C言語において、配列とポインタは密接な関係を持っています。一見すると異なる機能を持つように見えますが、実は配列変数は「ポインタの特殊な形」と捉えることもできます。ここでは、配列変数とポインタ変数の関係について、具体的なプログラム例や図表を使ってわかりやすく解説します。これを理解することで、より柔軟で効率的なC言語プログラミングができるようになります。

1.配列変数とポインタ変数の基本

1.1. 配列変数とポインタ変数の関係

C言語では、配列の名前は「配列の先頭アドレス」を示すため、配列変数はポインタ変数のようにも利用できます。
以下は例となるプログラムです。

サンプルプログラム1:配列の先頭アドレスの代入

プロジェクト/ファイル名: Lesson54_1/main.c

#include <stdio.h>

int main(void) {
    int numbers[4] = { 5, 10, 15, 20 };
    int* ptr = NULL;
    ptr = numbers; // 配列の先頭アドレスをポインタに代入

    for (int i = 0; i < 4; i++) {
        printf("numbers[%d]=%d, *(ptr+%d)=%d, ptr[%d]=%d\n", 
                i, numbers[i], i, *(ptr + i), i, ptr[i]);
    }
    return 0;
}

出力結果

numbers[0]=5, *(ptr+0)=5, ptr[0]=5
numbers[1]=10, *(ptr+1)=10, ptr[1]=10
numbers[2]=15, *(ptr+2)=15, ptr[2]=15
numbers[3]=20, *(ptr+3)=20, ptr[3]=20

このように、配列変数とポインタ変数は、アクセスの仕方が同じであることが分かります。

1.2. 配列とポインタのアドレス・値の関係(表で理解)

配列表現アドレス表現ポインタによる表現ポインタによる値
numbers[0]&numbers[0]ptr*ptr
numbers[1]&numbers[1]ptr+1*(ptr+1)
numbers[2]&numbers[2]ptr+2*(ptr+2)
numbers[3]&numbers[3]ptr+3*(ptr+3)

2.配列変数とポインタ変数の違い

2.1. 配列変数とポインタ変数の本質的な違い

 配列変数は「特定のメモリ領域」を表し、その領域の先頭アドレスは固定されています。一方、ポインタ変数は「どこのアドレスでも参照可能」で、自由に指す先を変更できます。

サンプルプログラム2:配列変数の代入の禁止例

プロジェクト/ファイル名: Lesson54_2/main.c

#include <stdio.h>

int main(void) {
    int a[3] = {1, 2, 3};
    int b[3];
    b = a; // これはコンパイルエラーとなる
    return 0;
}

出力結果

重大度レベル	コード	説明
エラー (アクティブ)	E0137	式は変更可能な左辺値である必要があります
エラー	C2106	'=': 左のオペランドが、左辺値になっていません。	

解説
配列同士の直接代入はできません。配列の先頭アドレスは固定されているため、b = a のような代入は許されません。

2.2. ポインタ変数の柔軟性

 ポインタ変数は、参照する先を動的に切り替えられます。また、ポインタの値(アドレス)をインクリメントすることで、配列の各要素を順に参照することも可能です。

サンプルプログラム3:ポインタのインクリメント

プロジェクト/ファイル名: Lesson54_3/main.c

#include <stdio.h>

int main(void) {
    char str[4] = { 'A', 'B', 'C', '\0' };
    char* p = str;
    while (*p != '\0') {
        printf("%c ", *p);
        p++; // ポインタのインクリメント
    }
    printf("\n");
    return 0;
}

出力結果

A B C

図:ポインタのインクリメントのイメージ

str(char配列): | 'A' | 'B' | 'C' | '\0' |
                   ↑     ↑     ↑
                   p    p+1   p+2

3.ポインタ変数と配列変数の相互利用

3.1. ポインタ変数を配列のように使う

ポインタ変数に配列の先頭アドレスを代入すれば、ptr[i] のような配列表現が可能です。

サンプルプログラム4:ポインタ変数の配列表現

プロジェクト/ファイル名: Lesson54_4/main.c

#include <stdio.h>

int main(void) {
    float data[3] = { 1.1, 2.2, 3.3 };
    float* fp = data;
    for (int i = 0; i < 3; i++) {
        printf("fp[%d]=%.1f  *(fp+%d)=%.1f\n", i, fp[i], i, *(fp + i));
    }
    return 0;
}

出力結果

fp[0]=1.1  *(fp+0)=1.1
fp[1]=2.2  *(fp+1)=2.2
fp[2]=3.3  *(fp+2)=3.3

3.2. 配列変数をポインタのように使う

逆に、配列変数そのものも、ポインタとして利用できます。
例: *data*(data+1) など。

4.注意点と応用

4.1. 配列変数でできないこと

 配列変数はポインタ変数と同じように見えても、「他の変数のアドレスを指す」ことや「代入で配列同士を入れ替える」ことはできません。
それに対して、ポインタ変数は柔軟にアドレスを変更できます。

4.2. 応用:ポインタで部分配列を扱う

ポインタを利用すれば、配列の一部分だけを参照することも簡単です。

サンプルプログラム5:部分配列参照

プロジェクト/ファイル名: Lesson54_5/main.c

#include <stdio.h>

int main(void) {
    int arr[5] = { 10, 20, 30, 40, 50 };
    int* p = &arr[2]; // 配列の3番目の要素を指す
    for (int i = 0; i < 3; i++) {
        printf("%d ", *(p + i));
    }
    printf("\n");
    return 0;
}

出力結果

30 40 50

まとめ

 C言語では、配列変数とポインタ変数は非常に密接な関係を持っています。配列変数は「ポインタの特殊形」と考えると理解しやすく、両者は多くの点で似たように扱うことができます。しかし、配列変数には「代入不可」などの制約があり、ポインタ変数のほうが柔軟性が高いという違いもあります。この違いをしっかり把握することで、より効率的なプログラム設計が可能となります。