C言語では、自分で関数を作ることができます。 きょうのプログラムもサブディレクトリ ~iwase/prog に置いてありますので、 必要なかたはコピーして利用してください。
== 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 を呼び出す」と言うときがある。
main のあとの部分で,関数 power の定義をしています。そこを説明します。
関数は,cos(0) が 1 であるように,値をもっています。 その値のことを「返り値(かえりち)」と言います。 「int power」の int は、power の返り値が int 型であることを示しています。
また、関数を使うときには power(x,y) のようにして使いますが、 この x と y のことを「引数(ひきすう)」と言います。 この power の場合は int 型の引数を二つとります。 power(x,y) でもいいのですが、 関数の定義の中で引数の意味がわかりやすいように、 最初の引数を base, あとの引数を n と呼ぶことにしましょう。 それらはいずれも int 型にしました。 「int power」の後ろの小カッコの中の「int base, int n」はそのことを示しています。
※ 引数には「仮引数」と「実引数」の区別があるがここではふれない。
その次の行の「int i, p;」は、 関数 power の中で使う変数の宣言です。 いままで main の中で宣言していたのと全く同じスタイルになっています。
そのあとの部分では base の n 乗を計算し、 最後の「return p;」でその値を返しています。 つまり、return のうしろに書かれた式の値がこの関数の値になる、というわけです。
「プロトタイプ宣言」に戻りましょう。 ここには、power の定義の一行目と同じものを書きます。 ただし、最後の開き中カッコ「{」の代わりにセミコロン「;」をつけます。 これはコンパイラに「これから出てくる power というのはこういう関数なんだぞ」 と教えるためのものです。
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 や Taylor 展開を利用して exp や sin を計算する関数を自作してみると、 くり返しと関数の両方の練習になります。 Taylor 展開の式については高木貞治「解析概論」の 66, 67 ページなどを見てください。 テスト用 main ではもともとある関数と自作の関数を呼んだ結果を並べて出力するようにすれば、 “答え合わせ”も簡単です。:-)
※ 階乗を計算する関数の名前は factorial がいいだろう。 自作の exp や sin に「exp」「sin」という名前をつけると、 もともとある関数の名前と重複してしまう。 こんなときは「myexp」「mysin」とするのも一案だ。 「私の exp」「私の sin」といった感じ。
素数 p を与えると p を法とする原始根(primitive root) の一つを返す関数を書いてください。 「p を法とする原始根」 とは、 「a の i 乗(0 < i < p-1)は p で割った余りが 1 ではないが、 a の p-1 乗は p で割った余りが 1」となる自然数 a のことです。 このような a は 0 < a < p の範囲に少なくとも一つは存在することが知られています。
関数が正しく書けていることをチェックするための main 関数もつけて、コンパイラを通ること、正しく動くことをチェックしてから、 ソースファイルをメールで私の ID であるところの eb00d48 に送ってください。 なお、Subject は「kadai 3」とし、 本文の最後には成績表などに記載されている形で (i.e. 漢字で登録してあるひとは漢字で) 氏名を書いて送ってください。
締め切りは日本標準時の 1999 年 01 月 28 日(木) 17 時 00 分。
※ 最初は「01 月 21 日(木)」となっていたのを訂正した。
※ 友だちどうしで相談したり教えあったりするのはかまわないが、 丸写ししたりファイルごとコピーしたりするのは避けること。 この規模の課題になれば全く同じものができるとは考えられない。
1999 年 01 月 09 日(金)は冬休み期間中ですが、 10 月 02 日を休みにしてしまった分の補講をします。 くわしくは授業中に。
※ 最初は「来週 25 日(金)は冬休み期間中ですが」 となっていたのを訂正した。