ほぷしぃ

納得C言語!

[第6回]関数

関数


1.関数とは?

関数とは、まとまりのある処理を1つにしたプログラムのことを指します。

例題1 関数ってとりあえずこんなものなのです

#include <stdio.h>

void func();   //関数のプロトタイプ宣言

int main()
{
    func();    //関数呼び出し
    printf("ここがメイン関数内\n");
    return 0;
}

void func()    //自作関数
{
    printf("ここは自作関数内\n");
    return;    //「return;」は省略可
}

結果

関数を使って出力してみました

今までは100行もないような短いプログラムだけでしたが、プログラムに慣れてきて1000行を超えるような大きなプログラムになると、どこでどのような処理が行われているかが把握しづらくなります。
そこで、いくつかの関数に分け、プログラムをコンパクトにすることによってどの部分でどのような処理が行われているかをはっきりさせることができます。
その他にも同じような処理を複数回行っているのを1つの関数にすることで同じ処理を何度も書く手間を省くこともできちゃいます。
関数を用いることは一石二鳥・・・いや一石五・・・、ともかくそれ以上の役割を果たすのです!
ちなみに、ここまで来る最中にmain文を何回か打ち込んできましたよね? 実はあのmain文も関数なんです。
え?そんなこと知ってるって?・・・グスン
ここで初めて関数を学習すると思いますが、実は知らず知らずのうちに関数を使っていたのです。


2.関数の処理の流れ

自作関数を入れることによってプログラムはどのような処理をするのでしょうか。
例とともに説明します。
下の例は先ほど書いたプログラムと同一です。

プログラムの処理の動き

(1)main関数の先頭からfunc()まで移動する。
(2)func関数が呼び出され、func関数に移動する。
(3)func関数の処理が実行される。
(4)func関数の処理が終了したら、func関数を呼び出した所へ戻る。
(5)main関数の残りを実行する。

このようにして関数間の処理の移動が行われています。


3.関数の作り方

ここでは基本的な関数の作り方について説明します。形式は以下の通りになっています。

関数の基本形

戻り値の型は、intやvoidなどで表される型名で表します。
何も指定しないと自動的にint型になりますが、きちんと宣言してた方が分かりやすいでしょう。
関数名は作成した関数自体の名前で、名前の付け方は変数名をつける時と一緒のルールです。
引数は関数に渡される値の事で、渡された関数内で扱うことができます。
引数は省略することができ、省略すると引数はvoid(引数なし)となります。
ローカル変数の宣言は必要あるときに宣言します。
必要ないときは省略しても構いません。
return文は呼び出した関数に返す値で、returnの後の式を返します。
必ず戻り値の型とそろえて下さい。
戻り値の型がvoid型の場合はreturn文を省略することができます。

以下のプログラムは入力した数値を関数を使って2倍の値を出力するプログラムです。

例題2 2倍の値を出力するプログラム

#include <stdio.h>

int var(int i); //関数のプロトタイプ宣言

int main()
{
    int s = 0, n = 0; //変数の宣言と初期化

    printf("数値を入力してください\n");

    scanf("%d", &s); //数値の入力
    n = var(s);      //関数var()を呼び出し、引数sを2倍する
    printf("入力した数値の2倍は%dです\n", n);

    return 0;
}

int var(int i)    //自作関数
{
    int ans = 0;
    ans = i * 2;  //変数を2倍する計算式
    return ans;   //ansを返す
}

引数をこのように渡しています

この図は上の例題の引数の渡し方を表しています。
「n = var(s);」でmain関数はvar関数に変数sの値を渡しています。
var関数は変数sの値を変数iで受け取っています。
var関数の処理が終了すると、var関数は変数iを2倍した変数ansの値を返します。
そしてその値はmain関数の変数nが受け取ります。
よって変数nには変数ansの値が格納されます。

この時に変数sのことを実引数、変数iのことを仮引数といいます。

結果

2倍の値になりました

この通り、2倍の値になりました。

A「戻り値って?」
B「簡単に言うと、結果の事を言うんだ。例題2では引数を2倍した結果の事だよ」
A「この結果が、(※)のように呼び出し元に返るんだね?」
B「That's right!!」
A「じゃあ、戻り値の型っていうのは結果の型ってこと?」
B「That's right!!int型で宣言したのに結果が小数だと、コンパイルはできるけど思った結果(整数じゃない)と違っちゃうから気をつけてね」
A「戻り値の型を実際に戻る値は同じにしないといけないのか・・・」


4.関数の使い方

