/* procedure_sample * * (C) J. Makino Version 1.0 Jan 11, 1997 */ #include <stdio.h> #include <math.h> #define PI 3.14159265358979 void print_volume(double radius) { printf("Radius = %15.7f ", radius); printf("Volume = %15.7f\n", radius*radius*radius*PI*4.0/3.0); } main() { double x; printf("Enter radius : "); scanf("%lf",&x); print_volume(x); }このプログラムは、単に適当な数字を読み込んで、その値を半径とする球の体 積を表示するプログラムである。このプログラムでは、実際に体積を計算して 答を表示するのを、 print_volume という名前の関数が行なってい る。
この「関数」は英語の function の訳語であるが、数学的な「関数」と いうより、機能とか働きとかいった意味合いに近い。ただし、すぐあと で説明するように、値を返す関数というものもあり、こちらは数学的な 意味での関数に少し似ている。 値を返さない関数は、
[void] 名前(型 引数1 [,引数2, ...] [, 型[ ... 引数i [,引数i+1,...]]) { [変数宣言] 実行部 }という形をとる。このような記述が、プログラムのなかにあると、 もとのプログラム、つまり main() で始まっているところの実行部のな かからここで新しく作った関数を「呼び出す」ことができる。
なお、 C 言語では、 main() で始まるところ、つまり主プログラムも他 の関数と形式的には同じ形をしている。これは、言い換えると、「Cで書 いたプログラムでは、 mainという名前の関数から実行が始まる」という ことができる。
細かい話になるが、少し古い本や、人のプログラムを見ると、関数が、
void print_volume(double radius) { }という形でなく、
void print_volume(radius) double radius; { }という形になっていることがある。これは、ATTのベル研で最初にできた C言語の決まりがそうなっていたからである。しかし、この形だといろい ろ不便なことがあるので、現在の形に変更された。古い形でもプログラ ムを書くことはできるが、しない方がいい(理由は後で述べる)
さて、上のプログラムを実行すると何が起こるかを考えてみる。メインプログラムで print_volume(x); というところまで来ると、次に実行されるのは void print_volume(real radius) の実行部の先頭の文 printf("Radius = %15.7f ", radius); である。関数の中の変数 radius の値は、メインプログラムで手続きを呼び出す時に設定した値、 すなわちメインプログラムの変数 x の値がそのままコピーされている。
手続き実行部の2行めの printf文で、球の体積が出る。これ が実行部の最後なので、その後メインプログラムに戻る。メインプログ ラムは戻ればもう終っているので、これですべてのプログラムがおしま いである。
Enter radius : 2 Radius = 2.0000000 Volume = 33.5103216
しかし、ある程度複雑なプログラムで、同じようなことをあちこちでする時に は、その処理をまとめておいて、同じようなことを繰り返してプログラムしな くてもいいようにしたい。そのような時に関数は役に立つ。
もちろん、 for, while などを使った繰り返しでも、ある程度のことはできる が、さらに「関数」という形でまとめることで、便利に使えるようになる。
例えば、グラフィックスで使った initgraph, line, circle といった関 数は、中では実際には非常に複雑な処理をしている。これがあらかじめ 関数としてまとめられているおかげで、我々は簡単に画面に絵を描けるわけである。
/* bisection * * (C) J. Makino Version 1.0 Jan 11, 1997 */ #include <stdio.h> #include <math.h> double f(double x) { double y ; y = x*x*x - 2; return y; } void bisection(double * xmin, double * xmax, double eps) { double x, f_min, f_max; f_min = f(*xmin); f_max = f(*xmax); if (f_min * f_max > 0.0){ printf("cannot find solution...\n"); }else{ while(*xmax - *xmin > eps){ x = (*xmin + *xmax) *0.5; if (f(x) * f_min > 0.0 ){ *xmin = x; } else{ *xmax = x; } printf("x= %20.16f f(x)= %e\n", x, f(x)); } } } void main() { double x0,x1, eps; x0 = 0.0; x1 = 2.0; eps = 1e-10; bisection( &x0, &x1, eps); printf("Final x = %20.14f %20.14f\n", x0,x1); }ここでは、「関数」らしく値を返すものを使ってみている。値を返さな いものとの違いは、
void 名前(引数の宣言); の代わりに 型 名前(引数の宣言); となることと、実行部の最後で、 return 式;の形の戻すべき値を指定することである。このようにして宣言した関数 は、 C言語の標準のライブラリに入っている sin, cos など の関数と全く同じように使うことができる。
関数では、値を一つしか返せない。したがって、上の例のように、二分 法で方程式を解いて、区間の両端の値を戻したければ、引数の形で返すことに なる。
とはいうものの、最初の例のところで上に書いたように、Cでは関数の引 数の値はコピーされる。で、コピーされた方を書き換えても、元の値は 書き換わらない。元の変数の値を書き換えるためには、変数ではなく 「変数へのポインタ」を引数にする。
これはどういうことかというと、ポインタというのは、「変数がどこにあ るか」、つまり変数のアドレスを表現するのに使うものである。宣言は
double * pointer_to_double; int * pointer_to_int;といったふうに、*をつけて宣言する。scanf のところ(C言語 の一回めの資料を見ること)でやったように、変数名の前に & をつ ければその変数のアドレスになる。逆に、ポインタの指している場所の 値を得るには、その前に*をつけることになる。
上のプログラムは、「2分法」というやり方で、方程式の(近似的な)解を求 めるものである。方程式は、関数=0 という形になっているものとしよう。 このやりかたでは、まず最初にどの範囲に答があるかは知っているものとする。 そうすると、下図にあるように、その範囲の両端で関数の符号が違っているはず である。
その区間の中点で関数の値を計算する。図のように、中点での値と左端での値 の符号が同じなら、答えは中点と右端の間にある。この時は、中点の値で左端 の値を置き換える。逆に中点での値と左端での値の符号が違えば、もちろん答 えはその間にある。この時は右端の値を置き換える。いずれの場合でも、答が あるとわかっている区間の幅がもとの半分に狭まる。これを繰り返していって、 答をもとめる。
これは、例えば辞書で単語を探す時に、まず真ん中あたりを開いてみて、探し ている単語がそのページよりも後ろなら、後ろ半分のさらに真ん中あたりを開 く。というのを繰り返していくのと全く同じことである。人間がやるとまだるっ こしいが、計算機は速いので、こういうやり方でも結構あっというまにかなり 正確な答えにたどり着くことができる。
double f(x) { ... } void bisection(...) { ... = f(...); } main() { ... bisection(...); }といった風に、「使う関数はあらかじめその前に定義されている」とい う形になっている。で、例えば引数の対応が間違っているとコンパイラ がチェックしてくれる。これでうまくいくのはまあいいような気がするわけ だが、良く考えてみるとちょっと変である。
というのは、scanf, printf, initgraph, line といった関数はプログラ ムの中で定義されているわけではないのに、ちゃんと使えているからで ある。このために使っているのが、「プロトタイプ宣言」と呼ばれる機 能である。プロトタイプ宣言は、例えば以下のような形をしている。
void bisection(double * xmin, double * xmax, double eps);つまり、関数の最初のところだけ書いて、その後に実行部をつけないで セミコロンを書いておしまいにしたものである。これは、「この名前の 関数はこういう引数をもらって、こういう値を返します」ということを コンパイラに教える役割をはたしている。例えば line といった関数に は、このプロトタイプ宣言が xgrah.h というファイルの中に書いてある ので、これを #include で取り込んでいる。
この、プロトタイプ宣言というものは、要するにある関数について、 「それが外からどう見えるか」を規定している。えらそうにいえば「イ ンターフェース」を決めているということもできる。
double f(x) { double y; ... } void main() { double y; ..... }というプログラムを考えてみる。ここでは、 f(x) の中の変数 y と、 main の中の変数 y は全く別物であって、例えば f(x) の中で y を書き 換えたらその結果が main に伝わったりはしない。物理的には、これは、 メモリの違う場所におかれるということを意味している。
こういうことができるのは、ある関数の中で宣言した変数は、その関数 のなかでだけ有効だからである。その関数以外の関数が、勝手に変数を 書き換えたりはできない。これは、不便なような気がするかもしれない が、大きなプログラムを作るという場合とか、たくさんの人が協力して プログラムを作る時とかには非常に重要な機能である。
ただし、抜け道も準備されている。例えば
double y; double f(x) { ... y = ... } void main() { f(x) = ... printf("y=%f\n",y); }といったように、「関数の外」で変数を宣言することもできる。この場 合には、変数宣言の下に出てくるどの関数でも、この変数を使うことが でき、それらはみな同じものである。したがって、上の例のように、fの 中でその変数に代入し、 main の中でその値を見れば、fで入れた値が出 てくることになる。
void rect(int x1, y1, x2, y2);をつくる。これを使って、大きさ、位置を変えながらたくさんの長方形を画面 に書くプログラムを作ってみる。
96 LII-1 620001 J. Makinoというような形でサインを書く手続きを作る。なお、画面に字を書くには、手 続き outtextxyを使う。