計算数学演習第8回

関数

今日の演習

今日は以下の内容で演習を行います。

  • 関数の定義と利用

今日の目標

関数の定義の仕方と使い方を習得する。

関数の定義と利用

これまで、標準関数、数学関数と呼ばれる関数(printf 関数や scanf 関数、sin() や sqrt())をいくつか使いました。
C言語では、自分で関数を定義し利用することができます。
例えば 、beki(a,b) = a^b という関数を自分で定義して利用する場合、次のサンプルプログラムのようになります。
func-1.c として打ち込んでください。

func-1.c

#include <stdio.h>
 
int beki(int, int);
 
int main() {
    int n, x, y;
    
    x = 2;
    y = 3;
    n = beki(x, y);
    printf("%d %d\n", n, beki(3, 2));
    
    return 0;
}
 
int beki(int a, int b) {
    int i, a_b;
    
    a_b = 1;
    for (i=1; i<=b; i++) {
        a_b = a_b*a;
    }
    
    return a_b;
}

3行目および16~25行目が関数の定義に関わる部分で、8行目および9行目で、定義した関数を使用している部分です。
まずは定義している部分から見ていきましょう。
3行目: 関数のプロトタイプ宣言と呼ばれる宣言です。
後に定義する関数の戻り値の型と、引数の型を仕様として、プログラムの先頭部分(main 関数より前)にこのように書きます。
関数を定義する場合には、このプロトタイプ宣言が必ず必要だと思っておいてください。
5~14行目: main 関数の定義です。
16~25行目: beki 関数の定義です。
※ 実は、関数のプロトタイプ宣言を使わない方法もあり、ひと昔前までは主流でした。しかし現在では、プロトタイプ宣言を行うことが推奨されています。理由は「他の言語との兼ね合い」や「分割コンパイルを見据えて」など、色々ですが、まぁ使うようにしましょう。

戻り値と引数(ひきすう)

例えば
y = sin(x)
と書いたとき、sin が関数名で x が関数 sin の引数変数 y には sin(x)の戻り値が入ることになります。
当然変数 x, y にはそれぞれ型があり、関数自体がもつ値(戻り値)にも型があります。
先のサンプルプログラムの3行目のプロトタイプ宣言や、16行目の関数定義の部分で、 int と整数型が宣言されているのはその為です。
余談ですが、実はmain関数の最初にintが付いているのも、main関数の戻り値がint型だから、という理由です(return 0がある)。
戻り値がない関数は「void」型を使いますが、正しく関数が実行されたかの判定のためにも、void型は避け、int型を使うことを推奨します。

beki 関数の定義

では、beki 関数の定義部分を詳しく見ましょう。16行目を見ると
int beki (int a, int b)
となっています。
はじめの int は、 beki 関数の戻り値の型が int であるという意味です。
続いて関数名 beki があり、その後括弧に囲まれ、2つの変数が定義されています。
これらは、引数として渡される値を格納する為の変数であり、この関数内ではそれぞれ変数 a, b として用いることができ、型はそれぞれ整数型としています。例:beki(1,2) とこの関数が呼ばれた場合、 a には 1 が、 b には 2 が代入されることになります。

つづいて、中括弧に囲まれた、関数本体の処理内容が続きます。
この中で、新たに(この関数の定義内でのみ有効な)変数 i と a_b を用意しています。
さらには、先の引数部分にあった、変数 a, b が参照されています。
もちろんa, b には、関数が呼ばれた時に引数として渡されている変数の内容である数値、または直接数値として渡された場合にはその数値が代入されています。

最後の return により、 a_b の内容が beki 関数の戻り値として返され、関数が終了します。
以上、関数を作る場合に必要なことをまとめると

  • 関数の名前は何にするか
  • 関数に渡すデータの型と名前は何であって、数はいくつか(引数の数)
  • 関数から戻るデータ(戻り値)の型は何か
  • 関数のプロトタイプ宣言の記述(プログラムの先頭部分。main 関数の定義より前)
  • 関数本体の処理方法の記述
  • 結果を戻し、関数を終了するための記述(return 文)