では、実際に関数を使ってプログラミングしていきましょう。
最初で見たプログラム例を再度みてみることにします。
とりあえず例を打ち込んで実行してみよう。

例題3 最初の例

#include <stdio.h>

void func();   //関数のプロトタイプ宣言

int main()
{
    func();    //関数呼び出し
    printf("ここがメイン関数内\n");
    return 0;
}

void func()    //自作関数
{
    printf("ここは自作関数内\n");
    return;    //「return;」は省略可
}

結果

しつこい!

プログラム例を見ていて、ずっと気になっている人もいるでしょう。
3行目のコメントにも書かれている「関数のプロトタイプ」と書かれている一文。
この「プロトタイプ宣言」と呼ばれているものの正体とは一体何でしょうか?
プロトタイプを辞書などで調べてみると、「原型」「基本形」などと出てきます。
つまりプロトタイプ宣言とは、プログラムの基本形を宣言するということになります。
なぜこのようなものが必要なのでしょうか?

A「プロトタイプ宣言って何で必要なの?」
B「C言語はプログラムを上からコンパイルするけど、コンパイルの途中でプログラマーが作った関数が出てきたらどうなると思う?」
A「ん〜、、、どんな関数が分からないからエラーになるのかな〜?」
B「そうだね。試しにプロトタイプ宣言を書かないでビルドしてごらんよ」
A「プロトタイプ宣言を書かないでっと・・・やっぱエラーになっちゃったよ〜」
B「例の通りにプログラムを書くときには、プロトタイプ宣言は絶対にしなくっちゃ。」
A「分かったよ!」

プロトタイプ宣言はこのように宣言します

プロトタイプ宣言

戻り値の型、関数名、引数のルールは関数を作る際とほとんど同じです。
但し、プロトタイプ宣言する行の最後には「;」が必要となります。
プロトタイプ宣言の仕方を覚えたところでさらに詳しく説明します。

例題4 ちなみにこのままではコンパイルできません

#include <stdio.h>

int main()
{
    func();    //関数呼び出し
    printf("ここがメイン関数内\n");
    return 0;
}

void func()    //自作関数
{
    printf("ここは自作関数内\n");
    return;    //「return;」は省略可
}

おそらくこのようなエラーが出るでしょう

エラーです

例題4のようなプログラムがあるとします。
main関数の最初にfunc( )という関数が出てきます。
人間は「func()が出てきたらmain関数の下のfunc関数に行く」と言うことは目で見て分かりますが、コンパイラは「func()って何処にあるんだ?」という状態になります。
コンパイラはfunc関数が関数内の何処にあるか分からないので、先に進めることができません。
人間も「○○へ来てください」と誰かから言われても「○○」がどこにあるか分からない限りは目的地へ行くことはできませんよね?
コンパイルもそれと同じことです。
結局、コンパイラは目的地が分からないのでエラーを出して助けを求めています。
ここで、プロトタイプ宣言の出番となります。先程のプログラムに・・・

例題5 例題4に下線部追加

#include <stdio.h>

void func();   //関数のプロトタイプ宣言

int main()
{
    func();    //関数呼び出し
    printf("ここがメイン関数内\n");
    return 0;
}

void func()    //自作関数
{
    printf("ここは自作関数内\n");
    return;    //「return;」は省略可
}

結果

コンパイルできましたね

下線を引いた部分を追加してみてください。
こうすることでコンパイルはfunc関数の存在を知ることができます。
すなわち、コンパイラが行くべき目的地がどこにあるか分かるということです。
よって、main関数でfunc()が出てきてもコンパイラは「func関数へ行けばいいんだな!」と目的地がはっきりしているのでコンパイラは先に進むことができます。
これがプロトタイプ宣言の役割です。
ちなみに・・・

例題6 func関数をmain関数の前に持ってくると…

#include <stdio.h>

void func()    //自作関数
{
    printf("ここは自作関数内\n");
    return;    //「return;」は省略可
}

int main()
{
    func();    //関数呼び出し
    printf("ここがメイン関数内\n");
    return 0;
}

結果

結果は同じです

このように記述すると、プロトタイプ宣言をする必要がありません。
なぜなら、プログラムのスタート地点でもあるmain関数を通る前にfunc関数を通ってますからね。
すなわち、スタートする前からfunc関数の場所が分かっている状態になるのです。
但し、この書き方は最近ではあまり使われていないそうです。


5.練習問題

5回の演習問題の加減乗除の計算のプログラムを関数を使って1つにまとめてみましょう。
加減乗除の部分をそれぞれ4つの関数に分けて書いてみよう。


[第5回]演習問題I ページのトップ 解答