C言語編 第9章 関数

先頭へ戻る

この章の概要

この章の概要です。

関数とは

これまでに、main関数、puts関数、fgets関数といった「関数」を使ってきました。 あまり深入りせずに進めてきましたが、この章と次の章を使って、もう少し詳細に説明をしていきます。

関数は、「何らかの処理を行うために必要な命令文を書き並べたもの」といえます。 例えば、puts関数なら「文字列を標準出力へ出力するために必要な命令文を書き並べたもの」です。

C言語は、複数の関数を組み合わせていくことでプログラムを組み上げていきます。

ところで、これまで関数を「使う」と表記してきましたが、より正確には、 (関数を)呼び出すといいます。

関数には、「関数名」「引数」「戻り値」という3つの構成要素があります。

関数名は、関数に付けられた名前です。 例えば、main関数なら「main」が名前になります。 関数名として有効な名前は、変数名の規則と同様です(第3章参照)。

引数(「ひきすう」と読みます)は、関数を呼び出すときに、その関数に指定する情報のことです。 引数を指定することを一般に、引数を渡すと表現します。 例えば、puts関数を「puts( "Hello, World" );」のように呼び出すとすると、 ( ) の内側に記述したものが引数です。 この場合なら、"Hello, World" という引数を、puts関数に渡していることになります。
なお、後で取り上げますが、引数には、仮引数と実引数という用語の区別があります。

引数には、型があります。 そのため、渡す情報は、その型に合わせなければなりません。 例えば、puts関数の引数は文字列型なので、文字列しか渡すことができません。

戻り値(返却値)は、関数内の処理が終わったとき、関数側から返される情報です。 返し値返り値と呼ぶ人もいます(返り値はちょっと物騒な名前ですが…)。 実は、main関数の終わりに書いていた return文は、戻り値を返すための命令文です。
戻り値にも型があります。

引数という仕組みがあるので、関数が仕事をこなすために必要な追加情報を、関数を呼び出す側から与えることができます。 逆に、関数が行った仕事の結果は、戻り値の仕組みによって、呼び出した側に返すことができます。
引数にしても戻り値にしても、それが必要無いのであれば「無し」であることもあります。 引数や戻り値が無いことは、void という型を使って表現します。 これまでの章で、main関数の引数は「void」でした。

main関数

C言語のプログラムは main関数から始まると決められているため、 main関数は必ず記述しなければなりません

main関数の引数と戻り値にはルールがあります。 基本的に、以下のいずれかを選択して使うことになります。

int main(void) { /*省略*/ }
int main(int argc, char** argv) { /*省略*/ }

2つ目の方は現段階では説明しません(第43章で説明します)。

これら以外に、3個目以降の引数が使えるコンパイラも存在しますが、 どのコンパイラでも使えるというものではありません。

戻り値の型が int なので、main関数は、何らかの戻り値を返すように実装します。 戻り値を返すために使う命令が、return です。 これまでの章の例で「return 0;」と記述していたのは、0 という戻り値を返すことを意味しています。
main関数の戻り値を 0 にすると、問題無く正常に処理が終了したことを表し、 0以外を返すと、何らかのエラーが発生して終了したことを表すことになっています

main関数の戻り値の型が void になっているプログラムを見かけることがありますが、これもコンパイラ依存の方法です。

main関数の戻り値の型を省略しているプログラムも見かけることがあります。 この場合、C95以前では、int型として扱われていましたが、C99以降では、これは禁止されました。

標準ライブラリ関数

C言語の規格によって、あらかじめ用意されている関数のことを、標準ライブラリ関数といいます。 puts関数や fgets関数のようなものが標準ライブラリ関数に当たります。

標準ライブラリ関数を使うには、「#include <stdio.h>」のような記述が必要になります。 このような記述によって、標準ライブラリ関数のソースコードが書かれている場所を指定しています。 「stdio.h」の部分が、場所を示しています。 #include が、場所を示す命令です。

どの関数が、どこにあるのかは規格によって定められていますから、使いたい関数に応じて、正しく指定しなければなりません。よく使う関数は覚えてしまえばいいですが、関数は非常にたくさんありますから、まれにしか使わない関数は、自分でリファレンスなどを調べる必要があります。別に暗記しなければならないものではありません。調べる習慣を身に付けて下さい。
当サイトの標準ライブラリのリファレンスもご利用下さい。

