情報処理演習
関数
その1
システム科学科 生物工学コース 佐々木耕太
http://www7.bpe.es.osaka-u.ac.jp/~kota/classes/jse.html kota@fbs.osaka-u.ac.jp
/* ふたつの非負の数の相加平均と相乗平均を計算する(do-whileの例) */
#include <stdio.h>
#include <math.h>
int main(void) {
double val1, val2, arith_mean, geo_mean;
printf(“ふたつの非負の数の相加平均と相乗平均を計算します\n”); do { printf(“ひとつめの値を入力してください:”); scanf(“%lf”, &val1); } while (val1 < 0.0); do { printf(“ふたつめの値を入力してください:”); scanf(“%lf”, &val2); } while (val2 < 0.0);
arith_mean = (val1 + val2) / 2.0; /* 相加平均 */ geo_mean = sqrt(val1 * val2); /* 相乗平均 */
printf(“相加平均は%f、 相乗平均は%fです\n”, arith_mean, geo_mean); return 0; } ほとんど同じ手続きが複数ある 関数を作る 冗長だし、変更が必要になったら、 似たような手続きのところをすべて 変更しなくてはいけない(離れた場 所にあったら変更し忘れるかもしれ ない。もっとたくさんあったら変更 は面倒だし、どこか変更し忘れた り、変更し間違えるおそれもある。)
関数のつくりどころ
•
似たような手続きがいくつかあるとき ある程度汎用的に使用できるように関数を作成する•
プログラムの構造(アルゴリズム)を明確にしたいとき 煩雑な計算が関数になっていたり、必要に応じて手続 きの段階ごとに関数にわかれていたりする方が、プロ グラムの構造がわかりやすい。特に、自作の関数を利 用せずに、mainのブロックだけが数千行にもわたる プログラムはとても読みづらく、わかりづらい。•
いろいろなプログラムで共通に使用(再利用)できる ように、ライブラリを作成するときHandyGraphicや各種SDK(software development kit) 例:
関数: 一連の手続き(作業)をまとめたもの
もしsin(x)が決まったxの値についてしか正弦の値を計算
できないなら、汎用性がなく、使い勝手がよくない 例:
関数の利点
同じことを何度も書かなくてよい 同じように関数を呼び出せばよい(1文ですむ) 似たようなことを何度も書かなくてよい 引数を変えて関数を呼び出せばよい(1文ですむ) 関数が実際に何をしているのか必ずしも知らなくてよい 煩雑な手続きを隠せる(ブラックボックス化 できる)ので、プログラムがわかりやすくなる 正弦の値を得るための面倒くさい計算を毎度見る ことも、いちいち気にすることもなく、sin関数 を使えば正弦の値が得られる 例:関数の利点
関数ごとに作成し、デバッグできる(部品化) 関数だけを独立に作成、デバッグ(、コンパイル)し、 完成させたら、他のプログラムから(再)利用したり、 他の人に配布したりすることもできる プログラムを作成(開発)する効率が上がる http://www.beembee.com/2010/lego-cities (規格化された)単純な部品を(再)利用して “すごい” ものを作る関数の仕様
sin関数を例にdouble
sin
(
double x
)
{
/* 実際にすること */
return
double型の(変)数;
}
戻り値の型
•
戻り値がないときはvoid型•
戻り値はあってもひとつだけ•
void型のとき、returnの文はreturn;とするか、書かない引数
•
引数の数、それぞれの型•
引数が複数あるときは,で区切る•
引数をとらないときはvoid型関数の名前
/* ふたつの非負の数の相加平均と相乗平均を計算する */
#include <stdio.h>
#include <math.h>
double getNonNegativeDouble(void); int main(void)
{
double val1, val2, arith_mean, geo_mean;
printf(“ふたつの非負の数の相加平均と相乗平均を計算します\n”); val1 = getNonNegativeDouble();
val2 = getNonNegativeDouble();
arith_mean = (val1 + val2) / 2.0; /* 相加平均 */ geo_mean = sqrt(val1 * val2); /* 相乗平均 */
printf(“相加平均は%f、 相乗平均は%fです\n”, arith_mean, geo_mean); return 0;
}
double getNonNegativeDouble(void)
/* キーボードから入力された非負の実数を返す関数の定義 */ { double val; do { printf(“非負の実数の値を入力してください:”); scanf(“%lf”, &val); } while (val < 0.0); return val; } 関数のプロトタイプ宣言 関数の定義 (関数が実際に行う手続き) なんという名前で、どういう引数を取って、 どんな戻り値を返すかという、関数の簡単な 紹介 ;を忘れずに 関数の戻り値 関数の呼び出し 何をする関数なのか、 必ず説明を書く
関数のプロトタイプ宣言
double getNonNegativeDouble(void); getNonNegativeDoubleという名前で、引数を取らずに、 double型の戻り値を返す関数 例 解説double power(double x, int n);
powerという名前で、ひとつめの引数にdouble型、ふたつめの引
数にint型の変数を取り、double型の戻り値を返す関数
void swap(int x, int y);
swapという名前で、int型の引数をふたつ取り、戻り値はない関数
int fact(int n);
factという名前で、int型の引数をひとつ取り、int型の戻り値を 返す関数
/* 実数のべき乗を求める関数を使う */
#include <stdio.h>
#include <math.h>
double power(double x, int n); /* power関数のプロトタイプ宣言 */ int main(void)
{
int n; double x;
printf(“実数xを何乗か(自然数乗)します\n”);
printf(“実数xの値を入力してください:”); scanf(“%lf”, &x);
printf(“何乗しますか(自然数)?:”); scanf(“%d”, &n);
printf(“%.3fの%d乗は%.3fです\n”, x, n, power(x, n)); return 0;
}
double power(double x, int n)
/* 実数xのn乗を返す関数powerの定義 */ { int i; for (i = 1; i < n; i++) { x *= x; } return x; } 引数や戻り値があるなら、 それらの説明を怠らない この関数は期待通りには 動作しない。訂正せよ。
変数のスコープ(有効範囲)
•
変数は、それを宣言してから、そのブロックの中でのみ 使うことができる(変数のスコープ(有効範囲)は、変 数を宣言したブロックに限られる)•
よって、main関数の中の変数はmain関数の中でしか使 えないし、自分で作った関数の中の変数はその関数の中 でしか使えない•
すなわち、自分で作った関数の中の変数に、main関数 の中の変数と同じ名前をつけたとしても、それらの変数 はまったく別のものである•
また、引数として渡された変数の値を関数の中で変更し ても、呼び出し元の変数の値はもとのまま保存されてお り、破壊されない(変数の値渡し)変数のスコープ(有効範囲)
main関数内の変数xの値が、foo関数
の中で宣言された、別の変数xに渡さ
れる(変数の値渡し)
/* #include <stdio.h>など */
int main(void) { int i, n; double x, y; /* xへの値の代入など */ y = foo(x); /* yの値の出力など */ return 0; }
double foo(double x) /* foo関数の定義 */
{ int i; double y; /* なにか計算する */ return y; } foo関数の中で宣言された変数 は、main関数の変数と(名前が同 じでも)まったく別のもので、 foo関数の中でしか使えない foo関数の中の変数yの値が、 main関数内の別の変数yに渡される
ふたつの変数の値を入れ替えたいが、
以下の関数はうまくいかない。なぜ?
/* うまくいかないswap関数... */#include <stdio.h>
void swap(int x, int y); /* swap関数のプロトタイプ宣言 */ int main(void)
{
int x, y; x = 5; y = 3;
printf(“swap関数を呼び出す前:x = %d, y = %d\n”, x, y);
swap(x, y);
printf(“swap関数を呼び出した後:x = %d, y = %d\n”, x, y); return 0;
}
void swap(int x, int y)
/* 受け取った2つの引数の値を入れ替える関数swapの定義 */ {
int tmp;
printf(“swap関数に入ってきたとき:x = %d, y = %d\n”, x, y); tmp = y;
y = x; x = tmp;
printf(“swap関数を出るとき:x = %d, y = %d\n”, x, y); }
戻り値がないとき、 returnの文は
引数として与えた変数の値を
変更するには
…
•
授業の範囲外(で、かつ、C言語でおそらく一番 つまづくところ)なので説明しないが、実はだい ぶ前からお世話になっている scanf(“%d”, &n);•
授業の範囲外といっても、次次回にあつかう「関 数に引数として配列を渡す」のは、そう説明しな いだけで、アドレス渡しに他ならない ポインタ引数へのアドレス渡し 参考: キーボードから入力された値をnに代入 してほしいのに、「nは別の変数だから 値は変更されない」では役に立たない &はアドレス(コンピュタの メモリ上で変数がある場所) を得る演算子関数の再帰呼び出し
/* 1からNまで階乗を計算する */#include <stdio.h>
#define N 10
int fact(int n); /* fact関数のプロトタイプ宣言 */ int main(void) { int i; double x; printf(“1から%dまで階乗を計算します\n”, N); for (i = 1; i <= N; i++) {
printf(“%3d! = %d\n”, i, fact(i)); }
return 0; }
int fact(int n)
/* nの階乗を返す関数factの定義 */ { if (n == 1) { return 1; } else { return n * fact(n - 1); } } 関数の中で、その関数自身を呼び 出すことを再帰呼び出しという 例えば、fact(3)を実行すると… 3 * fact(2) 2 * fact(1) 4.fact(1)の 戻り値の代入 2.fact(2)の 呼び出し 1 3.fact(1)の 呼び出し 5.fact(2)の 戻り値の代入 6.fact(3)の戻り値が得られる 1.fact(3)の 呼び出し
printf関数などのプロトタイプ宣言は?
•
printfやscanfも関数なので、これらを使用するた めにはプロトタイプ宣言が必要である。•
このため、こうしたよく使う関数(標準Cライブラリ の関数)を使用するための変数宣言やプロトタイプ宣 言が書かれたヘッダーファイルが用意されている。•
プログラム冒頭の#include <stdio.h>というおま じないは、(printfなどの)標準入出力(standard input/output)関数のプロトタイプ宣言などをstdio.h というヘッダーファイルから読み込むために書いてい た(同様に、#include <math.h>は…)。•
コンパイルのときに、関数の使われ方(引数の型や数 など)がプロトタイプ宣言に違反していないか確認さ れる。このとき、関数が定義されているかどうかはと りあえず気にしない。では、標準
Cライブラリの
関数の定義はどう
“解決”する?
ソース ファイル オブジェクト ファイル 実行可能 ファイル 標準Cライブラリの ライブラリ ファイル プリプロセス と コンパイル リンクによる 関数定義の解決 • #で始まる命令(includeやdefineなど)を、プリプロセッサ指令(ディレク ティブ)といい、これらはコンパイルを行う前に処理される(プリプロセス) • ライブラリファイルとは、いろいろなプログラムで共通に使用(再利用)でき るように、いくつもの関数のオブジェクトファイルをアーカイブにしたもの • gccコマンドを使用するときに-vをつけると(verbose)、実行可能ファイルが 生成されるまでの過程をひとつひとつ表示してくれる…
…
実行可能ファイル生成の概略 このライブラリファイルに 関数の定義がまとめられているgccのリンカオプション-lmと
undefined reference to ‘***’
•
標準Cライブラリの関数の定義は、ライブラリファイルにまとめ られている。•
GCC(授業で使用しているコンパイラ。コンパイルだけでなく、 リンクを含め、実行可能ファイルを生成するために必要なことを すべて行ってくれる。)は、標準入出力関数をはじめとするほと んどの関数について、ライブラリファイルから必要なものをリン クし、関数定義を解決してくれる。•
しかし、数学関数のライブラリファイルは上記のライブラリファ イルとは別になっており、 GCCは自動的にリンクしない。した がって、関数の定義を解決するために必要なライブラリファイル をリンクするよう、gccコマンドを使用するときにリンカオプ ション-‐lmを明示しなくてはいけない。•
プログラムの中で数学関数を使用しているのにgccコマンドで -lmを忘れたときのundefined reference to ‘***’という エラーは、「***という関数っぽいものを呼ぼうとしてるけど、 定義されていないからどうしたらいいかわからない (ノω・、)ウゥ・・・」というコンパイラの嘆き。「おまじない」の役割を
説明できる?
/* ふたつの非負の数の相加平均と相乗平均を計算する(do-whileの例) */ #include <stdio.h> #include <math.h> int main(void) {double val1, val2, arith_mean, geo_mean;
printf(“ふたつの非負の数の相加平均と相乗平均を計算します\n”); do { printf(“ひとつめの値を入力してください:”); scanf(“%lf”, &val1); } while (val1 < 0.0); do { printf(“ふたつめの値を入力してください:”); scanf(“%lf”, &val2); } while (val2 < 0.0);
arith_mean = (val1 + val2) / 2.0; /* 相加平均 */ geo_mean = sqrt(val1 * val2); /* 相乗平均 */
printf(“相加平均は%f、 相乗平均は%fです\n”, arith_mean, geo_mean); return 0; } 何のため? ここのintとvoidは何? これは何をしている?