配列の使い方について
この項目で学ぶこと
配列とは
配列とは、複数の同じ方の変数を1つにまとめたものです。複数の変数を一列に並べたものと考えてもいいでしょう。
使い方
配列を使ってint型の変数「a」を10個用意したいときはこのように記述します。
int a[10];
この宣言により、a[0],a[1],a[2]...a[8],a[9]という10個の変数が使えるようになりました。配列で用意された変数のことを要素といいます。カッコの中の数字を添え字(インデックス)といいます。添え字は0から始まるので、最後の添え字は「要素数-1」になります。太字ばっかり。
要素は同じ型の普通の変数のように扱うことが出来ます。int型の配列を使って適当に計算して結果を出力してみましょう。また、配列の初期化の仕方も見ておきましょう。
要素は同じ型の普通の変数のように扱うことが出来ます。int型の配列を使って適当に計算して結果を出力してみましょう。また、配列の初期化の仕方も見ておきましょう。
配列を使った計算
#include<stdio.h> int main(){ int a[4]; // 配列を宣言 a[0] = 3; // 代入 a[1] = 4; a[2] = a[0] + a[1]; // 和 a[3] = a[2] * 2; // 積 printf("a[0] = %d\n",a[0]); // 値を表示 printf("a[1] = %d\n",a[1]); printf("a[2] = %d\n",a[2]); printf("a[3] = %d\n",a[3]); }
ループと組み合わせて使用してみる
添え字には定数で指定する方法のほかに、整数型の変数を用いて指定することが出来ます。そのため、ループを用いることで次々と要素にアクセスすることが出来ます。ループと配列を組み合わせたサンプルソースを見てみましょう。
要素の値を次々と出力する
#include<stdio.h> int main(){ int a[4],i; a[0] = 3; // 代入 a[1] = 4; a[2] = a[0] + a[1]; // 和 a[3] = a[2] * 2; // 積 for(i=0 ; i<4 ; i++){ printf("a[%d] = %d\n",i,a[i]); } }
アドレスとポインタについて
ポインタとは変数のアドレスを保持する変数です。ここでいうアドレスとはコンピュータのメモリに割り振られている数字のことを言います。コンピュータ内部の番地のようなものです。変数は宣言されるとメモリ上に実体を生成します。その生成された場所を保持する変数がポインタなのです。なんかややこしいですね。
これまで扱ってきた変数のアドレスを調べるには、変数名の前に&(アンド、アンパサンド)を付けます。
これまで扱ってきた変数のアドレスを調べるには、変数名の前に&(アンド、アンパサンド)を付けます。
#include <stdio.h> int main(){ int a; printf("a のアドレスは %p\n", &a); return 0; }
printf の書式指定の %p はポインタ型の値を表示します。実行結果は環境によって違いますが、筆者の環境では
a のアドレスは 0012FF60
となりました。まあ、具体値はそんなに重要ではありません。次に、配列の各要素のアドレスを見てみましょう。
#include <stdio.h> int main(){ int a[4], i; for(i = 0; i < 4; i++) printf("a[%d] のアドレスは %p\n", i, &a[i]); printf("a は %p\n", a); return 0; }
結果
a[0] のアドレスは 0012FF54 a[1] のアドレスは 0012FF58 a[2] のアドレスは 0012FF5C a[3] のアドレスは 0012FF60 a は 0012FF54
これを見て気付くことは、
- 配列の添え字が1増えるとアドレスは4増える
- &a[0]とaの値が一致している
ということです。筆者の環境では、int型のサイズ(sizeof(int))は4バイトです(コンパイラによって違う)。つまり、int型の変数は4バイトのメモリ空間を占有するということです。文章を読むより図を見た方がわかりやすいでしょう。
上の例では、a[0]は0012FF54~0012FF57を占有しています。a[0]のポインタはその先頭番地である0012FF54を示したのです。また、配列変数に添え字を付けないでやると、配列の先頭アドレスを返します。&a[0]とaの値が一致するのはそのためです。これまでにも無意識のうちにポインタを使ってきましたね。scanf関数の第2引数以降に渡す値は、変数には&を付け、文字列(文字配列)には&を付けませんでした。つまり、scanfの第2引数以降はポインタを受け取っていたのです。
次に、ポインタ型とポインタ変数について。ポインタ変数(単にポインタとも)はアドレスを記憶する変数です。intやdoubleなど、データ型によってメモリの占有領域やビットの扱い方などが異なりますので、どのデータ型のポインタ変数なのか区別する必要があります。ポインタ変数の宣言は次のようにします。
次に、ポインタ型とポインタ変数について。ポインタ変数(単にポインタとも)はアドレスを記憶する変数です。intやdoubleなど、データ型によってメモリの占有領域やビットの扱い方などが異なりますので、どのデータ型のポインタ変数なのか区別する必要があります。ポインタ変数の宣言は次のようにします。
データ型 *ポインタ変数名;
宣言では変数名の前に*(アスタリスク)を付けます。一度宣言されたポインタ変数は*を付けないで使います。
#include <stdio.h> int main(){ int a, *p; p = &a; printf("&a は %p\n", &a); printf("p は %p\n", p); return 0; }
実行結果(環境により異なる)
&a は 0012FF60 p は 0012FF60
ポインタ変数の前に*を付けることで、ポインタが示すアドレスを通常の変数のように扱うことができます。読込も書込もできます。特に、書込を行うと、ポインタ変数が示すアドレスの変数の値も書き換わります。
#include <stdio.h> int main(){ int a = 3, *p = &a; printf("a は %d\n", a); printf("*p は %d\n", *p); *p = 100; printf("a は %d\n", a); return 0; }
実行結果(これはどの環境でもこうなる)
a は 3 *p は 3 a は 100
こんなことをして何が嬉しいかというと、後でやる関数で役立ちます。
ポインタに配列変数の先頭アドレスを入れることもできます。これも後にやる関数で使います。今は何のありがたみもありません。ちょっとこれを見てください。
ポインタに配列変数の先頭アドレスを入れることもできます。これも後にやる関数で使います。今は何のありがたみもありません。ちょっとこれを見てください。
#include <stdio.h> int main(){ int a[] = {123, 456, 789}, *p = a, i; // まず配列を見てみる for(i = 0; i < 3; i++){ printf("a[%d]=%d\t", i, a[i]); printf("*(a+%d)=%d\t", i, *(a + i)); printf("a+%d=%p\n", i, a + i); } puts(""); // puts(文字列)は文字列を出力後改行する関数(printf("%s\n", 文字列)みたいなもん) // 次にポインタを見てみる for(i = 0; i < 3; i++){ printf("p[%d]=%d\t", i, p[i]); printf("*(p+%d)=%d\t", i, *(p + i)); printf("p+%d=%p\n", i, p + i); } puts(""); // ポインタをインクリメントしてみる for(p = &a[0]; p < &a[3]; p++){ printf("*p=%d\t", *p); printf("p=%p\n", p); } return 0; }
実行結果
a[0]=123 *a =123 a =0012FF40 a[1]=456 *(a+1)=456 a+1=0012FF44 a[2]=789 *(a+2)=789 a+2=0012FF48 p[0]=123 *p =123 p =0012FF40 p[1]=456 *(p+1)=456 p+1=0012FF44 p[2]=789 *(p+2)=789 p+2=0012FF48 *p=123 p=0012FF40 *p=456 p=0012FF44 *p=789 p=0012FF48
見せたかったことは、
- 配列変数もポインタ変数も同様の操作が可能
- a[i] と *(a+i) は同じ
- ポインタ型に1を足すとデータ型のサイズだけ増える
ということです。ポインタ変数の加算がこういう仕様であるおかげで、プログラマはポインタの具体値を知らなくてもポインタを使うことができるのです。というか、もう具体値を見ることはありません。重要なのは、ポインタを通常の変数と同じように使いこなすことです。
多次元配列について
今まで使用してきた配列は正確には1次元配列と呼ばれるもので、要素が横に伸びていくイメージでした。配列は宣言の仕方を変えることで多次元的に使用できます。
2次元配列の使い方
#include<stdio.h> int main(){ int a[4][2]={ // 2次元配列の宣言 {0,1}, // 初期化の書き方 {2,3}, {4,5}, {6,7}, }; // セミコロンを忘れない int y,x; for(y=0 ; y<4 ; y++){ // ループで各要素にアクセスしてみる for(x=0 ; x<2 ; x++){ printf("a[%d][%d] = %d\n",y,x,a[y][x]); } } }
多次元配列の考え方はゲームプログラミングにおいてとても重要です。ドラクエやポケモンなどのマップ情報、テトリスやぷよぷよのようなパズルゲームなどは2次元配列を用いることで実現できます。三次元以上の配列は扱い(イメージの仕方)がややこしくなります。使う機会はほとんどありませんので紹介しません。興味のある方は自分で調べてください。
添付ファイル