応用理工学情報処理 4回目

次回、5回目(10月31日)の最後に、本日までの範囲で試験形式の演習を行います。通常の演習問題と異なり表示はTeamsの課題のみとし、開始時に公開して終了時刻(30分を予定)を提出期限とした遅延提出を認めない演習です。通常の試験と同様に他の受講者の答案を見ることは禁止ですが、教科書などを参考にすることは自由です。それまでに本日までの内容を復習しておきましょう。

今日は、テキスト第5章に従って配列を学びます。配列は、同じ型の変数の集まりを番号で管理します。この番号は添字(index)と呼ばれる整数値です。科学計算などで多数のデータを扱う場合には、必ず配列を用いることになります。他に、関数の基本的な部分を学びます。

(a) 配列 

配列は、同じ型の変数の集まりです。
整数型の配列宣言では

int a[5];//領域は確保されるが値は不定

int a[5] = {1, 2, 3, 4, 5};

int a[5]= {0}; // 全て0で初期化される

int a[] = {1, 2, 3, 4, 5}; a[5]が確保され指定の数値で初期化される。

という宣言方法があります。int の部分をdoubleとすれば倍精度浮動小数点型の配列が確保されます。a[5]とした場合に確保されるのはa[0]からa[4]までであり、a[5]は確保されないので注意してください。

「1」次のプログラムは配列の使い方を示したものです。配列は、まず宣言して領域を確保する必要があります。また、int vc[5]と宣言すると確保される変数はvc[0]からvc[4]までであることに注意してください。配列の添字は0から始まります。

/*
	配列の各要素に先頭から順に1,2,3,4,5を代入して表示
*/

#include  <stdio.h>

int main(void)
{
	int	 vc[5];		/* 要素数が5の配列 */

	vc[0] = 1;
	vc[1] = 2;
	vc[2] = 3;
	vc[3] = 4;
	vc[4] = 5;

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

	return (0);
}

「2」次のプログラムではforループを使って上のプログラムを効率よく記述しています。(List5-3)

/*
	配列の各要素に先頭から順に1,2,3,4,5を代入して表示(for文)
*/

#include  <stdio.h>

int main(void)
{
	int	 i;
	int	 vc[5];		/* 要素数が5の配列 */

	for (i = 0; i < 5; i++)
		vc[i] = i + 1;

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

	return (0);
}

「3」配列を使用する前には、一般にそれぞれの要素に特定の値を代入して初期化する必要があります。これは宣言と同時に行うことも出来ます。

/*
	配列の各要素を先頭から順に1,2,3,4,5で初期化して表示
*/

#include  <stdio.h>

int main(void)
{
	int	 i;
	int	 vc[5] = {1, 2, 3, 4, 5};		/* 初期化 */

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

	return (0);
}

「4」配列は、同じ型・サイズの配列であっても代入演算子で一度に全ての要素を別の配列にコピーすることはできません。常にひとつの要素ごとのコピーとなります。

/*
	配列の全要素を別の配列にコピー
*/

#include  <stdio.h>

int main(void)
{
	int	 i;
	int	 va[5] = {15, 20, 30};		/* {15,20,30,0,0} で初期化 */
	int	 vb[5];	

	for (i = 0; i < 5; i++)
		vb[i] = va[i];

	puts(" va vb");
	puts("-------");
	for (i = 0; i < 5; i++)
		printf("%3d%3d\n", va[i], vb[i]);

	return (0);
}

「5」全要素を逆順に並び替えるプログラム
配列とfor文を使うと、大きなデータに同じ処理を繰り返す計算を簡潔に表現することができる。
下のプログラムの配列の要素数を10個に変更してみよう。

/*
	配列の全要素を逆順に並べかえる
*/

#include  <stdio.h>

int main(void)
{
	int	 i;
	int	 vx[5];

	for (i = 0; i < 5; i++) {
		printf("vx[%d]:", i);
		scanf("%d", &vx[i]);
	}

	for (i = 0; i < 2; i++) {
		int  temp = vx[i];
		vx[i]     = vx[4 - i];
		vx[4 - i] = temp;
	}

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

	return (0);
}

