C言語講座 第6回

関数の作成(written by Taku Nakahara)

この講座ではこれまで、main()の中にすべてのプログラムを書いていました。これまでは短い練習用のプログラムばかりだったので全く問題なかった訳ですが、これが数千行、数万行のプログラムになったらどうでしょうか?

「あれー、まえにint i;って定義してたっけなー」(変数の有効範囲の問題)

「なんかメモリリークが原因でプログラムが走んねーとおもったら15,365行前でおんなじ名前でファイルポインター定義してfclose()してなかったじゃねーか!」(メモリアロケーションの問題)

「あたしは5000~10000行目までの担当で書いてたら、定義しようとした変数が、既に上の方で別のやつが使ってた。じゃま。」(多人数でおこなうプロジェクトを遂行する上での問題)

といったことになるのは目に見えています。

そこで現れたのが「構造化プログラミング」という考え方です。詳しくはウィキペディアの「構造化プログラミング」の項目を読んでほしいと思いますが、要は「関数」というものを定義してプログラムのモジュール性を高め、保守開発が容易になるように工夫することです。別に「関数」がなければプログラミングが出来ないということではありませんが、あるとだいぶ便利です。構造化プログラミングの考え方は現在用いられているほとんどの言語で取り入れられており、最近はさらに進んでオブジェクト指向、アスペクト指向、デザインパターン、アジャイル開発などと言ったプログラミング手法に発展しています。もしあなたが将来SEやプログラマーを目指しているのならさけては通れません。あらゆる先進的なプログラミング手法につながる基本ですので、さっそく身に付けましょう。

「関数(function)」とは、数学ででてくる2次関数とか三角関数とかの数式(equation)をイメージするとちょっと違います。プログラミングにおける「関数」はもっと広い意味で、「何らかの仕事を受け持ってくれる部分」と言えます。

たとえば全部main()に書いたプログラムは

int main()
{
 *コマンドライン引数からファイル名を受け取って、
 *そのファイル名からファイルポインターを準備して、
 *一行ずつ読み込んで、
 *もし文字列がATOM or HETATMで始まっていたらN++して、
 *最後にNがいくつになったか結果を書き出す。
 return 0;
}

と言ったようにすべてmain()関数一人でやらなくてはなりません。一方、関数を利用してプログラミングすると、

int main()
{
 *このファイルのファイルポインターつくっといて > [ファイルポインタ生成関数]
 *このファイルポインタからATOM or HETATMがいくつあるか数えといて > [原子カウント関数]
 *結果を書き出す。
 return 0;
}

[ファイルポインタ生成関数]
{
 *指定されたファイル名からファイルポインターを生成する。
}

[原子カウント関数]
{
 *指定されたファイルポインターの示すファイルを開く。
 *ファイルの中のATOM or HETATM行を数える。
}

と言う風になります。これまでmain()関数一人でやっていた仕事を別の関数([ファイルポインタ生成関数]と[原子カウント関数])に代わりにやってもらって、main()は彼らが結果をだすのを待って、最後に結果を表示するだけです。関数とは、自分の仕事を分担してやってくれる手下みたいなものなのです。

では具体的にプログラミングをしてみましょう。最初は第1回でやった文字を書き出すだけのプログラムを関数を使って書き直してみましょう。

(LoveMe.c)

#include <stdio.h>
void sayLoveMe();
int main()
{
    sayLoveMe();
    return 0;
}
void sayLoveMe()
{
    printf("I love you.\n");
}

2行目でvoid sayLoveMe();としています。これは、これからsayLoveMe()という関数を作りますよという「宣言」です。これはsayLoveMe()を呼び出す関数(今回はmain()関数)より先に宣言されていなければなりません。voidというのは後で説明します。

5行目のmain()関数の中でsayLoveMe()関数を呼び出しています。こうするとsayLoveMe()関数がやってきて仕事をしてくれる訳です。

8~11行目はsayLoveMe()関数の本体です。sayLoveMe()は呼び出されると(仕事なので)I love you.と言ってくれます。これが最も簡単な関数です。

関数に何らかの値をあたえて、それを処理させることも出来ます。

(AD2Shouwa.c)

