今週のプログラムその6

前回に引き続いて、関数(関数副プログラム)の使い方。

ポイント

  1. FORTRANで配列を関数の変数とする場合

    たとえば、大きさ3の配列aと変数xを受け取る関数funcを 次のように宣言する。

    
          function func(x,a)
          real*8 func,x,a(3)
          関数副プログラム本体
          end
    
    つまり、変数aが大きさ3の配列であることは、関数の中で普通の配列宣言と同様に宣言する。 一方、この関数を使う側では
    
          real*8 func,x,a(3)  変数の宣言
          y=func(x,a)       関数の呼び出し
    
    などとすればよい。

    しかし、これでは配列の大きさが固定されてしまうので、 関数の汎用性が低くなって嫌かもしれない。 配列の大きさを固定したくない場合には、 配列の大きさも変数としてしまい、それを関数副プログラムに引数として 与えることができる。 配列aの大きさをnorderという変数で表わすことにすると、関数funcは

    
          function func(x,a,norder)
          real*8 func,x,a(norder)
          関数副プログラム本体
          end
    
    と定義すればよい。配列の宣言では、大きさとして変数norderが 使われている。この変数は必ず関数の引数に現れていなくてはならない。 一方、関数を使う側では
    
          real*8 func,x,a(3)  変数の宣言
          y=func(x,a,3)       関数の呼び出し
    
    などとすればよい。つまり、ここでは引数norderに具体的に3という 数を代入している。

    同様に多次元配列も使える。たとえば、n1*n2の大きさをもつ二次元配列aを変数とするなら、

    
          function func(x,a,n1,n2)
          real*8 func,x,a(n1,n2)
          関数副プログラム本体
          end
    
    関数を使う側では
    
          real*8 func,x,a(3,4)  変数の宣言
          y=func(x,a,3,4)       関数の呼び出し
    
    などとすればよい。配列の大きさを固定する書き方は一次元と同様。

    ところで、第三回で紹介したように、パラメータ文で配列の大きさを宣言したほうが便利な場合もあるかもしれない。パラメータ文の有効範囲はプログラム単位ごとなので、mainプログラムに書かれたパラメータ文は関数副プログラムに影響を与えない。 したがって、関数副プログラム内でも、あらためてパラメータ文を書かなくてはならないことに注意。たとえば

    
          program main
          (parameter n=3)
          real*8 func,x,y,a
          y=func(x,a)
          write(*,*)y
          end
    
          function func(x,a)
           (parameter n=3)
          real*8 func,x,a(n)
          関数副プログラム本体
          end
    
    などとする。

  2. Cで配列を関数の変数とする場合

    配列aと変数xを受け取る関数funcを次のように宣言する。

    
    double func(double x, double a[])
    {
           関数本体
           return 値
    }
    
    つまり、aが配列であることは、引数の中の[]という記号で表現している。 ただし、配列の大きさは宣言しなくてよいことに注意。 また、関数プロトタイプ宣言は
    
    double func(double, double []);
    
    のようにする。実際に使うプログラム中では
    
      double x,a[3];    変数の宣言
      y=func(x,a);     関数の呼び出し
    
    などとすればよい。 関数中では配列の大きさを指定する必要がないことに注意。 もちろん、もし、関数中での計算に配列の大きさを使う必要があるなら (たとえば、ループの長さとして)、それを普通の変数として 関数に渡せばよい。

    Cでは2次元配列の使い方は少々やっかいである。 たとえば、大きさ3*4の二次元配列a[3][4]を変数とするなら、

    
    double func(double x, double a[][4])
    {
           関数本体
            return 値
    }
    
    のように、二次元目の大きさだけは指定しなくてはならない。 ただし、関数プロトタイプ宣言は
    
    double func(double, double [][]);
    
    のように、大きさを指定しなくてよい。 また、実際に使うプログラム中では
    
      double x,a[3][4];    変数の宣言
      y=func(x,a);     関数の呼び出し
    
    などとすればよい。

    二次元目の大きさも指定したくないとすれば、第三回で紹介した#defineを用いるのがいいだろう。たとえば

    
    #include<stdio.h>
    #define NN 4
    #define MM 3
    
    double func(double, double [][]);
    
    main(){
     double x,y,a[MM][NN];
     y=func(x,a);
    }
    
    double func(double x, double a[][NN]){
        関数本体
        return 値;
    }
    
    
    などとなる。

  3. 名前の通用範囲について確認

    変数名(配列名も含む)はプログラム単位(mainプログラムや個々の関数)の中 でだけ有効となる。たとえばxという変数を、mainと関数の中とでまったく関係ない 使い方をしてもかまわない。main中に現れるxと関数中に現れるとは、全然別の ものとして処理がなされる。 一方(ちょっと考えればわかるけど)、関数の名前はプログラム全体で有効 で、funcという名前の関数はどのプログラム単位からでも、"func" という名前で使える。 したがって などという書き方でも大丈夫。変数x,yがmainとnibaiの両方で逆に使われてい るが、名前はプログラム単位ごとに決めていいので、これでオッケー。

