プログラムを複数個の関数に分割すれば, ソースコードを短く,分り易く記述できる.
関数は,プログラムの部品であり, 関数を組み合わせてプログラム全体を作り上げる.
関数の分類:
例:printf(),scanf()
例:main()
なお,メイン関数 main() は特別なユーザ関数であり, プログラムを実行すると,この関数が最初に自動的に呼び出される.
部品の切り分けと組み合せをうまく設計しておけば, 大規模なプログラムの開発でも,効率的に進められるようになる.
記述方法:
// ユーザ関数の定義 戻り値の型 関数名(型1 仮引数1, 型2 仮引数2, ..., 型N 仮引数N) { ... return (戻り値); } // メイン関数の定義 int main(void) { ... 変数 = 関数名(実引数1, 実引数2, ..., 実引数N); // 関数の呼出 ... }
具体例:
// 実数 a, b の最大値を返す関数...関数名と仮引数(変数名)は自由に設定 double max(double a, double b) { if (a > b) return (a); // a の方が大なら a を返し,呼出元へ戻る return (b); // b を返し,呼出元へ戻る } // メイン関数...プログラムはここからスタート int main(void) { double m; ... m = max(1.23, 4.56); // max(1.23, 4.56) を呼び出すと... // max() 側で a = 1.23,b = 4.56 となる. // max() が戻り値 4.56 を返して来る. // その結果,main() 側で m = 4.56 となる. ... }
ところで,変数では,使う前に型宣言が必要だった. 関数でも同様に,呼び出し前に プロトタイプ宣言(戻り値と仮引数の型宣言)が必要となる. このため基本的には,ユーザ関数の定義はメイン関数の前に記述すること. または場合によっては,関数の定義を呼び出しのメインの後に書きたければ, プロトタイプ宣言をメインの前に追加すること:
double max(double a, double b); // 関数の宣言...型と名前だけ記述,末尾にセミコロン int main(void) { ... ... max(...); // 何型?あー宣言されてたー double だねー ... } double max(double a, double b) // 関数の定義...処理内容も記述 { if (a > b) return (a); return (b); }
特例措置:
例:int main(void)
例:後述の void bar(int n)
数学ライブラリ関数 sqrt() の利用例 sqrt.c:
#include <math.h> // ヘッダファイル math.h の取込...sqrt() の宣言 int main(void) { return ((int)sqrt(100.0)); // 平方根関数 sqrt() の呼出 } // sqrt() の定義は他のファイル(ライブラリ)内に書かれている
$ cc sqrt.c -lm -o sqrt # ライブラリファイル libm の連結...sqrt() の定義 $ ./sqrt $ echo $? # main() の戻り値を表示 10 # √100 = 10
なお, 超基本的な関数(printf() や scanf() 等)の ライブラリファイル libc については, cc ... -lc ... としなくても自動的にリンクされる.
コンパイルコマンド cc は, 内部的には,次の3手順で動作する:
コンパイラの実行(機械語への変換)の前に, ソースファイルの #include 行が ヘッダファイルの内容に置き換えられる. sqrt.c の置換結果:
// math.h 内のプロトタイプ宣言 ... extern double sqrt(double x); extern double sin(double x); extern double cos(double x); ... // extern は「関数の定義は他のファイル内にあるよ」という意味 int main(void) { return ((int)sqrt(100.0)); }
ソースファイル(C言語)を変換(翻訳)し, オブジェクトファイル(機械語)を生成する.
生成されたオブジェクトファイル(ユーザ関数 main() 等の定義)と 既存のライブラリファイル(ライブラリ関数 sqrt() 等の定義)とを 連結(リンク)し,実行可能なプログラムファイルを生成する.
Linux の場合, ヘッダファイル *.h はディレクトリ /usr/include/ 等に, ライブラリファイル lib*.* はディレクトリ /lib/ 等に, それぞれ保管されている. ヘッダファイルの内容は,ソースと同様にC言語で書かれている. 一方,ライブラリファイルの内容は,ソース(C言語)ではなく, コンパイル済みのオブジェクト(機械語)となっている.
最初の例として, 3つの非負整数 a,b,c を棒グラフ表示するプログラムを考えよう. まず,これまで通りの方法でソースコードを作成すると, 次のようになるだろう:
$ vim bar.c
#include <stdio.h> int main(void) { int a = 10; int b = 20; int c = 15; int i; // a の棒グラフ for (i = 0; i < a; i++) { printf("*"); } printf("\n"); // b の棒グラフ for (i = 0; i < b; i++) { printf("*"); } printf("\n"); // c の棒グラフ for (i = 0; i < c; i++) { printf("*"); } printf("\n"); return (0); }
$ cc bar.c -o bar $ ./bar ********** ******************** ***************
このソースでは, ほとんど同じ処理を3回も書いており, コードが無駄に長くなってしまっている. こんな冗長なコードを書いてはいけません.
共通部分をユーザ関数として定義し,1箇所にまとめよう:
#include <stdio.h> /* 棒グラフを描く関数 引数 n:棒の長さ,非負整数 戻り値:なし */ void bar(int n) // 戻り値なしの場合,型を void とする { int i; for (i = 0; i < n; i++) { printf("*"); } printf("\n"); // return; // 戻り値なしの場合,return 自体を省略してもよい } int main(void) // 引数なしも void とする { int a = 10; int b = 20; int c = 15;int i;bar(a); // a の棒グラフ bar(b); // b の棒グラフ bar(c); // c の棒グラフ return (0); }
なお,「/* 〜 */」は複数行に渡るコメント, 一方,「//」は行末まで一行だけのコメント. ユーザ関数には使い方のヒントとなるようなコメントを必ず記述しておこう. 特に,引数と戻り値の意味については,記述は面倒だが,後で楽をするために重要.
関数化によって,実行結果は変わらないが, ソースコードは短く・読み易くなった.
次の例として, 非負整数の累乗 z = xy を求めるプログラムを作成しよう. まず,ユーザ関数を定義しない(メイン関数だけを定義する)場合のソース power.c:
#include <stdio.h> int main(void) { int x, y, z; int i; // 入力部 printf("x, y > "); scanf("%d %d", &x, &y); // 計算部 z = 1; for (i = 0; i < y; i++) { z *= x; // x倍をy回 } // 出力部 printf("%d^%d = %d\n", x, y, z); return (0); }
なお,計算部にある z *= x は, z = z*x と同じことだ. (z*x の計算結果を z の新しい値としている.) この計算を y 回繰り返すと, z の値が x の y 乗になる.
さて,このソースには例(1)のような無駄な重複は見られないが, まあ,計算部を関数化してみよう:
#include <stdio.h> /* 累乗を計算する関数 x:整数 y:非負整数 戻り値:x^y(xのy乗) */ int power(int x, int y) { int i; int z = 1; for (i = 0; i < y; i++) { z *= x; } return (z); } int main(void) { int x, y, z;int i;// 入力部 printf("x, y > "); scanf("%d %d", &x, &y); // 計算部 z = power(x, y); // 出力部 printf("%d^%d = %d\n", x, y, z); return (0); }
この変更で,ソースコードが長くなってしまったように感じるかもしれない. しかし,メイン関数だけに注目すると,計算部がかなり短かくなり, 「プログラム全体の見通しがよくなった」とも思うハズだ. メイン関数の部分だけをちょっと眺めるだけで, プログラムの全体像を把握できるようになった.
実行例:(sin カーブ)
$ ./graph * * * * * * * * *
提出: