プログラムを作成していると、同じような処理が幾つか出てくる場合があります。そのような場合その都度コーディングするのではなく、共通処理を関数としてまとめておきそれを呼び出して処理させる事が出来ます。
これによりソースの冗長が解消され可読性が向上しバグの発生率を抑えたり、またデバッグも行い易いなど多くの恩恵を受けることが出来ます。先に使用したmain()やprintf()もC言語で用意されている標準関数です。
関数は一連の処理をおこなうのに必要な命令をまとめたものであり、処理で必要な値(引数リスト※1)を渡して、処理結果(戻り値)を受け取る形になります。関数の構文は次の通りです。
戻り値の型 関数名(引数リスト) { 処理; return 戻り値; }
引数がない場合
戻り値の型 関数名(void) {
処理;
return 戻り値;
}
戻り値がない場合
void 関数名() { 処理; return; ← または省略 }
※1 引数が複数ある場合、カンマで区切って指定します。
例として、2つの整数を引数で取得しそれらを足し算して返す関数を作成し、呼び出してみます。
・サンプルソース(sample0601.c)
#include <stdio.h> int add(int a, int b) { return (a + b); } int main() { int x = 10; int y = 20; printf("%d + %d = %d\n" , x, y, add(x, y)); return 0; }
・実行結果
C:\dev\c>sample0601 [Enter]
10 + 20 = 30
上記のソース(sample011.c)でmain()関数とadd()関数の記述順を入替えると警告や時にはコンパイルエラーになります。原因はmain()関数でadd()関数を呼び出していますが、add()関数の記述がmain()関数より後にあるためです。
このように、C言語では関数を使用する前に、その定義を記述しておく必要があります。
・サンプルソース(sample0601.c)の変更
#include <stdio.h> int main() { int x = 10; int y = 20; printf("%d + %d = %d\n" , x, y, add(x, y)); /* いきなりadd()関数の呼出し */ return 0; } int add(int a, int b) { return (a + b); }
・コンパイル
C:\dev\c>bcc32 sample0602.c [Enter] Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland sample0602.c: Warning W8065 sample0602.c 11: Call to function 'add' with no prototype in function main Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
とは言え、必ず呼出しより前に定義を記述しなければならないとすると管理が大変です。このような場合に関数のプロトタイプ宣言を使用することで関数の記述順を気にする必要がなくなります。
プロトタイプ宣言の構文は次の通りです。
戻り値の型 関数名(引数リスト);
上記の例では次のように、プロトタイプ宣言を記述することで警告やコンパイルエラーが回避出来ます。
・サンプルソース(sample0602.c)
#include <stdio.h> int add(int, int); /* add()関数のプロトタイプ宣言を追加 */ int main() { int x = 10; int y = 20; printf("%d + %d = %d\n" , x, y, add(x, y)); /* いきなりadd()関数の呼出し */ return 0; } int add(int a, int b) { return (a + b); }
・コンパイル
C:\dev\c>bcc32 sample0602.c [Enter] Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland sample0602.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland ← 警告は解消された
変数にはスコープ(有効範囲)があり、そのスコープの違いから次の2種類に大別されます。
グローバル変数とローカル変数は”{}”を境界にして次のような関係(スコープ)になります。また”{}”はネストできることから、これらの関係は相対関係にあるといえます。
上記の図において、グローバル変数領域からローカル変数へはアクセス出来ませんが、ローカル変数領域からグローバル変数へのアクセスは可能です。但し、ローカル変数名がグローバル変数名と同じ場合はそのローカル変数領域内におてローカル変数の方が使用されグローバル変数へのアクセスが出来なくなります。
通常、ローカル変数は一度そのスコープから抜けると、無効となり値は破棄されます。しかし「static変数」を使用することによりローカル変数の値を保持する事が出来ます。static変数の宣言方法は次の通りです。
static データ型 変数名;
・サンプルソース(sample0603.c)
#include <stdio.h> void sample(void) { int x = 0; static int y = 0; x += 1; y += 1; printf("=========================================================\n"); printf("■staticなしの変数を使用した呼出カウント = %d 回呼出し\n", x); printf("■static変数を使用した呼出カウント = %d 回呼出し\n", y); printf("=========================================================\n"); } int main() { /* 関数を3回呼び出す。 */ sample(); sample(); sample(); return 0; }
・実行結果
C:\dev\c>sample0603 [Enter] ========================================================= ■staticなしの変数を使用した呼出カウント = 1 回呼出し ■static変数を使用した呼出カウント = 1 回呼出し ========================================================= ========================================================= ■staticなしの変数を使用した呼出カウント = 1 回呼出し ■static変数を使用した呼出カウント = 2 回呼出し ← 値が保持されている。 ========================================================= ========================================================= ■staticなしの変数を使用した呼出カウント = 1 回呼出し ■static変数を使用した呼出カウント = 3 回呼出し ← 値が保持されている。 =========================================================
再帰関数とは関数の処理内で自身の関数を呼び出す処理の事です。再帰関数の例として、再帰サンプルのお約束「階乗計算処理」を紹介します。
ちなみに階乗計算とは、整数nに対して「n!」と表し、「1×2×3 ・・・・ n」をおこなう処理の事です。
・サンプルソース(sample0604.c)
#include <stdio.h>
int factorial(int a) {
if (a <= 1) {
return 1;
}else {
return a * factorial(a-1); /* 再帰処理 */
}
}
int main() {
int a = 5;
printf("%d! = %d", a, factorial(a));
return 0;
}
・実行結果
C:\dev\c>sample0604 [Enter]
5! = 120