苦しんで覚えるC言語 13.1 複数の変数をまとめて扱う P236 – P245

配列の概念

配列とは変数の集合体です。
例えば、int型の変数を100個用意したい場合、100個分の変数を変数名を考えて宣言しても問題ではありません(int test1 みたいに書いて、int test100まで書き続けても良い)

もう少しスマートに楽に宣言しつつ、処理内で使える手段が配列になります。
配列の宣言の方法は以下になります。

  型名 配列名[必要な要素数];

例: int test[100];

上記の例は、int型のtestという名前の配列を100個の要素で用意した。というものです。
変数の宣言と類は似ているので、覚えやすいと思います。

ポイントは、型の種類を混同することはできません。
例えば、int型を50個、char型を50個といった使い方はできません。

配列の取り扱い

配列の宣言時に必要な要素数を [ ] の中に記載すると解説しました。
配列を使って処理をする際、各要素にアクセスしながら制御をするのですが、
各要素は番号指定を行ってアクセスすることになります。

int test[100];という配列を宣言した場合、
用意された配列の中身は以下の様なイメージになります。

test[0];
test[1];
test[2];
test[3];
test[4];

 ・
 ・
test[99];

100個ある各要素に番号が振られています。
注意が必要な点は、先頭の要素は 0番 から始まるということです。
即ち、最後の要素は指定数より一つ少ない数値になります。

配列の使い方

配列の使い方は変数と同じイメージです。
上述した各要素に代入したい値を入れたり、取り出したりすることで使います。

また、宣言時に初期値を設定することも可能です。
例えば、初期値の設定イメージは以下です。

int test[5] = {0,0,0,0,0};

int型のtestという名前の配列を5つ要素を指定して宣言を行い、
宣言と同時に5つの要素は全て 0 で初期値を設定した。という状態です。

初期化の書き方は、配列名と要素数の右側に = で代入演算子を記載して、
{ } で囲まれた中に、設定したい初期値を , (カンマ)で区切って書きます。

以下、参考書のコード例の様に、要素数よりも初期化数が少なくてもエラーにはなりません。

#include <stdio.h>

int main(void)
{
    int array[10] = {42,79,13};

    printf("array[0] = %d\n", array[0]);
    printf("array[1] = %d\n", array[1]);
    printf("array[2] = %d\n", array[2]);
    printf("array[3] = %d\n", array[3]);
    printf("array[4] = %d\n", array[4]);

    return 0;
}

int型のarrayという配列名で10個の要素を宣言し、0番目に42、1番目に79、2番目に13を初期値として設定しています。
3番目と4番目には何も設定しない状態ということです。

printf関数で各要素内の値を表示させており、初期値格納された要素と0が格納されている要素があり、意図通りになっていることがわかります。
printf関数の使い方でも見て取れると思いますが、アクセスしたい要素には要素番号を指定することでアクセス可能です。

先頭の要素であれば、array[0]を指定すると、この要素に格納されている値が取り出せたり、違う値を設定したりできます。

以下の様に記載すると要素数は、初期化した数だけ生成されます。

#include <stdio.h>

int main(void)
{
    int array[] = {42,79,13}; /* 要素数が省略されている */

    printf("array[0] = %d\n", array[0]);
    printf("array[1] = %d\n", array[1]);
    printf("array[2] = %d\n", array[2]);

    return 0;
}

int array[10]; として、10個の要素を指定していましたが、int array[] と要素数を空白にすると、{42,79,13}; と3つの要素を初期値で設定したので、3要素用意されます。
つまり、int array[3]; と書いたのと同じです。

処理の途中で、array[1] = {42,79,13}; と一つの要素に複数の値を代入することはできません。エラーになります。宣言時に要素数を空白できましたが、処理の途中ではエラーになりますので、int array[] = {42,79,13}; の様に書けません。

全要素の表示

配列とfor文の組み合わせは、非常に良く使います。
全要素に対して、値を設定したり、各要素に格納されている値を取り出したり、for文を用いて配列の全要素へアクセスします。

#include <stdio.h>

int main(void)
{
   int array[] = {42,79,13,75,19};
   int i;

   for(i = 0; i < 5; i++)
   {
       printf("array[%d] = %d\n", i, array[i]);
   }

   return 0;
}

