『楽しく!』『できる!!』C講座のページ


第六回:関数ってなに?引数?戻り値??

 さて突然ですが、関数という言葉を聞いたことがあるでしょうか?高校生以上の方は「三角関数」というものが数学で出てきたかもしれません。
 簡単にいうとC言語での関数とは「何かの値を受け取って処理するもの」の事です。
 変数とは「入れ物」だ。という風に説明しましたが、関数は「変数に入った値を材料にして、ものを作り出す機械」と考えれば良いでしょう。
 通常、この機械(関数)は次のように書かれます。

    f(x)

 ここでのxは材料(変数)であり、fは機械(関数)の名前です。
 例えば、材料(変数)に1を加える機械(関数)を考えましょう。名前は自由に決められるのでplusとしましょう。
 またここで、せっかく1を加えたのですから、その処理の結果(x+1)をyという入れ物(変数)出力することにしましょう。
 すると次のように書くことができます。

    y=plus(x);

 こうすれば、plusという機械(関数)はxという材料(変数)を取りこんで、1を加えた結果をyという入れ物(変数)に代入することになります。
 ここで関数(機械)の材料として用いられる変数のことを『引数(ひきすう)』と呼び、関数が引数を用いる事を『引数を取る(もつ)』といいます。
 つまりこの例ではxが引数となります。
 また、関数が処理の結果を出力するとき、『関数が値を返す』といい、その値を『戻り値(返り値)』と呼びます。

 整理しましょう。

@ 関数の名前は、自分で決定できる。
A 関数は変数を材料を使う。その材料を引数という。
B 関数が引数を使う事を、引数を取る(もつ)という。
C 関数は引数をもとにして、加工し、その結果を出力する。
D 関数が加工した結果を出力する事を、関数が値を返すという。
E 関数が返す値を、戻り値(返り値)という。

 例えばさきの関数plusに対して、

 int x,y;

 x=1;


 y=plus(x); 

 というようにプログラム中で書かれたならば、plusはxに1を加えた値を返し、yには2が代入されるのです。
 また、関数は変数同様、宣言後に使用することになります。書式は

  型名 関数名(引数1, 引数2, …) 
  {

動作内容

return 変数名;
  }

です。
 最初の型名で、その関数の戻り値の型を宣言します。
 引数はコンマで区切る事で、並べることが出来ます。
 中カッコで動作内容を囲みます。
 戻り値はreturn文と呼ばれる

 
 return 変数名; 
 

の書式により、値を返します。

 つまり、先ほどの関数plusの定義は

 int plus( int x ) 
 {
  int r;

  r = x+1;

  return r;
 }

 のようになるでしょう。
こうすれば、「引数を整数型xとして取りこみ、その値に1を加えた値をrに代入し、そのrを整数として返す。」ということになります。
 口で説明していても分かりづらいでしょうから、プログラミングしながら見ていく事にしましょう。
 2つの引数の合計値を返す関数addを作り、使用したソースです。打ちこむ前に、ソースの下にある説明を読んでください。

 #include <stdio.h>

 int add(int a, int b) // 関数addの宣言、引数はa,b、戻り値の型はint
 {
  int  n;       // 関数内でのみ使われる変数

  n = a + b; // 関数の動作内容

  return n; // 戻り値は return文 で返す
 }      // ここまでが関数 add

 void main(void)  // ここからがメインルーチン
 {
  int x=1, y=2;
  int ans;

  ans = add(x, y);      // 関数add の呼び出し、戻り値をansに確保

  printf("%d",ans);     // 確保した ans を出力
 }



 関数を使ったプログラム。ということですが、一目見て分かる通り、メイン文が一番最初にきていません。これは、メイン文のなかで関数addを使用するため、それ以前に関数を定義しておかなければいけない為です。変数を使う前に宣言しておかなければならないのと同じですね。このため、まずaddの定義が並んでいます。
 addの説明をしましょう。

  int add( int a, int b )  

は、int型の値を返す関数addは、2つのint型の変数a,bを引数にとる。という意味でした。
そして

 {
   int n;     // 関数内でのみ使われる変数 

   n = a + b;   // 関数の動作内容

   return n;    // 戻り値は return文 で返す 
 }          // ここまでが関数 add

