8-2. 関数のしくみ
関数の概念を図にして説明してみようと思います。
ではまず、void型関数
┌main()────┐ │ │ │function(); ─┼───┐ │ ←─┼┐ ↓ │ ││ ┌function()──┐ │ ││ │ │ │ ││ └┬──────┘ │ │└──┘ └───────┘
これは見てのとおりfunction()がサブルーチンとして動いています。
プログラム中に同じ処理が回数的に多い時このようにあるまとまった処理を関数として定義すればメモリーの節約にだけでなくプログラム修正や管理が楽になります。
次にvoid以外の型、具体的にint型で説明します。
┌main()────┐ │ │ │function(); ─┼───┐ │ ↑ │ ↓ │ └───┼┐ ┌function()──┐ │ ││ │return 0 ; │ │ ││ └┬──────┘ │ │└──┘戻り値として0が呼出側のfunction()に代入。 └───────┘
ここでvoidと違うのはfunction()が終了すると0値を持つことです。
もし、function()を呼び出す時、
printf(“%d”,function());
としていれば、function()が正しく実行された時画面に0が出力されます。
これがvoid型だと値を持たないので画面出力されません。
(試していませんが確かコンパイルエラーになります。)
ここでは、関数から値が出力されることを説明しましたが、関数には値の入力ができます。たとえvoid型でも可能です。(下の例はvoid以外の型)
┌main()────┐ │ │ │function(x);─┼───┐xを代入 │ ↑ │ ↓ │ └───┼┐ ┌function(x) ─┐ │ ││ │return y ; │ │ ││ └┬──────┘ │ │└──┘yが出力される └───────┘
これこそ、数学的な関数と一致します。
関数に値を代入する時は括弧の中に代入する値をいれます。
〇ここからは数学の時間です。(苦手な人はごめんなさい)
式:y = 3*x – 6 (3x-6)
があるとします。
xに値を代入すればyが求まりますが、右辺を関数として定義します。
y = f(x) として
f(x) = 3*x – 6 とします。
理系の型ならここでハッと気がついたと思いますが、f(x)が今から作る関数です。
それでは、プログラム
#include<stdio.h> /* プロトタイプ宣言 */ int f(int); /* 代入するxは整数にするのでintと書く(必須) */ main() { int x=3,y; /* 取りあえずx=3の時とします */ y = f(x); /* お決まりですねf(3)がyになるそのままです */ printf("y = %d",y); /* 結果の表示 */ } int f(int i) /* 受け取りの変数の宣言:プロトタイプ宣言に合わせる */ { return (3*i - 6); }
ちなみに答えは3です。
今度は、これを-10<x<10のときにするには、for文を追加するだけでOKです。
/* mainの部分だけ */ main() { int x,y; for(x=-10;x< =10;x++){ y = f(x); printf("x = %3d y = %3d\n",x,y); } }
結果:(LSI-Cで確認)
x = -10 y = -36
x = -9 y = -33
x = -8 y = -30
x = -7 y = -27
x = -6 y = -24
x = -5 y = -21
x = -4 y = -18
x = -3 y = -15
x = -2 y = -12
x = -1 y = -9
x = 0 y = -6
x = 1 y = -3
x = 2 y = 0
x = 3 y = 3
x = 4 y = 6
x = 5 y = 9
x = 6 y = 12
x = 7 y = 15
x = 8 y = 18
x = 9 y = 21
x = 10 y = 24
となりました。これを応用すればグラフの表現などに使えます。
上記で説明のためこのような図を利用しました。
┌main()────┐ │ │ │function(x);─┼───┐xを代入 │ ↑ │ ↓ │ └───┼┐ ┌function(x) ─┐ │ ││ │return y ; │ │ ││ └┬──────┘ │ │└──┘yが出力される └───────┘
今度は、この図を一般的に「両替機」で表現してみようと思います。
自動販売機のほうが一般的ですが両替機のほうが単純で説明しやすいので…
上の図ではxは投入する硬貨、そしてyは出てくる硬貨とします。
┌行動─────┐ │ │ │両替機の呼出 │ │両替機(x);──┼───┐x円硬貨を投入 │ ↑ │ ↓ │ └───┼┐ ┌両替機(x) ──┐ │ ││ │return y ; │ │ ││ └┬──────┘ │ │└──┘y円硬貨がでてくる └───────┘
となります。この両替機の場合、硬貨の種類によって両替されて出てくる硬貨もちがってくるので、ある意味関数です。(C言語では本当に関数ですが…)
ここで、メリットとなるのは、両替を1回でなく何度も行う時もいちいち両替のたびに両替機(関数)を作らなくていい事です。実際、両替のたびに両替機を作るのではシャレになりませんからね…
そして、普段私達が両替機を利用するのに自分で作ったものを利用する人は希だと思います。つまり、あらかじめ誰かが作った両替機があれば中のメカニズムなんて知らなくても利用はできるのです。これは関数にも言えます。
つまり、printf()等、誰もが使うようなものはC言語のライブラリーに用意されているので、実際、画面に文字を出力するアルゴリズムを知らなくともprintf()を利用するだけで画面出力ができるのです。
さて、前回と違い今回は文章だらけですが(文系向けというのもあるので)
この両替機とやらを作ってみようかとプログラムを書いてみました。
#include<stdio.h> int change(int); main() { char c=''; int m=0; printf("硬貨を投入してください\n"); printf("1:500円\n2:100円\n3:50円\n"); scanf("%c",&c); switch(c){ case '1': m=500;break; case '2': m=100;break; case '3': m=50;break; default : printf("投入された硬貨がわかりません\n");return; } printf("%d円が投入されました\n",m); m=change(m); printf("%d円に両替されました\n",m); } int change(int money) { switch(money){ case 500: return 100; case 100: return 50; case 50: return 10; } }
それでは、まず上記のプログラムを解説します。
#include<stdio.h> /*------( 1 )------*/ int change(int); main() { /*------( 2 )------*/ char c=''; int m=0; /*------( 3 )------*/ printf("硬貨を投入してください\n"); printf("1:500円\n2:100円\n3:50円\n"); scanf("%c",&c); /*------( 4 )------*/ switch(c){ case '1': m=500;break; case '2': m=100;break; case '3': m=50;break; default : printf("投入された硬貨がわかりません\n");return; } /*------( 5 )------*/ printf("%d円が投入されました\n",m); m=change(m); printf("%d円に両替されました\n",m); } /*------( 6 )------*/ int change(int money) { switch(money){ case 500: return 100; case 100: return 50; case 50: return 10; } }
(1) 両替機の関数changeのプロトタイプ宣言です。
投入硬貨(円)が入力、両替された硬貨(円)が出力でともに整数型(int)を使用します。
(2) 変数の初期化、選択入力するための文字型変数 c は「文字なし」で初期化
現在の硬貨単位(円)を記憶させる整数型変数 m は0で初期化
(3) 硬貨投入の受付です。投入する硬貨に値する番号を入力してください
(4) 判別処理です。ここで認識できない硬貨はエラーがでます。
(5) 無事に認識できたら両替機システムへ硬貨の値を渡します。
そして、戻ってきた値をもとに対象硬貨を弾き出します。
(6) ここは両替機システム内部です。
入力された硬貨の値に応じて、対応する値を戻しています。
◆関数の基本のまとめ
ここで、ちょこっとだけ今までのまとめをしてみます。
void型:出力する値が無い(returnの後に値を書くのは不可)
int型:関数の終了時に戻り値として整数値を返す。
その値は親関数から呼び出した部分に挿入される。
その他:戻り値がそれぞれの型に準する。それ以外は同じ
入力値について:
入力できる値の数や種類・順番はプロトタイプ宣言で決める事ができる。
プロトタイプ宣言:
グローバル領域で宣言すればグローバル関数となりどこからも呼出し可能
ほかの関数内で宣言すればローカル関数となりその関数内でのみ呼出しができる
戻り値について:
戻り値は呼び出した関数の値となるので関数の型と一致させること。
また関数の値はもちろん1つなので、戻り値は1つしか指定できない。
と、こんなところです。
◆ちょっと復習
今回の復習のメインは関数の解説の前に触れた部分で関数を関わりの深いところの復習です。
まずはスコープ。
基本原則は、宣言された領域より外側({}の外側)では使えないと言う事です。
例えば、
void func();
int a,b,c;
main()
{
int x,y,z;
…..;
func();
…..;
}
void func()
{
int i,j,k;
…….;
…….;
}
とあった場合、
main()が使える変数は a,b,c,x,y,z
func()が使える変数は a,b,c,i,j,k
となります。お互いに関数内で宣言した変数は関数の外側では使用できないのです。
ここで一つ疑問があります。
func()はmain()の中で使用されています。
main()がi,j,kを使えないのは当然として、なぜmain()の中にあるfunc()はmain()のx,y,zが使えないのでしょう。
・・・とこれではfunc()はグローバルだからmain()の中に入ってないジャンと突っ込まれそうなので、プロトタイプ宣言を以下のようにmain()内にします。
int a,b,c;
main()
{
int x,y,z;
void func();
…..;
func();
…..;
}
void func()
{
int i,j,k;
…….;
…….;
}
これでfunc()はmain()の中で宣言されたもので完全にmain()内のものになりますが、これでもfunc()はx,y,zを利用できません。
つまり関数は呼び出されるものであって含まれるとか含まれないとかではないのです。動作としては呼び出した後、処理が移行するわけで呼出し元のオブジェクトが使えないのは当然なのです。また、すぐ上に書いたfunc()がmain()内のものというのはあくまでその宣言においての有効範囲(スコープ)なのです。
余談ですがC++にはinline関数といって呼出し元にコードを含ませるものがありますがそれでも例外なく呼出し元の変数は使えませんでした。