2000 年度「計算機基礎論3B」 2000-11-24

前のが終わっていない人は前のに取り組んでください。 早く進んでいる人には次回以降しばらくのあいだ、 オプション的な内容を用意する予定です。

C言語での関数の自作方法

C言語では、自分で関数を作ることができます。

== power.c ====================================================================
#include <stdio.h>                  /* K&R2 より、一部改変して引用 */

int power(int base, int n);         /* プロトタイプ宣言 */

int main() {                        /* ベキ乗関数をテストする */
    int i;

    for (i = 0; i < 10; i++) {
        printf("%d %d %d\n", i, power(2, i), power(-3, i));
    }
    return 0;
}

int power(int base, int n) {        /* base の n 乗を返す (n >= 0) */
    int i, p;

    p = 1;
    for (i = 1; i <= n; i++) {
        p = p * base;
    }
    return p;
}
===============================================================================

まずは「プロトタイプ宣言」の行は飛ばして、 「power(x,y) は x の y 乗を計算する関数」 と思って main の終わりまでを読んでみてください。 関数 power を何回か利用して、 2 と -3 のベキ乗を計算しているプログラムであることがわかると思います。

※ 実は main にもいままでと違う点が二箇所ある。それはあとで。

※ 「power(2, i)」などとしてその値を計算させることを 「関数 power を呼び出す」と言うときがある。

この power は、C言語に元々そなえつけの関数ではなく、 自分で作ったものです。

※ 本当は自分で作ったのではなく本から引用したんだけど、 そのへんはつっこまないように。:-)

main のあとの残りの部分が関数 power の定義です。 そこを説明します。

関数は、cos(0) が 1 であるように、値をもっています。 その値のことを「返り値(かえりち)」と言います。 「int power」の int は、 power の返り値が int 型であることを示しています。

また、 関数を使うときには power(x,y) のようにして使いますが、 この x と y のことを「引数(ひきすう)」と言います。 この power の場合は x, y は int 型と決めましたので、 「int 型の引数を二つとる」と言ったりします。

関数の定義のところでも x, y と書いてもいいのですが、 引数の意味がわかりやすいように、 最初の引数を base, あとの引数を n と呼ぶことにしましょう。 「int power」 のあとの小カッコの中には引数の型と名前を、 引数ごとにカンマで区切って書きます。 この例では第一引数は int 型で名前は base, 第二引数も int 型で名前は n, というわけです。

※ 引数には「仮引数」と「実引数」の区別があるがここではふれない。

その次の行の「int i, p;」は、 関数 power の中で使う変数の宣言です。 いままで main の中で宣言していたのと全く同じスタイルになっています。

そのあとの部分では base の n 乗を計算していることがわかりますか?  そして最後の「return p;」でその値を返しています。 つまり、return のうしろに書かれた式の値がこの関数の値になる、 という約束です。

「プロトタイプ宣言」に戻りましょう。 ここには、power の定義の一行目と同じものを書きます。 ただし、最後の開き中カッコ「{」の代わりにセミコロン「;」をつけます。 これはコンパイラに 「これから出てくる power というのはこういう関数 (int 型の引数を二つとって int 型の値を返す関数) なんだぞ」 と教えるためのものです。

※ C言語に元々そなえつけの関数(printf, sin など) を使う場合はプロトタイプ宣言を自分で書く必要はなかった。 実はそれらは include された stdio.h や math.h に書いてあるのだ。 これらのファイルはディレクトリ /usr/include にある。

さて、 main を注意深く読んだ人は、 いままで「main() {」となっていた部分が「int main() {」となっていること、 main の最後に「return 0;」があることに気づいたでしょう。

main も関数の一つなので返り値があります。 実は main は OS に int 型の値を返します。 その使いみちについてはこの実習では説明しませんが、 プログラムが正常に終わったときは 0 を、 そうでないときは 0 以外を返すのが普通とされています。

このプログラムは main の中で変数 i を使い、 関数 power の中でも変数 i を使っています。 しかし、どちらもそれぞれの関数の中だけの変数なので、 両者は全く無関係です。 すなわち、 power の中で i の値が変わっても、main 側の i は変わりません。

※ return は、必要なら一つの関数の中に複数個おいてもよい。

    if (...) {
        return 0;
    } else {
        return 1;
    }
のように。

※ 実用の上では、 ベキ乗関数は前に紹介した pow を使うのがよい。 なお、 二乗や三乗の場合は pow(x,2), pow(x,3) よりも x*x, x*x*x のほうが速い。

== power2.c (単独では動かない) ==============================================
int power(int base, int n) {            /* K&R2 より、一部改変 */
    int p;

    for (p = 1; n > 0; n--) {
        p = p * base;
    }
    return p;
}
===============================================================================

power.c のうちの関数 power の定義の部分だけを上の power2.c で置き換えても、 プログラムは同じように動きます。

main の中の for ループを何度か回わって、 i = 3 となって power(2, i) が実行されるときのことを考えてみてください。 power の側では最初 base が 2, n が 3 です。 その n をだんだん減らしながら power 内の for ループを回わり、 n が 0 になるとループを抜けて main に帰ります。 main に戻ったとき、i は 0 に減っているのでしょうか?

そうではありません。 i は 3 のままです。 power(2, 3) を計算するときに power に渡した 3 は i のコピーであって変数 i そのものではない、 というのがC言語の約束です。

このことを「call by value(値による呼び出し)」ということがあります。

いろいろな関数を書いてみよう

いろいろな関数を書いて、くり返しと関数の両方の練習をしましょう。 (課題ではありません。)

まずは階乗を計算する関数 factorial を書いてみましょう。 引数の型、名前はどうしますか?  返り値の型は?  自分で考えて書いてみてください。 「関数を書いてみる」というのは、 「テキストファイルとして作成して終わり」ではなく、 検査用の main を書いて、コンパイルして実行し、 思ったように動くことを確認することです。 main では

    0 の階乗は 1 です.
    1 の階乗は 1 です.
    2 の階乗は 2 です.
    3 の階乗は 6 です.
    4 の階乗は 24 です.
     ...
のように出力させてみるとよいでしょう。 ここでもループの練習ができます。

初等超越関数を自作してみるのも面白いでしょう。 たとえば

                     2      3      4
                    x      x      x
  exp(x) = 1 + x + ---- + ---- + ---- + ...
                    2!     3!     4!

                   2     3     4
                  x     x     x
  log(1+x) = x - --- + --- - --- + ...
                  2     3     4

                 3      5      7
                x      x      x
  sin(x) = x - ---- + ---- - ---- + ...
                3!     5!     7!

                 2      4      6
                x      x      x
  cos(x) = 1 - ---- + ---- - ---- + ...
                2!     4!     6!
と、 Taylor 展開を利用して計算します。 log では x の範囲は -1 < x <= 1 ですから注意してください。 ほかの三つでは x は全ての実数で OK ですが、 x の絶対値が小さいほど速く収束しますから、 三角関数では 2π が周期であることなどを利用して |x| を小さくしてから計算するなどの工夫がいるところです。 でも、最初はあまり気にしなくていいでしょう。 テスト用 main ではもともとある関数と自作の関数を呼んだ結果を並べて出力するようにすれば、 “答え合わせ”も簡単です。:-)

※ 自作の exp や sin に「exp」「sin」という名前をつけると、 もともとある関数の名前と重複してしまってまずい。 こんなときは「myexp」「mysin」とするのも一案だ。 「私の exp」「私の sin」といった感じ。


岩瀬順一