以下のプログラムのうち、program 13は関数定義だけしか用意していません。 主プログラムは自分で用意してください。 前回作った二分法やニュートン法の主プログラムを使ってもいいでしょう。

まずはC版

program 13


/*  少し複雑な関数の例
    xの値によって違う関数を返す場合 
  (主プログラムは自分で作ること)
*/
double func(double x)               関数の定義
{
/*  xが負なら-sqrt(-x) (sqrtは平方根を計算する関数) */
  if(x < 0){                        副プログラム中でifを使ってもよい
    return -sqrt(-x);               return以降で計算された値を関数値として返す
  }

/*  xが0または正ならsqrt(x) */
  else {                             上のifが成立しなければこっち
    return sqrt(x);                  戻す値は sqrt(x)
  }
}

program 14


#include<stdio.h>
#include<math.h>                数学関数を使うおまじない
/*  さらに複雑な関数の例  
    多変数関数の場合  
    */

double func(double,double);         二つの変数をとる関数のプロトタイプ宣言

main()
{
  double x,a;
  int i;

  scanf("%lf",&a);        aの値を読む

  for(i=-5; i<=5; ++i){
    x = i*0.1; 
    printf("%f %f\n",x,func(x,a));
  }
}

double  func(double x, double a)     二変数関数の定義
{
/*  xが負なら-(-x)**a */
  if(x < 0){
    return  -pow(-x,a);            x<0 の時の値
  }
     
/*  xが0または正ならx**a */
   else{
     return pow(x,a);             x>=0 の時の値
   }
}

program 15


#include<stdio.h>
#include<math.h>
#define NORDER 4              プログラム中のNORDERをすべて4に
/*  さらに複雑な関数の例  
    配列を変数として使う場合      */

double func(double,double []);  倍精度変数と倍精度配列を変数とする関数のプロトタイプ宣言

main()
{
  double x,a[NORDER];
  int i;

  for(i=0; i<NORDER; ++i){
    scanf("%lf",&a[i]);            配列aの値を読む
  }

  for(i=-5; i<=5; ++i){
    x = i*0.1; 
    printf("%f %f\n",x,func(x,a));  関数の呼び出し方は前のプログラムと同じ
  }
}

/*  (norder-1)次の多項式。各次数の係数が配列aに入っている */
double  func(double x, double a[])   関数の定義。aは配列なので"a[]"と書く
{
  double f;
  int i;

  f = a[0];
  for(i=1; i<NORDER; ++i){          副プログラムの中で繰り返し処理をしてもよい
    f += a[i]*pow(x,i);
  }
    return  f;                   上の行までで計算したfの値を関数値として返す
}

FORTRAN版

program 13


C 少し複雑な関数の例
C    xの値によって違う関数を返す場合 
C  (主プログラムは自分で作ること)

      function func(x)              関数副プログラムの定義

      real*8 func,x

C  xが負なら-sqrt(-x) (sqrtは平方根を計算する関数)
      if(x.lt.0)then                 副プログラム中でifを使ってもよい
        func = -sqrt(-x)             関数値を計算
        return                  関数を終了してmainプログラムに戻る
C  xが0または正ならsqrt(x)
      else                            上のifが成立しなければこっち
        func = sqrt(x)                この場合、関数値はこっち
        return                  関数を終了してmainプログラムに戻る
      endif
      end

program 14


C  さらに複雑な関数の例
C  多変数関数の場合
      program main
      real*8 func,x,a

      read(*,*)a
      do i=-5,5
        x=i*0.1d0                  0.1d0は0.1e0と同じ数だが、倍精度
        write(*,*)x,func(x,a)    二変数関数の呼びだし
      enddo
      end

      function func(x,a)         二変数関数funcの定義
      real*8 func,x,a

C  xが負なら-(-x)**a 
      if(x.lt.0)then
        func = -(-x)**a          x<0のときの値
        return                   関数を終了してmainプログラムに戻る
C  xが0または正ならx**a
      else
        func = x**a              x>=0のときの値
        return                  関数を終了してmainプログラムに戻る
      endif
      end

program 15


C  さらに複雑な関数の例
C  配列を変数として使う場合
      program main
      parameter(norder=4)          主プログラム中のnorderの値をすべて4に   

      real*8 func,x
      real*8 a(norder)              配列の宣言

      read(*,*)(a(i),i=1,norder)    配列aに値を読む
      do i=-5,5
        x=i*0.1d0                   0.1d0は0.1e0と同じ数だが、倍精度
        write(*,*)x,func(x,a,norder)   関数の呼びだしかた
      enddo
      end

C  (norder-1)次の多項式。各次数の係数が配列aに入っている。
      function func(x,a,norder)    関数の定義
      real*8 func,x,a(norder)      倍精度変数と倍精度配列を使う事を宣言

      func=a(1)
      do i=2,norder
        func = func + a(i)*x**(i-1) 
      enddo
      end                          上の行までで計算されたfuncの値が関数値として戻される