今週のプログラムその6
前回に引き続いて、関数(関数副プログラム)の使い方。
ポイント
-
- 関数(関数副プログラム)もプログラムであることにはかわりないので、
その中で条件判断や繰り返し、必要によっては入出力などの操作を行なうことが
できる。
- 多変数関数や配列を変数とする関数も作れる。たとえば、行列を与えるとその対角和(Trace)を返す関数や、ふたつのベクトルを与えるとその内積を返す関数などを作ることができる。なお、今回は答として一個の数を返す関数だけを扱う。答として配列を返す(たとえば、行列を与えるとその逆行列を返すなど)方法については次回以降にとりあげる。ちなみに、このように関数に渡す変数(配列でも普通の変数でも)のことを
”引数”とよぶ。
-
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
|
などとする。
-
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 値;
}
|
などとなる。
-
名前の通用範囲について確認
変数名(配列名も含む)はプログラム単位(mainプログラムや個々の関数)の中
でだけ有効となる。たとえばxという変数を、mainと関数の中とでまったく関係ない
使い方をしてもかまわない。main中に現れるxと関数中に現れるとは、全然別の
ものとして処理がなされる。
一方(ちょっと考えればわかるけど)、関数の名前はプログラム全体で有効
で、funcという名前の関数はどのプログラム単位からでも、"func"
という名前で使える。
したがって
- Cの場合
#include<stdio.h>
double nibai(double);
main()
{
double x,y;
x = 1;
y = nibai(x);
printf("%f %f\n",x,y);
}
double nibai(double y)
{
double x;
x = 2*y;
return x;
}
|
-
FORTRANの場合
program main
real*8 nibai
real*8 x,y
x = 1
y = nibai(x)
write(*,*)x,y
end
function nibai(y)
real*8 nibai
real*8 x,y
x = 2*y
nibai = x
end
|
などという書き方でも大丈夫。変数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の値が関数値として戻される