#include <stdio.h>
#include <stdlib.h>
void seireki2shouwa(int year);
int main(int argc, char *argv[])
{
    int ad = atoi(argv[1]);
    seireki2shouwa(ad);
    return 0;
}
void seireki2shouwa(int seireki)
{
    int shouwa;
    shouwa = seireki - 1925;
    printf("A.D.%d is Shouwa %d.\n", seireki, shouwa);
}

これは西暦を与えると昭和を返すプログラムです。2行目の#include は6行目でatoi()という関数を呼び出すためのおまじないです。atoi()は与えられた文字列を整数型の値(int)にかえる関数です。

7行目ではコマンドライン引数からえた西暦をseireki2shouwa()という関数に投げています。このseireki2shouwa()という関数は3行目で定義されています。

seireki2shouwa(int year);

さきほどの LoveMe.c では関数の宣言の括弧内には何も書いてありませんでしたが、こんどは(int year)と書いてあります。これは「この関数にはint型の変数を与えることができますよ」という意味です。7行目のmain()関数内での呼び出しは、seireki2shouwa(ad[int型の変数])という格好になっています。括弧内にあるint型の変数は、変数の実体を記述した10行目以下の部分でint seirekiとして受け取られます。seireki2shouwa()関数内では西暦から1925を引くことで昭和の年を計算し、結果を表示します。

seireki2shouwa(int seireki)のseirekiは、関数を呼ぶ側(main()関数)が呼ばれる側(seireki2shouwa()関数)に与える値で、こういった値のことを「引数」と呼びます。

(以下は混乱する可能性があるので、分からない人は気にしなくて大丈夫です)

このプログラムではseireki2shouwa()と3回書いてあります。3行目のvoid seireki2shouwa(int year)は「関数の定義部」です。「このプログラムでは、int型の変数を受け取る関数seireki2shouwa()を使いますよ」という宣言です。7行目のseireki2shouwa(ad)は「関数の呼び出し部」です。ここで、adという具体的な値を持った変数をseireki2shouwa()関数に与えています。10行目のvoid seireki2shouwa(int seireki)は「関数の実体部」です。ここにseireki2shouwa()関数が実際に行うことが書いてあります。ここで注目してほしいのはseireki2shouwa(*)の括弧内の変数名がそれぞれyear, ad, seirekiと異なっていることです。定義部のyearは仮変数といって、実は意味がありません。ここでは単にint型の何らかの変数がありますよと言っているだけです。adにはコマンドライン引数から具体的な値が代入されています。それを受け取るseirekiは名前はadではありませんが、adの中身を受け取っています。seireki2shouwa()関数の中ではadの値はseirekiとしてあつかわれ、adという変数は定義されていません。adはmain()関数の中だけで通用し、seirekiはseireki2shouwa()関数の中だけで通用します。このようにある変数の通用する範囲のことを「スコープ」と言います。adのスコープはmain()関数内で、seirekiのスコープはseireki2shouwa()関数内です。

(以上、混乱するかもしれないこと終わり)

AD2shouwa.cでは、関数を呼ぶ方が呼ばれる方に値(引数)を渡す方法を説明しました。今度は逆に呼ばれた方の関数から呼んだ方の関数に値を返す方法を説明します。このような値は「返り値」と呼ばれます。

(baigaeshi.c)

#include <stdio.h>
#include <stdlib.h>
int bainishite(int i);
int main(int argc, char *argv[])
{
    int n1 = atoi(argv[1]);
    int n2 = bainishite(n1);
    printf("%d wo bainishitara %d da!\n", n1, n2);
    return 0;
}
int bainishite(int n)
{
    int bai = n*2;
    return bai;
}

このプログラムで注目すべきは、関数の定義が、

int bainishite(int i);

となっている点です。この関数の定義の頭についている“int”は、この関数はint型の値を返してくれますよ(=返り値はint型ですよ)ということを意味しています。bainishite()関数の中身(11〜15行目)では、引数として受け取ったnを2倍にしてint baiに代入し、

return bai;

として、返しています。returnは返り値を与えるための制御文です。

練習問題

  1. 今度は、コマンドライン引数に名前をいれると、“Shurrup (名前)!”と諭してくれるプログラムを書きなさい。 (LoveMe2.c)
  2. baigaishi.cを改造して、ふたつのコマンドライン引数を与えると、そのふたつを掛けて返してくれるプログラムを書きなさい。(n_baigaeshi.c)