C言語入門
第 03 章 関数の作成と利用

前章では算術演算子や比較演算子のような演算子と、変数とデータ型について説明しました。本章では関数について説明します。C言語の関数は、いわゆる数学で一般的に使われる関数とよく似ていますが、いくらか違ったところもあります。本章では、関数とはなにか、その書き方や使い方を説明します。

C言語における関数

ここまで紹介したプログラムは main 関数のみを含むものでした。プログラムを実行すると、この main 関数が計算(実行)されました。しかし main 以外の関数を作ることもできます。以下では独自の関数を作ってみます。

関数とはおおよそ、数学で普通に登場する関数と同じもの、と考えてよいでしょう。前章で変数について説明した際、以下のようなプログラムを示しました。
#include <stdio.h>

int main() {
int x = 5; /* 変数 x に 5 割り当てる */
printf("%d\n", x * x * x + x * x + x + 1);
return 0;
}
変数を使うことで、4 行目を変更するだけで、異なる x の値について、 x3+x2+x+1 の計算ができるようになりました。これをさらに進めて、x3+x2+x+1 を簡単に何度も計算できるように関数を作ってみましょう。

関数の宣言と呼び出し

上のプログラムを関数を使うように書き直したものを示します。
#include <stdio.h>

/* x^3 + x^2 + x + 1を計算する */
int f(int x) {
return x * x * x + x * x + x + 1;
}

int main() {
int x = 5; /* 変数 x に 5 を割り当てる */
printf("%d\n", f(x)); /* f(x) を計算 */
return 0;
}
4 行目から 6 行目で関数 f を定義しています。 正しくは関数 f を宣言した、といいます。数学で普通に使われる記法で書くなら

f(x) = x3 + x2 + x + 1

と定義したのと同じです。

定義された関数は、数学の関数と同じような記法で使えます。上の例では、10 行目で f(x) と書いているので、関数 f が引数(ひきすう) 5 で計算されます。引数が 5 なのは 9 行目で変数 x に 5 を割り当てているためです。f(x) のように書いてコンピュータに計算を指示することを、関数 f を呼び出す、といいます。

引数は、数でも、変数でも、入れ子になった関数呼び出しでも、それらを組み合わせた式でもかまいません。引数の個数が複数の場合もあります。これまで文字を表示するために
printf("%d\n", f(x));
などと書いてきましたが、これも関数 printf の呼び出しでした。少し読みにくいかも知れませんが、括弧の中でカンマで区切られた "%d\n"f(x) の 2 つが関数の引数です。なお、printf 関数は定義済みなので、プログラムの中で改めて定義する必要はありません。

関数は何回呼び出してもよいので、次のようなプログラムを書くこともできます。
#include <stdio.h>

/* x^3 + x^2 + x + 1を計算する */
int f(int x) {
return x * x * x + x * x + x + 1;
}

int main() {
int x = 5; /* 変数 x に 5 を割り当てる */
printf("%d\n", f(x)); /* f(x) を計算 */
printf("%d\n", f(4)); /* f(4) を計算 */
return 0;
}
11 行目に f(4) を呼び出して、計算結果を表示する行を挿入しました。

では次のプログラムはどうでしょうか。今度は f(x) を2回呼び出します。
#include <stdio.h>
int f(int x) {
printf("f(%d)=\n", x); /* 表示 */
return x * x * x + x * x + x + 1;
}

int main() {
int x = 5;
printf("%d\n", f(x)); /* f(x) を計算 */
printf("%d\n", f(x)); /* f(x) を計算 */
return 0;
}
変数 x の値は 5 ですから、f(x) の計算結果は何回呼び出しても 156 です。しかしながら関数が呼び出されると、コンピュータは呼ばれるたびにその関数の定義にしたがって計算をやり直します。たとえ計算結果が同じであっても。

このため、このプログラムの実行結果は、次のようになります。
f(5)=
156
f(5)=
156
f(5)= が2回表示されていることに注意してください。

関数 f の定義の中にある printf 関数の呼び出し(3 行目)によって、f(5)= が表示されます。f(x) は 2 回呼び出されるので、関数 f は 2 回計算されます。printf 関数は計算されるたびに(呼び出されるたびに)、文字を表示するので、結局、f(5)= が 2 回表示されます。

C言語の関数は、定義の仕方が少し変わっていることを除けば、おおよそ数学の普通の関数と同じように考えていてよいのですが、呼び出されると、必ず計算をやり直す点には注意が必要です。printf のような関数があるので、計算をやり直すかやり直さないかで、プログラムの実行結果に目に見える違いが出てしまうことがあります。

引数と戻り値

関数の宣言は一般に次のような形をとります。
《戻り値の型》 《関数名》(《引数列》) {
  定義の本体
}
戻り値の型は int や double のようなデータ型です。関数名は f や printf、main のような名前です。引数列とは、引数をカンマで区切って並べたものです。引数列はなし(空)でもかまいませんし、引数が1つであればカンマは不要です。
引数列の中に現れた引数は、関数定義の本体で、他の変数と同様に使うことができます。変数を使うときは、そのデータ型を書かなければなりませんでした。引数の場合も同様で、引数のデータ型と引数の名前を空白で区切って並べて書かなければなりません。

以下に 2 個の引数をとり x2 + xy + y2 を計算する関数 g の宣言を例として示します。

/* x^2 + xy + y^2 を計算する */
int g(int x, int y) {
return x * x + x * y + y * y;
}
関数の計算結果の値を戻り値とよび、一般に関数が「戻り値を返す」という言い方をします。戻り値は関数内にある return の後ろで指定します。ここに書いてある式を計算した結果が、戻り値となります。return から行末のセミコロンまで、つまり
return 戻り値の式 ;
の行のことを return 文といいます。C言語では return 文まで計算を進めると、関数の計算は終了してしまいます。return の次の行に何か処理を書いても実行されることはありません。

戻り値についても、変数や引数と同じ理由でデータ型の指定が必要です。戻り値のデータ型は関数名の前に書きます。int と書けば return に続く式の計算結果は int でなければなりません。もし計算結果のデータ型が、戻り値のデータ型として指定されたものと異なる場合には、エラーとなり、コンパイルに失敗します。

なお第 1 章でも触れたように、main 関数にも return 文を書きます。通常、main() 関数の戻り値はプログラムの中では利用されません。これは他のプログラムと組み合わせて利用する、といった高度な使い方をするためにあります。この場合、 一般的に値が 0 の場合には実行が成功したことを、それ以外の値の場合は失敗したことを表すのが慣例となっています。

最後に関数の宣言について注意点があります。C言語では、ある関数を呼び出す場合、それより前にその関数が宣言(定義)されていなければなりません。前、というのは、プログラムの上の方の行で、という意味です。後で宣言される関数を呼び出す方法もありますが、少し工夫が必要なので、当面はまず関数を宣言するのが先、と覚えておいて下さい。

スコープ

プログラム中に現れる変数や引数には有効範囲があります。これをスコープといいます。例として次のプログラムを見て下さい。
#include <stdio.h>

int f(int x) {
return x * x * x + x * x + x + 1;
}

int main() {
int x = 5;
printf("%d\n", f(x + 1)); /* 引数が x + 1 */
printf("%d\n", x); /* x の値を表示 */
return 0;
}
先に示したプログラムとほとんど同じですが、関数 f の呼び出しの際の引数が x + 1 です。変数 x の値は 5 ですから、今度は f(6) の計算を指示することになります。

関数 f の宣言の方を見てみると、引数が x で計算結果は x3 + x2 + x + 1 の値です。では引数 x の値は何かと考えると、f(6) の計算が指示されたのですから、当然 6 です。

さて元々、変数 x の値は 5 だったはずですが、どちらが正しいのでしょうか?

あるいは f(x + 1) を呼び出す前は変数 x の値は 5 で、呼び出した後は 6 に変わるのでしょうか? そうだとすると、2 つ目の printf 関数の呼び出しによって表示される変数 x の値は 6 でしょうか?
正解は、2 つの変数 x は(偶然名前は同じだけれど)異なる変数とみなされる、です。関数 f の引数 x と関数 main の中で使われている変数 x は、異なるものとみなされます。したがって 2 回目の printf 関数の呼び出しよって表示される変数 x の値は 5 のままです。

簡単に規則を説明すると、1 つの関数の宣言の中では、引数も変数も互いに異なる名前でなければなりません。一方、異なる関数同士であれば、引数や変数が同じ名前をもっていても、互いに異なるものとして扱われます。コンピュータ内部で適当に違う名前に読みかえて解釈される、と考えればよいでしょう。関数の引数やその中で使われている変数には有効範囲があり、その範囲の外で同じ名前の変数や引数が使われていても問題ないのです。引数や変数の有効範囲のことをスコープといいます。

このようなスコープ規則がないと、変数の名前を決めるのがとても大変になってしまいます。大きなプログラムでは非常に多数の変数が使われるからです。プログラム全体を見て、同じ変数名がないかどうかを確認する必要があります。しかし、実際にはスコープがあるため、変数の名前を決めるときは、そのスコープの中で名前の重複がないことをだけを確認すればよいのです。

演習 3−1

摂氏温度を華氏温度に変換する関数を書き、摂氏 10 度、12.3 度、24.3 度を華氏温度に変換するプログラムを作成し、実行結果を示してください。変換をおこなう関数は引数が 1 つで、その引数のデータ型は double 型、戻り値の型も double 型とします。

演習 3−2

身長と体重から BMI 指数(Body Mass Index)を算出する関数を書き、以下の場合について BMI を計算して表示するプログラムを作成し、実行結果を示してください。BMIは 体重 (kg) ÷ 身長 (m)2 で求めることができます。

BMI を算出する関数は引数が 2 つで、その引数のデータ型は両方とも double 型、戻り値の型も double 型とします。

  • 身長 165 cm、体重 55 kg
  • 身長 182 cm、体重 69 kg
  • 身長 170 cm、体重 89 kg
/* x^2 + xy + y^2 を計算する */
int g(int x, int y) {
return x * x + x * y + y * y;
}

既に用意されている関数

C言語では、プログラミングを簡単にするために、あらかじめ多くの関数が用意されています。 そのような関数はいちいち自分で書かなくても、すでにあるものとして使うことができます。このような関数をライブラリ関数と呼びます。 第 1 章から利用してきた printf 関数もライブラリ関数のひとつです。

ライブラリ関数の例として、数値計算をおこなう際によく使われる関数を以下に示します。 引数、戻り値ともに double 型です。

関数名演算内容
sin(x)正弦。引数 x は角度をラジアンで示したもの。
cos(x)余弦。引数 x は角度をラジアンで示したもの。
tan(x)正接。引数 x は角度をラジアンで示したもの。
fabs(x)x の絶対値。
sqrt(x)x の平方根。

これらの関数を呼び出すプログラムは、冒頭に以下の行を含んでいなければなりません。

#include <math.h>
これらの関数を使ったプログラムの例を示します。
#include <stdio.h>
#include <math.h>

int main() {
printf("sin(0.0) = %f\n", sin(0.0));
printf("cos(0.0) = %f\n", cos(0.0));
printf("tan(0.0) = %f\n", tan(0.0));
printf("fabs(-3.14) = %f\n", fabs(-3.14));
printf("sqrt(2.0) = %f\n", sqrt(2.0));

return 0;
}
実行結果は次のようになります。
sin(0.0)      = 0.000000
cos(0.0) = 1.000000
tan(0.0) = 0.000000
fabs(-3.14) = 3.140000
sqrt(2.0) = 1.414214

関数の再帰呼び出し

関数を定義する際に、その関数自身を使って再帰的に定義することがあります。C言語でも、そのような関数の定義が可能です。ある関数の定義の本体( { } の中)の中で、その関数自身を呼び出すことを再帰呼び出しといいます。例えば以下の関数を考えてみてましょう。
a(n) = 2          --- if n = 0
a(n) = a(n-1) + 3 --- otherwise
a(n) の値は n = 0 のときは 2、それ以外は a(n - 1) + 3 です。これを C言語のプログラムで表現すると、次のようになります。
#include <stdio.h>

int a(int n) {
return n == 0 ? 2
: a(n - 1) + 3;
}

int main() {
printf("a(0) = %d\n", a(0));
printf("a(1) = %d\n", a(1));
printf("a(2) = %d\n", a(2));
printf("a(3) = %d\n", a(3));
printf("a(4) = %d\n", a(4));
return 0;
}
4 行目から 5 行目にかけての return 文の戻り値を 3 項演算子で計算しています。これにより n が 0 の場合とそうでない場合とで戻り値の計算方法を変えています。

関数呼び出し a(n) を計算させると、まず引数 n の値を調べて 0 でなければ、a(n - 1) を呼び出します。呼び出したら、呼び出された  a(n - 1) の計算が終わるまで a(n) の計算は一時中断されます。a(n - 1) の計算が終わって呼び出しが終了したら、a(n) の計算が再開され、a(n - 1) の戻り値と 3 の和を計算し、これを計算結果として a(n) の計算が終了します。

もし 3 項演算子を使わずに、常に戻り値を a(n - 1) + 3 と計算することにすると、プログラムは永遠に終了しなくなってしまいます(実際には、途中でメモリが足りなくなって強制的にエラーで終了します)。例えば a(1) の値を計算させると、その途中で関数呼び出し a(0) が実行され、a(0) の値を計算する途中で関数 a(-1) が呼び出され、その途中で今度は a(-2) が、と永遠に関数呼び出しが入れ子になって繰り返されます。

同様に関数 a の定義を間違えて次のようにしてしまっても、プログラムは永遠に終了しなくなってしまいます(同様に実際には途中でエラーで終了します)。
int a(int n) {
return n == 0 ? 2
: a(n) + 3;
}
C言語のプログラムとしては間違ってはいません。コンパイルするとエラーなく完了し、実行することができますが、永遠に終了しません。例えば a(1) を計算させると、a(1) の計算を完了する前に、再び a(1) を呼び出します。しかし 2 回目の関数呼び出し a(1) の計算をおこなうため、再び a(1) を呼び出します。このように関数呼び出しが永遠に続くのでプログラムは永遠に終了しません。

まとめ

本章では関数の定義方法と利用方法について説明しました。またC言語であらかじめ用意されている関数のうち、数値計算に関するものを紹介しました。また本章では、再帰的に関数を定義することが可能であることを示しました。次章以降では、必要に応じてさまざまな関数を書いてゆきます。

演習 3−3

1 から指定された値までの整数の合計値を求める関数を書き、その関数を使って 1 から 10 までの和を計算して答えを表示するプログラムを作成し、実行結果を示してください。なお、指定された値までの合計値を求める関数は int 型の引数を 1 つだけとります。定義は以下のようになります。
f(n) = 0            --- if n = 0
f(n) = n + f(n - 1) --- otherwise

演習 3−4

フィボナッチ数列の第 n 項を計算する関数を書き、第 10 項までのフィボナッチ数列を表示するプログラムを作成してください。 この関数は int 型の引数をひとつとります。この引数が計算するフィボナッチ数の項を示すこととします。なお、第 n 項のフィボナッチ数列を求めるための関数は以下のようになります。
f(n) = n               --- if n = 0 or 1
f(n) = f(n-2) + f(n-1) --- otherwise

目次


Copyright 2009-2011 the Compview project, Tokyo Institute of Technology. All rights reserved.