C言語編 第9章 関数

この章の概要

この章の概要です。

関数とは

これまでに、main関数、puts関数、fgets関数といった「関数」を使ってきました。 関数は「何らかの処理を行うために必要な命令文を書き並べたもの」といえます。 例えば、puts関数なら「文字列を画面に表示するために必要な命令文を書き並べたもの」です。 C言語は、関数を組み合わせてプログラムを記述していきます。

関数には、「関数名」「引数」「戻り値」という3つの構成要素があります。

関数名は、関数に付けられた名前です。 例えば、main関数なら「main」が名前になります。 関数名として有効な名前は、変数名の規則と同様です(第3章参照)。

引数は、関数を呼び出すときに、その関数に指定する情報のことです。 例えば、puts関数を「puts( "Hello, World" );」のように呼び出しますが、ここで ( ) の中に記述している情報が引数です。 なお、「引数」は「ひきすう」と読みます。「いんすう」と呼ぶ人がたまにいるようですが、間違いです。

戻り値は、関数内の処理が終わったとき、関数側から返される情報です。 返し値返り値と呼ぶ人もいます(返り値はちょっと物騒な名前ですが…)。 実は、main関数の終わりに書いていた return文は、戻り値を返すための命令文です。

関数は次のような形式で記述します。

戻り値の型 関数名( 引数の型と名前, 引数の型と名前, … )  /* 引数は 0個以上 */
{
	/* 関数の本体 */
	
	return /* 戻り値 */		/* 戻り値がなければ return文自体が不要 */
}

この形式を見ると分かるように、引数や戻り値には型があります。また、引数には1つ1つに名前を付けます。 引数は0個以上の任意の個数が記述できます。 引数が0個の場合には、そのことを意味する「void」というキーワードを使います。 void と書かずに空白になっている例を見かけることがありますが、それは厳密にいえば void とは異なるものです。

C++ では、void と書く場合と、何も書かずに省略する場合とで、完全に同じ意味になります。 意外と、C言語と C++ とでは細かい差異があるので注意が必要です。

一方、戻り値の方ですが、戻り値がなければ、やはり void と書きますが、戻り値の場合に、記述を省略すると、int であると認識されます。 何とも分かりづらい仕様ですが、常に void と書くようにするのが無難です

引数は、関数を呼び出す側と、呼び出される側とで、呼び名を変えることがあります。 呼び出す側は、引数を実引数と呼び、呼び出される側(定義の側)では仮引数と呼びます。

C99 (戻り値の型指定の省略)

C99 では、戻り値の型指定を省略することはできなくなりました。

VisualC++ 2013/2015/2017、clang 3.7 では、省略した場合には C95以前の規格と同じく、int型を指定したものとみなされます。

main関数

ご存じのように、C言語では main関数は必ず記述しなければならない必須の関数です。 なぜなら、C言語のプログラムは main関数から始まると決められているからです。 main関数がないと、どこから開始していいのか分かりません。

main関数の引数と戻り値にはルールがあります。次の2択です。

int main(void){}
int main(int argc, char** argv){}

2つ目の方は現段階では説明しません(第43章で説明します)。 この2つの形式以外に、戻り値が void になっている例をよく見かけますが、C言語の規格上は正しいものではありません。 また、引数が3個ある main関数もたまに見かけますが、これも同様に、C言語の規格上は正しくありません。

戻り値の型が int型である以上、main関数には必ず return文があります。 main関数の戻り値を 0 にすると、問題無く正常に処理が終了したことを表し、 0以外を返すと、何らかのエラーが発生して終了したことを表すことになっています

標準ライブラリ関数

C言語の規格によって、あらかじめ用意されている関数のことを、標準ライブラリ関数といいます。 puts関数や fgets関数のようなものが標準ライブラリ関数に当たります。

標準ライブラリ関数を使うには、「#include <stdio.h>」のような記述が必要になります。 「stdio.h」の部分は、関数ごとに決まっています。 main関数はこのような記述が不要なので、標準ライブラリ関数とは別です。

