next up previous contents index
: デバッガ(より進んだ使い方) : 関数 : 関数と局所変数   目次   索引

プログラム例

例題 8.1   最大値を返す関数を定義しよう.
プログラム func1.rr

/* func1.rr */
def max(A,B) {
  if (A>B) return(A);
  else return(B);
}
end$

実行例

[123] load("func1.rr");
1
[124] max(10,20);
20
    
max(A,B)AB の大きい方を 戻す関数である.

例題 8.2   互除法で GCD を計算する関数はつぎのようにかける.
def abs(A) {
  if (A<0) return(-A);
  return(A);
}
def mygcd(A,B) {
  if (abs(B)>abs(A)) {
    T=A; A=B; B=T;}
  while (B != 0) {
    R = A % B;
    A = B; B = R;
  }
  return(A);   
}

    
[349] mygcd(13,8);
1
[350] mygcd(8,6);
2
abs(A)A の絶対値を戻す関数である.

例題 8.3   次は局所変数の考えかたの復習問題である. 出力がどうなるか間違いなくいえないといけない.
プログラム

/*func2.rr*/
def main() {
  I = 0;
  print(I);
  foo();
  print(I);
}
def foo() {
  I = 100;
}
main();
end$

    
左のプログラムの実行結果は
[48] load("func2.rr");
1
[49] 
0
0
となる. けっして、100とは表示されない. たとえfoo
def foo() {
  I=100;
  return(I);
}
としたろころで同じである.

例題 8.4   次に 漸化式 $ x_n = 2 x_{n-1} + 1,\ x_0 = 0 $ できまる数列の $n$ 項めをもとめるプログラムを作ろう.
プログラム

/* func4.rr */
def xn(N) {
    Re=0;
    for (K=0; K<N; K++) {
      Re = 2*Re+1;
    }
    return(Re);
}

    
実行例は以下のとうり.
[345] load("func4.rr")$
[346] xn(1);
1
[347] xn(10);
1023
[348] xn(20);
1048575

例題 8.5   次にリストを引数として その要素の最大値と最小値を戻す関数をつくろう. 考え方は前節のプログラムと同じである. 二つの値を戻すために, 戻り値はリストかベクトル にするとよい.
/* func3.rr */
def minmax(A) {
  N = length(A);
  if (length(A) == 0) return(0);
  Max = Min = A[0];
  for (K=1; K<N; K++) {
     if (Max < A[K]) {
        Max = A[K];
     }
     if (Min > A[K]) {
        Min = A[K];
     }
  }
  return([Max,Min]);
}
end$

    
答えは [ 最小値, 最大値 ] のリストの形にして戻す. このような関数を多値関数と呼ぶ時もある. 実行例は以下のとうり.
[345] load("func3.rr")$
[346] minmax([1,4,2,6,-3,-2]);
[6,-3]

length(L) はリスト L のサイズ(長さ)を 戻す関数である. ちなみに, ベクトル V のサイズ(長さ)をもどす には size(V)[0] とすればよい.


さて, 前の節で引数はその関数の実行中のみ存在する変数であると 説明した. ベクトルやリストが引数として関数にわたされたときは内部的にはその先頭 アドレスが関数にわたされベクトルやリスト自体は複製されていないことを 了解しておく必要がある. このことを明確に理解するには, C のポインタや機械語の間接アドレシングの 仕組みをきちんと勉強する必要があるかもしれない.
次のプログラムは, あたえらたベクトルのすべての成分を 1 にかえる.

def vector_one(V) {
  N = size(V)[0];
  for (I=0; I<N; I++) {
    V[I] = 1;
  }
}
end$

    
実行例:
[349] A=newvect(10);
[ 0 0 0 0 0 0 0 0 0 0 ]
[350] vector_one(A);
1
[351] A;
[ 1 1 1 1 1 1 1 1 1 1 ]
このようにベクトル A のすべての要素が 1 にかきかえれた.
size(V)[0] は ベクトル V の長さを戻す.

問題 8.1 (10)   上のプログラムで関数 vector_one() の最後の行に
                           V = 0;
を加えると,
                          [351] A;
                          [ 1 1 1 1 1 1 1 1 1 1 ]
となるだろうか. それとも,
                          [351] A;
                          0
となるだろうか? 関数実行中のメモリの様子を考えてどちらか答えよ.

8.3 にあるように, 関数にベクトルがわたされても その複製は作成されない. これが普通の引数と違う点である.

図 8.3: メモリの図解
vector_one(A) のよびだし直前
場所 内容
A A[0](先頭)のアドレス
A[0] 0
A[1] 0
$\cdots$ $\cdots$
A[9] 0
   
    
vector_one(A) の終了直前
場所 内容
A A[0](先頭)のアドレス
A[0] 1
A[1] 1
$\cdots$ $\cdots$
A[9] 1
V A[0](先頭)のアドレス

プログラム言語 Pascal では引数の配列を関数内で複製する(clone)か 複製しないかを指示できる. キーワード var をつけると複製せず, つけないと複製する. プログラム言語 C や Java の場合は複製はされない. 複製は明示的におこなう必要がある. Asir でもベクトルやリストの複製は明示的におこなう必要がある. 代入演算子を用いても複製されていないことに注意. たとえば, A がベクトルとするとき B に代入しても 複製はつくられない. 次の例を見よ.

[347] A=newvect(10);
[ 0 0 0 0 0 0 0 0 0 0 ]
[348] B=A;
[ 0 0 0 0 0 0 0 0 0 0 ]
[349] B[0]=100;
100
[350] B;
[ 100 0 0 0 0 0 0 0 0 0 ]
[351] A;
[ 100 0 0 0 0 0 0 0 0 0 ]
A がベクトルとするとき複製(close)はつぎのようにおこなう.

               N=size(A)[0];
               B=newvect(N);
               for (I=0; I<N; I++) {
                  B[I] = A[I];
               }

問題 8.2 (20)       
  1. 与えられたベクトルを複製する関数 clone_vector をつくりなさい.
  2. あたなたの作った clone_vector は 要素がベクトルのときはどのような動作をするか? たとえば, 引数に newvect(3,[1, newvect(2,[10,20]),3]) を与えたときはどのように動作するか?


next up previous contents index
: デバッガ(より進んだ使い方) : 関数 : 関数と局所変数   目次   索引
Nobuki Takayama 平成15年9月12日