多数のデータを手軽に取り扱うため,配列の適切な使い方を理解しよう.
複数の同型の変数の集合を一気に用意できる.
ソースコード記述方法:
型名 配列名[要素数];
具体例:
int data[100];
これで,100個の int型の要素変数 data[0],data[1],data[2],...,data[99] から成る配列 data が用意される. なお,要素数 100 と要素番号 0〜99 との違いに注意しよう.
配列 a[N] の場合...
ソースコード内の数値に名前を付けておける.
ソースコード記述方法:
#define マクロ名 数値
具体例:
#define N 100 // マクロ名 N として数値 100 を定義 int data[N]; // コンパイル時に int data[100]; へ書き換えられる N = 0; // NG.コンパイル時に 100 = 0; へ書き換えられ,エラーとなる
コンパイル時にソースコード中のマクロ名 N が 定義内容の数値 100 へ自動的に書き換えられる. マクロは,変数と似ているように見えるが,まったく異なり, プログラム実行時に内容(数値)の変更はできない. プログラム実行中に不変であるべき数値を ソースコード全体で一貫的に矛盾なく記述したい場合に利用する.
いつもの通り,作業用ディレクトリを準備しよう:
$ cd $ mkdir c-0620 $ cd c-0620
複数データの入力・修正が可能なプログラム record.c を作成してみよう. (前回の平均計算プログラムでは,入力データについて, 最新のもの1個以外を捨てていた. 今回は入力データをすべて残す.)
データ入力については後回しとして, データ表示の機能だけ作り, とりあえず,配列の本的な使い方だけ確認してみよう:
$ vim record.c # エディタは何でも OK
#include <stdio.h> int main(void) { int data[3]; // データ記録用の配列 int i; // 要素番号のカウンタ printf("データ:"); for (i = 0; i < 3; i++) { printf("%d ", data[i]); // 配列要素の数値を表示 } printf("\n"); // 改行 return (0); }
$ cc record.c -o record $ ./record ...
普通の変数と同様,配列要素にもゴミが入っている. 大抵の場合,初期化が必要になる. ソースコードの配列宣言の部分を 次のそれぞれの行と差し替えて試してみよう:
// int data[3];int data[3] = {1, 2, 3}; // 初期値を一気に指定できる // int data[] = {1, 2, 3}; // 初期値を指定したら要素数は省略できる // int data[3] = {1, 2, 3, 4, 5}; // 初期値が多すぎ...これはNG // int data[3] = {1, 2}; // 初期値は少ないが...これはOK // int data[3] = {}; // これもOK.初期値を一気にゼロにできる
データを順番に入力できるようにしてみよう;
... int data[3] = {}; ... for (i = 0; i < 3; i++) { printf("%d番目の整数データ > ", i); scanf("%d", &data[i]); // 要素 data[i] へデータを入力 } printf("データ:"); ...
さらに,要素番号も指定してデータ入力できるようにしてみよう:
...// for (i = 0; i < 3; i++) {while (1) { printf("要素番号(-1 で終了)> "); scanf("%d", &i); // 要素番号を入力 if (i < 0) break; printf("%d番目の整数データ > ", i); scanf("%d", &data[i]);// }printf("データ:"); ... } return (0) ...
これで,データの書き換えも自由にできるようになった. しかし,要素番号として大きすぎる数値を入力してしまうと... 実行時エラーによって強制終了させられたり, 異常動作に陥ることもある. (いろいろな数値で試すこと.)
原因は,配列用のメモリ領域の範囲外に データが書き込まれてしまったことである. これはバッファオーバラン(buffer overun)と呼ばれ, C言語プログラムの(本来あってはならないが)よくある重大な欠陥である.
バッファオーバランを防止してみよう:
... printf("要素番号(-1 で終了)> "); scanf("%d", &i); if (i < 0) break; if (i >= 3) continue; // 範囲外の要素番号を無視 ...
これで,とりあえずオーバランは回避できただろう. しかし,次の通り,これだけでは不充分だ.
データ配列の要素数について,3 では少なすぎるので,増やしてみたい. この場合,ソースコード中の複数箇所にある定数 3 をすべて書き換える必要がある. しかし,この時,間違いが発生しやすい. たとえば,if (i >= 3) の 3 だけを 10 に書き換え, 配列宣言 data[3] の 3 をそのまま書き換え忘れてしまうと? バッファオーバランが簡単に発生してしまう.
プログラミング時の間違いの予防策を取り入れてみよう. 配列要素数をマクロ定義しておき, 即値(定数 3 そのまま)を ソースコード内に何度も書かないようにしておけばよい:
#include ... #define N_DATA 3 int main(void) { int data[N_DATA]; ... while (1) { ... if (i >= N_DATA) continue; ... for (...; i < N_DATA; ...) { ... ... } ... }
こうしておけば,1箇所(#define の 3)だけ変更すれば, 関連するすべての部分を漏れなく自動的に書き換えてもらえる. 最初から,こんな書き方をしておくべきだったんだ.
教科書 pp.113-114 の練習問題 7-1 および 7-4 に取り組め.
試験成績(0 〜 100 の整数)を入力すると, 得点分布表を出力するプログラム histogram.c を作成せよ. ただし,得点欄には1桁目を切り捨てた点数を表示すること. (たとえば,得点 76 を入力すると,70 点台の人数としてカウントされる.) また,人数(データの個数)は無制限とする.
実行例:
$ ./histogram 得点(最後に -1)> 76 83 91 77 ... -1 得点 人数 100 : 2 90 : 6 80 : 8 . . . 10 : 1 0 : 0
得点範囲が 0 点台,10 点台,20 点台,...,100 点の 11 段階なので, 配列は int count[11].
...というか,使えない. 得点を配列に記録しようとすると, 要素数(人数の上限)を事前(ソース作成時)に 固定しなければならなくなる.
詳しく...得点の変数を int point とすると, count[point/10]++ すればよい. これは,たとえば point = 65 のとき, point/10 → 6 なので, つまり,count[6]++ となる. これで 60 点台が 1 人増えることになる. これを全入力データに対して繰り返す.
余裕のある人は,人数を棒グラフ表示してみては?
得点 人数 グラフ 100 : 2 : ** 90 : 6 : ****** 80 : 8 : ******** . . . 10 : 1 : * 0 : 0 :
提出: