05 月 01 日(金)1-2h

型変換と配列

前回,さまざまなデータ型について学んだ. 今回は,異なるデータ型の間の変換について理解しよう.

また,同じ型の変数がたくさん必要な場合,配列を使うと便利だ. 配列の使い方についても理解しよう.


自動的な型変換

ある型の変数には,同じ型のデータを代入できる. これは,当たり前だ.

しかし実は,異なる型のデータを代入してもかまわない. この場合,データの型は代入先の型に自動的に変換される. ただし,代入後のデータの値は,代入前とは違うものに変わってしまうかもしれない.

前回説明した通り,型が異なると,値の範囲も異なるためだ.

このことを List 1 のプログラムで確認しよう.

List 1. 代入による型変換のテスト conv1.c
main()
{
	double d;
	int    i;

	d = 12;		// double 型変数に整数を代入している
	i = 2.5;	// int 型変数に実数を代入している

	printf("d = %f\n", d);
	printf("i = %d\n", i);
}								

まず実行前に,ソースコードをよく読み,実行結果を予想しておこう:

そして実行後に,実際の結果と比較してみよう. 予想は的中したか?

よくある誤解: 「表示 printf( ) のときに型が変換される」 わけではない. printf( ) の変換指定子「%○」は, 表現(画面上の表示形式)を変換するだけだ. List 1 の場合,変換せず(整数を整数として)そのまま表示していることに注意. データ型(メモリ内部の記録形式)が変換されるのは,代入のときだ.

その証拠: 前回の練習問題で, 表示したいデータの型と合わない変換指定子を使ったとき, (一見して)無意味な表示になってしまったことを思い出そう. 上のソースコードでは,データの型に合った変換指定子を使っている. つまり,printf のときに値が変わったわけではない.

まだ疑うなら,たとえば,次のコードを追加してみるとよい: printf("i = %f\n", i); 実数 2.5 が代入された変数 i を実数として表示しようとしている. もし,この変数が実数を記録しているとしたら,2.5 を表示するハズですが何か?

次に,複数データ間で計算した場合について,データ型を調べて行こう. 計算結果のデータ型は,次の規則から自動的に決定される:

List 2 は,この変換規則を確かめるためのプログラムだ. これについても,結果を予想してから実行してみよう.

List 2. 演算による型変換のテスト conv2.c
main()
{
	// 整数÷整数
	printf("int/int is int.\n");
	printf("  10/6 = %d\n\n", 10/6);

	// 実数÷実数
	printf("double/double is double.\n");
	printf("  10.0/6.0 = %f\n\n", 10.0/6.0);

	// 実数÷整数
	printf("double/int is int ?\n");
	printf("  10.0/6 = %d\n\n", 10.0/6);
		/* 実数÷整数を整数として表示している.
		計算結果が int 型でなければ無意味な数が出るハズ. */

	printf("double/int is double ?\n");
	printf("  10.0/6 = %f\n\n", 10.0/6);
		/* 実数÷整数を実数として表示している.
		計算結果が double 型でなければ無意味な... */
}								

データ型と変換指定子との対応が悪いと (整数値を実数として表示したり,実数値を整数として表示したりすると), おかしな表示になる. 前回の練習問題を思いだそう.(クドい?)

なお,「/* 〜 */」は複数行のコメント(説明文), 「//」は一行のコメント. これらを入力する必要はない.


強制的な型変換

キャスト(cast)を利用すれば, データ型を強制的に変換できる. 次のような形式でソースコードに記述する:

(型)データ

たとえば, 次のように変換される:

では,List 3 のプログラムで実際に確認しよう.

List 3. キャストのテスト cast.c
main ()
{
	double d = 2.5;
	int    i = 12;

	printf("(int)double is int ?\n");
	printf("  (int)d = %d\n\n", (int)d);
		// キャスト結果が int 型でなければ無意味な数のハズ

	printf("(double)int is double ?\n");
	printf("  (double)i = %f\n\n", (double)i);
		// キャスト結果が double 型でなければ無意味な...
}								
ここでもまた,表示形式とデータ型の間の整合性を利用して, 変換結果の型を確認している.

以上,List 1 〜 List 3 のプログラムの実験結果から総合的に, 異なるデータ型の間の変換規則を学び取れたハズだ.


例:平均値計算プログラム

List 4 は整数データの平均値を計算するプログラムのソースである. このプログラムでは,データの個数およびその個数分の整数値を入力すると, 平均値が表示されるようになっている.

List 4. 平均値計算プログラム averager.c
main()
{
	int i, n, x, total=0;

	// データの個数の入力
	printf("データの個数 > ");
	scanf("%d", &n);

	// データの入力
	printf("%d 個の整数データ > ", n);
	for (i = 0; i < n; i++) {
		scanf("%d",&x);
		total += x;
	}

	// 平均値の表示
	printf("average = %f\n", (double)total/(double)n);
}								
データ入力関数 scanf( ) については後日説明.

なお,記号「+=」は,左辺の変数に右辺の値を加算するというものだ. たとえば,x += ax = x + a と同じことである. さらに,x++x += 1 と同じことになる. これら「+=」と「++」は インクリメント演算と呼ばれている.

ちなみに,デクリメント演算-=」と「--」もある. 意味はわかるよね?

配列変数

同じ型の複数個の変数を一列に並べたものが 配列(array)である.

たとえば,次の宣言文によって,10 個の int 型変数 a[0]a[1]a[2],…,a[9] が用意される:

int a[10];

Cでは,配列要素の添字(そえじ,要素番号,index) を 0 から数え始めるので, 変数 a[10](11 個目の配列要素)は存在しない ということに注意しよう. 要素数 n の配列宣言 int a[n] の場合, 用意される変数は a[0]a[n-1] の n 個である.

添字のデータ型は,もちろん,整数型でなきゃダメ.

では,List 5 を実行してみよう.

List 5 にはエラーが含まれている.訂正してみよう.
List 5. 配列のテスト array.c
main()
{
	int  a[10];		// 配列の宣言

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

実行結果を見るとわかるように, 前回の普通の変数と同様, 配列要素にもゴミが入っている. 大抵の場合,初期化が必要になる.

配列を初期化するには,宣言を次のように書けばよい:

int a[10] = { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 };

また,この例のように宣言と初期化を同時に行なう場合には, 次のように,配列の要素数を省略してもよい:

int a[] = { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 };
しかし,間違いを予防するため,要素数を省略しない方がよいだろう. (宣言の要素数と初期値の個数とでダブルチェック.)

次のコードでも同じ結果になる:

int a[10];
a[0] = 0;
a[1] = 1;
a[2] = 4;
a[3] = 9;
...
a[9] = 81;

しかし,これは非常に面倒だ. できるだけ短く済ませたい.

矛盾するかもしれないが... 「ソースコードは短かけりゃ短かいほど良い」ってことではない. 短かくしすぎると,わかりづらくなってしまう場合もある. 短かさよりもわかり易さを優先すること!

この例のように各要素の初期値に規則性がある場合には, 次のように for 文を利用して,初期化してもよい:

int a[10];
for (i = 0; i < 10; i++) {
	a[i] = i*i;	// i の2乗
}

なお,次のようなコードは間違い

int a[10];
a = { 0, 1, 4, 9, ..., 81 };	// コンパイルエラーになる

配列全体を一度に初期化できるのは,宣言と同時の場合だけだ.

また,特例として,全要素の初期値をゼロにしたければ, 次のようにしてもよい:

int a[10] = {};		// int a[10] = { 0, 0, 0, ..., 0} と同じ

次のコードも試してみよう:

int a[10] = {1, 2, 3};		// 一部の要素だけ初期値を指定...どーなる?

本日の課題

  1. 次の計算式の計算結果について, およびはそれぞれ何か?
  2. ヒント:List 2 を利用して実験しよう.
  3. 試験成績(0 〜 100 の整数)を入力すると, 次のような得点分布表を出力するプログラム histogram.c を作成せよ. ただし,得点欄には 1 桁目を切り捨てた点数を表示すること.
  4. 実行例:

    $ ./histogram
    データ数 > 40
    得点データ > 76 83 91 77 ...
    
    得点 人数
    100 :  2
     90 :  6
     80 :  8
      .
      .
      .						
     10 :  1
      0 :  0	
    

    ヒント:

    余裕のある者は,次のように人数を棒グラフ表示するように工夫してみよう.

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

レポートには実行結果,ソースコード,考察(説明,推理,疑問)などを書くこと. 考察の文章については,論理的に!

レポート提出

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