第8講 関数の学習
第1話 関数とは?
関数というと、ブルーな気分になってしまう方もいらっしゃるかもしれませんね。
『苦手だな。』と。
でも全然いりませんよ。
すでに説明したとおり、関数とは独立パーツのことです。
プラモデルでいえば、部品に相当します。
プラモデルの部品は、独立部品です。
独立とはどういうことでしょうか。
人間の内臓もいろいろなパーツももっています。
胃、腎臓、肝臓など。
しかし、これらのパーツは独立していません。
例えば、腎臓が悪くなると、将来的には肝臓などの他の臓器なども影響を受けます。
肝臓もやられると、やがてはいろいろな臓器が悪くなっていきます。
内臓を含む人間の中のいろいろなパーツは、
有機的に相互に複雑に関連しあっています。
だから、糖尿病が進行すると最悪失明します。
それに対して、プラモデルの方は、ある部品が壊れても他の部品には何の影響も与えません。
部品同士が、有機的に複雑に関連しあっているのではなく、単純結合をしているだけだからです。
それに対して、例えば脳細胞はシナプスを通して複雑に結びついています。
Webサイト同士の関係も蜘蛛の巣のように非常に複雑に絡み合っています。
プラモデルの部品は結びつきが簡単です。高々数カ所で繋がっているだけです。
もし、プログラムを構成するパーツが脳細胞やWebサイトのように複雑に絡み合っているとすれば、
理解が難しく、組むのも大変になってしまいます。
昔のBASICは、各パーツが独立していなくて、それぞれのパーツが脳細胞のシナプス結合のように複雑に関連しあっていました。
Go To文を通して、あちらこちらを行ったり来たりしていて、スパゲッティプログラムと呼ばれていました。
最大の問題点は、各パーツが独立していないので、
同じ変数名を使っていると知らないうちに他のパーツで値が書き換えられれしまう可能性がありました。
つまり、かつてのBASICでは、常にプログラムの全体を見ていないと、プログラムが組めなかったわけです。
そして、パーツの中にいくつも出口と入り口があって、プログラムを複雑なものにしていました。
これだともちろん何百人の人が分業して、プログラムを組むことは不可能だったわけです。
幸いにしてC言語の方は、最初から構造化プログラミングという考え方がしっかりしていました。
構造化プログラミングというは、独立パーツをプラモデルのように単純に結合させていくプログラミングです。
ポイントは、二つです。
一つは、各部品は完全に独立していてたとえ同じ変数名を使用していても、
他のパーツで書き換えられてしまう心配はありません。
そして、入り口と出口が原則一つであるというのが二つ目のポイントです。
結びつきを単純にするためです。
独立部品から全体を組み立てることを構造化プログラミングというのです。
具体例を作って説明していきましょう。第7講で作ったdo..while文による2次元ループ開いてください。
もし、上書きなどでそのソフトがすでにない場合は、下のように新たにタイピングして下さい。
#pragma endregion
private: System::Void button1_Click(System::Object^
sender, System::EventArgs^ e) {
//変数の宣言
int
a[5][5],b[5][5],i,j;
String^
w=L"";
//aの自然配列データ作成
i=0;
do{
j=0;
do{
a[i][j]=5*i+j+1;
j++; //これを忘れると永久ループになる。
}while(j<5);
i++; //これを忘れると永久ループになる。
}while(i<5);
//データaの表示
i=0;
do{
j=0;
do{
if(a[i][j]<10)w+=L"0"+(a[i][j]).ToString()+L"
";
if(a[i][j]>=10)w+=(a[i][j]).ToString()+L"
";
j++; //これを忘れると永久ループになる。
}while(j<5);
w+=L"\n";
i++; //これを忘れると永久ループになる。
}while(i<5);
label1->Text=w;
//bのデータ(aの配置行列)作成
i=0;
do{
j=0;
do{
b[j][i]=5*i+j+1;
j++; //これを忘れると永久ループになる。
}while(j<5);
i++; //これを忘れると永久ループになる。
}while(i<5);
//データbの表示
i=0;
w=L"";
do{
j=0;
do{
if(b[i][j]<10)w+=L"0"+(b[i][j]).ToString()+L"
";
if(b[i][j]>=10)w+=(b[i][j]).ToString()+L"
";
j++; //これを忘れると永久ループになる。
}while(j<5);
w+=L"\n";
i++; //これを忘れると永久ループになる。
}while(i<5);
label2->Text=w;
//データaとデータbの合計の表示
i=0;
w=L"";
do{
j=0;
do{
if(a[i][j]+b[i][j]<10)w+=L"0"+(a[i][j]+b[i][j]).ToString()+L"
";
if(a[i][j]+b[i][j]>=10)w+=(a[i][j]+b[i][j]).ToString()+L"
";
j++; //これを忘れると永久ループになる。
}while(j<5);
w+=L"\n";
i++; //これを忘れると永久ループになる。
}while(i<5);
label3->Text=w;
}
};
}
このプログラムコードは、長すぎて全体を見通すのが大変です。
そこで、枠で囲った部分を独立させます。
そして、それぞれのパーツ名を順にf1()、f2()、f3()、f4()、f5()とすると、
#pragma endregion
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^
e) {
int a[5][5],b[5][5],i,j;
String^ w=L"";
f1(); //データ1発生
f2(); //データ1表示
f3(); //データ2発生
f4(); //データ2表示
f5(); //データ1とデータ2の和の計算と表示
}
};
}
とかなり見通しのよいものになります。
各部品を関数といいます。実は、
#pragma endregion
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^
e) {
int a[5][5],b[5][5],i,j;
String^ w=L"";
f1(); //データ1発生
f2(); //データ1表示
f3(); //データ2発生
f4(); //データ2表示
f5(); //データ1とデータ2の和の計算と表示
}
も関数の一つです。ただし、これは独立部品5つf1()、f2()、f3()、f4()、f5()から組み立てたものになっています。
独立部品=関数を作るには、
void 関数名(void){
}
とします。ここで、voidとは値なしを意味します。
整数型の値を返す関数なら、
int 関数名(void){
}
また、関数に整数型の値を引き渡し関数がそれを加工して整数型の値を返すなら、
int 関数名(int x){
}
となります。これが本来の関数ですね。f(x)は、値xを渡され、値を加工して値を返すものだからです。
渡す値を引数(ひきすう)といいます。そして、返される値を戻り値といいます。
つまり、
#pragma endregion
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^
e) {
int a[5][5],b[5][5],i,j;
String^ w=L"";
f1(); //データ1発生
f2(); //データ1表示
f3(); //データ2発生
f4(); //データ2表示
f5(); //データ1とデータ2の和の計算と表示
}
void f1(void){
i=0;
while(i<5){
j=0;
while(j<5){
a[i][j]=5*i+j+1;
j++;
}
i++;
}
}
void f2(void){
i=0;
while(i<5){
j=0;
while(j<5){
if(a[i][j]<10)w+=L"0"+(a[i][j]).ToString()+L"
";
if(a[i][j]>=10)w+=(a[i][j]).ToString()+L" ";
j++;
}
w+=L"\n";
i++;
}
label1->Text=w;
}
void f3(void){
i=0;
while(i<5){
j=0;
while(j<5){
b[j][i]=5*i+j+1;
j++;
}
i++;
}
}
void f4(void){
i=0;
w=L"";
while(i<5){
j=0;
while(j<5){
if(b[i][j]<10)w+=L"0"+(b[i][j]).ToString()+L"
";
if(b[i][j]>=10)w+=(b[i][j]).ToString()+L" ";
j++;
}
w+=L"\n";
i++;
}
label2->Text=w;
}
void f5(void){
i=0;
w=L"";
while(i<5){
j=0;
while(j<5){
if(a[i][j]+b[i][j]<10)w+=L"0"+(a[i][j]+b[i][j]).ToString()+L"
";
if(a[i][j]+b[i][j]>=10)w+=(a[i][j]+b[i][j]).ToString()+L"
";
j++;
}
w+=L"\n";
i++;
}
label3->Text=w;
}
};
}
これで一見良さそうなのですが、ビルドするとエラーしてしまいます。
実は、
int a[5][5],b[5][5],i,j;
String^ w=L"";
に問題があります。
理由は、関数の独立性です。
各変数は、関数の中でのみ有効なのです。
したがって、i,j等は赤い枠の中では有効ですが、
その下では、有効ではありません。
したがって、i,j等は無宣言の変数と見られてしまいます。
関数の中でのみ有効な変数をローカル変数といいます。
基本的にC言語で出てくる変数は、ローカル変数なのです。
しかし、プログラム全体に有効な変数も用意されています。
それをグローバル変数といいます。
グローバル変数は、メモリーを無駄遣いするので、基本的には使わないことが推奨されていますが、
初心者用の簡単なプログラムにおいては、グローバル変数を使っても全然問題ありません。
ただ、この講が進むとすべてローカル変数で対応できるようになります。
ローカル変数を宣言するには、int a[5][5],b[5][5],i,j;についてはプログラム冒頭
に入れ、に
String^ w=L"";つきましてはprivate: System::Void button1_Click(System::Object^ sender, System::EventArgs^
e) {の前の行
に入れます。
(この際にString^ w=L"";と欲張って初期化までするとエラーします。
初期化は、関数の中でしかできないものと解釈して下さい。)
すると、ビルドに成功して
関数の中でしか通用しないローカル変数は、不便なようですが、
これが関数の独立性を保証するものになります。
第7講第6話へ 第2話へ