====== 演習問題 4A (p4A.c) ==============

ある授業での試験の点数が以下の配列で与えられているとする(この部分はコピーして利用して下さい)。ただし、最後の-1は点数ではなくデーターが1つ前の要素で終わりであることを示し、受講者数はこの-1が検出される前までの配列の要素数とする(ソースコードの中にnTotal = 55;のような形で直接数値を指定しないこと)。

int score[] = 	{ 	91, 87, 95, 80, 78, 74, 89, 72, 82, 38, 95, 63, 82, 89, 55,
						53, 84, 82, 78, 79, 87, 75, 83, 72, 72, 78, 84, 75, 69, 85,
						93, 98, 77, 82, 97, 74, 80, 81, 77,  0, 67, 81, 92, 74, 69,
						74, 66, 45, 32, 58, 100, 78, 93, 45, 72, -1
					}; //試験の点数>=0、終端は-1とする。

この授業の評価をA: 80点以上、B: 70点~79点、C: 60点~69点、D: 59点以下、のようにしてそれぞれの評価人数と平均点を出力するプログラムを以下のように作成せよ。 

(1)main関数の最初に点数配列と評価人数などの変数を以下のように宣言する。

int score[] = { /* 上記の配列要素 */ };
int AL = 80, BL = 70, CL = 60; //評価A, B, Cの下限値
int nA = 0, nB = 0, nC = 0, nD = 0, nTotal = 0; //評価A, B, C, D, および全体の人数
 
(2)実行結果は以下のように出力されること。
受験者数:55人
平均点 :75.1
A: 25人
B: 17人
C:  5人
D:  8人
======

OKの基準:配列scoreの数字の数(要素数)を増やしても正常に動作すること。インデントのずれがあったとしても軽微なこと。

(b) オブジェクト形式マクロ

「1」オブジェクト形式マクロ

オブジェクト形式マクロとは、文字列の置換命令です。ソースコートをコンパイルして実行プログラムを作成する際に、最初に実行される処理になります。
たとえば
#define X 5
というオブジェクト形式マクロが存在した場合、最初にソースコード中のXを5で置き換える処理が行われます。
行の末尾にセミコロン( ; )が付かないことに注意してください。次の例はオブジェクト形式マクロの機能を示しています。このように、円周率の値をPIという文字で表すことは良くあります。

/*
	オブジェクト形式マクロのテスト
*/

#include  <stdio.h>
#define PI 3.14159265358979323846264338327950288

int main(void)
{
	printf("X = %.20f\n", PI);

	return (0);
} 
このプログラムはprintf関数で指定した%.20fの桁数20桁まで小数点以下を表示できません。それは、double型で表示できる桁数がおよそ15桁に制限されているためです。オブジェクト形式マクロは単なる置換命令なので、コンパイルの途中では、printf関数の引数PIは#defineで設定した長い桁数の数字で置き換えられています。コンパイルの最初に行われるプリプロセッサ―による処理(#で始まる命令)の結果を確認することが出来ますが、方法は処理系に異存します。Visual Studio 2022のコンパイラを使用している場合には、上記プログラムがEx4ホルダーにtest.cというファイル名で存在しているとして、cd ./Ex4 (Enter)でファイルの存在するホルダーに移動して、cl /P ./test.c (Enter)とすると、拡張子"i"の付いたtest.iというファイルが作成されます。このファイルでは#include <stdio.h>による部分が最初に10000行ぐらい続くのですが、最後の部分でPIがどのように置換されたかを確認することが出来ます。

====== 演習問題 4B (p4b.c) ==============

演習問題 4Aで与えられたscore配列の点数データの中央値と昇順に並べ替えた点数を出力するプログラムを以下のように作成せよ。中央値は受験者数が偶数で2n人の場合には、n番目とn+1番目の点数を四捨五入して整数で答えよ。
(1)score配列の末尾は必ず-1で終わるが、受験者数が変化しても動作するようにすること。ただし、受験者数0人の場合は無いものとして良い。
(2)配列要素の昇順並び替えにはforの2重ループを用いたバブルソートを用いてscore配列の要素自体を並び替えること。ただし末尾の-1は移動しないこと。下記の説明にあるバブルソート以外を用いた場合は不可
(3)実行出力は以下のようにすること。点数は10人ごとに改行して表示している。

受験人数: 55人
中央値 ; 78点
昇順に並べ替えた点数:
  0,  32,  38,  45,  45,  53,  55,  58,  63,  66, 
 67,  69,  69,  72,  72,  72,  72,  74,  74,  74, 
 74,  75,  75,  77,  77,  78,  78,  78,  78,  79, 
 80,  80,  81,  81,  82,  82,  82,  82,  83,  84, 
 84,  85,  87,  87,  89,  89,  91,  92,  93,  93, 
 95,  95,  97,  98, 100, [End] 
----------------------------------
(バブルソートによる降順並び替えの説明)
N人の点数が格納されている配列をdata[j]とする(j = 0, 1, 2, 3,..., N-1)。 data[0]とdata[1]を比較して、data[0]の方が大きければ比較した2つの値を入れ替える。 次にdata[1]とdata[2]を比較して、data[1]の方が大きければ比較した2つの値を入れ替える。 同様の処理をdata[i], data[i+1] (i = 2, 3,..., N-2)について行えば、data[N-1]が最大値となる。下図では、最初に最大値がdata[0]だったとした場合に比較のたびにその値が右側に移動して最後にdata[4]が最大値となる様子を示している。 この部分の処理を下のプログラム例に示す。ただし変数i, jはint型として宣言されているものとする。 変数iをi = 1;からi = N-1まで1ずつ変化させながら下記のforループを繰り返せば最大値が順番にdata[N-1], data[N-2]と格納されて並び替えが終了する。
i = 1;
for (j = 0; j < N-i; j++) {
    if (data[j] > data[j+1]) {
        int temp = data[j];
        data[j] = data[j+1];
        data[j+1] = temp;
    }
}

---------------------------------
アドバイス:最初は配列要素数をint score[] = {5, 4, 3, 2, -1}; のように少なくしてバブルソート部分を作成し、動作を確認してから実際のscore配列に置き換えると間違いに気づきやすいです。
===============================================================

(c) 2次元配列

多次元配列はテキスト5-2節を参考にしてください。
多次元配列の初期化方法は、1次元配列と同様の規則に従います。

整数型の2次元配列宣言では

int a[5][5]; //領域は確保されるが値は不定

int a[2][2] = {{1, 2}, {3, 4}}; // それぞれの値は、{{a[0][0], a[0][1]}, {a[1][0], a[1][1]}}に対応する。

int a[5][5]= {0}; // 全て0で初期化される

int a[][3] = {{1, 2, 3}, {4, 5,6}}; a[2][3]が確保され指定の数値で初期化される。初期化子が与えられている場合、最初の要素数を省略することが可能

という宣言方法があります。int の部分をdoubleとすれば倍精度浮動小数点型の配列が確保されます。

次の行列を加算するプログラムを実行してみましょう。

/*
	2行3列の行列を加算する
*/

#include  <stdio.h>

int main(void)
{
	int  i, j;
	int	 ma[2][3] = { {1, 2, 3}, {4, 5, 6} };
	int	 mb[2][3] = { {6, 3, 4}, {5, 1, 2} };
	int	 mc[2][3] = { 0 };

	for (i = 0; i < 2; i++)
		for (j = 0; j < 3; j++)
			mc[i][j] = ma[i][j] + mb[i][j];

	for (i = 0; i < 2; i++) {
		for (j = 0; j < 3; j++)
			printf("%3d", mc[i][j]);
		putchar('\n');
	}

	return (0);
}
配列ma, mbを初期化子を与えて初期化し、両者を加えた結果をmcに代入しています。配列mcも初期化子を与えて0で初期化していますが、これは特に必要ありません。

(d) 関数(1) 関数の基本