a^bを表示したいだけなら「関数の定義」等をするのはむしろ作業をややこしくしているだけに見えます。
しかし、例えば自然数 x, y, z に対しxy, yz, zx, xy+z を表示したい場合、関数を使わないと少々見難いプログラムになります。
beki関数を定義して使うと、以下のようにすっきりとしたプログラムになります。
(このサンプルでは x=2, y=3, z=4 としています。)

func-1a.c

#include <stdio.h>
 
int beki(int, int);
 
int main() {
    int x, y, z;
    
    x = 2;
    y = 3;
    z = 4;
    printf("%d %d %d %d\n", beki(x, y), beki(y, z), beki(z, x), beki(x, y+z));
    
    return 0;
}
 
int beki(int a, int b) {
    int i, a_b;
    
    a_b = 1;
    for (i=1; i<=b; i++) {
        a_b = a_b*a;
    }
    
    return a_b;
}

このように、繰り返し使用する演算(手続き)は、関数として定義しておくと便利です。
他の例を見てみましょう。
例えば、実数値の絶対値を表示するプログラムを考えましょう。
これまでの知識からプログラムは大体次のようなものになります。

#include <stdio.h>
 
int main() {
    double a;
    
    printf("実数を入力してください:");
    scanf("%lf", &a);
    
    if (a < 0.0) {
        a = -a;
    }
    
    printf("入力された実数の絶対値は %lf です。\n", a);
    
    return 0;
}

このプログラムでは、 if 文を使って絶対値を求めていますが、絶対値を求める作業が多量にある場合、いちいち if 文を書くのは面倒です。
絶対値を求める関数を作ってみましょう。(func-2.c)

func-2.c

#include <stdio.h>
 
/** 絶対値を計算する関数 double myabs(double f) の宣言 **/
double myabs(double);
 
int main() {
    double a;
    
    printf("実数を入力してください:");
    scanf("%lf", &a);
    printf("入力された実数の絶対値は %lf です。\n", myabs(a));
    
    return 0;
}
 
/** 絶対値を計算する関数 double myabs(double f) **/
double myabs(double f) {
    /* f に代入されている値がが負なら, 符号を変える */
    if (f < 0.0) {
        f = -f;
    }
    /* 計算結果を呼び出された場所へ戻す */
    
    return f;
}

このように自分で関数を作る場合には、その関数に自由な名前をつけることができます。
しかし、標準関数、数学関数として既にある関数の名前をつけることはできません(例えば、printf や scanf といった関数を新たにつくることはできません)。
実は絶対値を求める標準関数 abs がありますので、今回は myabs という名前にしています。
また、関数の名前も変数の名前の規則同様使える文字と使えない文字があります。注意してください。
数字から始まらない英数字を関数名としてください
これは変数の名前と同じルールです。

関数を扱う際に覚えておくこと

異なる関数内で定義された変数は、別物です。
次のようなプログラムを考えるとします。

#include <stdio.h>
 
int main() {
    int a = 10;
    
    a = 2*a;
    printf("a = %d in the main().\n",a);
    
    return 0;
}

当然、実行結果は、
a = 20 in the main().
となります。
では、次のようなプログラムは、どのような結果を返すでしょうか?

#include <stdio.h>
void nibai(int);
 
int main() {
    int a = 10;
    
    nibai(a);
    printf("a = %d in the main().\n",a);
    
    return 0;
}
 
void nibai(int num) {
    num = 2*num;
}

結果は、
a = 10 in the main().
となります。
関数nibai の中で、変数の値を二倍にして代入し直していますが、
この代入は、関数を呼び出した側であるmain関数の変数aの値には全く影響を及ぼしません。
このように、関数の中からはmain関数で使われている変数に直接アクセスできなくなるということを覚えておいてください。
(興味のある人は,グローバル変数とローカル変数について調べてみてください。)
ちなみに、上のプログラムで、

void nibai(int num) {
    a= 2*a;
}

とおいてしまうと、エラーとなります。
*関数から直接main関数で使っている変数の中身を操作しようとするためには、関数の引数として、main関数で使われている変数の「ポインタ」を渡す必要があります。
(ポインタはこの授業の範囲を超えるので、今回は説明しません。興味のある方は調べてみてください)

課題1

上のプログラムを改造して、A[0] ~ A[9] の10個の実数を入力すると、それぞれの絶対値を表示するプログラムを作成せよ。

