6 関数 6-1 関数とは少し長いプログラムを作るようになると 同じ処理を何度も行う場面が出てくる そのたびに処 理を書いていたのでは明らかに無駄であるし プログラム全体の見通しも悪くなる そこで登場す るのが 関数 である 関数を使うことを 関数を呼び出す ともいう どのように使うのか 実際に見てみよう 次のプログラムは 2 つの数字のうち 大きい方を求 めるものである int max(int a, int b) int m; if (a > b) m = a; else m = b; return m; int n1, n2, dai; printf( 整数を二つ入力してください n ); printf( 整数 1: ); scanf( %d, &n1); printf( 整数 2: ); scanf( %d, &n2); dai = max(n1, n2); printf( 大きい方の数字は %d です n, dai); #include の行と main の行にはさまれた のが関数部分である 最初の int は int 型の値を返しますよ と いう宣言 値を返さない場合もあるが それは後 ほど の名前とかぶっても構わない 別物として扱われる 次の max が関数の名前 関数名のあとの ( ) の中は 呼び出し元からこう いう変数を受け取ります という意味 これを 仮 引数 ( かりひきすう ) という 仮引数は ここで型 宣言する必要がある 仮引数のない関数もあるが それも後ほど 次の 内 ( ブロック内 ) が関数が行う処理の内容 である ここは main とほぼ同じであるが いく つか注意点がある 1 仮引数は型宣言済みと考えて良いので ブロッ ク内で改めて宣言する必要はない 2 関数で新たに決める変数の名前は 呼び出し元 3 値を返す場合は return に続けて返す値を明示する ここでは変数が返されているが 定数で も構わない この例を詳しく見てみよう 最初の行は この関数は max という名前で int 型の二つの値を 受け取り それを使って int 型の値を返します という意味になる int max (int a, int b) 返す値の型関数の名前 呼び出し元から受け取った値 次に a,b はすでに宣言済みなので 新たに必要な m だけ定義しておく m には a と b の内の 大きい方 ( 正確には a=b のときは b) が代入される 最後の return m; で m が返される つまりも大きい方の値が返される
では main の方では関数をどのように使っているのか 利用しているのは dai = max(n1, n2); の部分 関数呼び出し部と 関数本体の関係は次のようなイメージ max(n1, n2) で 関数 max に n1 と n2 を渡して処理してもらう この n1 と n2 を 実引数 ( じつひきすう ) と呼ぶ max 関数はそれを受け取り m を返す 言い換えれば m= max(n1, n2) とも言え すなわち dai =m( 関数 max 内での ) ここで見て欲しいのが main で関数に渡した変数の名前と 関数で受け取った関数の名前が違うということ 呼び出し元から関数に送るのは値そのものであり ( 現段階では ) 変数名は便宜上のものと考えていい もちろん同じでもいいが 同姓同名の別人だと思って欲しい 引数と仮引数の対応についての注意 引数と仮引数は 個数 並び順 型がすべて同じでなくてはならない 次に 戻り値と引数 ( 仮引数 ) に付いてみていこう 上の例では 引数があって戻り値もあったが 処理内容によってはどちらとも無くてもよい したがって 次の 4 通りが考えられる ちなみに 戻り値がないときの型宣言は void とし 引数がないときは ( ) 内をからにしておく 1 戻り値 引数ともにある type name( 仮引数 ) 2 戻り値のみある type name ( ) 3 引数はあるが 戻り値はない void name( 仮引数 ) 4 戻り値 引数ともに無い void name ( ) 1 はすでに登場したので 2~4 の実例を見てみよう /* 引数のない関数 ( 2 ) の例 */ #include <stdlib.h> #include <time.h> /* 1 から 100 までの乱数を発生して返す関数 */ int int_rand( ) return rand() % 100 + 1; int num; srand(time(null)); num = int_rand( ); printf ( 乱数 :%d n, num); 左のプログラムにある関数は 1~100 の整数の ( 疑 似 ) 乱数を発生させるものである 乱数を発生させるコマンドが rand( ) の部分で C 言語では一般的に 0~32767 までの整数を返す rand( ) を使用するためには stdlib.h が必要となる rand ( ) %100 で 100 で割った余りが求まるので 0 ~99 が得られる それに 1 を足せば 1~100 が得られ ることになるので それを返す main 内の srand(time(null)); は 乱数の種を 初期化するためのおまじないみたいなものだと思って 欲しい ここで time( ) を使っているので time.h が 必要となる 乱数を使うときの定番として覚えておく といい
/* 引数があって戻り値のない関数 ( 3 ) の例 */ /* 指定した文字を 指定回数繰り返し表示する関数 */ void show(char c, int n ) for (i = 0; i < n; i++) printf( %c, c); このプログラムは main で文字と数値を代入し それを関数に送って文字を指定回数表示させている 関数は表示を行うだけなので 戻り値はない int num; char moji; printf( 出力する文字 : ); scanf( %c, &moji); printf( 出力する回数 : ); scanf( %d, &num); show(moji, num); /* 引数も戻り値もない関数 ( 4 ) の例 */ /* 時間稼ぎの関数 ( 一定時間何もしない ) */ void timer( ) for (i = 0; i < 10000; i++) printf( r ); 最後は 引数も戻り値もない例 このプログラムを実行すると 明日は と表示した後 ちょっと間をおいて 晴れです と表示する 関数では 1 万回 r を表示しているが 見た目は何もしてないように見える 時間稼ぎなので printf は無くてもいいのだが 本当に何もしないと処理時間が短すぎるのでこうしてみた printf( 明日は n ); timer( ); printf( 晴れです n ); 6-2 実引数と仮引数の関係 void swap(int a, int b ) int t; t = a; a = b; b = t; int a = 5, b = 10; swap(a, b); printf( a=%d b=%d n, a, b); 先に 関数が受け取る仮引数は 値そのものと書いた 言い換えれば コピーを渡すと考えてもいい 次のプログラムは 関数で変数の値を交換しようとして作った 実行するとどうなるか 値は変化していないはずである というのも 関数にはコピーを送っただけで 本体の変数は何もいじっていないからである これを思った通りの動きをさせるためには ポインタの知識が必要となる
6-3 配列を引数にする配列を引数とするときは 次のような書き方をする int max(int ten[ ]) int max, i; max = ten[0]; for (i = 1; i < 5; i++) if (ten[i] > max) max = tem[i]; return max; int point[5] = 10, 90, 75, 45, 80; printf( 最高点は %d です n, max(point); 仮引数で 配列の宣言をする 1 次元配列の場 合は 要素数を省略できる 多次元配列でも 最 初の要素数だけは省略できるので int num[ ][3] のような書き方ができる 実引数は 1 次元でも多 次元でも 配列名だけを書けばよい void init(int ten[ ]) for (i = 1; i < 5; i++) ten[i] = 0; int point[5] = 10, 90, 75, 45, 80; init(point); for (i = 0; i < 5; i++) printf( point[%d] = %d, i, point[i]); 上の例では配列の値を利用するだけで 配列そのものへの変更を加えていない では 左のようなプログラムを実行したらどうなるか 下の実行結果が示すとおり すべて 0 になってしまう 実行結果 point[0] = 0 point[1] = 0 point[2] = 0 point[3] = 0 point[4] = 0 というのも 普通の変数の場合は値のコピーを渡していたが 配列の場合は 配列そのものを渡 しているようなものとなる ( 厳密には違うが これを正確に理解するためにはポインタの知識が必 要 ) したがって 引数として配列を使う場合には 十分注意が必要となる 6-4 関数プロトタイプ多くの入門書では 関数を使う場合 main の前に関数を置いていると思う 関数の置き方はもう 一つ main の後に置く方法もある この場合 関数プロトタイプといって 使用する関数の名前 戻り値 仮引数を main の前に宣言する必要がある 書式は次の通り int max (int a, int b); 返す値の型関数の名前 呼び出し元から受け取る値 関数の 1 行目と同じ書き方だが 最後に ; を付けるのを忘れないこと また 仮引数の名前 は 実際の関数で使う名前と違っても構わない 関数プロトタイプで分かり易い名前を使って 関 数の方では短い名前を使うというのも一つの手である
さて 関数を前に置くのと後に置くのでは どちらがよいのだろうか 小さなプログラムの場合は 前に置いた方が関数プロトタイプを記述しない分 楽ではあるが 正当とされているのは後に置く方法である 関数を前に置く方法だと 互いに呼び合う関数の記述ができない 呼ばれる関数を先に記述しなければならないため 関数のリスト上の出現順序が不自然になる 機能ごとに並べたり abc 順に並べたりできない 他人のプログラムと異なったスタイルになる といった不利が生じる また関数プロトタイプを記述することにより プログラムの先頭部分を見るだけで そのプログラムの中で使われている関数の一覧情報が分る というメリットがある
次のプログラムの 内を埋めよ 問題 6-1 /* 平均を求めて返す関数 */ 1 heikin( 2 kei, 3 num) return kei / num; int point[5] = 25, 50, 80, 10,100; double total = 0.0, ave; /* total: 合計 ave: 平均 */ for (i = 0; i < 10; i++) total += 4 ; ave = 5 ; /* ave に関数を使って平均を代入する */ printf( 合計 :%3.0f 平均 :%5.1f n, 6, 7 ); 次のプログラムは 配列要素内の数だけ指定した文字を表示するプログラムだが いくつか間違いがある その間違いを修正せよ 問題 6-2 int show(char ch, int a) int i, j; for (i = 0; i < 5; i++) printf( a[%d]:, i); for (j = 0; j < a[i]; j++) printf( %d, ch); int num[5] = 5, 4, 0, 3, 7; char moji = * ; Show(num, moji); 実行結果 a[0] :***** a[1] :**** a[2] : a[3] :*** a[4] :*******
< 解答 > 問題 6-1 /* 平均を求めて返す関数 */ 1 double heikin( 2 double kei, 3 int num) return kei / num; int point[5] = 25, 50, 80, 10,100; double total = 0.0, ave; /* total: 合計 ave: 平均 */ for (i = 0; i < 10; i++) total += 4 point[i] ; ave = 5 heikin(total, 5); /* ave に関数を使って平均を代入する */ printf( 合計 :%3.0f 平均 :%5.1f n, 6 total, 7 ave ); 問題 6-2 int show(char ch, int a[ ]) /* 配列を受け取るので 仮引数も配列 */ int i, j; for (i = 0; i < 5; i++) printf( a[%d]:, i); for (j = 0; j < a[i]; j++) printf( %c, ch); /* 文字を表示するので %c */ int num[5] = 5, 4, 0, 3, 7; char moji = * ; show(moji, num); /* 名前は 大文字小文字を区別 引数の並びが違う */