計算数学演習第8回

関数

今日の演習

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

  • 関数の定義と利用

今日の目標

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

関数の定義と利用

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

#include <stdio.h>

int beki(int a, int b);

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行目および15~27行目が関数の定義に関わる部分で、9行目および12行目で、定義した関数を使用している部分です.
まずは定義している部分から見ていきましょう.

3行目: 関数のプロトタイプ宣言と呼ばれる宣言です.
後に定義する関数の戻り値の型と、引数の型を仕様として、プログラムの先頭部分(main 関数より前)にこのように書きます.
関数を定義する場合には、このプロトタイプ宣言が必ず必要だと思っておいてください.

5~14行目: main 関数の定義です.

16~28行目: beki 関数の定義です.

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

例えば

y = sin(x)

と書いたとき、sin が関数名で x が関数 sin の引数変数 y には sin(x)の戻り値が入ることになります.
当然変数 x, y にはそれぞれ型があり、関数自体がもつ値(戻り値)にも型があります.

先のサンプルプログラムの3行目のプロトタイプ宣言や、15行目の関数定義の部分で、 int と整数型が宣言されているのはその為です.

beki 関数の定義

では、beki 関数の定義部分を詳しく見ましょう.15行目を見ると

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 としています.)

#include <stdio.h>

int beki(int a, int b);

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)

#include <stdio.h>

/** 絶対値を計算する関数 double myabs(double f) の宣言 **/
double myabs(double f);

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 という名前にしています.
また、関数の名前も変数の名前の規則同様使える文字と使えない文字があります.注意してください.
数字から始まらない英数字を関数名としてください
これは変数の名前と同じルールです。

課題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 sosu_hantei(int n)

仕様: 整数型の数 n を受け取り、n が素数なら 1 を, 素数でないなら 0 を戻り値として返す

問い:7つの自然数 a[0] ~ a[6] を入力すると、入力された数の中に幾つ素数があったか表示するプログラム.

レポート課題

提出するプログラム名はrep20190108.cとして、bb9から提出すること

課題7

次の仕様を満たす関数を作成し、以下の問いに対するプログラムを作成せよ.

関数名:int sosu_hantei(int n)

仕様: 整数型の数 n を受け取り、n が素数なら 1 を, 素数でないなら 0 を戻り値として返す(課題6と同じ)

関数名:int sosu_count(int n)

仕様: 整数型の数 n を受け取り、n以下の素数の個数を戻り値として返す。

問:1つの自然数 m を入力すると、m以下の自然数のうち何%が素数であるかを表示するプログラム。

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

関数は、引数で

次のようなプログラムを考えるとします。

#include <stdio.h>

int main()
{
    int a = 10;

    a = 2*a;

    printf(“a = %d in the main().\n”,a);

    return 0;
}

当然、実行結果は、

a = 20 in main().

となります。

では、次のようなプログラムは、どのような結果を返すでしょうか?

#include <stdio.h>

int nibai(int num);

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 main().

となります。

関数nibai の中で、変数の値を二倍にして代入し直していますが、

この代入は、関数を呼び出した側であるmain関数の変数aの値には全く影響を及ぼしません。

このように、関数の中からはmain関数で使われている変数に直接アクセスできなくなるということを覚えておいてください。
(興味のある人は,グローバル変数とローカル変数について調べてみてください。)

ちなみに、上のプログラムで、

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

とおいてしまうと、エラーとなります。

*関数から直接main関数で使っている変数の中身を操作しようとするためには、関数の引数として、main関数で使われている変数の「ポインタ」を渡す必要があります。

(ポインタはこの授業の範囲を超えるので、今回は説明しません。興味のある方は調べてみてください)

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

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

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

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