課題2

上のプログラムを改造して、A[0] ~ A[9] の10個の実数を入力すると、その和の絶対値と、それぞれの絶対値の和を表示するプログラムを作成せよ。

課題3

次の仕様を満たす関数を作成し、以下の問いに対するプログラムを作成せよ。
関数名: double sa(double a, double b)
仕様: 実数型の2つの数 a, b を受け取り、それらの差の絶対値を戻り値として返す。
問い:3つの実数 x, y, z を入力すると |x-y| + |y-z| + |z-x| を求めて表示する
(発展問題:|sin(x-y)| – sin(|x-y|) を求めて表示する。)

課題4

サンプルプログラムや課題3を参考に, 3つの整数 x, y, z を入力すると(x-y)^{|x-y|}+(y-z)^{|y-z|}+(z-x)^{|z-x|}を表示するプログラムを作成せよ。
(関数は必要なだけ定義, 使用する事ができます。)

課題5

次の仕様を満たす関数を作成し、以下の問いに対するプログラムを作成せよ。
関数名:int round_off(int n)
仕様: 整数型の数を受け取り、その一の位を四捨五入した値を戻り値として返す
問い:7つの自然数 a[0] ~ a[6] を入力すると、その和の一の位を四捨五入した値と、それぞれの一の位を四捨五入した値の和を表示する。

課題6

次の仕様を満たす関数を作成し、以下の問いに対するプログラムを作成せよ。
関数名:int round_down(int n)
仕様: 整数型の数 n を受け取り、その一の位を切り捨てした値を戻り値として返す
問い:5つの自然数 a[0] 〜 a[4] を入力すると、その和の一の位を切り捨てした値、および、それぞれの一の位を切り捨てした値の和を表示するプログラム。

課題7

次の仕様を満たす関数を作成し、以下の問いに対するプログラムを作成せよ。
関数名:int sosu_hantei(int n)
仕様: 整数型の数 n を受け取り、n の絶対値が素数なら 1 を, 素数でないなら 0 を戻り値として返す
問い:7つの自然数 a[0] ~ a[6] を入力すると、入力された数の中に幾つ素数があったか表示するプログラム。

レポート課題

次の仕様を満たす3つの関数を作成し、以下の問いに対するプログラムを作成せよ。
関数名1:double my_pow(double x, int n)
仕様: 実数型の数 x および整数型の数 n を受け取り、x^nを戻り値として返す
関数名2:double my_factorial(int n)
仕様: 整数型の数 n を受け取り、n!を実数型に変換し、戻り値として返す(最初から実数型として計算しないとオーバーフローが起こる)
関数名3:double my_func(double x)
仕様: 実数型の数 x を受け取り、\sum_{n=0}^{10}\frac{(-1)^{n}x^{2n+1}}{(2n+1)!}を戻り値として返す
問い:分割された各点をx_i =\frac{2\pi*i}{100} (i=0,1,...,100)として、y_i=\sum_{n=0}^{10}\frac{(-1)^{n}{x_i}^{2n+1}}{(2n+1)!}を計算し、x_iy_iの間にスペースを入れて出力せよ。(下の例を参照)

0.000000 0.000000
0.062832 0.062791
.
.
.
1.570796 1.000000
1.633628 0.998027
.
.
.
3.078761 0.062791
3.141593 0.000000
3.204425 -0.062791
.
.
.
6.220353 -0.062725
6.283185 0.000083

※ ソースファイル(C言語のファイル)を提出(ファイル名は問わない)
※ プログラムの先頭に学生番号と氏名をコメント文として挿入すること
※ プログラム中には適宜コメント文でプログラムの説明入れること
※ 適切にインデントを入れ、読みやすいプログラムにすること
提出締め切り:2024年1月17日(水)午後6時

おまけ(再帰プログラム)

関数内で他の関数を呼び出すことができるように、こんなこともできます。

int f1(int n) {
    if(n == 0) {
        return 1;
    }
    else {
        return n*f1(n-1);
    }
}

この関数がどのような働きをするのか考えてみてください。

Department of Mathematical and Life Sciences, Graduate School of Integrated Sciences for Life, Hiroshima University