プログラミング入門2 第6回 関数 情報工学科 篠埜 功
今日の内容 --- 関数 関数(function)とは、プログラムの処理の一部に名前をつけるための構成要素(construct)である。
例1 (右側のプログラムを打ち込んで確認) int addTwice (int x, int y) { return (x + y) * 2; } int main (void) { int x, y; x = addTwice (10, 20); y = addTwice (15, 25); printf (“x=%d, y=%d\n”,x, y); return 0; #include<stdio.h> int main (void) { int x, y; x = (10 + 20) * 2; y = (15 + 25) * 2; printf (“x=%d, y=%d\n”,x, y); return 0; } 赤字の部分の2つの式は、2つの数を足して2倍するという部分が共通している。これにaddTwiceという名前をつけてそれを使うようにしたのが右のプログラムである。
関数定義の構文 返り値の型名 関数名 (仮引数宣言列) 複合文 型名 変数名 関数は、基本型(これまでに紹介したものではint, double)を引数とし、基本型を返り値とする場合、以下の形で定義される。 返り値の型名 関数名 (仮引数宣言列) 複合文 複合文の部分が、関数の本体である。 仮引数宣言列は、仮引数宣言1つか、あるいは仮引数宣言がコンマで区切られて並んだものである。 仮引数宣言は、以下の形のものである。 型名 変数名 返り値がない場合は返り値の型名の部分にvoidと書く。 引数がない場合は仮引数宣言列の部分にvoidと書く。
関数の例2(打ち込んで確認) #include<stdio.h> int add (int x, int y, int z) { return x + y + z; } int main (void) { int a; a = add (2,3,4); printf (“a=%d\n", a); return 0; 赤字の部分はreturn文であり、このreturn分が実行されると関数の呼び出し元へ戻る。その時に、x+y+zの値が返される。
return文の構文1 return文の構文1 return 式 ; return文 return e; の意味 return e; が実行されると、関数を呼び出した箇所(関数呼び出し式という)に戻る。その際、式eの評価結果が関数呼び出し式の値となる。 関数の本体の中でreturn文は複数個所にあってもよい。 また、返り値の型名がvoid(つまり返り値無し)の関数の本体中に現れてはならない。
return文の構文2 return文の構文2 return ; return文 return; の意味 返り値なしのreturn文は、返り値の型名がvoid(つまり返り値無し)の関数の中でのみ用いることができる。 返り値無しの関数においてはreturn文はなくてもよい。return文がない場合は、関数本体の複合文が終了したときに終了する。
関数の例3(打ち込んで確認) #include <stdio.h> void hello (void) { printf ("hello\n"); return; } int main (void) { hello (); return 0; helloは、引数無し、返り値なしの関数であり、helloと表示するだけの関数である。 赤字の部分の関数呼び出し式hello () は値を持たないので、代入式の右辺などに書いてはならない。 (書いた場合の動作は未定義)
関数呼び出し式 関数呼び出し式の構文 関数名 (引数列) 関数呼び出し式 f (e1, e2, …, en)の意味 引数列は、式が0個以上並んだものである。2個以上の場合はコンマで区切る。 関数呼び出し式 f (e1, e2, …, en)の意味 e1, e2, …, enを評価し、それぞれの評価結果を関数fの仮引数に代入し、関数fの本体の複合文を実行する。返り値のある関数の場合、返り値が関数呼び出し式の値となる。
関数の例4(打ち込んで確認) この例の場合、関数呼び出し式addTwo (3)の評価は、まず引数の3を評価し、その結果である3を関数addTwoの仮引数xに代入し、関数本体の複合文を実行するという順で行われる。関数本体の中でreturn文があり、x+2の評価結果5が関数の返り値として返される。その値5が、関数呼び出し式addTwo(3)の値となり、それがaに代入され、a=5が表示される。 #include<stdio.h> int addTwo (int x) { return x+2; } int main (void) { int a; a = addTwo (3); printf (“a=%d\n”, a); return 0;
仮引数の有効範囲 #include<stdio.h> int addTwo (int x) { return x+2; } int main (void) { int a; a = addTwo (3); printf (“a=%d\n”, a); return 0; 関数addTwoの仮引数xの有効範囲は、addTwoの本体の複合文(赤字部分)である。 (ただしaddTwoの本体の複合文の中にさらに複合文を書いてその中でxを宣言した場合は、そのxの有効範囲は除く。)
関数の例5(打ち込んで確認) #include<stdio.h> int addTwo (int x) { x = x + 2; return x; } int main (void) { int a, x = 3; a = addTwo (x); printf (“x=%d\n”, x); printf (“a=%d\n”, a); return 0; この例の場合、関数呼び出し式addTwo (x)の評価は、まず引数のx(赤色)を評価し、その結果である3を関数addTwoの仮引数x(緑色)に代入し、関数本体の複合文を実行するという順で行われる。関数本体の中の代入式x=x+2の部分でx(緑色)に5が代入され、return文 return x;でx(緑色)の評価結果5が関数の返り値として返される。その値5が、関数呼び出し式addTwo(x)の値となり、それがaに代入される。そして、x=3, a=5が表示される。 addTwoの引数x(緑色)とmain関数中のx(赤色)は別の変数である。
例6(打ち込んで確認) /* 1からnまでの和を表示 */ /* 左の続き */ int main (void) { #include <stdio.h> int sum (int n) { int i=1, sum=0; while (i<=n) { sum = sum + i; i = i + 1; } return sum; /* 左の続き */ int main (void) { int n; printf ("自然数を入力してください: "); scanf ("%d", &n); printf ("1から%dまでの和は%dです。\n", n, sum (n)); return 0; } 関数定義の本体は複合文であり、これまでmain関数の本体で書いていたように変数を宣言したりwhile文を使ったりして、自由にプログラムを書くことができる。
例7(打ち込んで確認) /* 1からnまでの和を表示 */ #include <stdio.h> int sum (int n) { if (n<=0) return 0; else return n + sum (n-1); } /* 左の続き */ int main (void) { int n; printf ("自然数を入力してください: "); scanf ("%d", &n); printf ("1から%dまでの和は%dです。\n", n, sum (n)); return 0; } 関数sumの中で、関数sumを呼び出している。このような関数を再帰関数という。関数呼び出しには仮引数への代入等に時間が少しかかるので、例6のようにループで回した方が一般に実行速度は速い。
注意事項 関数呼び出し式 f (e1, e2, …, en)の評価においては式e1, e2, …, enの評価がまず行われるが、これらの式の評価順序は未規定である。 関数の引数の式には副作用のある式は書かないようにする。
補足1 関数は、他の言語では手続き(procedure)と言うこともある。 値を返さない場合は手続き、値を返す場合は関数と呼ぶのが普通だと思われるが、区別しないで使う場合もある。Cでは値を返さない関数も定義できる(返り値の型の部分をvoidと記述すればよい)。
補足2 関数定義が、関数呼び出し場所より後にある場合はプロトタイプ宣言を関数呼び出しより前で行わなければならない。 #include<stdio.h> int main (void) { double x, y; x = addTwice (10.0, 20.5); y = addTwice (15.0, 25.0); printf ("x+y=%f\n", x+y); return 0; } double addTwice (double x, double y) { return (x + y) * 2; この例ではaddTwiceの関数定義が使用箇所より後ろにある。このような場合、関数の返り値はint型として処理される(ので型が合わなくなる)。プロトタイプ宣言が必要。
例8(打ち込んで確認) #include<stdio.h> double addTwice (double, double); int main (void) { double x, y; x = addTwice (10.0, 20.5); y = addTwice (15.0, 25.0); printf ("x+y=%f\n", x+y); return 0; } double addTwice (double x, double y) { return (x + y) * 2; 赤字の部分がプロトタイプ宣言。関数の返り値の型および引数の型を記述する。
Cプログラムの実行について 複数の関数が定義されているCプログラムにおいては、main関数から実行が開始される。
基本課題1 整数値を3つ入力してもらい、その中で最も小さい整数を表示するプログラムを作成せよ。ただし、3つの整数を引数にとり、その中の最小値を結果として返す関数min3を以下のように定義し、それを用いたプログラムとせよ。 int min3 (int n1, int n2, int n3) { … } [実行例] $ ./kihon6-1 整数を入力してください: 5 整数を入力してください: 10 整数を入力してください: 7 最小値は5です。
基本課題2 キーボードから正の整数を受け取り、1からその数までの2乗の和(12 + 22 + 32…)を表示するプログラムを作成せよ。ただし、nを引数にとり、1からnまでの2乗和を結果として返す関数squareSumを以下のように定義し、それを用いたプログラムとせよ。 int squareSum (int n) { … } [実行例] $ ./kihon6-2 正の整数を入力してください: 10 1から10までの2乗和は385です。
発展課題1 キーボードから正の整数を2つ受け取り、それらの最大公約数を表示するプログラムを作成せよ。ただし、n, mを引数にとり、n, mの最大公約数を結果として返す関数gcdを以下のように定義し、それを用いたプログラムとせよ。 int gcd (int n, int m) { … } [実行例] $ ./hatten6-1 正の整数を入力してください: 12 正の整数を入力してください: 18 12と18の最大公約数は6です。
発展課題2 キーボードから正の整数を2つ受け取り、それらの最小公倍数を表示するプログラムを作成せよ。ただし、n, mを引数にとり、n, mの最小公倍数を結果として返す関数lcmを以下のように定義し、それを用いたプログラムとせよ。 int lcm (int n, int m) { … } [実行例] $ ./hatten6-2 正の整数を入力してください: 12 正の整数を入力してください: 18 12と18の最小公倍数は36です。 [ヒント] lcmの中でgcdを呼べば簡単に記述できる。
発展課題3 キーボードから正の整数を受け取り、その数が素数かどうかを表示するプログラムを作成せよ。ただし、nを引数にとり、nが素数の場合1, そうでない場合0を返す関数isPrimeを以下ように定義し、それを用いたプログラムとせよ。 int isPrime (int n) { … } [実行例] $ ./hatten6-3 正の整数を入力してください: 10 10は素数ではありません。