は、コメント文の説明のままですね。
 ここまでで、関数addを使用する為の下準備が出来ました。
 そして、関数addを使用しているのは、ココです。

  ans = add(x, y);  

 int型の変数x,yを引数として与え、その戻り値をansに代入しています。

 それでは、自分で動作を確認してみてください。答えはx+yつまり3と表示されたはずです。

 こんなに単純な足し算を関数にすると、余計ソースがややこしくなったような気がします。
 しかし、if文やswitch文での判定を関数にまとめればどうでしょう?例えば今まで作ってきた「じゃんけんゲーム」の勝敗判定や、コンピュータの手を決める動作を関数にすれば、メイン文を

 void main( void )
  {
    char player, com;
    scanf("%c" &player); 

    com=computer();   /* コンピュータの手を決定する関数の 
                    返り値を変数comに代入      */
    hantei(player,com); /* プレイヤーの手とコンピュータの手を引数に 
                   勝敗を判定する                 */ 
  }

 というたった4行にまとめることもできるのです。

 どうです?あのだらだらとしたソースがたったの4行です。関数を使えばソースが煩雑になる事を防ぐことができます。

 しかし、上のように関数の定義をだらだらと書いていては、関数が3つ、4つ、と増えてくれば、ますますソースは煩雑になるでしょう。
ためしに、3つ関数を使用する場合を考えましょう。


 #include <stdio.h>  

 int add(int a, int b)
 {
   int n;

   n = a + b;

   return n;
 }

 int sub(int c, int d)
 {
  int n;

  n = c - d;

  return n;
 }

 int mul(int e, int f)
 {
  int n;

  n = e * f;

  return n;
 }


 void main(void)
 {
  int x=1, y=2;
  int ans, ans2, ans3;

  ans = add(x, y);
  ans2 = sub(x, y);
  ans3 = mul(x, y);

  printf("%d\n",ans);
  printf("%d\n",ans2);
  printf("%d\n",ans3);
 }


 一番大切な(?)メイン文がいつ始まるんだ?って感じですね。では、これならどうでしょう。


 #include <stdio.h>

 int add(int a, int b);
 int sub(int c, int d);
 int mul(int e, int f);

 void main(void)
 {
   int x=1, y=2;
   int ans, ans2, ans3;

   ans = add(x, y);
   ans2 = sub(x, y);
   ans3 = mul(x, y);

   printf("%d\n",ans);
   printf("%d\n",ans2); 
   printf("%d\n",ans3); 
 }


 int add(int a, int b)
 {
   int n;

   n = a + b;

   return n;
 }

 int sub(int c, int d)
 {
   int n;

   n = c - d;

   return n;
 }

 int mul(int e, int f)
 {
   int n;

   n = e * f;

   return n;
 }

 この場合、ほとんどメイン文の位置が明確で、関数の定義も後にまとめられている為読みやすいですね。
 このようにメイン文より前に、関数名と引数、戻り値の型を宣言しておき、後ろにその動作を定義することができます。
 このとき、メイン文より前の宣言、ここでは

 int add(int a, int b);
 int sub(int c, int d);
 int mul(int e, int f); 

 ですね。これらをプロトタイプ宣言と呼びます。
プロトタイプ宣言を使用する事で、ソースが関数の定義で煩雑になることを防ぐ事ができます。大掛かりなプログラムを書くと、2桁ぐらいの関数が出てくる事はざらなので、この方法は重要です。(もっともそこまで多い場合はヘッダファイルにまとめるのですが…。)

 さて、話しが広がり過ぎたかもしれません。もう一度整理しましょう。

F 関数は使用する前に宣言する必要がある。
G そこでは「型名」「関数名」「引数の型」「引数の名前と数」を宣言する。
H 関数の宣言をメイン文の前に書くとソースが煩雑になる可能性が大きいので、
 関数のプロトタイプ宣言を利用し、関数の動作の定義はメイン文の後ろに書く。

 つまり、最初のソースの、よりよい形はこうなります。


 #include <stdio.h>

 int add(int a, int b);    // プロトタイプ宣言

 void main(void)       // ここからがメインルーチン
 {
   int x=1, y=2;
   int ans;

   ans = add(x, y);     // 関数add の呼び出し、戻り値をansに確保 

   printf("%d",ans);    // 確保した ans を出力
 }


 int add(int a, int b)    // 実際の動作の定義
 {
   int n;

   n = a + b;

   return n;
 }



と書けば、関数が増えてもソースが煩雑になるのを防ぐことができるわけです。

 突然『関数』と言う言葉がでてきて、とまどってしまうかもしれませんが、なにも新しいことではないのです。実際にこの講座の中で今までになんども関数を使用しています。そのひとつはprintfです。
 さっき関数はf(x)の形で書かれるといいました。printfの書式を思い出してください。

printf("文字列");

 でしたね。そうです。これはprintfという名前の関数だったのです。いままでの例では 数値を引数にとる場合 を紹介しましたが、 引数は、変数・実際の値・文字・文字列…その関数が対応しているものであれば、なんでも良いのです。
 さらにprintfは実はこのようにも書くことができます。

x = printf("あいうえお");

 こうすれば、xには(文字数+1)の値が入るのです。(なぜ文字数+1になるのかは追々説明します。)
 つまり、「printfは文字列を引数に取り、その文字列の数(+1)を返す関数」と言えます。
 しかし、実際は上のような書き方をせず、『戻り値を無視』して使っています。
 このことからも分かるように 『戻り値は無視することができる値』で、値を返すからといってその値を使用しなくともかまいません。

 また、いままで一度も漏らさずに使用してきた main(){} でさえ、関数なのです。
「おまじない」「おまじない」といってきた

void main(void)

voidとは、"無""からっぽ"の意味で、この場合「なにも値を返さないし」「なにも引数を取らない」ということなのです。
 ではこのメイン関数が値を取ったり、返したりすることがあるか?と言うことになりますが、あるのです。
 メイン関数は、2つの引数を持つ事ができるのですが、今は持つことがある。とだけ頭の隅に置いておいてください。
 戻り値に関しては、メイン関数の最後にreturn文を加えることで終了状態を判定できます。

  return 0;   // 正常終了 
  return -1;  // 異常終了

 などと書き、if文などと組み合わせて使用します。
 また話が広がってしまいました。
もう一度整理しましょう。

I printfは実は「文字数+1を戻り値とし」「文字列を引数にもつ」「関数」だった。
J main()すら関数だった。
K main関数は2つの引数を実は持つことができる。
L main関数内の最後にreturn文をつけることで終了状態を判定できる。


さて、いままでに出てきた重点をもう一度並べてみましょう。


@ 関数の名前は、自分で決定できる。
A 関数は変数を材料を使う。その材料を引数という。
B 関数が引数を使う事を、引数を取る(もつ)という。
C 関数は引数をもとにして、加工し、その結果を出力する。
D 関数が加工した結果を出力する事を、関数が値を返すという。
E 関数が返す値を、戻り値(返り値)という。
F 関数は使用する前に宣言する必要がある。
G そこでは「型名」「関数名」「引数の型」「引数の名前と数」を宣言する。
H 関数の宣言をメイン文の前に書くとソースが煩雑になる可能性が大きいので、 
 関数のプロトタイプ宣言を利用し、関数の動作の定義はメイン文の後ろに書く。
I printfは実は「文字数+1を戻り値とし」「文字列を引数にもつ」「関数」だった。
J main()すら関数だった。
K main関数は2つの引数を実は持つことができる。
L main関数内の最後にreturn文をつけることで終了状態を判定できる。

これが今回のポイントです。
うーん。なんて内容の濃い講座なのでしょう(苦笑)
I以降は豆知識程度で良いと思います。それより前の項目は重要ですから、暗記しないまでも理解したほうが良いと思います。
 自分で書いていても分かるのですが、このあたりから急激に難易度が上がり、挫折する人も増えてくるようです。分からない事を放置しても、連鎖反応で分からない事を生み出すだけなので、掲示板またはメールでどんどん質問して下さい。僕の答えられる範囲で答えますので。
 ただ、 今回の講座の内容は”一度で覚えることではなく”『後からついてくる事』の部類に含まれるものです。ですから、今回の講座をすべて覚えようとすることなく、頭の片隅において置く。ぐらいに考えるのがちょうど良いのかとおもいます。

前回の課題の解答

前回の課題は、自分で作ったプログラムをswitch文に置き換えよ。というものでしたが、どうだったでしょうか?
分からない点があれば、質問して下さい。

今回の課題

 今回の課題は、ジャンケンプログラムを関数で…といいたいところなのですが、第七回のソースが関数を用いないものになっているため、今回の課題はなし。とします。