標準ライブラリ関数の価値は、どのコンパイラを使っていても同じ使い方ができて、同じ結果を得られるということです。 余程の事情がない限り、標準ライブラリ関数でできることは、自力でやらずに、標準ライブラリ関数を使うようにするべきです。 そうすることで、作ったプログラムを様々な環境で使いまわせる可能性が高まります。

コンパイラの種類によっては、標準でない関数を幾つか用意していることがあります。 このような関数には、標準ライブラリ関数では出来ないような、かなり便利な機能が含まれていることが多いのですが、 当然、他のコンパイラでは使えません。 本サイトでは、標準でない関数については原則として取り上げていません。

関数を定義する

標準ライブラリ関数だけでなく、自分で関数を作って、使うことも可能です。 これまでのように、プログラムを main関数だけで完結させることもできますが、 1箇所に処理を詰め込む方法では、ある程度、規模が大きくなってくると、読みづらくなってきますし、管理が難しくなります。 1つの関数を1つの部品のように考え、部品を組み合わせていくスタイルで作るのが、C言語のプログラムの基本的なやり方です。

変数の場合と同様、関数も宣言を行い、定義を書く必要があります。 関数にとっての宣言とは、「こういう名前、こういう引数、こういう戻り値をもった関数がある」ということを明示する行為です。 また、関数にとっての定義とは、宣言されている関数の中身を記述することです。
標準ライブラリ関数の場合は、これがあらかじめなされているので、 それが書かれている場所を、#include を使って示してやれば、すぐに使えるという訳です。

定義は宣言を兼ねるので、この章ではまず、定義の方だけを示します。 宣言については、次の章にまわします。

関数の定義は、次のような形式で記述します。

戻り値の型 関数名( 引数の型と名前, 引数の型と名前, … )  /* 引数は 0個以上 */
{
    /* 関数の本体 */

    return /* 戻り値 */		/* 戻り値がなければ return文自体が不要 */
}

既に、引数に型があることには触れていますが、それに加えて、名前もあることが分かります。 これは、関数の本体、つまり関数を実装しているコードの部分で使うために、名前が必要だからです。

引数は0個以上の任意の個数が記述できます。 引数が0個の場合には、そのことを意味する「void」というキーワードを使います。 void と書かずに空白になっている例を見かけることがありますが、それは void とは異なるものです。 この違いについては、次の章で取り上げます。

C++ では、void と書く場合と、何も書かずに省略する場合とで、完全に同じ意味になりますが、 C言語では、別の意味です。

なお、引数という用語は、関数を呼び出す側と、呼び出される側とで別の呼び方をします。 呼び出す側で指定する引数は、実引数(じつひきすう)と呼び、 呼び出される側(つまり定義の側)では、仮引数(かりひきすう)と呼びます。


一方、戻り値の方には型だけを指定し、名前は付けません。 こちらは、関数本体では名前を使う機会が無いからです。

戻り値が必要なければ、void と書きます。 C95 までの規格では、戻り値の指定を省略することができ、その場合は int であると認識されます。 これは分かりづらい仕様ですし、新しい規格では禁止されているので、 常に void と書くようにするのが無難です

C99 (戻り値の型指定の省略)

C99 では、戻り値の型を省略することはできなくなりました。

VisualStudio、clang では、省略した場合には C95以前の規格と同じく、int型を指定したものとみなされます。

関数を自作して使う

例として、三角形の面積を求める関数 calcAreaOfTriangle を作ってみます。

#include <stdio.h>

/*
    三角形の面積を求める。
    引数
        base:	底辺の長さ。
        height:	高さ。
    戻り値
        面積。
*/
double calcAreaOfTriangle(double base, double height)
{
    return ( base * height / 2 );
}

int main(void)
{
    double base, height, area;


    base   = 3.0;
    height = 5.0;
    area   = calcAreaOfTriangle( base, height );
    printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

    base   = 7.0;
    height = 4.0;
    area   = calcAreaOfTriangle( base, height );
    printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

    base   = 4.4;
    height = 3.6;
    area   = calcAreaOfTriangle( base, height );
    printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );

    return 0;
}