これまで、printf( ), scanf( )といったC言語が提供するライブラリ関数を使ってきました。これらの関数と同様の関数をプログラム作成者が作ることが出来ます。複雑なC言語プログラムを作成する場合には、プログラム全体をそれぞれが単純な機能を持つ部分に分解して関数に割り当てることによって、見通しよくプログラムを作成することが出来ます。

関数の定義: 

返却値型 関数名(仮引数リスト){ 関数の本体 }

下の関数では、返却値型がint, 関数名がmaxof, 仮引数リストがint x, int y となっています。2つの整数値を引数として受け取って、大きいほうの値を返す関数です。

int maxof( int x, int y)
{
    if ( x > y) return (x);
    else return (y);
}

関数をプログラム中で使用するには、

関数名(実引数リスト)

という形で関数を呼び出します。

上記のmaxof( )関数を用いて整数3, 5のうち大きいほうの値を求めてxという変数に代入するには、プログラム中で以下のように書きます。

x = maxof(3, 5);

次のプログラムは、上の関数を用いて入力した2つの整数のうち大きいほうを出力するプログラムです。

/*
	二つの整数の大きい方の値を返す関数
*/

#include  <stdio.h>

/*--- 大きい方の値を返す ---*/
int maxof(int x, int y)
{
	if (x > y)
		return (x);
	else
		return (y);
}

int main(void)
{
	int  na, nb;
	
	puts("二つの整数を入力してください。");
	printf("整数1:");	  scanf("%d", &na);
	printf("整数2:");	  scanf("%d", &nb);

	printf("大きい方の値は%dです。\n", maxof(na, nb));
	
	return (0);
}

関数の定義部分は、その関数を使用しているmain( )関数より前にある必要があります。関数呼び出しでコンパイラが実引数の型や数をチェックするため、その情報を前もって知っている必要があるからです。

====== 演習問題 4C (p4c.c) ==============

以下のプログラムは、物体を地表から初速度velocity(m/s)、仰角(水平面からの角度)angle_d(度)で投げた場合の水平到達距離dist(m)を求めるプログラムです(空気抵抗は無視)。数学関数sin( )を使っているので対応するヘッダーファイルmath.hをインクルードしています。
#include <stdio.h>
#include <math.h>
#define G 9.80665 // 重力加速度(m/s^2)
#define PI        3.14159265358979323
int main(void)
{
    double velocity; //(m/s)
    double angle_d, angle; // elevation angle (degree, radian)
    double dist; // 水平到達距離(m)
    printf("初速度(m/s):"); scanf("%lf", &velocity);
    printf("仰角(degree):"); scanf("%lf", &angle_d);
    angle = angle_d*PI/180.0;
    dist = velocity*velocity*sin(2*angle)/G;
    printf("水平到達距離:%.1f m\n", dist);
    return (0);
}


このプログラムを参考にして、物体の初速度velocity(m/s)をキーボードから入力してその水平到達距離を仰角0度から90度まで15度間隔で計算するプログラムを以下の指定の通りに作成せよ。
(1)初速度velocity(m/s)と仰角angle (rad)を引数として受け取り水平到達距離(m)を返す関数を作成して用いること
(2)プログラムを実行したときの出力は以下の出力例の通りとすること。
========= 出力例 ===================

仰角を変化させた場合の水平到達距離を計算します。
初速度(m/s)を入力:10
Angle(degree), Distance(m)
         0,          0.0
        15,          5.1
        30,          8.8
        45,         10.2
        60,          8.8
        75,          5.1
        90,          0.0
===================================

OKの基準:指定された形の関数を作成して利用していること。引数の単位間違いは不可。インデントのずれがあったとしても軽微なこと。

Excelでグラフを表示させてみよう
これは提出の必要はありませんが、早く出来た人はこのプログラムを実行した際のTerminal出力をコピーしてExcelに張付けてグラフを表示してみましょう。Excelのデータタブの真ん中あたりにある「区切り位置」の設定を「コンマ」とする必要がありますが、出力の数値をExcelの表にペーストすることが出来ます。その表全体を選択して、挿入=>グラフで散布図を選択すればグラフを表示することが出来ます。

=========================================