自作の関数

自作の関数


サイト全体の目次

稿


稿

gets 関数の真似

このセクションのソース

前章で文字に関する関数をいくつか紹介しましたが、 gets 関数は知らなくても同じ機能のプログラムを書くことができます。試しに getchar 関数を使って gets 関数と同じ機能を持つプログラムを書いてみましょう。(練習になるので、できたらリストを見ないで作ってみてください。)

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   char ch, str[32];
/*06*/   char *p = str;
/*07*/ 
/*08*/   while((ch = getchar()) != '\n'){
/*09*/     *p = ch;
/*10*/     p++;
/*11*/   }
/*12*/   *p = '\0';
/*13*/ 
/*14*/   printf("%s\n", str);
/*15*/ 
/*16*/   return 0;
/*17*/ }

(untitled)

どうですか?文字列を入れるための変数、str と、それに対応するポインタ変数 p を作ります。そして、while 文を使い、ポインタを移動させながら1文字づつ「’\n’」まで getchar で代入していくのです。

もし、gets 関数が無かったらこのようにして文字列を入力すればよいのですが、入力のたびにこんなことを書いていたら大変ですね。そこで、この機能を関数にしてしまうことができます。

gets 関数を作る

このセクションのソース

それでは先ほどの例から文字列を読み取る部分を抜き出して gets 関数の代わりとなる mygets を作ってみます。

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   void mygets(char *);
/*06*/   char str1[32], str2[32];
/*07*/ 
/*08*/   mygets(str1);
/*09*/   mygets(str2);
/*10*/ 
/*11*/   printf("str1 -> %s\n", str1);
/*12*/   printf("str2 -> %s\n", str2);
/*13*/ 
/*14*/   return 0;
/*15*/ }
/*16*/ 
/*17*/ void mygets(char *p)
/*18*/ {
/*19*/   int ch;
/*20*/ 
/*21*/   while((ch = getchar()) != '\n'){
/*22*/     *p = ch;
/*23*/     p++;
/*24*/   }
/*25*/   *p = '\0';
/*26*/ }

(untitled)

main 関数の中で mygets という関数を呼び出しています。この関数が自作の関数で、標準ライブラリの gets 関数と同等の機能を持たせています。使い方も gets 関数と同じに作ってあります。そして、mygets の実態は「void mygets(char *p)」以下にある部分で書かれています。

なお、一般に関数を書いて実行することを関数を呼び出すとか関数を call するという言い方をします。関数を書いたところまで処理が来ると、その実態を呼び出して処理させるように見える(実際にそう)からですね。

さて、関数の説明を行うにあたりイメージはつかんでもらえたでしょうか。具体的な説明をする上でこの例はちょっと大変なので、次はまずもう少し簡単な例から見ていくことにします。

べき乗を計算する関数

このセクションのソース

べき乗を計算する関数としては math ヘッダの中の pow 関数がありますが、整数だけを扱う pow 関数の簡易版を考えてみます。


今回作成した pow 関数は x と y を入力すると x の y 乗を求めるものです。なおここでは x も y も計算結果も int 型としました。計算結果が int 型の範囲を超えてしまうとおかしな値を返すことになるので、テストするときにあまり大きな値を入れないよう気をつけてください。

(untitled)

まず始めに思い出してもらいたいことがあります。それは main も関数だった、という事です。ただし、main 関数はC言語にとって特殊なものであり、プログラムを実行すると最も初めに main 関数が実行されるようになっているのです。いうなれば、OS が main 関数を呼び出しているのです。いずれにしても、関数を作る場合、main 関数と同じように「 ~ ( ) { } 」(~は関数名:変数名と同様、自由につけてかまいません)の形で書けばよさそうだということがわかります。

全体の概略を見てみましょう。最初に main 関数があって、その下に pow 関数の実態が書いてあります。さて、プログラムは main 関数から実行されるのですが、と同時にプログラムは上から順に実行されていくという大原則もあります。つまり、main 関数が始まった時にはまだ pow 関数がどんなものか読まれておらず、そのままでは何の効力も持ちません。そこで変数のように、その関数(ここでは main 関数)の先頭でどんな関数を使うのか宣言する必要があります。

int pow(int m, int n);

こうして、実態が下のほうに書かれている関数を使うために、それを宣言することをプロトタイプ宣言と言います。これに対して、自分で作った関数をそれよりも(ここでは main 関数)上に書いておくとプロトタイプ宣言なしに使うことができますが、C言語ではプロトタイプ宣言を「強く推奨」しています(なぜかは知りませんが)。

(untitled)

では、プロトタイプ宣言は少し置いといて関数の実態部分を説明します。

関数の書式を簡単に書くと

戻り値の型 関数名(引数1, 引数2, ...)
{
    /*実行文*/
}

となります。戻り値については以前にも説明しましたが、引数と共にもう一度確認しましょう。

一般に関数とは、何か値を与えられると、ただ1つの値を結果として返す(はきだす)ものです。これがC言語では引数を与えると、ただ1つの戻り値を結果として返すものということになります。(「何か」や「引数」は複数でもかまわないです。).

一般には:
[何か] ---> [関数(処理)] ---> [ただ1つの結果]

C言語では:
[引数] ---> [関数(処理)] ---> [ただ1つの戻り値]
	

では

戻り値の型 関数名(引数1, 引数2, ...)

の部分について見てみましょう。

まず「戻り値の型」ですが、C言語では返る値(戻り値)がどのような型の値なのか明示する必要があります。それが上に書いた「戻り値の型」で、例では「int」となっています。つまりこの関数は int 型の結果を返します。

そして関数名というのは見てのとおり、この例では pow です。変数名と同様の規則で好きな名前をつけてかまいません。 (*1)