コンパイラの種類によっては、標準でない関数を幾つか用意していることがあります。 このような関数には、標準ライブラリ関数では出来ないような、かなり便利な機能が含まれていることが多いのですが、当然、他のコンパイラでは使えません。 本サイトでは、このような標準でない関数については取り上げません。

自作関数

プログラマは、自分で新たな関数を作ることができます。 C言語では、このような自作の関数を次々に作り、それらを組み合わせてプログラム全体を形作っていきます。

もちろん、これまでのように main関数だけで完結させることもできますが、1箇所に処理を詰め込む方法では、 ある程度の規模になったとき、読みづらくなってきます。 1つの関数を、1つの部品と考え、部品を組み合わせていくスタイルを取ります。

例として、自作関数を作り、それを使うプログラムを見ておきましょう。

#include <stdio.h>

/*
	三角形の面積を求める。
	引数
		base:	底辺の長さ。
		height:	高さ。
	戻り値
		面積。
*/
double calcAreaOfTriangle(double base, double height)
{
	return ( base * height / 2 );
}

int main(void)
{
	double base, height, area;


	base   = 3.0;
	height = 5.0;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	base   = 7.0;
	height = 4.0;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	base   = 4.4;
	height = 3.6;
	area   = calcAreaOfTriangle( base, height );
	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

	return 0;
}

実行結果:

底辺:3.000000  高さ:5.000000  面積:7.500000
底辺:7.000000  高さ:4.000000  面積:14.000000
底辺:4.400000  高さ:3.600000  面積:7.920000

三角形の面積を求める関数 calcAreaOfTriangle を作りました。 この関数は、2つの double型の引数を持ち、それぞれが三角形の底辺の長さ、三角形の高さを表しています。 戻り値も double型で、これが計算で求められた面積になります。

double型の仮引数が2つなので、calcAreaOfTriangle関数を呼び出すときには、double型の実引数が2つ必要になります。 もちろん、指定する順番も重要で、今回なら1つ目の実引数に底辺の長さを、2つ目の実引数に高さを指定しなければなりません。

return文で返される戻り値ですが、関数を呼び出す側はこれを代入と同じ方法で変数に受け取ることができます。 実際、これは代入に他なりません。 従って、

printf( "%f\n", calcAreaOfTriangle( base, height ) );

のように書くこともできます。この場合、戻り値をそのまま別の関数(printf)の実引数にしていることになります。

なお、戻り値を受け取ることは必須ではなく、呼び出し側は戻り値の存在を無視することもできます

calcAreaOfTriangle関数の中身ですが、return文が1つあるだけです。 関数の仮引数には名前が付けてある訳ですが、これは実は引数の宣言をしているのと同じことです。 つまり、仮引数の base と height は、変数のように使用でき、その初期値として実引数の値が入っていると考えられます。 実引数がそれぞれ 3.0 と 5.0 であったとするならば、

/* 正しいC言語のコードではない! */
double calcAreaOfTriangle(double, double)
{
	double base   = 3.0;
	double height = 5.0;

	return ( base * height / 2 );
}

こんな感じで書くのと同じ意味合いです。 最大のポイントは、3.0 や 5.0 と決め打ちで書いた部分が、実引数によって自由に決められるという点にあります。 何度も同じ処理を繰り返す場合、その部分を関数にし、変化のある部分は実引数を変えることで対応できます。 今回のプログラム例は、3回に渡り、同じ処理を繰り返しますが、そのたびに底辺の長さと、高さを変えています。

なお、main関数がコード全体の下の方に記述してありますが、 main関数はどこに書いても必ず「最初に実行される」関数なので問題ありません。 実は今回のプログラムの場合、main関数を上の方に持ってくると、うまくいきません。 この辺りは次章で説明します。

関数から関数を呼び出す

先ほどのサンプルプログラムの場合、出力時のフォーマットも同じになっていますから、この部分を関数にしてしまうことも考えられます。 その場合、次のように割と大きく書き変えることもできます。

#include <stdio.h>

/*
	三角形の面積を求める。
	引数
		base:	底辺の長さ。
		height:	高さ。
	戻り値
		面積。
*/
double calcAreaOfTriangle(double base, double height)
{
	return ( base * height / 2 );
}

/*
	三角形の情報を出力する。
	引数
		base:	底辺の長さ。
		height:	高さ。
*/
void printTriangleInfo(double base, double height)
{
	double area = calcAreaOfTriangle( base, height );

	printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );
}

int main(void)
{
	printTriangleInfo( 3.0, 5.0 );
	printTriangleInfo( 7.0, 4.0 );
	printTriangleInfo( 4.4, 3.6 );

	return 0;
}

実行結果:

底辺:3.000000  高さ:5.000000  面積:7.500000
底辺:7.000000  高さ:4.000000  面積:14.000000
底辺:4.400000  高さ:3.600000  面積:7.920000

三角形の底辺の長さと高さを引数として渡すと、それらに面積を加えて printf関数で出力するような自作関数 printTriangleInfo を追加しました。 printTriangleInfo関数は、三角形の面積を求めるために、更に自作関数の calcAreaOfTriangle を呼び出しています。

このように、関数の中から、別の関数を呼び出すことも、もちろん可能です。 今までも、main関数の中から printf関数を呼び出すようなことはしていました。

自作関数と標準ライブラリ関数とを完全な別物である、あるいは標準ライブラリ関数がとても特別なもののように考える人がいるようです。 どちらもただの「関数」であることに変わりはありません。 標準ライブラリ関数といえども、どこかに自作関数と同じように中身を記述している部分があります (この中身が実際に覗けるようになっているか、それとも見えないように隠されているかは、コンパイラによります。 見えないようになっているせいで、特別なものと考えてしまうみたいですが、こういう部分も「商品」ですから、 見せないという選択肢を取っていてもおかしくないです)

文字列を引数や戻り値にするには

引数や戻り値には型の指定が必要ですが、文字列の場合はどうすればいいのでしょうか。 結論から言えば、「まだ手を出すのは早い」と言うところです。 特に戻り値の方は、意味を正確に理解するまでは危険です。

引数の方は、知っておいてもいいでしょう。 例えば、次のように書きます。

#include <stdio.h>

/*
	文字列を [ ] で囲んで出力する。
	引数
		str:	出力する文字列。
*/
void bracketsPuts(char* str)
{
	printf( "[%s]\n", str );
}

int main(void)
{
	bracketsPuts( "Hello, World!" );

	return 0;
}

実行結果:

[Hello, World!]

bracketsPuts関数は、引数で渡された文字列の前後を [ ] で囲んで出力します(ちなみに [ ] をブラケットと言います)。 このサンプルのように、文字列を引数にするには、仮引数の部分で「char* 」と記述します


練習問題

問題@ int型の引数を2乗して返す関数を作って下さい。

問題A 2つの文字列を渡すと、その2つの文字列で、 + を挟んで出力するような関数を作成して下さい。
例えば、"abc" と "def" を渡すと、"abc+def" と出力するようにして下さい。

問題B 問題Aでは + を挟みましたが、挟む文字も含めて指定できるように関数を作成して下さい。 挟む文字は必ず1文字であるとします。

問題C 円の面積を求めて出力するプログラムを、以下の関数を使って作成して下さい。半径は double型とします。


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/10/18 clang 3.2 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/14 VisualC++ 2008 の対応終了。

'2014/1/10 clang 3.0 に対応。

'2013/4/14 C99 では、関数の戻り値の型指定を省略できなくなったことを追記。

'2012/8/15 問題Aの問題文の不備を修正。

'2009/3/17 「この章の概要」を追加。

'2009/2/2 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