11.1 標準ライブラリ関数 11.2 関数呼び出しのオーバーヘッド 11.3 大域変数 11.4 プロトタイプ宣言 11.5 関数引数 第11章 関数について 11.1 標準ライブラリ関数 11.2 関数呼び出しのオーバーヘッド 11.3 大域変数 11.4 プロトタイプ宣言 11.5 関数引数
11.1 標準ライブラリ関数 予め定義されており、ユーザが定義・作成しなくても使える関数 ヘッダ部に以下のマクロが必要 #include <stdio.h> ← printf, scanf等の入出力関数 #include <math.h> ← sqrt, sin等の数学関数
11.2 関数呼び出しのオーバーヘッド 関数を呼び出す時、実際の計算以外のことに使われるマシンへの余分な負荷をオーバーヘッドという 関数に渡されるデータのコピーを作るための実行時間や、そのためのメモリ消費など → データ数が大量になると無視できなくなる 関数の呼び出し回数が少なくて済むプログラムに
プログラム例11.2.2 #include <stdio.h> double sum(double, double); int main(void) { int count = 5; double a[] = {1.23, 2.34, 3.45, 4.56, 12.34}; double b[] = {4.56, 5.67, 6.78, 7.89, 19.2}; double c[50]; int i; for (i = 0; i < count; i++) { c[i] = sum(a[i], b[i]); printf("%6.2f %6.2f %6.2f\n", a[i], b[i], c[i]); } return 0; double sum(double x, double y) return (x + y); プロトタイプ宣言はこれでOK 関数 sum をデータの数だけ呼出している。 (その度にオーバーヘッドの時間がかかる ので、データが大量になるとバカにならない)
プログラム例11.2.3 関数sumの呼び出しは1回 #include <stdio.h> void sum(int, double *, double *, double *); int main(void) { int count = 5; double a[] = {1.23, 2.34, 3.45, 4.56, 12.34}; double b[] = {4.56, 5.67, 6.78, 7.89, 19.2}; double c[50]; int i; sum(count, a, b, c); for (i = 0; i < count; i++) printf("%6.2f %6.2f %6.2f\n", a[i], b[i], c[i]); return 0; } void sum(int n, double *xp, double *yp, double *zp) for (i = 0; i < n; i++) { *zp = *xp + *yp; zp++; xp++; yp++; プロトタイプ宣言はこれでOK a と&a[0]は同じもの 関数sumの呼び出しは1回 ポインタの中味*zpは変化するが、 引数(ポインタ)自体は戻されない ふつうは zp[i] = xp[i] + yp[i] → 第12章
11.3 大域変数(global variable) グローバル変数 全ての関数からアクセス可能な変数 10.4で学習した局所変数(local variable)とは対立する概念 → 以下の3つのプログラム例を参照
10.4 関数と変数の可視範囲 より引用 関数内で宣言した変数は、その関数内でのみ可視 アクセス可能 変数の可視範囲 ⇒ スコープ という 変数の可視範囲 ⇒ スコープ という ある関数の中でのみ通用する変数 ⇒ 局所変数 という
この a,b のスコープは scope_rule のみ 局所変数の例―プログラム例 10.4.1― mainとscope_ruleの両方で同じ変数a,bを用いても良い int main(void) { int a = 10, b = 30; printf(“関数呼び出し前 a = %d b = %d¥n”, a, b); scope_rule(); printf(“関数呼び出し後 a = %d b = %d¥n”, a, b); return 0; } void scope_rule(void) int a, b; a = 200; b = 400; printf(“関数内部では a = %d b = %d¥n”, a, b); この a,b のスコープは main のみ 同じ変数名 a,b でも、関数毎に 別々に変数領域が割り当てられる この a,b のスコープは scope_rule のみ
関数 sum や difference に引数は不要 大域変数の例―プログラム例 11.3.1― #include <stdio.h> void sum(void); void difference(void); double x, y; int main(void) { x = 12.5; y = 56.7; sum(); difference(); return 0; } void sum(void) printf(“和は %f¥n”, x + y); void difference(void) printf(“差は %f¥n”, x - y); この x,y のスコープは全ての関数 関数 sum や difference に引数は不要 関数 sum や difference で新たに変数宣言していない ので、x,y といえば、4行目で宣言された変数 x,y を指す 一見、便利!
混在する場合―プログラム例 11.3.2― 大域変数を使わないプログラミングを心がけよう! この u,v のスコープは全ての関数 #include <stdio.h> void scope_rule(void); int u, v; int main(void) { u = 10; v = 20; printf(“関数の呼び出し前 %d %d¥n”, u, v); scope_rule(); printf(“関数の呼び出し後 %d %d¥n”, u, v); return 0; } void scope_rule(void) u = 100; v = 200; printf(“関数内では %d %d¥n”, u, v); この u,v のスコープは全ての関数 混乱を招くので 乱用しない 関数内での局所変数名に大域変数名と同じ ものを用いても構わない。 この u,v のスコープは scope_rule のみ 大域変数を使わないプログラミングを心がけよう!
11.4 プロトタイプ宣言 プロトタイプ宣言とは?(教科書p.78参照) プログラム例 10.1.1 プロトタイプ宣言 #include <stdio.h> double sum(double x, double y); int main(void) { ・ } double sum(double x, double y) double z; z = x + y; return z; sum という関数を使う その詳細は後で定義 引数は double 型2個 戻り値は double 型 1行でこれだけの意味を持つ sum という関数の定義部
プログラム例 11.4.2 プロトタイプ宣言では、 関数の引数となる変数名は 省略できる 当然だが、関数定義では 仮引数は必要 #include <stdio.h> int foo(int, int, double); double bar(double, double); int main(void) { ...; return 0; } int foo(int u, int v, double w) double bar(double m, double n) プロトタイプ宣言では、 関数の引数となる変数名は 省略できる 当然だが、関数定義では 仮引数は必要
11.5 関数引数 関数の引数として、関数へのポインタも使える これまでに習ってきた例:実数 x,y が引数 プログラム例 11.5.1 より double integral(double lower, double upper, int n, double (*f)(double)) { double h, sum; int i; sum = 0.0; ・ } 変数なら double f ポインタなら double *f となるところ… これまでに習ってきた例:実数 x,y が引数 プログラム例 10.1.3 より double sum(double x, double y) { double z; z = x + y; return z; }
11.5 関数引数 プログラム例 11.5.1 #include <stdio.h> #include <math.h> double circle(double); double parabola(double); double integral(double, double, int, double (*f)(double)); int main(void) { printf("%f¥n", integral(0.0, 1.0, 100, circle)); printf("%f¥n", integral(0.0, 1.0, 100, parabola)); return 0; } double型関数へのポインタが 引数であることを意味する 1行目では関数 circle を, 2行目では関数 parabola を 実引数としている 次のスライドに続く
プログラム例 11.5.1 関数 circle、関数 parabola と 関数 integral の関数定義部 double integral(double lower, double upper, int n, double (*f)(double)) { double h, sum; int i; sum = 0.0; h = (upper - lower) / n; for (i = 1; i < n; i++) sum += f(lower + i * h); return 0.5 * h * (2.0 * sum + f(lower) + f(upper)); } double circle(double x) return sqrt(1.0 - x * x ); double parabola(double x) return x * x; f(…)の部分では、main で呼ばれたときに 実引数となっている関数を呼び出す integral(…, circle) なら関数 circle を呼び、 integral(…, parabola) なら関数 parabola を呼ぶ
スキルアップタイム 1 以下はプログラム例11.2.2の改変。何を変えたか考えながら実行してみよう #include <stdio.h> double sum(double x, double y); int k = 0; int main(void) { int i, count = 8; double a[] = {1.23, 2.34, 3.45, 4.56, 5.67, 6.78, 7.89, 8.91}; double b[] = {2.34, 3.45, 4.56, 5.67, 6.78, 7.89, 8.91, 1.23}; double c[8]; for (i = 0; i < count; i++) { c[i] = sum(a[i], b[i]); printf("%7.3f %7.3f %7.3f %3d\n", a[i], b[i], c[i], k); } return 0; double sum(double x, double y) k++; return (x + y);
スキルアップタイム 2 以下はプログラム例11.2.3の改変。何を変えたか考えながら実行してみよう #include <stdio.h> void sum(int n, double *x, double *y, double *z); int k = 0; int main(void) { int i, count = 8; double a[] = {1.23, 2.34, 3.45, 4.56, 5.67, 6.78, 7.89, 8.91}; double b[] = {2.34, 3.45, 4.56, 5.67, 6.78, 7.89, 8.91, 1.23}; double c[8]; sum(count, a, b, c); for (i = 0; i < count; i++) printf("%7.3f %7.3f %7.3f %3d\n", a[i], b[i], c[i], k); return 0; } void sum(int n, double *xp, double *yp, double *zp) int i; k++; for (i = 0; i < n; i++) { *zp = *xp + *yp; zp++; xp++; yp++;
スキルアップタイム 3 Integral 台形公式 プログラム例11.5.1を、何を求めているのか良く考えながら実行してみよう 1 circle 0 1 1 parabola 台形公式 0 1