注:math.h がインクルードされていると、pow は定義済みの名前になってしまうためエラーとなります。

次に引数の部分ですがここには、関数に渡ってくる引数を格納するための変数を宣言しておき、例のように必要に応じた分だけ用意することができます。逆に、この関数を呼び出す時は必ずここに書いた引数の分だけ、引数を渡してやらなければなりません。この pow 関数は、2つの引数を必要としているので、呼び出す時もきちんと対応するように2つ書かなければいけないという事です。

さて、プロトタイプ宣言はこの部分とまったく同じように、

int pow(int m, int n);

といった具合に書けばいいのですが、プロトタイプ宣言の引数部分で書いている変数は何の効力も持ちません。書いたとしても、そんな変数の領域はメモリ上のどこにも確保されませんから、メモ程度に思って好きなものを書いてかまいません。実態部分と異なる変数名を書いてもかまいませんし、たとえ
int pow(int x, int y);

のように書いても、その下に宣言してある変数 x、y には何の影響も与えません。いっそのこと
int pow(int, int);

のように書かなくてもかまわないのです。ただし、引数の「型(ここでは int)」は実態の部分と同じになるように必ず書いてください。

また、実態部分の後ろは main 関数同様「;」がないのに対し、プロトタイプ宣言には「;」を付けるのも忘れないで下さい。

もう1つ呼び出す側の注意なのですが、引数について気を付けて欲しいことがあります。

z = pow(x, y);

は変数「x」「y」という箱を渡しているわけではありません。「x」「y」に入っている「値だけ」を渡しているのです。ですから当然、渡した先の関数で「x = 10;」などとやっても呼び出した main 関数の x に大しては何もおきません。

これに関連して「スコープ」を思い出してください。「ある関数内で宣言された変数は、その関数内でしか使えない」ということに気をつけましょう。変数の有効範囲について復習しておくとよいでしょう。

main()
{
    int func1(int m);
    int func2(int n);
    int func3(int x);
    int x;

    /* 処理 */
}

int func1(int m)
{
    x = 10;    /*これはダメ。他の関数内の変数は操作できない*/

    /* 処理 */
}

int func2(int n)
{
    int x;    /*この x は main、func3 関数の x とは無関係*/

    /* 処理 */
}

int func3(int x){    /*この x は main、func2 関数の x とは無関係*/
    /* 処理 */
}

再び実態部分の説明に戻ります。最初の引数部分で宣言した変数ですがこの変数は、その関数の中で普通に宣言された変数として自由に使えます。唯一違うのは、渡された値が最初から入っているということです。また、これ以外の変数を使いたい時は main 関数のときと同様に宣言が必要となります。

以上のことをふまえて関数の中に処理を書く(for 文、printf 関数など、main 関数に記述してきたのと同様の命令が書けます)わけですが具体的には、戻り値にするための値を1つ求めておくことになります。この例では「re」がそれになります。

最後に、戻すべき値が求まったら return 文で値を返し、関数を終了します。

return 戻す値;

これで指定された値が(この例では re の値が)戻り値として返ります。ただし、この戻り値の型は当然、先に指定した戻り値の型(ここでは int)と一致していなければなりません。

return 文が実行されるとその関数は、指定された値を呼び出し側に返すと共に、関数自体の処理も正常終了させて、処理の流れをもとの場所(呼び出したところ)に戻します。簡単に言えば、return 文が関数終了の合図となり、それ以下の命令は実行されません。ですから、複数の値を返そうと思って return 文をいくつか書いても、最初の return 文が実行されれば関数はそこで正常終了し、それ以下の実行文及び return 文はまったく無意味なものになってしまいます(if 文などでどの return 文を実行するか制御することは可能です)。逆にいえば、if 文等で条件分岐した際に「もう関数の処理を終了したい」という場合は、goto 文などで関数の最後まで処理を飛ばさなくても、その場で return して構わないわけです。このことは main 関数でもいえることです。

次に、関数を呼び出した側についてです。以前にも書きましたが、関数を呼び出して戻り値がある場合、呼び出した部分にはあたかもその戻り値の値がそこに記述されているかのように処理されます。例では

z = pow(x, y);

となっていますが、「z」には関数 pow の結果(戻り値)の値が代入されます。例えば、pow 関数から return 文で「10」という値が返ってきたとすれば、それは
z = 10;

と同じことだという事です。もちろん以下のような記述が正しいことも、以前書いたとおりです。
printf("計算結果は %d です。\n", pow(x, y));

ちなみに、戻り値は無視してもかまいません。つまり、関数は呼び出したけどその戻り値は必要ないという場合、代入などをせずにほおっておいても,、何も害はないという事です。実際、printf 関数は「出力した文字数」を戻り値で返していますが、普通は無視しています。試しに、次のような実行文を行なってみてください。

n = printf("abcde\n");    /*「\n」 も1文字*/
printf("%d\n", n);

実は printf 関数が値を返していたのだということがわかります。

また、戻り値を型キャストすることもできます。例えば pow 関数は int 型の値を返しますが、z が float 型の変数だったら

z = (float)pow(x, y);

のように型キャストして代入するとよいでしょう。

最後に補足説明になりますが、関数はいくつでもつくることができ、きちんと関数内でプロトタイプ宣言しておけばその中で別の関数を使うこともできます。しかし、関数の中で関数を使うような場合、きちんとした階層構造を意識して作るなどしないと、goto 文のように複雑になり、また、関数の独立性も失われてしまうでしょう。ですから自分で関数を作る場合は各関数の関係が簡潔になるよう、分かりやすくする工夫が必要でしょう。


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