Top >C言語入門 とりあえずのC言語

9. 関数を作る

関数を作る
・*:..☆ プロローグ ☆..:*・

 それほど昔ではない頃のことですある初心者のプログラマーが、湖のそばでプログラムを作っていましたところが手が滑って作っていた関数を湖に落としてしまったのですプログラマーは困ってシクシクと泣きましたすると湖の中から神さまが出て来てきました神さまはピカピカに光る金の関数を持っていました

「お前が落としたのは、この金の関数か?」

「いいえ。わたしが落としたのは、そんなに立派な関数ではありません。」

 神さまは、次に銀の関数を出しました

「では、お前が落としたのは、この銀の関数か?」

「いいえ。そんなに整った関数でもありません。」

「では、お前が落としたのは、この関数か?」

 神さまが三番目に見せたのは、ちょっと出来の悪い関数でした

「そうです、そうです。拾って下さってありがとうございます。」

「そうか、そうか。お前は正直なプログラマーだな。」

 神さまは感心して、金の関数も銀の関数も男にくれました喜んだ男は、この事を友だちのプログラマーに話しました友だちはうらやましがって、おれも金の関数をもらおう、と、湖へ出かけましたそして、わざと出来の悪い関数を湖に投げ、うそ泣きを始めましたすると、神さまが出て来て金の関数を見せました

「お前が落としたのは、この金の関数か?」

「そうです。金の関数です。うっかり湖に落としてしまったんです。」

 それを聞くと神さまは苦笑いをして、こう言いました

「このうそつきの欲張り者め。でも、まあ、出来の悪い関数を使うくらいなら誰かの力をかりた方が良い。プログラミングとはそういうものさ。」

 神さまは男に金の関数を渡すと、湖の中に戻って行きました

 最初にも述べましたが、C言語は、関数によって全体が構成されるプログラム言語ですそして、C言語のプログラムを作るという事は、関数を作るという事です

 

 一般的に、1つの関数は、それほど大きなものにはしませんコードが 1000 行にもなる関数は、かなり大きな関数であると言えるでしょう作成するプログラムの大きさに比例して、関数が大きくなるわけではなく、関数の数が増えていくのが普通です

 

 関数の設計は非常に重要で、どのような機能の関数を作るかによって、開発効率、実行効率、メンテナンスの良し悪しが変わります

 

 ここでは、自分で関数を定義し、利用する方法について学習します

9.1. 関数の定義

関数の定義

 関数の定義は、次のように記述します

戻り値型 関数名( 引数リスト )
{
    処理
}
  • 戻り値型
    関数は、関数の呼び出し側に対し、1つの値を返すことが出来ますこれを戻り値と呼び、ここでは、その値のデータ型を指定します
  • 関数名
    関数を識別するための名称です
  • 引数リスト
    関数に渡されるデータが何であるかを定義します
  • 処理
    変数宣言や文を記述します

return 文と戻り値

return 文と戻り値

 return 文は次のように記述します

return;

 または、

return 式;

 return 文は、次の2つの機能を持っています

  • 関数の処理を終了し、呼び出し側に戻る
  • 戻り値を与える

 return 文が実行されると、それ以降に記述されている処理を実行せず、ただちに呼び出し側に戻りますその際、return の右側に記述されている式の値が戻り値となりますこの値のデータ型は、定義した戻り値型と一致していなければなりません

 関数が return 文を含まない場合には、最後の文が実行された時点で呼び出し側に戻ります

 

 戻り値型の指定は、省略してもかまいません省略した場合には int であると見なされますしかし、出来るだけ省略はせず、何らかのデータ型を指定した方が良いでしょう

 

 戻り値の無い関数を作成する場合には、void を指定しますその場合は、呼び出し側で戻り値を参照することはできませんまた、return の右側には式を記述しません

return文の使用例:
int sum( int x, int y )
{
    return x + y;
}

void main()
{
    printf( "12と16の合計は%dです\n", sum( 12, 16 ) );
}

引数リスト

引数リスト

 呼び出し側から関数に渡されるデータを、仮引数、または単に引数、あるいは、パラメータと呼び、その定義を引数リストとして記述します

 引数の定義は、変数の宣言と同じように、データ型と引数名から成ります2つ以上の場合にはカンマ,で区切って記述します

例:
 ( int x, int y, char ch, double d )

 関数の呼び出し側では、引数リストに合わせて値を渡さなければなりません渡される順番は、記述した順番と同じになります

void myfunc( int x, int y, char c, double d )
{
    printf( "x=%d, y=%d, c=%c, d=%f\n", x, y, c, d );
}

void main( void )
{
    myfunc( 7, 13, 'P', 3.14 );
}

 呼び出し側から関数に渡す値を、実引数、または単に引数と呼びます

 

 引数が必要の無い関数の引数リストは、空にするか void と記入します

9.2. 戻り値

