さまざまな関数

さまざまな関数


サイト全体の目次

稿


稿

戻り値の無い関数

このセクションのソース

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   void pow1(int m, int n);
/*06*/   int x, y;
/*07*/ 
/*08*/   printf("x の y 乗を求めます。: x y -> ");
/*09*/   scanf("%d%d", &x, &y);
/*10*/   pow1(x, y);
/*11*/ 
/*12*/   return 0;
/*13*/ }
/*14*/ 
/*15*/ void pow1(int m, int n)
/*16*/ {
/*17*/   int i, re = 1;
/*18*/ 
/*19*/   for(i = 0; i < n; i++){
/*20*/     re *= m;
/*21*/   }
/*22*/   printf("計算結果は %d です。\n", re);
/*23*/ }

(untitled)

この例の関数 pow1 は、前章の pow 関数の改良(というよりは改悪)版で、関数内で結果を表示するようにしたものです。しかし、この関数は引数に対応する計算結果を出力するだけで、戻り値がありません。プロトタイプ宣言、関数の実態部ともにvoidという型宣言がありますね。この void は空虚型というもので、関数の戻り値の型にこれを書くと戻り値無しという型宣言になります。そして、戻り値が無いので return 文もありません。しかし、関数の終了を明示しないと気持ち悪いという人は、return 文を書いてもかまいません。その場合、

return;

のように戻すべき値は書きません。また、この場合でも return 文が実行されるとその関数はすぐに正常終了され、つまりそれ以下の実行文は実行されずに呼び出したところへ処理を戻します。

引数の無い関数

このセクションのソース

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   int pow2(void);
/*06*/   int z;
/*07*/ 
/*08*/   z = pow2();
/*09*/   printf("計算結果は %d です。\n", z);
/*10*/ 
/*11*/   return 0;
/*12*/ }
/*13*/ 
/*14*/ int pow2(void)
/*15*/ {
/*16*/   int m, n, i, re = 1;
/*17*/ 
/*18*/   printf("x の y 乗を求めます。: x y -> ");
/*19*/   scanf("%d%d", &m, &n);
/*20*/   for(i = 0; i < n; i++){
/*21*/     re *= m;
/*22*/   }
/*23*/   return re;
/*24*/ }

(untitled)

この例の関数 pow2 は、やはり前章の pow 関数の改良(改悪)版で、関数内でデータの入力をおこなうようにしたものです。なので、引数はいりません。今度はプロトタイプ宣言、実態部ともに、引数の部分に void を書くことで「引数無し」という宣言になります。

また、計算結果も表示できるようにして、引数も、戻り値も持たない関数などを作ることもできます。

二つ以上の値を返す

このセクションのソース

次は今回のメインで、どうしても2つ以上の値を返したいという場合についてです。

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   void func(int x, int y, int *ap, int *sp);
/*06*/   /* void func(int, int, int *, int *); とかいても正しい */
/*07*/   int x, y, add, sub;
/*08*/ 
/*09*/   printf("x + y 及び x - y を求めます。: x y -> ");
/*10*/   scanf("%d%d", &x, &y);
/*11*/   func(x, y, &add, &sub);
/*12*/   printf("x + y は %d です。\n", add);
/*13*/   printf("x - y は %d です。\n", sub);
/*14*/ 
/*15*/   return 0;
/*16*/ }
/*17*/ 
/*18*/ void func(int x, int y, int *ap, int *sp)
/*19*/ {
/*20*/   *ap = x + y;
/*21*/   *sp = x - y;
/*22*/ }

(untitled)

2つ以上の値を返したい場合、その結果を直接戻り値として返すことは出来ません。そこで、結果を格納したい変数のアドレスを引数で渡してやり、呼び出した関数ではその指定されたアドレスに結果を格納してやるという事になります。それほど難しいことではありません。実際、これまで使ってきた scanf 関数は読み取った結果を戻り値で受け取るのではなく、代わりに指定されたアドレスへ文字列を代入していました。これと同じです。(ちなみに scanf は代入された項目数を返しています。)

まず、プロトタイプ宣言の引数部分を見てください。最初の2つは今までと同じ int 型で、ここには計算する値が入るようにします。そして、後ろの「int *」はここにくる引数が int 型の「アドレス」であることを示しています。そしてこの例の場合、実際に使う時はここに足し算の結果を格納する変数 add のアドレス及び、引き算の結果を格納する変数 sub のアドレスを書いておきます。確かにアドレスを示すよう「&」を付けて、「&add」「&sub」としてアドレスを書いていますね(もちろん何かアドレスの入ったポインタ変数を書いてもいいですが、その場合は当然「&」記号はいりません)。

では、実態部のほうに目を向けてみましょう。こちらの引数部分では、やはりプロトタイプ宣言に対応する変数を用意しておきます。確かに int 型の変数が2つと、int 型のポインタ変数が2つ宣言されていますね。さて、引数でこの関数に渡ってくるものは相変わらず、箱である変数そのものではなく、値だけです。ここで、アドレスとはつまるとこと整数値にすぎないのだという事を思い出してください。ということは、ポインタ変数「*ap」「*sp」には何が入りますか?もう分かりますね。*ap には add の、*sp には sub のアドレスが入ります。ですから、代入で「*ap = x + y」とやればポインタ ap の指す先、すなわち add に値が代入されるのです。

