94 4. 復習 関数 (その1)
関数名 ( 引数
機能 の並び ) 引数の型 関数値の型 説明 逆正弦 asin(a) double double sin−1a∈[−π2,π2] 逆余弦 acos(a) double double cos−1a∈[0, π]
逆正接 atan(a) double double tan−1a∈[−π2,π2] atan2(a,b) double double tan−1 ab ∈[−π2,π2] 双曲線正弦 sinh(a) double double sinha
双曲線余弦 cosh(a) double double cosha 双曲線正接 tanh(a) double double tanha
整数部と小 doubleと aの小数部(符号はaと同じ) 数部に分離 modf(a,ptr) (double *) double を返し、aの整数部を ptr
の指す領域に格納 仮数部と指 doubleと 関数呼び出し直後は 数部に分離 frexp(a,ptr) (int *) double a=(関数値)
×2(ptrの指すint型の値)
補足: C言語では、数学的関数には分類されていないが次のような関数も標準ライブラ リに用意されている。[詳しくはケリー&ポール付録A.13節,浦&原田付録5などを参照し て下さい。RAND_MAX は/usr/include/stdlib.h の中で定義されたマクロ名、div_t と ldiv_t は /usr/include /stdlib.h の中で定義された「構造体」の名前である。構造体 についてはこの講義ノートの第11節を参照して下さい。]
関数名 ( 引数
機能 の並び) 引数の型 関数値の型 説明
乱数 rand() なし int 区間[0,RAND_MAX)の間の疑似乱数
srand() unsigned int なし 疑似乱数発生器の状態を初期化
絶対値 abs(a) int int |a|
labs(a) long long |a|
商と剰余 div(a,b) int div_t aをbで割った時の商と剰余の組
ldiv(a,b) long ldiv_t aをbで割った時の商と剰余の組
4.2. 関数定義 95
例題 4.2 (二項係数の計算) n個のものからk個を選ぶ組合せの数 nk は n
k
!
= n!
k! (n−k)!
と計算できる。2つの正整数データnとk を読み込みこの計算式に基づいて組合せの 数 nkを計算して出力するCプログラムを作成せよ。
(考え方) 計算式に階乗計算が3箇所もあるので、main()関数とは別に、階乗計算を行
う関数 factorial() を定義するのが自然であろう。 引数として整数値を受け取りその階
乗値を返す関数 factorial() が記述されていれば、組合せの数 nk の計算は、数学関数 と同じ様にfactorial() を呼び出して
n k
!
= factorial(n)
factorial(k) factorial(n−k)
という風に行うことができる。関数 factorial() に与える引数データは、我々が入力す る正整数、およびそれらの差であるので、そのデータ型は int とするのが妥当である。
また、factorial()の関数値は本来整数であるが、int型で表せる範囲を越えてしまう危 険性もあるので、そのデータ型を実数型にして階乗値も組合せの数も近似計算する方が無 難である。 階乗計算については、例題1.3 と同じ風に行えば良い。
(プログラミング) 階乗計算を行う関数 factorial() は、引数としてint 型のデータを
受け取り、関数値としてdouble型のデータを返すものとする。関数factorial() の中で は、引数として受け取るデータを格納するために k という名前のint型変数を、1!,2!,3!,...
の値を保持するためにfactという名前のdouble変数を、そしてfor文による繰り返しを 制御するために i という名前のint型変数を用意する。 また、main()関数の中では、
読み込んだ正整数 nとk を格納するために各々 n と k という名前のint型変数を用意し てプログラムを構成した。このCプログラムと、これをコンパイル/実行している様子を 次に示す。(下線部はキーボードからの入力を表す。)
[motoki@x205a]$ nl function-binomial-coeff.c Enter
1 /* 2つの正整数データ n と k を読み込み */
2 /* 二項係数 n!/(k!*(n-k)!) を出力するCプログラム */
3 #include <stdio.h>
4 double factorial(int k);
5 int main(void) 6 {
7 int n, k;
8 printf("It will compute a binomial coefficient.\n"
9 "Input two positive integers n and k(<=n): ");
96 4. 復習 関数 (その1)
10 scanf("%d%d", &n, &k);
11 printf("\nThe number of the combinations of\n"
12 " n objects taken k at a time = %20.14g\n", 13 factorial(n)/(factorial(k)*factorial(n-k)));
14 return 0;
15 }
16 /*---*/
17 /* 階乗値を計算してその結果を返す関数 */
18 /*---*/
19 /* (仮引数) k : 何の階乗を計算するかを表す整数 */
20 /* (関数値) : k! の値をdoubleで */
21 /*---*/
22 double factorial(int k) 23 {
24 int i;
25 double fact;
26 fact = 1.0;
27 for (i=2; i<=k; ++i) 28 fact *= (double)i;
29 return fact;
30 }
[motoki@x205a]$ gcc function-binomial-coeff.c Enter [motoki@x205a]$ ./a.out Enter
It will compute a binomial coefficient.
Input two positive integers n and k(<=n): 50 25 Enter The number of the combinations of
n objects taken k at a time = 1.2641060643775e+14 [motoki@x205a]$
ここで、
• プログラムの4行目 は、22∼30行目で定義した関数factorial() がどういう型の引 数を受け取りどういう型の値を返すのかを宣言した文で、関数プロトタイプ(または 関数原型)と呼ばれる。4行目には k という名前の変数名が書かれているが、これは 省略可能で、これによって変数領域の確保を指示している訳ではない。一般に関数プ ロトタイプは次のような構造をしている。
関数値のデータ型 関数名 ( データ型 名前, . . . , データ型 名前 );
または
関数値のデータ型 関数名 ( データ型 , . . . , データ型 );
コンパイラはプログラムを前から順に見て行くので、この宣言が無いとコンパイラ は13行目を見る時点で、呼び出された関数 factorial()の引数として計算したもの
4.2. 関数定義 97
をどういうデータ型に変換して factorial() に引き渡せば良いかも分からないし、
factorial() の計算結果として返って来たデータをどういう風に解釈すれば良いか
も分からない。 #includeの指令は、大抵の場合、printf() や scanf() といった標 準ライブラリ関数についての、関数プロトタイプを読み込むのが目的となっている。
• プログラムの5∼14行目 は関数main()を定義した部分、22∼30行目 は関数factorial() を定義した部分になっている。main()については関数値の型が宣言されていないが、
省略されているので int型が暗黙に仮定される。
• プログラムの7行目 にも22行目 にも k という名前の変数が確保されているが、これ らは別の変数として扱われる。 実際、8∼13行目で変数k を使うと7行目で確保した 変数 k として扱われ、23∼29行目で変数kを使うと22行目で確保した変数kとして 扱われる。
• プログラムの13行目 では関数factorial() が3回呼び出されている。関数が呼び出 されると、関数に引き渡されたデータの値を記憶する変数 k、および関数本体の最初 に宣言されている変数 i のための領域が 新たに動的に 確保される。
• 一般に、呼び出しの際に関数名の後の丸括弧の中で指定し、関数に実際に引き渡す データのことを実引数と呼ぶ。これに対し、実引数の値を記憶するために関数の中に 用意される変数のことを仮引数と呼ぶ。
• プログラム29行目 の return 文は、関数の実行を終了し factorial()の計算結果
(関数値) として式fact の値を呼び出し元に返すことを表す。
'
&
$
% 補足:
関数の呼び出し元に戻される値は関数の計算結果を表すので、これまでこの値の ことを関数値と呼んできたが、C言語では通常この値のことを戻り値(あるいは 返却値)と呼ぶ。
• プログラムの16∼21行目 は関数factorial() の仕様、すなわちこの関数を呼び出す 側に対してどういう機能を提供するかを明確に記述した注釈である。
'
&
$
% 関数の仕様を記述することの利点:
各々の関数に仕様が書かれていると、作り上げた関数の処理内容を理解する際、
その関数から呼び出す別の関数については処理内容を詳しく見る代わりに仕様を 見るだけで良いので、一度に把握するプログラムの範囲が小さくて済む。
=⇒3 プログラムを理解し易くなる。
3 多数の関数が複雑に絡み合った大きなプログラムを作る場合もしっかり としたプログラムを作ることが可能となる。
関数仕様の書き方について:
仕様としては、関数を使う側に対してどういう機能を提供するのかを書く。
それゆえ、
3 与えられた引数に対して、どういう関数値が返されるかを書く。
3 関数の外側で確保された変数等の値を変える作用、いわゆる副作用がある 場合は、どういう副作用があるかも書く。
3 関数の内部の細かな変数や、処理手順についての記述は控えるべきである。
98 4. 復習 関数 (その1)