配列とポインタ


配列に間接参照する

より深く、配列とポインタの関係を見てみましょう
C言語では、配列とポインタの関係は重要とされます

以前gets()関数を使ったときには配列を渡しましたね
しかし、添え字(要素番号)は指定しませんでした
なぜならば、実はgets()関数に渡していたのはポインタだったのです

配列変数は、添え字を省略して配列変数名だけにすると、それは配列の先頭アドレスへのポインタを表します
詳しく触れませんでしたが、以前 gets() scanf() 関数に配列名だけを渡していた理由がここにあります
scanfは変数の前にアドレス演算子 & をつけなければいけないお約束がありましたが、配列の場合はつけませんでした
しかしそれは、配列の場合は添え字省略の変数名は配列の先頭アドレスへのポインタだからだったのです
#include <stdio.h>

int main() {
        char str[] = "kitty on your lap";

        printf("str[0]の内容\t\t= %c\n" , *str);
        printf("str[0]のアドレス\t= %x" , str);

        return 0;
}
勘のよい人はお気づきかもしれませんが、ということは配列のある要素へ間接参照することも可能です
ポインタのアドレスと配列の関係は前章で少し触れています

ということは、ポインタに格納されているアドレスを演算することによって
ポインタから配列の特定要素にアクセスできるはずです
ポインタからアドレスを演算して配列に間接参照を試みてみましょう
#include <stdio.h>

int main() {
        int ary[3] = { 10,20,30 };
        int *ary_p;
        ary_p = ary;

        printf("間接参照\t=%d,%d,%d\n", *ary_p , *(ary_p + 1) , *(ary_p + 2));
        printf("添え字指定\t=%d,%d,%d" , ary[0] , ary[1] , ary[2]);
        return 0;
}
結果は次のようになります

間接参照 =10,20,30
添え字指定 =10,20,30

こうすれば、配列で添え字指定するのと同じ効果がえられます
勘違いしないでほしいのは、ary_pに格納されているのは、あくまでary[0]のアドレスだけですよ
ary[1]などを指定するときに、*(ary_p + n) のようにしているのは、演算子の優先順位のためです(*の方が+より優先される)


多次元配列に間接参照する

では、多次元配列になるとアドレスの関係はどうなるのでしょうか

実は、多次元配列への間接参照になった瞬間、異様にややこしくなります
まず、生成される型が多次元なので、手作業で演算してアドレスを指定するには多次元では困ります
そのため、ポインタにアドレスを代入するときに型キャストする必要があります

pointa = (type *)ary[0];

次に、多次元への添え字指定の計算方法です
*(pointa + (指定一次元添字 * 二次元要素数) + 指定二次元添字)です
つまり、多次元配列 ary[10][5] のうち ary[5][3] にアクセスしたい場合は次のようになります

*(pointa + ( 5 * 5 ) + 3);

実例を見てください
#include <stdio.h>

int main() {
        int ary[2][3] = {
                { 10 , 20 , 30 } ,
                { 40 , 50 , 60 } ,
        };
        int *ary_p;
        ary_p = (int *)ary;

        printf("間接参照 = %d,%d,%d\n" , *(ary_p + (1 * 3)) ,
		 *(ary_p + (1 * 3)+ 1) , *(ary_p + (1 * 3) + 2));
        printf("添え字指定 = %d,%d,%d" , ary[1][0] , ary[1][1] , ary[1][2]);
        return 0;
}
ary[1]行目の各要素を呼び出しています
間接参照で得られる結果は、添え字指定でやっていることと同じことです
ご覧のとおりややこしいので、これはあまり使われません

しかし、「知っているが使わない」と「わからないから使わない」では大違いです
自分でソースを作って、いろいろなパターンを研究してみてください


直接ポインタに添え字をつける

通常、ポインタから配列への間接参照には演算による手動の添え字指定で行います
しかし、多次元配列の添え字指定などのように演算が複雑になると逆に面倒です

そのような場合はポインタでも直接添え字を指定します
実は…ポインタでもそのまま添え字を指定して間接参照することは可能なのです
#include <stdio.h>

int main() {
        int ary[] = { 10,20,30,40,50 };
        int *ary_p;
        ary_p = ary;

        printf("ary[0] = %d\n" , ary_p[0]);
        printf("ary[1] = %d\n" , ary_p[1]);
        printf("ary[2] = %d\n" , ary_p[2]);
        printf("ary[3] = %d\n" , ary_p[3]);
        printf("ary[4] = %d\n" , ary_p[4]);
        return 0;
}
ただし、ポインタに添え字をつけられるのはポインタの指すアドレスが配列変数である場合だけです
ポインタが通常の変数などを指している場合に添え字を指定すると、構文ミスにはなりませんが間違ってます

通常は、ポインタから配列へ間接参照するときには演算を使います
このように直接添え字を指定するようなことはめったにありません
なぜならば、処理が遅くなるということと演算のほうが柔軟で便利であることがあげられます


高度な配列の扱い

配列変数名だけの場合はその配列の先頭アドレスへのポインタであることは何度も説明しました
ということは、これを利用してポインタ演算で配列にアクセスできないのでしょうか
実は可能なのです

もちろん、正式なポインタ変数ではないので、注意する面もあります
配列変数名を演算して間接参照することは可能ですが、ポインタ変数のように代入はできないのです
配列変数名は常にその配列変数の先頭アドレスのポインタでありつづけます
添え字省略の配列変数名は配列の先頭アドレスを指す定数と考えられますね
#include <stdio.h>

int main() {
        int ary[] = { 10 , 20, 30 , 40 , 50 , 0 };
        int count = 0;

        while (ary[count]) {		/*forを使ったほうが無難かも...*/
                printf("ary[%d] = %d\n" , count , *(ary + count));
                count++;
        }
        return 0;
}
このプログラムの *ary は宣言されたポインタではありません
配列変数名、つまりary[0]のアドレスを直接ポインタ演算しています
何度も言うように、配列変数名のポインタには代入することはできません

ary++

この場合はコンパイラがエラーを出すはずです
配列変数の先頭アドレスを指す ary は常にary[0]のアドレスが格納されています



前のページへ戻る次のページへ