関数

関数の概念

一般に、実用的なソフトウェアを開発しようとすると、プログラムは必然的に長いものになります。すると、その中に、複雑な処理が現れたり、何度も同じ処理を行ったりすることが必要になります。C言語では、そういった処理に特別な名前を与え、何度でも再利用したりすることが出来るような仕組みがあります。それを関数(かんすう)と言います。

サンプルプログラム

関数について詳しく説明する前に、実際にあるプログラムから、重複する処理を関数として定義するという処理を実際にやって見ましょう。まずは、以下のプログラムを見てください。

list6-1:main.c
#include <stdio.h>

void main(){
   double d1,d2,d3;
   double a = 1.2,b = 3.4,c = 2.7;
   //	同じ計算が3回
   d1 = (a + b) / 2.0;
   d2 = (4.1 + 5.7) / 2.0;
   d3 = (c + 2.8) / 2.0;
   printf("d1 = %f,d2 = %f,d3 = %f¥n",d1,d2,d3);
}

実行結果
d1 = 2.300000,d2 = 4.900000,d3 = 2.750000

d1,d2,d3は、それぞれ様々な数値の平均値を求めて入れる処理だと仮定します。このようなプログラムをかくと、7行目から9行目のように、 同じ処理を複数記述しています。もしも、今後同じような処理を何度も行わなくてはならないとすると、同じ計算をかく時に非常に面倒です。

そこで、関数の出番になるわけです。この平均値を求める処理を、それとわかる名前の関数に置き換えて変更したのが以下のプログラムです。

list6-2:main.c
#include <stdio.h>

//	平均値を求める関数の定義
double avg(double l,double m){
	//	引数l,mの平均値を求め、rに代入する。
	double r = (l + m) / 2.0;
	return r;
}
 
void main(){
   double d1,d2,d3;
   double a = 1.2,b = 3.4,c = 2.7;
   //	同じ計算が3回(関数を呼び出して計算)
   d1 = avg(a,b);
   d2 = avg(4.1,5.7);
   d3 = avg(c,2.8);
   printf("d1 = %f,d2 = %f,d3 = %f¥n",d1,d2,d3);
}

実行結果は、list6-1と一緒です。ただ、main()の中を見てみるとわかると思いますが、先程の平均値の計算処理を行っている個所が、全て、avgという文字列に入れ替わっています。 このavgが関数であり、main()の手前で定義されています。では、一体、この関数はどのような仕組みになっているのでしょうか?

関数の仕組み

関数の仕組みを説明するために、まずは関数の書式をみてみます。

関数の書式
戻り値の型 関数名(引数の型 引数1,引数の型 引数2,・・・,引数の型 引数n)
{
    処理
    return 戻り値; ← 省略されることもある
}

基本的に関数には、好きな名前を付けることができます。list6-2の場合、avgという名前をつけています。関数の名付けのルールは変数の場合と似ています(2日目参照)。そのため、予約語を用いることはできないので、注意ましょう。

引数と戻り値

また、その名が示す通り、関数は、入力する値に対し、何らかの処理を行って値を出力することが求められます。入力する値のことを、引数(ひきすう)と言います。 引数は、複数定義することが可能であり、その場合は、間を,で区切ります。また、省略することも可能です。(図6-1)

図6-1.関数のイメージ①
C言語の関数のイメージ1

list6-2のケースでは、double型の変数、mとnが引数となっています。これらの変数には、関数を呼び出す側が入れた数値が入ります。 例えば、15行目では、avg(4.1,5.7)としていますので、この場合、lに4.1が、mに5.7が代入されます。

この値が、計算の用いられ、それがに代入され、return(リターン)文によって、呼びだした側(この場合はmain)に返されます。 return r;とは、「戻り値として、rの値を返しなさい」という意味であり、この結果は、doubleであることから、戻り値の型の部分にdoubleと記述してあります。

このようにして定義された関数はを呼び出しているのが、main()の中の14~16行目です。ここでの戻り値はここで変数d1~d3に代入されます。(図6-2)

図6-2.関数のイメージ②
C言語の関数のイメージ2

プロトタイプ宣言

また、list6-2は、更に発展させて、以下のように書くことができます。

list6-3:main.c
#include <stdio.h>
//	関数avgのプロトタイプ宣言
double avg(double,double);
 
void main(){
   double d1,d2,d3;
   double a = 1.2,b = 3.4,c = 2.7;
   //	同じ計算が3回(関数を呼び出して計算)
   d1 = avg(a,b);
   d2 = avg(4.1,5.7);
   d3 = avg(c,2.8);
   printf("d1 = %f,d2 = %f,d3 = %f¥n",d1,d2,d3);
}

//	平均値を求める関数
double avg(double l,double m){
	//	引数l,mの平均値を求め、rに代入する。
	double r = (l + m) / 2.0;
	return r;
}

