2002 年度「計算機基礎論3B」 2002-11-29

練習問題(前回の補足)

(例えば)20 個の int 型変数の配列 a[ ]を宣言し、 それらを(例えば)a[0] は 1 で、 a[1]a[0] の二倍で、 a[2]a[1] の二倍で、 ……と埋めてゆく。 そしてそれらを順に出力せよ。

関数の自作

C言語では、自分で関数を作ることができます。 K&R2 §1.7, 1.8 も合わせて読んでください。

次のプログラムは K&R2 §1.7 からの引用です。 (一部変更してあります。)

#include <stdio.h>

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

int main()                          /* 関数 power() をテストする */
{
    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)xy 乗を計算する関数」 と思って main() の終わりまでを読んでみてください。 関数 power() を何回か利用して、 2 と -3 のベキ乗を計算しているプログラムであることがわかると思います。

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

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

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

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

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

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

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

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

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

そのあとの部分では basen 乗を計算していることがわかりますか?  そして最後の「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 のほうが速い。






次のプログラム片は、K&R2 §1.8 からの引用です。 (一部変更してあります。)

int power(int base, int n)          /* K&R2 より */
{
    int p;

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

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

例えば、 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(値による呼び出し)」ということがあります。

※ 教科書では省略されている中カッコもこのプリントでは省略せずにつけているのは、 次のようなミスに気づかないことがあるからだ。 上の main()for ループを K&R2 のように中カッコなしで書いたあと、 「0 のベキ乗もテストに加えよう」と思いたち

int main()
{
    int i;

    for (i = 0; i < 10; i++)
        printf("%d %d %d\n", i, power(2, i), power(-3, i));
        printf("%d\n", power(0, i));
    return 0;
}
のようにもう一つの文をつけ加えたが、 0 のベキ乗は一つしか出力されない……?!

練習問題

いろいろな関数を書いて、くり返しと関数の両方の練習をしましょう。 すぐできるもの、手間のかかるものが混じっていますので、 適当に取捨選択して取り組んでください。

なお、「関数を書いてみる」というのは、 「テキストファイルとして作成して終わり」ではなく、 検査用の main() も書いて、コンパイルして実行し、 思ったように動くことを確認することです。


岩瀬順一