配列

多数のデータを手軽に取り扱うため,配列の適切な使い方を理解しよう.

教科書の該当範囲:第7章,第16.2節

基礎知識

配列の宣言

複数の同型の変数の集合を一気に用意できる.

ソースコード記述方法:

型名 配列名[要素数];

具体例:

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個以外を捨てていた. 今回は入力データをすべて残す.)

Step 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.初期値を一気にゼロにできる

Step 2. 配列データの入力・修正

データを順番に入力できるようにしてみよう;

...
	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言語プログラムの(本来あってはならないが)よくある重大な欠陥である.

バッファオーバランの無茶な実験のひとつとして, 要素番号にはマイナス値を指定してもよいだろう. ソースコードの if-break 行をコメント化し, 負の要素番号を入力してみよう.

Step 3. 安全対策

バッファオーバランを防止してみよう:

...
		printf("要素番号(-1 で終了)> ");
		scanf("%d", &i);
		if (i < 0) break;
		if (i >= 3) continue;	// 範囲外の要素番号を無視
...

これで,とりあえずオーバランは回避できただろう. しかし,次の通り,これだけでは不充分だ.

データ配列の要素数について,3 では少なすぎるので,増やしてみたい. この場合,ソースコード中の複数箇所にある定数 3 をすべて書き換える必要がある. しかし,この時,間違いが発生しやすい. たとえば,if (i >= 3) の 3 だけを 10 に書き換え, 配列宣言 data[3] の 3 をそのまま書き換え忘れてしまうと? バッファオーバランが簡単に発生してしまう.


Step 4. 更なる安全対策

プログラミング時の間違いの予防策を取り入れてみよう. 配列要素数をマクロ定義しておき, 即値(定数 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]

  • 人数が無制限なので,得点を記録するための配列は使わない.
  • ...というか,使えない. 得点を配列に記録しようとすると, 要素数(人数の上限)を事前(ソース作成時)に 固定しなければならなくなる.

  • 階級値(得点÷10 の整数値)が得点配列の要素番号となるので, その配列要素をインクリメントする.
  • 詳しく...得点の変数を int point とすると, count[point/10]++ すればよい. これは,たとえば point = 65 のとき, point/10 → 6 なので, つまり,count[6]++ となる. これで 60 点台が 1 人増えることになる. これを全入力データに対して繰り返す.

  • 配列要素の操作では,バッファオーバランに注意すること.
  • 結果表示の得点欄で1桁目を切捨てるには? この場合,割り算(得点÷10)ではなく,掛け算(階級値×10)を使えばよい. (逆転の発想.)
  • 結果表示では,変換指定子("%...")を工夫し レイアウトを整えよう.

余裕のある人は,人数を棒グラフ表示してみては?

得点 人数 グラフ
100 :  2 : **
 90 :  6 : ******
 80 :  8 : ********
  .
  .
  .
 10 :  1 : * 
  0 :  0 : 

提出:


(c) 2018, yanagawa@kushiro-ct.ac.jp