実行結果は、list6-1およびlist6-2と一緒ですが、list6-2との違いは、main()の前にあったavg関数の定義が、後ろに移動したことです。 かわりに、プロトタイプ宣言と呼ばれる処理が、書かれています。これはそのあとに呼び出す関数の型をあらかじめ予告しておくものです。

なぜプロトタイプ宣言が必要かという理由は様々ですが、あらかじめ関数の型を決めておけば、そのあとで様々な場所で利用することが可能だからです。

なお、関数から、ほかの関数を呼び出すことも可能です。このとき、特に関数が、自分自身を呼び出すような処理を再帰呼び出しと言います。詳しくは、こちらを参照してください。

様々な関数

サンプルプログラム

次に、様々な関数を使ったプログラムをみてみましょう。

list6-4:main.c
#include <stdio.h>

//プロトタイプ宣言
int max(int,int);
void show(int);
void line();
 
void main(){
   int n1 = 4,n2 = 5;
   line();
   show(n1);
   show(n2);
   printf("二つの数のうち、大きい数は、%dです。¥n",max(n1,n2));
   line();
}

//	2つの整数のうち最大値を求める関数
int max(int a,int b)
{
	if(a > b){
		return a;
	}
	return b;
}
//	数値を表示する関数
void show(int n)
{
	printf("数値:%d¥n",n);
	return;
}
void line()
{
	printf("*********¥n");
}
実行結果
*********
数値:4
数値:5
二つの数のうち、大きい数は、5です。
*********

void

show()関数と、line()関数の戻り値の部分に書かれているvoid(ボイド)とは、関数の戻り値が無いということを意味しています。したがって、これらの関数は、戻り値を持ちません。 また、line()関数は、()内に何も書かれていないことからもわかるとおり、引数のない関数です。このような場合、以下のように

引数のない関数の他の記述例
void line(void);

のように、()の間にvoidをかくこともありますが、省略することも可能です。このケースでは、省略してる記述方法を採用しています。

また、戻り値がない関数は、returnを省略することができます。ここでは、line()関数でreturnが省略されています。show()関数では省略されていませんが、見てもわかるとおり、戻り値にあたるものが記述されていません。 通常、戻り値のない関数は、returnを省略するケースが多いのですが、何らかの理由で、途中で処理を抜ける場合などは、returnを使うと便利でしょう。

グローバル変数・ローカル変数

サンプルプログラム

次に、関数ごとの変数の有効範囲(スコープ)について説明しましょう。

list6-5:main.c
#include <stdio.h>

//	グローバル変数
int global = 10;

//プロトタイプ宣言
void func1(double,int);
void func2();
 
void main(){
	double a = 123.41;
	int b = 100;
	printf("main処理中¥n");
	printf("global=%d¥n",global);
	printf("a=%f b=%d¥n",a,b);
	printf("******************¥n");
	// func1を呼び出し
	func1(3.1,4);
	// func2を呼び出し
	func2();
}

//	func1
void func1(double a,int b)
{
	printf("func1処理中¥n");
	printf("global=%d¥n",global);
	printf("a=%f b=%d¥n",a,b);
	printf("******************¥n");
}
//	func2
void func2()
{
	double a = -4.1;
	int b = 2;
	printf("func2処理中¥n");
	printf("global=%d¥n",global);
	printf("a=%f b=%d¥n",a,b);
	printf("******************¥n");
}
実行結果
main処理中
global=10
a=123.410000 b=100
******************
func1処理中
global=10
a=3.10000 b=4
******************
func2処理中
global=10
a=-4.10000 b=2
******************

このプログラムでは、3種類の変数、a、bおよびglobalという3種類の変数が用いられています。プログラムをみると、a,bの値は、各変数で値が違いますが、globalは、全て同じ値が出ています。

ローカル変数とグローバル変数

a,bという変数は、それぞれ、main()、および、func1()、func2()の範囲内のみで有効です。このように、関数内で定義されている変数を、ローカル変数と言います。 ローカル変数は、その変数が定義されている関数内のみで有効です。func1()のように、関数の引数もこの範疇に入ります

それに対し、globalはプログラムの先頭で定義されており、どこで呼び出しても同じものを指します。このような変数を、グローバル変数と言います。

変数のスコープ

こういった、変数の有効範囲(スコープ)の違いは、定義されている場所に依存します。ローカル変数は、定義されている関数が違えば、同名のものを定義しても、 それぞれ別のものとして扱われます。それに対し、グローバル変数の場合は、プログラム全体でただ一つしかありません。したがって、名前の重複は許されません。(図6-3)

図6-3.関数と、変数のスコープ

変数のスコープとメモリに関しては、記憶クラスという概念があります。詳しくは、こちらを参照してください。

練習問題 : 問題6.