講義第9回:Pascal の基礎 --- 手続きと関数

先週はグラフィックスの機能を使ってみた。グラフィックスの機能は、すべて、 手続き、関数といったもので実現されている。今週は、それらはどんな風に作 ることができ、何をするかということについて学ぶ。

手続き

program procedure_sample(input, output);
const pi = 3.14159265358979;
var x : real;

    procedure print_volume(radius :real);
    begin
        write('Radius = ', radius:15:7);
        writeln('  Volume = ', radius*radius*radius*pi*4.0/3.0:15:7);
    end;
begin
    write('Enter radius : ');
    readln(x);
    print_volume(x);
end.
このプログラムは、単に適当な数字を読み込んで、その値を半径とする球の体 積を表示するプログラムである。このプログラムでは、実際に体積を計算して 答を表示するのを、 print_volume という名前の手続きが行なってい る。

手続きは、

procedure 名前([var ] 引数1 [,引数2, ...]:型[, ... 引数i [,引数i+1,...]:型]);
[変数宣言]
begin
    実行部
end;
という形をとる。このような記述(手続き宣言)が、もとのプログラムの実行 部の前にあると、もとのプログラム(以下、メインプログラムという。これに 対して手続きのことをサブプログラム、あるいはサブルーチンということもあ る)の中から手続きを「呼び出す」ことができる。

よびだされると何が起こるかを上の例で考えてみる。メインプログラムで print_volume(x); というところまで来ると、次に実行されるのは procedure print_volume(radius :real) の実行部の先頭の文 write('Radius = ', radius:15:7); である。手続きの中の変数 radius の値は、メインプログラムで手続きを呼び出す時に設定した値、 すなわちメインプログラムの変数 x の値がそのままコピーされている。

手続き実行部の2行めの writeln文で、球の体積が出る。 end; までくると、手続きですることがもうないのでメインプログラムに戻る。メイ ンプログラムは戻ればもう終っているので、これですべてのプログラムがおしまいで ある。

実行例

Enter radius : 2
Radius =       2.0000000  Volume =      33.5103216

「なぜこんなことをするか」ということ

上のプログラムでは、手続きを使ったからといって何かいいことがあるわけで はない。例えばプログラムの長さは、手続きを使わないほうが短い。

しかし、ある程度複雑なプログラムで、同じようなことをあちこちでする時に は、その処理をまとめておいて、同じようなことを繰り返してプログラムしな くてもいいようにしたい。そのような時に手続きは役に立つ。

もちろん、 for, while などを使った繰り返しでも、ある程度のことはできる が、さらに「手続き」という形でまとめることで、便利に使えるようになる。

例えば、グラフィックスで使った initgraph, line, circle といった手続き は、中では実際には非常に複雑な処理をしている。これがあらかじめ手続きと してまとめられているおかげで、我々は簡単に画面に絵を描けるわけである。

関数

program bisection(input, output);
var x0,x1, eps : real;
    function f(x:real):real;
    begin
        f :=  x*x*x - 2;
    end;
    procedure bisection(var xmin, xmax : real; eps:real);
    var x, f_min, f_max : real;
    begin
        f_min := f(xmin);
        f_max := f(xmax);
        if f_min * f_max  > 0.0 then begin
            writeln('cannot find solution...');
        end else begin
            repeat
                x := (xmin + xmax) *0.5;
                if f(x) * f_min > 0.0 then 
                    xmin := x
                else
                    xmax := x;
                writeln('x=', x:20:16, ', f(x)=', f(x));
            until xmax - xmin < eps;
        end;
    end;
begin
   x0 := 0.0;
   x1 := 2.0;
   eps := 1e-10;
   bisection( x0, x1, eps);
   writeln('Final x = ', x0:20:14,x1:20:14);
end.
関数は、手続きに良く似ているが、「値を返す」ことができるという違いがあ る。書き方の違いは、

procedure 名前(引数の宣言);

の代わりに

function 名前(引数の宣言):型;

となることと、実行部の中で、

(関数の)名前:= 式;
の形の代入文で戻すべき値をセットすることである。このようにして宣言した 関数は、 PASCAL がもともと持っている sin, cos などの関数と全く同 じように使うことができる。

関数では、値を一つしか返せない。したがって、上の例のように、二分 法で方程式を解いて、区間の両端の値を戻したければ、引数の形で返すことに なる。引数で値を返すためには、引数の宣言のところで上の例のように名前の 前に var をつける。こうしておかないと、手続きの中で値を書き換え てもそれが呼出元には伝わらない。

上のプログラムは何をしているか-方程式を解く

上のプログラムはそもそも何をするものかということをまだ説明していなかっ た。

上のプログラムは、「2分法」というやり方で、方程式の(近似的な)解を求 めるものである。方程式は、関数=0 という形になっているものとしよう。 このやりかたでは、まず最初にどの範囲に答があるかは知っているものとする。 そうすると、下図にあるように、その範囲の両端で関数の符号が違っているはず である。

その区間の中点で関数の値を計算する。図のように、中点での値と左端での値 の符号が同じなら、答えは中点と右端の間にある。この時は、中点の値で左端 の値を置き換える。逆に中点での値と左端での値の符号が違えば、もちろん答 えはその間にある。この時は右端の値を置き換える。いずれの場合でも、答が あるとわかっている区間の幅がもとの半分に狭まる。これを繰り返していって、 答をもとめる。

これは、例えば辞書で単語を探す時に、まず真ん中あたりを開いてみて、探し ている単語がそのページよりも後ろなら、後ろ半分のさらに真ん中あたりを開 く。というのを繰り返していくのと全く同じことである。人間がやるとまだるっ こしいが、計算機は速いので、こういうやり方でも結構あっというまにかなり 正確な答えにたどり着くことができる。

練習

以下にいくつか練習問題を出すので、時間に余裕がある人はやってみて欲しい。
  1. 前回にやったグラフィックスの手続きを使って、「二点の座標を与えると、 それを左上と右下の座標として長方形を書く」手続き
    procedure rect(x1, y1, x2, y2: integer);
    
    をつくる。これを使って、大きさ、位置を変えながらたくさんの長方形を画面 に書くプログラムを作ってみる。

  2. 画面の左下隅の方に、
    96 SII-1 650001 J. Makino
    
    というような形でサインを書く手続きを作る。なお、画面に字を書くには、手 続き outtextxyを使う。

  3. 2分法のプログラムで、関数の形をある程度変えられるようにしてみる。 具体的には、例えば3次関数 f(x)= ax^3 + bx^2 + cx + d (x^3は xの3乗のつ もり)で、係数 a, b, c, d の値を読み込めるようにしてみよう。

  4. さらに、区間の両端の値も読み込めるようにプログラムを変えてみよう。

  5. さらに、区間を指定したらその範囲のグラフも書くようなプログラムを つくってみよう。グラフを書くのは一つの手続きにまとめておくこと。

次回予告

来週は、配列というものについて学ぶ。