配列
ここでは、配列について説明します。
配列とは同じ型のデータの集合です。
ある関連したデータ(例:100人の学生の各々の成績等)を
普通に変数で宣言する場合には100個も変数を必要とします。
配列はその100個の変数を1つにまとめることで変数の数を減らします。
配列は
型 配列名[要素数]; |
と宣言します。
要素数とは、配列に必要な個数のことで、配列の1つ1つを要素と呼びます。
int a[10]; |
と宣言した場合はint型の配列が10個用意されます。
ただし、a[0]から始まりa[9]までの10個で、a[10]という配列の要素は存在しません。
例1
#include <stdio.h> int main(void) { int a[4]; a[0]=1; a[1]=3; a[2]=4; a[3]=6; printf("%d\n%d\n%d\n%d\n",a[0],a[1],a[2],a[3]); return 0; } |
このプログラムは、配列の各要素に値を代入させてその値を表示するものです。
例2
#include <stdio.h> int main(void) { int a[1000],i,sum=0; for(i=0;i<1000;i++){ a[i]=i+1; sum+=a[i]; } printf("%d\n",sum); return 0; } |
このプログラムは、配列a(要素数は1000)の各要素に要素数+1を代入させ、
配列のすべての要素の合計を表示させるというものです。
sumを0で初期化したのは、初期化しないとsumには適当な整数値で初期化されるので、
合計値が誤った数値になってしまうからです。
※ 上記のプログラムでa[i]という表記をしていますが、
宣言時には要素数に変数を使用することはできませんので注意してください。
例1では各要素各々に、例2ではfor文により配列に値を代入しましたが、
例1のように各要素それぞれに値を1個1個代入するのは面倒で、
また、例2では、各要素には計算で求まる決まった値しか代入できません。
そこで、配列の初期化を行うことで、各要素に簡潔に値を代入することができます。
int a[5]={1,2,3,4}; |
と宣言すると、
a[0]=1; a[1]=2; a[2]=3; a[3]=4; a[4]=0; |
と同じ意味になります。
配列の初期化時は{ }で初期化を行うと言うところに注意してください。
ここで、a[4]がなぜ0になるのかというと、
配列は初期化をする際、一部の要素のみが初期化された場合は、
初期化されていない要素は0で初期化するという決まりがありますので、a[4]は0となります。
また、
int a[5]={0} |
とした場合には全要素が0で初期化されます。
これは、a[0]は宣言時の初期化により0で初期化され、
残りの要素は、初期化されていない要素は0で初期化するという決まりにより
0で初期化されます。
これまで、配列の宣言時に配列の要素数を書きましたが、
実は、配列の初期化を行う際には配列の要素数は指定しなくてもいいのです。
ただし、0で初期化する場合を除きます。
int a[]={1,2,3,4}; |
と宣言した場合には配列aは自動的に要素数が4であると解釈されます。
これにより、配列の宣言における要素数より初期化する数値の個数が多いときに起きるエラーを回避することが出来ます。
配列を用いることで、あるデータの最大値、最小値を求めることが容易に出来ます。
例
#include <stdio.h> int main(void){ int data[]={75,32,84,62,93}; int i,max=data[0],min=data[0]; for(i=0;i<sizeof(data)/sizeof(data[0]);i++){ if(max<data[i]){ max=data[i]; /*最大値の更新*/ } if(min>data[i]){ min=data[i]; /*最小値の更新*/ } } printf("最大値=%d\n最小値=%d\n",max,min); return 0; } |
このプログラムはdata配列に値を代入し、初めにdata[0]を最大値、最小値としておき、
もし、次のデータが最大値よりも大きいまたは最小値よりも小さかったら更新をすることで最大値、最小値を求めています。
これを、配列を用いないと、for文は使用できず、また、そのデータの数だけ変数が必要になりますので、
if文はその変数の個数必要になります。よって配列を用いたほうが明らかに良いことがわかります。
for文に見慣れないsizeofというのがありますが、これはsizeof演算子といい、
sizeof(型、変数等); |
と記述するとその評価した値は型、変数等のサイズ(単位はバイト)となります。
一般にsizeof(int)と記述すれば4となります(環境によっては値が異なる場合があります)。
これは、int型は4バイト必要とするということです。
上のプログラムでは配列の要素数を求めるのにsizeof演算子を使用しています。
sizeof(data)は配列全体のサイズ、sizeof(data[0])は配列のdata[0]のみのサイズを求めています。
全体のサイズを1個のサイズで割っているのでその値はその配列の要素数となります。
よって配列の要素数を計算で求める場合にはsizeof演算子を利用して求めましょう。
上のプログラムをすこし変えると、データの平均値も求めることが出来ます。
変更例
#include <stdio.h> int main(void){ int data[]={75,32,84,62,93},data_size; int i,max=data[0],min=data[0],sum=0; for(i=0;i<(data_size=sizeof(data)/sizeof(data[0]));i++){ if(max<data[i]){ max=data[i]; /*最大値の更新*/ } if(min>data[i]){ min=data[i]; /*最小値の更新*/ } sum+=data[i]; } printf("最大値=%d\n最小値=%d\n",max,min); printf("平均=%f\n",(double)sum/data_size); return 0; } |
合計値を代入する変数としてsumを、data配列の要素数を代入する変数としてdata_sizeを新たに宣言しています。
for文の条件式のところは、data_sizeにdata配列の要素数を代入すると同時に、i<data_sizeの評価も行っています。
()をつけているのは演算子(ここでは<と=)の優先順位がプログラムの動作とは意図しないものになってしまうため、
()により優先順位を変更しています。
簡単に説明すると数学における+より×の方が計算の優先順位が上であることと同じです。
(優先順位について詳しくはは後に説明します)
ここからは多次元配列について説明します。
多次元配列とは、配列の配列の配列の…(次元が多いほど長くなる)と言うことです。
多次元配列は場合にもよりますが、おそらく多くて3次元配列までです(場合によってはそれ以上ということもあるかもしれません)。
多次元配列を使用することにより、行列の計算(行列が分からない人はググってください)などの複雑な計算が可能となります。
例
/*行列の四則演算(剰余算は除く)*/ #include <stdio.h> int main(void){ int i,j,k; int x[2][2],y[2][2],z[2][2]={0},w[2][2]={0},s[2][2]={0}; printf("2行列の行列の四則演算(剰余算を除く)を行います。\n"); printf("2つの行列x,yの値を入力してください。\n"); printf("x:\n"); printf("1行列="); scanf("%d",&x[0][0]); printf("1行列="); scanf("%d",&x[0][1]); printf("2行列="); scanf("%d",&x[1][0]); printf("2行列="); scanf("%d",&x[1][1]); printf("y:\n"); printf("1行列="); scanf("%d",&y[0][0]); printf("1行列="); scanf("%d",&y[0][1]); printf("2行列="); scanf("%d",&y[1][0]); printf("2行列="); scanf("%d",&y[1][1]); for(i=0;i<sizeof(x)/sizeof(x[0]);i++){ for(j=0;j<sizeof(x[0])/sizeof(x[0][0]);j++){ z[i][j]=x[i][j]+y[i][j]; } } for(i=0;i<sizeof(x)/sizeof(x[0]);i++){ for(j=0;j<sizeof(x[0])/sizeof(x[0][0]);j++){ w[i][j]=x[i][j]-y[i][j]; } } for(i=0;i<sizeof(x)/sizeof(x[0]);i++){ for(j=0;j<sizeof(y[0])/sizeof(y[0][0]);j++){ for(k=0;k<sizeof(x)/sizeof(x[0]);k++){ s[i][j]+=x[i][k]*y[k][j]; } } } printf("加算\n"); for(i=0;i<sizeof(x)/sizeof(x[0]);i++){ for(j=0;j<sizeof(x[0])/sizeof(x[0][0]);j++){ printf("%3d ",z[i][j]); } printf("\n"); } printf("減算\n"); for(i=0;i<sizeof(x)/sizeof(x[0]);i++){ for(j=0;j<sizeof(x[0])/sizeof(x[0][0]);j++){ printf("%3d ",w[i][j]); } printf("\n"); } printf("乗算\n"); for(i=0;i<sizeof(x)/sizeof(x[0]);i++){ for(j=0;j<sizeof(x[0])/sizeof(x[0][0]);j++){ printf("%3d ",s[i][j]); } printf("\n"); } return 0; } |
少し長いプログラムですが、2つ行列の加算、減算、乗算を求めています。
scanf関数により値を入力させて、for文の使用により計算しています。
ここでのsizeof演算子では、配列の最初の要素数(x[2][2]でいうところの最初の2)と
次の要素数(x[2][2]でいうところの2番目の要素数、以下ではこの表記を使用)を求めています。
上のプログラムの記述でおそらく次の要素数の求め方は分かったと思いますので説明は省略します。
他に、2次元配列の使用例は、縦と横のみの表のデータを扱う際に有効です。
例
|
国語 |
数学 |
社会 |
理科 |
1学期期末 |
90 |
23 |
56 |
25 |
2学期期末 |
75 |
85 |
42 |
71 |
3学期期末 |
65 |
46 |
74 |
95 |
上記の表を、2次元配列を用いると
int score[][4]={{90,23,56,25},{75,85,42,71},{65,46,74,95}}; |
と記述できます。
これは、各時期におけるテストごとにまとめたものです。
教科ごとにまとめると以下のようになります。
int score[][3]={{90,75,65},{23,85,46},{56,42,74},{25,71,95}}; |
1次元配列のところで、配列の要素数を省略できると書きましたが、それが出来るのは実は最初の要素数のみなのです。
なので、上記において次の要素数は記述しています。
また、1次元配列と同様に、要素を省略した場合はその部分は0に初期化されます。
さらに、{}の中にある{}は省略可能です。
例
int score[][3]={90,75,65,23,85,46,56,42,74,25,71,95}; |
また、以下のように記述することも可能です。
int score[][3]={{90,75,65}, {23,85,46}, {56,42,74}, {25,71,95}, }; |
最後の,は付けていても構わないことになっています。この記述は見かけ上のバランスを良くする場合に行います。
この2次元配列と先ほどの平均を求めるプログラムを使用すると、上のデータにおける平均点を求めることができます。
#include <stdio.h> int main(void){ int score[][3]={{90,75,65},{23,85,46},{56,42,74},{25,71,95}}; int i,j,sum1[4]={0},sum2[3]={0}; int score_size=sizeof(score)/sizeof(score[0]),score0_size=sizeof(score[0])/sizeof(score[0][0]); double average1[4],average2[3],averageaverage,sum=0; for(i=0;i<score_size;i++){ for(j=0;j<score0_size;j++){ sum1[i]+=score[i][j]; } } for(i=0;i<score0_size;i++){ for(j=0;j<score_size;j++){ sum2[i]+=score[j][i]; } } for(i=0;i<score_size;i++){ average1[i]=(double)sum1[i]/score0_size; } for(i=0;i<score0_size;i++){ average2[i]=(double)sum2[i]/score_size; } for(i=0;i<score_size;i++){ sum+=average1[i]; } averageaverage=sum/score_size; printf("\t\t国語\t数学\t社会\t理科\t平均\n"); for(i=0;i<score0_size;i++){ printf("%d学期期末\t",i+1); for(j=0;j<score_size;j++){ printf("%2d\t",score[j][i]); } printf("%4.2f\n",average2[i]); } printf("平均\t\t"); for(i=0;i<score_size;i++){ printf("%4.2f\t",average1[i]); } printf("%4.2f",averageaverage); printf("\n"); return 0; } |
毎回毎回sizeof演算子を用いて要素数を求めるというのは書くのが面倒なので、変数に要素数を代入しています。
文字列中にある\tは、水平タブといい、主に、文字列等の位置を合わせるのに使用します。
これにより、プログラムの実行結果が見やすくなります。
ここで、配列のコピー方法ですが、以下のように書いてもコピーはできません。(a、bともに同じ要素数の配列)
b=a; |
ではどうすれば良いかというとfor文を用いて配列の要素1つずつをコピーします。
for(i=0;i<sizeof(a)/sizeof(a[0]);i++){ b[i]=a[i]; } |
このように記述することで配列のコピーが可能となります。
次に#define疑似命令(命令、指令等呼び方多数存在)について説明します。
#define疑似命令は後に記述する関数に似た機能(マクロと呼ぶ)を持ちます。
今まで関数については特に詳しく記述をしていないのですが、
簡単に言うと関数とはmain関数やprintf、scanf関数のことです。
どのような使い方をするかというと
#define マクロ名 処理; |
と宣言すると、プログラム内でマクロ名を記述した際は、
その部分は処理の部分に書かれたものに置き換わります。
一般に変数と区別するためにマクロ名はすべて大文字で記述します。
※マクロ名についての制約は基本的に変数名と同じですが、
予約語を使用することができてしまいます。
普通は予約語をマクロ名にすることはまずないと思いますが、
絶対に行わないでください。たとえば、マクロ名をint、処理をdouble
としたときは、全てのintがdoubleに変わってしまうため
intが宣言できなくなってしまいます。また、自作関数における引数、戻り値も
intがdoubleになってしまいます。
例
#include <stdio.h> #define N 3 #define A 3.14 #define M "#define" #define X printf("%d\n%f\n%s\n",N,A,M); int main(void){ X return 0; } |
これは何を行っているかというと、#define擬似命令で3がN、
3.14がA、"#define"がM、printf("%d\n%f\n%s\n",N,A,M);がX
に置換されていて、main関数内でXとしか書いていないのは、
Xはprintf("%d\n%f\n%s\n",N,A,M);と解釈され、
printf関数内のN,A,Mはそれぞれ3、3.14、"#define"と解釈されています。
つまり、Xは
printf("%d\n%f\n%s\n",3,3.14, "#define");
と同じとみなされます。
つまり、#define擬似命令で定義されたマクロ名をプログラム内に記述すると
そのマクロ名の部分は処理の部分に書かれたものに置換されて実行されます。
普通、変数を宣言するときなどでは;を最後につけますが、
マクロ名の場合はそのまま置換されるため;をつけると
プログラムの途中に;が入ってしまうためコンパイルエラーとなります。
上のプログラムでN、A、Mのいずれかに;をつけると
コンパイルエラーになることが確認できます。
Xだとprintf関数の最後の部分に;が必要なために;をつけています。
なので、さらに;をつけても特にコンパイルエラーになることはありません。
また、#define擬似命令は特に記述する際に順番を気にする必要はありません。
なぜなら、プログラムを実行する前にすでに定義してしまうからです。
なぜこれをここで説明したかというと、
実は、配列の要素数には変数を使用することはできないと書きましたが、
マクロ名に関しては使用可能だからです。
例
#include <stdio.h> #define N 3 int main(void){ int i,a[X]; for(i=0;i<N;i++){ a[i]=i+1; printf("a[%d]=%d\n",i,i+1); } return 0; } |
このように書くと、わざわざsizeof演算子で配列の要素数を求めなくてもよく、
また、配列の要素数の変更はNの値を変更するだけで可能なので非常に簡単にできます。
#define擬似命令にはさらにマクロという機能があるのですが、
これは関数と酷似しているため、関数のところで詳しく説明します。