int array[] = {42,79,13,75,19};で5つの要素を持つ配列が宣言されました。
for文で5回処理を行い、arrayの要素をprintf関数で表示をしています。

array[i]と書いていることで、for文のi++;で1ずつ加算されていくので、
array[0] -> array[1] -> array[2] ・・・array[4]まで処理が進んで終了です。

ポイントは array[i] の i です。
要素数は数値だけでなく、変数を書いて変数の中に格納された値で要素指定ができる点です。

要素数を求める

上記のコード例では、5個の要素だったので面倒はありませんでしたが、
数十、数百の要素をint array[] = { ・・・} で指定されると、for文で何回処理をすべきかがわかりません。

この様な場合、以下の様に記載をすることで要素数だけ処理を行うことが可能です。

 sizeof(array) / sizeof(array[0])

sizeofというのは、サイズを取得する単項演算子です。
演算子なので、カッコ()がありますが、関数ではありません。

例えば、size_t size = sizeof(int); と記載しますと、sizeという変数名の中には、
4が格納されます。
厳密にはコンパイラ依存なので4とは言い切れないのですが、多くのコンパイラでは4が格納されます。

少し難しいかもしれませんが、4という数値はどこからきたか?
int型は4byteだからです。
sizeofはメモリサイズを取得すると解説しましたので、int型のサイズが得られるということです。

もう少し違う例としては、size_t size = sizeof(char); と書けば、size変数には1が格納されます。char型は1byteとなるためです。

それでは、sizeof(array) / sizeof(array[0]) はどうなるのでしょうか?
例として以下のコードを見てましょう。

int array[] = {42,79,13,75,19};
size_t size;
size = sizeof(array) / sizeof(array[0]);

size変数には、どの様な値が格納されるでしょうか?
sizeof(array) / sizeof(array[0]);の sizeof(array)は、array配列のサイズとなる値になります。
つまりは 20 です。

恐らく 20? 5でしょ?と思われたのではないでしょうか?
繰り返しになりますが、sizeofはメモリサイズを取得します。

array配列のメモリサイズは、4byte(int型)×5要素 = 20byteになり、20が格納されます。
右半分の sizeof(array[0]) はどうでしょうか?
array[0]のメモリサイズは1要素だけなので、int型 4byteです。

即ち、sizeof(array) / sizeof(array[0]); は、20 / 4 と置き換えでき、結果は 5 になります。
int array[] = {42,79,13,75,19}; で宣言した要素数は 5 なので、一致しており演算処理は問題ないことになります。

これで数十や週百の要素数であっても、一つずつ数えることなく要素数を算出することが可能です。

配列のコピー

配列の値を別の配列にコピーすることが可能です。
以下の様に書くことでコピーできます。
参考書のコードからポイント部分だけに絞って記載をします。

int array1[] = {42,79,13,19,41};
int array2[] = {1,2,3,4,5};

array2[0] = array1[0];
array2[1] = array1[1];
array2[2] = array1[2];

array2にarray1の要素をコピーしています。
要素0、要素1、要素2をarray1 -> array2へ一つずつ代入しています。

同じ要素番号でなくても問題ありません。
例えば以下の様な感じです。

int array1[] = {42,79,13,19,41};
int array2[] = {1,2,3,4,5};

array2[1] = array1[2];
array2[0] = array1[3];
array2[2] = array1[4];

該当する要素番号の値が、他方の指定された要素番号の要素へ格納されます。
その為、制御上でSwapさせたい場合などに用いることが可能です。

参考書では、for文のコピーをコード例として記載していますが、他の方法としてmemcpy関数が紹介されています。

memcpy関数は、過去に出てきた標準ライブラリの一つです。
標準ライブラリは、C言語の言語として汎用的に使用される処理を関数として予め用意された関数群のことです。

このライブラリの中に、コピーだけを処理してくれる関数がmemcpyになります。
ただ、組み込みのハードウェア制御を行うC言語では使用機会が限定されます。

メモリ容量が大きなマイコンを用いた製品開発であれば、使用機会があるかもしれませんが、
数名で開発する様な規模ですと、稀に目にする様な頻度になりますので、ここでは詳細な解説を割愛したいと思います。