今日は、テキスト第5章に従って配列を学びます。配列は、同じ型の変数の集まりを番号で管理します。この番号は添字と呼ばれる整数値です。科学計算などで多数のデータを扱う場合には、必ず配列を用いることになります。他に、関数の基本的な部分を学びます。
配列は、同じ型の変数の集まりです。
整数型の配列宣言では
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); }
ある授業での試験の点数が以下の配列で与えられているとする(この部分はコピーして利用して下さい)。ただし、最後の-1は点数では無くデーターが1つ前の要素で終わりであることを示し、受講者数はこの-1が検出される前までの配列の要素数とする(ソースコードの中にn = 47;のような形で直接数値を指定しないこと)。
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, 93, 98, 77, 82, 97, 74, 80, 81, 77, 0, 67, 81, 92, 74, 74, 66, 45, 32, -1 }; //試験の点数>=0、終端は-1とする。
この授業の合格点数(それ以上であれば合格とする)を整数値としてキーボードから入力して、受講者数、合格者数を以下の実行結果の通りに出力するプログラムを作成せよ。ただし、太字はキーボードから入力した値を示す。
==実行結果==
合格点を入力:60
受講者数:47人
合格者数:41人
======
OKの基準:配列scoreの数字の数を増やしても正常に動作すること。インデントのずれがあったとしても軽微なこと。
「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) ==============
5人の学生の点数(整数値)をキーボードから整数型配列に読み込んで、その合計点と平均点、及び昇順に並び替えたそれぞれの点数を下記出力例の通りに表示するプログラムを作成せよ。ただし、学生の人数はプログラムの冒頭でオブジェクト形式マクロを使って、#define N 5 と指定し、人数が変更される場合にはその数字のみ変更すれば良いように記述すること(プログラム中で人数に関係する部分にNのみを用いることを意味する)。また、並び替えにはforの2重ループを用いたバブルソートを用いること。
---出力例---
5人の点数を入力
1人目:24
2人目:65
3人目:83
4人目:47
5人目:59
合計点= 278
平均点= 55.6
昇順に並び替えた点数:24, 47, 59, 65, 83, [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; } }
多次元配列はテキスト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で初期化していますが、これは特に必要ありません。
これまで、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(度)で投げた場合の水平到達距離L(m)を求めるプログラムです(空気抵抗は無視)。#include <stdio.h> #include <math.h> #define G 9.8 // 重力加速度(m/s^2) #define PI 3.14159265358979323846264338327950288 int main(void) { double velocity; //(m/s) double angle; // elevation angle (degree) double dist; // 水平到達距離(m) printf("初速度(m/s):"); scanf("%lf", &velocity); printf("仰角(degree):"); scanf("%lf", &angle); angle *= PI/180.0; dist = velocity*velocity*sin(2*angle)/G; printf("水平到達距離:%.1f m\n", dist); return (0); }
このプログラムを参考にして、物体の初速度velocity(m/s)をキーボードから入力してその水平到達距離を仰角0度から90度まで15度間隔で以下の出力例の通りに出力するプログラムを作成して下さい。ただし、velocityとangleを引数として受け取って水平到達距離を返す関数
double hdist( double velocity, double angle)
{
//........(プログラムを記述)........
}
を作成して利用することを必須と致します。
========= 出力例 ===================
仰角を変化させた場合の水平到達距離を計算します。
初速度(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の表にペーストすることが出来ます。その表全体を選択肢て、挿入>グラフで散布図を選択すればグラフを表示することが出来ます。
=========================================