このようにアドレスとポインタの性質をうまく使うことで、擬似的に2つ以上の戻り値を返すことができます。特に「1文字」の集まりからなる「文字列」は、1つの戻り値で返すということができないのでこのような方法が多く使われます。実際に前章で例に挙げた文字列を入力する関数 mygets は、引数で文字列を格納する配列の先頭アドレス(配列の文字部分だけを書くとその先頭アドレスを示すのでしたね)を渡し、その「アドレスにもとづいて」1文字づつ代入をしていき、最後に文字列の終わり「'\0'」を代入するという作業をしています。ぜひ、前章に戻って確認してください。

これで、scanf や gets 関数では引数にアドレスを書かなければいけない理由がわかったでしょうか。

再帰呼び出し

このセクションのソース

次はほんの少し数学をやります。といっても、ここでやりたいことの本質は数学的なことではなく、ちょっと面白い関数の使い方で関数の再帰呼び出しといわれるものです。それについて理解するための例として、ある一つの数列を取り上げます。ではさっそく、

A(n) = A(n-1) + 3、A(1) = 10
->第 1 項から順に { 10, 13, 16, 19, 22, ... } となる。
/* 解説 */ 最初の項、即ち A(1) は 10 で、それ以降の項では「(前項) + 3」というのが
           その項の値になります。第 2 項の場合は、第 1 項 + 3 ですから、13 ですね。

という数列について考えてみましょう。プログラミングとしては、項番号 n を読み取ったらその項の値を求めるようにします。ただし、その項の値を求める部分は、項番号を引数に与えることにより値を計算して返す関数とします。つまり
int An(int n)

で引数 n に項番号を与えると、その値を計算して返すような関数を作るわけです。この場合、例えば引数に 3 を与えると 16 が返ってくればよいですね。

さて、数学的にこの数列を考えれば、

A(n) = (初項) + 3 * ((項番号) - 1) 即ち
A(n) = 10 + 3 * (n - 1)

と同じことですから、関数 An の実態部でそのように計算してやっても正解で、それが list-5.2.4 になります。
/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   int An(int n);
/*06*/   int n;
/*07*/ 
/*08*/   printf("計算したい項番号は?:");
/*09*/   scanf("%d", &n);
/*10*/   printf("%d です。\n", An(n));
/*11*/ 
/*12*/   return 0;
/*13*/ }
/*14*/ 
/*15*/ int An(int n)
/*16*/ {
/*17*/   return 10 + 3 * (n - 1);
/*18*/ }

このぐらいの数列ならば少し考えると計算式が分かりそうですね。しかし中には複雑で、容易に計算式が出せないような数列などもあります。そんな場合はどうしたらよいでしょうか?それが list-5.2.5 になります。

#include <stdio.h>

main()
{
	int An(int n);
	int n;

	printf("計算したい項番号は?:");
	scanf("%d", &n);
	printf("%d です。\n", An(n));

	return 0;
}

int An(int n)
{
	if(n <= 0){
		return 0;
	}else if(n == 1){
		return 10;
	}else{
		return An(n - 1) + 3;
	}
	/* ここまで処理は流れてこないはず */
	return 0;
}

こちらの関数 An は、最初にあげた数列の定義(A(n) = An(n - 1) + 3)そのままに作っています。つまり、第 n 項を求めるためには前項の値が必要なわけですから、その前項を求めるために関数が自分自身を An(n - 1) として呼び出すわけです。さらに、An(n - 1) として呼び出された関数では第 n - 1 項を計算するためにその前項、第 n - 2 項が必要なわけですから、また、An(n - 1) として(n は n -1 なので An(n - 2) を呼び出していることになります)自分自身を呼び出して計算させます。このように、関数が処理するのに必要なデータを得るために自分自身を呼び出すような関数を、再帰呼出関数と言います。ではこの例の関数ではいったいいつまで自分自身を呼び出すのでしょう?それはこの数列の定義に従って、再帰呼出をしなくても値が分かるようになるまでです。つまり、An(1) は再帰呼出をしなくても 10 だと分かります。そのことを if 文で制御しています。(n <= 0 の時はエラー処理として例外的に 0 を返している。)

補足

(untitled)

最後に用語の説明になりますが、関数がどのような働きをするか、すなわち実態部を書くことを関数を定義すると言います。ですから、今後は実態部と言わずに定義部と言うようにしましょう。また、定義部で関数名のあとの「 ( ) 」内に宣言された、値を格納するための変数を仮引数またはパラメータパラメータリストなどと言いこれに対し、実際に関数を呼び出す時に書く引数を実引数といいます。


読み込み中・・・
10秒待っても表示が変わらない場合、次の理由が考えられます。
・Javascript が無効になっています。
・検索エンジンのキャッシュを見ています。
サイトホームへ / 上位ページへ / ページトップへ / PAROFトレンドショッピングへ
Copyright (C) 2010 totobon all right reserved.