第四回(関数)


関数

関数とは今までに使用してきたprintfや、getcharのようなもののことです。これらは第一回で説明したとおり、 ヘッダファイル(#include<stdio.h>等)は便利な関数が使えるようになるものです。
ヘッダファイルの中を見てみると、関数の宣言のみがあり、その中の関数を使うとそれをコンパイラが自動的に呼び出してくれるようになっています。(さらに詳しく知りたい人は調べてみてください。) 関数は大規模なプログラムを書く場合、mainだけだと、非常に読みにくいので、分割して書いたほうが読みやすくわかりやすくなるために 使うものです。
関数は自分でも作ることが可能です。printfといつも書いていたこれも実はprintf関数と言う関数です。
ではまず、関数を使っていない例と使った例を見てみましょう。実行結果は両方一緒です。

関数を使っていない例
#include<stdio.h>
#include<stdlib.h>

int main(void);
int Sum(int x, int y);

int main(void){
	
	char number[256];
	int x, y, sum;
	
	printf("x = ?\n> ");
	fgets(number, 256, stdin);
	x = atoi(number);
	
	printf("y = ?\n> ");
	fgets(number, 256, stdin);
	y = atoi(number);
	
	sum = x + y;
	
	printf("%d + %d = %d\n", x, y, sum);
	
	return(0);
}
関数を使った例
#include<stdio.h>
#include<stdlib.h>

int main(void);
int Sum(int x, int y);

int main(void){
	
	char number[256];
	int x, y, sum;
	
	printf("x = ?\n> ");
	fgets(number, 256, stdin);
	x = atoi(number);
	
	printf("y = ?\n> ");
	fgets(number, 256, stdin);
	y = atoi(number);
	
	sum = Sum(x, y);
	
	printf("%d + %d = %d\n", x, y, sum);
	
	return(0);
}

int Sum(int x, int y){
	int sum;
	
	sum = x + y;
	
	return(sum);
}
実行結果
xに1、yに2と入力した場合、
x = ?
> 1
y = ?
> 2
1 + 2 = 3

では、ソースの関数に関する部分について説明します。

まず、int main(void);の下にあるint Sum(int x, int y);です。これは第1回のとき、プロトタイプ宣言と話したものです。

次にmain関数内にあるSum(x, y);が関数の呼び出しです。

そして、main関数の下のint Sum(int x, int y){から始まり}で終わる部分が関数の処理です。
説明だけではピンとこないと思うので、1つ1つ説明していきます。

引数

前々から少し話していましたが、引数(ひきすう)とは関数の中に渡す値を入れるもののことです。
例えば、atoi関数の場合、atoi(文字列)の文字列のところが引数です。また、getchar関数は引数がありません。

プロトタイプ宣言

書くものとしては返り値(戻り値、つまりreturnで戻る物の型)の型 関数名(仮引数の型 仮引数名)です。
プロトタイプ宣言は書かなくてもプログラムは動きますが、コンパイル時に引数のチェックを行うようにもなっていて、 バグを検出することができる仕組みになっているので、現在のC言語では標準的な書き方となっています。
この場合ではint Sum(int x, int y);と書いていますが、int Sum(int, int);と書くこともできます。しかし、 変数名もわかった方がいいので、あまり推奨されません。
また、引数がない場合は返り値の型 関数名(void);と書きます。

関数の呼び出し

呼び出し時に書くのは関数名(実引数)です。実引数については後述しますが、実引数の型は仮引数の型に合致するものを入れる必要があります。 (int型の変数の仮引数なら、int型の変数や、整数を入れる)

関数

関数自体の処理は返り値の型 関数名(仮引数の型 仮引数名){中の処理}と書きます。また、プロトタイプ宣言と同様、 引数がない場合は返り値の型 関数名(void){中の処理}と書きます。

実引数

実引数とは関数に渡してる値のことです。この場合、Sum(x, y);のxとyが実引数です。

仮引数

仮引数とは、実引数の値を渡された変数のことです。この場合、int Sum(int x, int y){のところにあるxとyです。
この変数は渡した順に代入されるので、int Sum(int y, int x){と宣言しても実引数のxの値が仮引数のyに入り、実引数のyの値が仮引数のxに入ります。 また、変数名は好きにしていいので、int Sum(int a, int b){と書いて変数の値を渡しても大丈夫です。

返り値(戻り値)

返り値(戻り値)は関数名のint Sum(int x, int y){の初めに書かれている型(この場合はint)で返ります。

関数内の変数

関数内の変数の変数名はmain関数や、他の関数にある変数の変数名と同じでも大丈夫です。 そして、関数内の変数は関数に入ってから、その関数がreturnする(一番下の処理まで行く)まで保存され、 終了すれば自然と値をなくしてメモリ領域が解放されます。これはmain関数の変数も同様で、これらをローカル変数と呼びます。

関数内の変数の初期化

関数内での初期化は関数に入ったときに行われます。2度関数に入った場合、毎回、変数に同じ値が初期化としてはいります。

関数内の処理

関数内では、関数内の変数宣言を行い、処理を行い、return(ない場合もあります)で終了します。 この場合だと、int sum;を宣言して、sumにxとyの和を代入し、その変数sumを返しています。

流れ

全体の流れです。まず、標準入力で文字列を取得し、数字に変換してx、yに代入しています。次にSum関数でxとyを渡し、 中の処理でxとyの和を返し、main関数の変数sumに代入しています。そして、xとyの和を表示して、終了しています。

同じ実行結果になるプログラムを別の書き方で書いてみました。

サンプルソース
#include<stdio.h>
#include<stdlib.h>

int main(void);
int Sum(int a, int b);

int main(void){
	
	char number[256];
	int x, y, sum_xy;
	
	printf("x = ?\n> ");
	fgets(number, 256, stdin);
	x = atoi(number);
	
	printf("y = ?\n> ");
	fgets(number, 256, stdin);
	y = atoi(number);
	
	sum_xy = Sum(x, y);
	
	printf("%d + %d = %d\n", x, y, sum_xy);
	
	return(0);
}

int Sum(int a, int b){
	int c;
	
	c = a + b;
	
	return(c);
}

2つのプログラムを見比べてみてください。
下にプログラムの流れを矢印で表現してみました。

また、返り値は演算しながらいれることも可能です。

サンプルソース
#include<stdio.h>
#include<stdlib.h>

int main(void);
int Sum(int a, int b);

int main(void){
	
	char number[256];
	int x, y, sum_xy;
	
	printf("x = ?\n> ");
	fgets(number, 256, stdin);
	x = atoi(number);
	
	printf("y = ?\n> ");
	fgets(number, 256, stdin);
	y = atoi(number);
	
	sum_xy = Sum(x, y);
	
	printf("%d + %d = %d\n", x, y, sum_xy);
	
	return(0);
}

int Sum(int a, int b){
	return(a + b);
}

グローバル変数

main関数や、普通の関数にある変数をローカル変数と呼びました。逆にグローバル変数というものもあります。 これは、どこででも変数名と変数の値の保持された変数です。大域変数とも呼びます。以下は実際の例です。

サンプルソース
#include<stdio.h>

int main(void);
void func(void);

int x = 0;

int main(void){
	
	printf("x = %d\n", x);
	x++;
	printf("x = %d\n", x);
	func();
	printf("x = %d\n", x);
	return(0);
}

void func(void){
	x++;
}
実行結果
x = 0
x = 1
x = 2

しかし、グローバル変数はプログラム全体の把握がしにくくなり、バグの修正や、プログラムを書き換えるのが困難になるので推奨されません。 使うのは稀にしてください。

スコープ(有効範囲)

スコープとは変数の有効範囲のことで、その有効範囲外の変数の値は有効ではありません。 以下のプログラムをCPadでコンパイルすると、

エラー E2451 a.c 12: 未定義のシンボル y(関数 main )
とエラーが出ます。

再帰呼び出し

再帰呼び出しとは関数が自分自身を呼び出すことを言います。
再帰関数では、どこかで再起呼び出しを終了させなければなりません。これを行わないと、無限ループができてしまいます。 以下は実際の再帰関数の例です。

サンプルソース
#include<stdio.h>

int main(void);
void Count(int num);

int main(void){
	
	Count(0);
	
	return(0);
}

void Count(int num){
	
	printf("[%d]\n", num);
	if(num < 10){
		Count(num + 1);
	}
	printf("{%d}\n", num);
	return;
}
実行結果
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
{10}
{9}
{8}
{7}
{6}
{5}
{4}
{3}
{2}
{1}
{0}

プリプロセッサ

プリプロセッサ(preprocessor)とはpre(前の)、process(処理)、or(するもの)という名前のとおり、コンパイルの前に行われる処理のことです。 いろいろな種類がありますが、特に使う2つを紹介します。

#include

#includeは既に何度か書いたことがあると思いますが、ヘッダファイルをインクルードするときに使うものです。これを書くことで、 ヘッダファイルの中で宣言されている関数を呼び出せるようになります。ヘッダファイルはたくさんありますが、 特に使われると思うものを以下に紹介します。

stdio.h

表示や標準入力等の基本的な処理に使う関数が含まれています。

stdlib.h

atoi関数や、メモリ領域の確保等の関数が含まれています。

string.h

文字列処理系の関数が含まれています。

ctype.h

文字の種類を判定したり、変更したりする関数が含まれています。

math.h

数学的な関数が含まれています。

#define

#defineは特定の文字を特定の文字に置き換えるもので、これをマクロ定義と言います。
書き方は以下の通りです。

#defineの書き方
#define 置き換え後の文字 置き換えたい文字

例えば、#define RCC 100と書けば、RCCが100と同等の意味合いになります。これは100という数字をたくさんの 場所で使っていて、全部9に変えたい時などに全部直すという手間がなく非常に便利です。以下はサンプルプログラムです。

サンプルソース
#include<stdio.h>
#include<stdlib.h>

#define MAX 256
#define NUM_MAX 1000

int main(void);
void multiplication(int num);

int main(void){
	
	char number[MAX];
	int num;
	
	fgets(number, MAX, stdin);
	num = atoi(number);
	
	multiplication(num);
	
	return(0);
}

void multiplication(int num){
	
	if(num > 0 && num <= NUM_MAX){
		printf("%d * %d = %d\n", num, num, num * num);
	}
	else if(num > NUM_MAX){
		printf("入力できる数は%d以下です。\n", NUM_MAX);
	}
	return;
}
実行結果
439
439 * 439 = 192721

コメントアウト

コメントアウトとはプログラムでは実行されないけど、関数の処理や関数の流れ等をメモしておきたいときに使うものです。 コメントアウトには2つ種類があります。
1つ目は//と書くもので、これを書いた右の文字はすべてプログラムには読まれなくなります。
2つ目は/* ○○ */と書くもので、○○の部分に好きに文字を書くことができます。こちらは/*で始まり、*/で終わるところまで 複数行でもプログラムに読ませないことができます。
例のプログラムを見てみましょう。

サンプルソース
#include<stdio.h>
#include<stdlib.h>

#define MAX 256
#define NUM_MAX 10

int main(void);
void mul(int num);

int main(void){
	
	char number[MAX];
	
	fgets(number, MAX, stdin);	//標準入力
	
	mul(atoi(number));
	/*かけ算を行う関数
	atoi関数を用いて返り値である文字列をint型の整数
	にしたものを関数に渡している。*/
	
	return(0);
}

void mul(int num){
	
	int i, j;	//forに使う変数
	
	if(num >= 1 && num <= 20){	//1~20の数字なら
		for(i = 1; i < NUM_MAX; i++){
			for(j = 1; j <= num; j++){
				printf("%4d", i * j);
			}
			printf("\n");
		}
	}
	else{
		printf("1~20の数字にしてください\n");
	}
	return;	//void型の関数なので、返り値はない。
}
実行結果1
15
   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
   2   4   6   8  10  12  14  16  18  20  22  24  26  28  30
   3   6   9  12  15  18  21  24  27  30  33  36  39  42  45
   4   8  12  16  20  24  28  32  36  40  44  48  52  56  60
   5  10  15  20  25  30  35  40  45  50  55  60  65  70  75
   6  12  18  24  30  36  42  48  54  60  66  72  78  84  90
   7  14  21  28  35  42  49  56  63  70  77  84  91  98 105
   8  16  24  32  40  48  56  64  72  80  88  96 104 112 120
   9  18  27  36  45  54  63  72  81  90  99 108 117 126 135
実行結果2
30
1~20の数字にしてください

基本問題

3つの数字を標準入力から取得して、その3つの数字を引数として渡してその中の一番大きい値を返すmymax関数を作りその値を標準出力 するプログラムを作成してください。

応用問題

標準入力から1つの数字を取得し、1からその数字までの総和を出力するプログラムを、再帰関数を用いて作成してください。 (数学の数列でやった 1 + 2 + 3 + … + kのことです。)

次回予告

次回は配列について説明します。

戻る