実行結果:

底辺:3.000000  高さ:5.000000  面積:7.500000
底辺:7.000000  高さ:4.000000  面積:14.000000
底辺:4.400000  高さ:3.600000  面積:7.920000

calcAreaOfTriangle関数は、2つの double型の引数を持ち、それぞれが三角形の底辺の長さ、三角形の高さを表しています。 戻り値も double型で、これが計算で求められた面積になります。
このように、引数や戻り値にどんな意味を持たせるのか決めるのは、関数を作るプログラマの仕事です。

double型の仮引数が2つなので、calcAreaOfTriangle関数を呼び出すときには、double型の実引数が2つ必要になります。 指定する順番も重要で、1つ目の実引数に底辺の長さを、2つ目の実引数に高さを指定しなければなりません。
このように、関数を作った側が決めたルールに従って、実引数を渡し、戻り値を受け取るのは、関数を使うプログラマの仕事です。 もちろん、関数を作る人と使う人は同一人物かも知れませんが、共同作業していれば異なる可能性もあります。

関数を呼び出す側は、戻り値を、代入と同じ方法で変数に受け取ることができます。 実際、次のように書いています。

area   = calcAreaOfTriangle( base, height );

これは、関数を呼び出して、返ってきた戻り値を、変数area へ代入しているということです。 変数へ代入せずに、次のように書くこともできます。

printf( "%f\n", calcAreaOfTriangle( base, height ) );

この場合、返ってきた戻り値を、そのまま別の関数(printf)の実引数にしていることになります。 変数を経由した方が、変数名を付けられるため分かりやすくなる場合もあるでしょうし、 余計な変数を作る方が、分かりづらい場合もあるでしょう。 この辺は経験も必要ですが、色々なやり方を検討してみると勉強になります。

なお、戻り値を使うことは必須ではなく、呼び出し側は戻り値の存在を無視することもできます。例えば、printf関数にも実は戻り値がありますが(→リファレンス)、ほとんどの場合、あまり使う意味がないので、無視します。

calcAreaOfTriangle関数の本体ですが、return文が1つあるだけです。 関数の仮引数には名前が付けてある訳ですが、これは変数を定義しているのと同じことです。 つまり、仮引数の base と height は、その関数内では変数のように使用することができます。 また、その初期値としては、実引数で指定した値が入っています。
よって、実引数がそれぞれ 3.0 と 5.0 であったとするならば、次のようなコードが書かれているのと、同じような意味合いです。

/* 正しいC言語のコードではない! */
double calcAreaOfTriangle(double, double)
{
    double base   = 3.0;
    double height = 5.0;

    return ( base * height / 2 );
}

最大のポイントは、3.0 や 5.0 と決め打ちで書いた部分が、実引数によって自由に決められるという点にあります。 何度も同じ処理を繰り返す場合、その部分を関数にし、変化のある部分は実引数を変えることで対応できます。
サンプルプログラムでは、3回に渡り「三角形の面積を求める」という同じ処理を繰り返しますが、 この処理が関数になっていることで、毎回実引数を変えれば、同じことを繰り返し書かなくて済みます。

ところで、main関数がプログラム全体の下の方に記述してありますが、 main関数はどこに書いても必ず「最初に実行される」関数なので問題ありません。 実は今回のプログラムの場合、main関数を上の方に持ってくると、うまくいきません。 この辺りは次章で説明します。

関数から関数を呼び出す

先ほどのサンプルプログラムの場合、出力時のフォーマットも同じになっていますから、 この部分を関数にしてしまうことも考えられます。 つまり、「三角形の面積を求めて、底辺の長さ、高さ、面積のすべてを標準出力へ出力する」という関数を作ることができます。

「三角形の面積を求める」関数はすでにあるので、これは流用しつつ、新しい関数を追加しましょう。

#include <stdio.h>

/*
    三角形の面積を求める。
    引数
        base:	底辺の長さ。
        height:	高さ。
    戻り値
        面積。
*/
double calcAreaOfTriangle(double base, double height)
{
    return ( base * height / 2 );
}

/*
    三角形の情報を出力する。
    引数
        base:	底辺の長さ。
        height:	高さ。
*/
void printTriangleInfo(double base, double height)
{
    double area = calcAreaOfTriangle( base, height );

    printf( "底辺:%f  高さ:%f  面積:%f\n", base, height, area );
}