戻り値

 戻り値のある関数の呼び出しでは、関数呼び出しの記述、つまり、関数名から小カッコ ( ) までが、戻り値になると考えてください

 int 型の関数であれば、その関数の呼び出しは、int 型の値と同じ意味を持ちます

 

 func_a, func_b, , , , の戻り値が int 型であるとすれば、次のようなコードを書くことが出来ます

int     i;
int     x;
int     a[10];

for( i = func_a() ; i < func_b() ; i += func_c() )
    printf( "%d, %x", func_d(), func_e() );

switch( func_f() )
{
    case 3:
        x = func_g();
        break;
    case 4:
        x = func_h();
        break;
}

while( func_i() )
    a[ func_j() ] = x;

 

 戻り値のある関数を呼び出すのであっても、必ず戻り値を利用しなければならないわけではありません戻り値を利用するかどうかは、呼び出し側にまかされます

 printf 関数は出力した文字数が戻り値になっていますが、あまり利用される事はありませんそれは、単に文字列を出力するという目的で使用されることが多いからですが、出力された文字数を知りたいのであれば、戻り値から得られます

 

 以下の例では、printf 関数の戻り値を使用しています

void main( void )
{
    printf("は、%d文字です\n", printf("Hello World"));
}

ポインタの戻り値

ポインタの戻り値

 ポインタを戻り値にする場合には、注意しなければなりません戻り値のポインタが、関数を終了した後でも有効でなければならないからです

 

 以下の関数は問題なく使用できます

int* larger( int* p1, int* p2 )
{
    if( *p1 > *p2 )
        return p1;
    else
        return p2;
}

 戻り値のポインタは、引数として渡されたものであり、関数が終了した後でも有効です

 

 以下の関数は障害を引き起こします

int* sum( int n1, int n2 )
{
    int a = n1 + n2;

    return &a;
}

 戻り値のポインタは、関数内の変数 a のアドレスなので、関数が終了した後では使用できません

 関数の呼び出し側で、戻り値のポインタを使用しても正しい結果は得られません

9.3. 引数

引数

 引数は、呼び出し側の実体ではなく、値のコピーが渡されますこの方法を値渡しと呼びます

 

 呼び出し側に変数 x があり、その値が 6 であったとしましょうある関数に x を引数として渡した場合、関数側に渡されるのは、変数 x ではなく、値が 6 である x のコピーなのです

 

 これは、次のようなコードで確認できます

void myfunc( int x )
{
    x = 7;            // xに7を代入する
}

void main( void )
{
    int     x = 6;    // x を6とする

    mufunc( x );

    printf( "x = %d\n", x );
}

 ここで、main で使用している変数名も、myfunc の引数名も、両方とも x としていますが、ここでの名前に関連はありませんx と y としても同じです

 

 変数 x は、初期値を 6 としていますその後、関数 myfunc を呼び出していますmyfunc では、引数 x に 7 を代入します

 

 myfunc を呼び出した後で、x の値を表示します結果はx = 6と表示されます

 これは、myfunc の引数 x が、main の変数 x そのものではなく、x の値をコピーしたものであるからです

ポインタの引数

ポインタの引数

 引数には、ポインタ型を用いることも出来ます

 ポインタ型の引数を使用する理由は、主に次の2つです

  • 配列を渡す
  • 呼び出し側の変数の値を変える

 配列はポインタとして表されているので、関数に配列を渡すのであれば、ポインタ型が引数になります

 また、間接演算子*を使用することによって、ポインタが指し示す先にアクセスできるので、呼び出し側の変数の値を変えることができます

 

 仮引数の定義では、ポインタ型を使用して int* pvalue のように記述するのですが、配列とポインタの関係から、次のようにも記述できます

int pvalue[]

 これは、記述方法が異なるだけでプログラムとしての違いはありません

例:配列を渡す場合
void myprint( char* ptext )
{
    printf( "%s\n", ptext );
}

void main( void )
{
    char    hello[] = "Hello World";

    myprint( hello );
}
例:呼び出し側の変数の値を変える
void pow2( int* x )
{
    *x = *x * *x;
}

void main( void )
{
    int     x = 3;

    pow2( &x );

    printf( "x = %d\n", x );
}

9.4. 再帰

再帰

 関数は、その関数の処理の中で、自分自身を呼び出すことが出来ます

 

 以下は、1 から、指定された 1 以上の整数までの合計を求める関数です

int nsum( int n )
{
    if( n > 0 )
        return nsum( n - 1 ) + n;
    else
        return 0;
}

void main( void )
{
    printf( "sum = %d\n", nsum( 10 ) );
}

 関数 nsum は、引数 n が 0 より大きければ、nsum( n - 1 ) の戻り値に n を加えた値を返しますnsum( n - 1 ) は、1 から n - 1 までの合計ですから、それに n を加えれば 1 から n までの合計が得られます

 引数 n が 0 以下の場合には、nsum を呼び出さずに 0 を返します

 

 関数を再帰的に使用する場合には、永遠に自分自身を呼び出すことが無いよう、注意しなければなりません

PAPER BOWL
NEZEN