int main(void)
{
    printTriangleInfo( 3.0, 5.0 );
    printTriangleInfo( 7.0, 4.0 );
    printTriangleInfo( 4.4, 3.6 );

    return 0;
}

実行結果:

底辺:3.000000  高さ:5.000000  面積:7.500000
底辺:7.000000  高さ:4.000000  面積:14.000000
底辺:4.400000  高さ:3.600000  面積:7.920000

printTriangleInfo関数は、三角形の面積を求めるために、更に自作関数の calcAreaOfTriangle を呼び出しています。 このように、関数の中から、別の関数を呼び出すことが可能です。 今までも、main関数の中から printf関数を呼び出していましたが、それと同じことです。

こうやって関数を分けることで、「三角形の面積は欲しいけど、出力したい訳ではない」ときには、calcAreaOfTriangle関数だけを使うという選択ができます。 一方で、「綺麗に整形して出力したい」ときには、printTriangleInfo関数を使えます。 使う可能性がありそうなレベルで、部品化(つまり関数化)をしておくと良いのです。 これは経験を積まないと難しいですし、経験を積んでもなお難しいのですが、ともかく色々考えることが大切です。

ところで、自作関数と標準ライブラリ関数とを完全な別の概念である、 あるいは標準ライブラリ関数が、何かとても特別なもののように考える人がいるようですが、 どちらもただの「関数」であることに変わりはありません。 我々が自分で作るのか、あらかじめ用意されているかという違いだけです。 標準ライブラリ関数といえども、どこかに自作関数と同じように中身を記述している部分があります (ただし、中身が実際に覗けるようになっているか、それとも見えないように隠されているかは、使っている環境によります)。

文字列を引数や戻り値にするには

引数や戻り値には型の指定が必要ですが、文字列の場合はどうすればいいのでしょうか。 これは結構難しい話題で、特に戻り値の方は、意味を正確に理解するまでは危険もありますから、 今のところは取り上げないことにします(第33章で取り上げます)。

引数で文字列を渡す方法については、知っておいてもいいでしょう。 例えば、次のように書きます。

#include <stdio.h>

/*
    文字列を [ ] で囲んで出力する。
    引数
        str:	出力する文字列。
*/
void bracketsPuts(char* str)
{
    printf( "[%s]\n", str );
}

int main(void)
{
    bracketsPuts( "Hello, World!" );

    return 0;
}

実行結果:

[Hello, World!]

bracketsPuts関数は、実引数で指定された文字列の前後を、[ ] で囲んで出力します(ちなみに [ ] をブラケットと言います)。

引数で文字列を扱うには、仮引数の型を「char*」とします。 変数として文字列を使うときには、「char str[80];」のように書いていましたが、まるで異なる構文になります。 「char*」の正確な意味は、第31章で説明します。

より良いプログラムを目指すためには、「char*」ではなく「const char*」とするべきです。 この意味は、第32章で説明します。


練習問題

問題① int型で渡される引数の値を、2乗して返す関数を作って下さい。

問題② 2つの文字列を渡すと、その2つの文字列で、 + を挟んで出力するような関数を作成して下さい。
例えば、"abc" と "def" を渡すと、"abc+def" と出力するようにします。

問題③ 問題②では + を挟みましたが、挟む文字も含めて、引数で指定できるように関数を作成して下さい。 挟む文字は必ず1文字であるとします。

問題④ 円の面積を求めて出力するプログラムを、以下の関数を使って作成して下さい。半径は double型とします。


解答ページはこちら

参考リンク

更新履歴

'2018/4/2 「VisualC++」という表現を「VisualStudio」に統一。

'2018/2/4 全面的に文章を見直し、修正を行った。

'2018/1/31 キーボードや画面といった表現を、標準入力や標準出力に改めた。

'2018/1/5 Xcode 8.3.3 を clang 5.0.0 に置き換え。

'2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

▼更に古い更新履歴を展開


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS

前の章へ(第8章 理解の定着・小休止①)

次の章へ(第10章 関数プロトタイプ)

C言語編のトップページへ

Programming Place Plus のトップページへ