第 11 章 関数について 11.1 標準ライブラリ関数 11.2 関数呼び出しのオーバーヘッド 11.3 大域変数 11.4 プロトタイプ宣言 数学関数の自作
11.1 標準ライブラリ関数 #include ← printf, scanf 等の入出力関数 #include ← sqrt, sin 等の数学関数 #include ← malloc, calloc, rand 等の関数 予め定義されており、ユーザが定義・作成し なくても使える関数 ヘッダ部に以下のマクロが必要
11.2 関数呼び出しのオーバーヘッド 関数の呼び出し回数が少なくて済むプログラムに 関数を呼び出す時、実際の計算以外のこと に使われるマシンへの余分な負荷をオー バーヘッドという 関数に渡すデータ(引数)などのコピー時 間や、そのためのメモリ消費など → 呼び出し回数が多いと無視できなくな る
プログラム例 改 #include #pragma comment(lib, "winmm.lib") #define N int main(void) { int i, t1, t2; static double a[N], b[N], c[N]; for (i = 0; i < N; i++) { a[i] = rand()/100.; b[i] = rand()/100.; } t1 = timeGetTime(); for (i = 0; i < N; i++) c[i] = a[i] + b[i]; t2 = timeGetTime(); printf("elapsed time = %d ms\n", t2-t1); printf("&i=%u, &t1=%u, &t2=%u\n", &i, &t1, &t2); printf("main=%u\n", main); printf("a=%10u, &a[N-1]=%10u\n", a, &a[N-1]); printf("b=%10u, &b[N-1]=%10u\n", b, &b[N-1]); printf("c=%10u, &c[N-1]=%10u\n", c, &c[N-1]); return 0; } 配列のたし算 ex11_2_1a.c 1000 万個の配列の足し算の 実行時間を計測
プログラム例 改 #include #pragma comment(lib, "winmm.lib") #define N double sum(double x, double y){return (x + y);} int main(void) { int i, t1, t2; static double a[N], b[N], c[N]; for (i = 0; i < N; i++) { a[i] = rand()/100.; b[i] = rand()/100.; } t1 = timeGetTime(); for (i = 0; i < N; i++) c[i] = sum(a[i], b[i]); t2 = timeGetTime(); printf("elapsed time = %d ms\n", t2-t1); printf("&i=%u, &t1=%u, &t2=%u\n", &i, &t1, &t2); printf("main=%u, sum=%u\n", main, sum); printf("a=%10u, &a[N-1]=%10u\n", a, &a[N-1]); printf("b=%10u, &b[N-1]=%10u\n", b, &b[N-1]); printf("c=%10u, &c[N-1]=%10u\n", c, &c[N-1]); return 0; } 配列のたし算 ex11_2_2a.c 1000 万個の配列の足し算の 実行時間を計測 プログラム例 改 にあえて関数を使ったら …
実行例 Z:\nyumon2>ex11_2_1a elapsed time = 250 ms &i= , &t1= , &t2= main= a= , &a[N-1]= b= , &b[N-1]= c= , &c[N-1]= Z:\nyumon2>ex11_2_2a elapsed time = 469 ms &i= , &t1= , &t2= main= , sum= a= , &a[N-1]= b= , &b[N-1]= c= , &c[N-1]= 関数 sum を 1000 万回呼び出しているため、 オーバーヘッドにより時間がかかっている プログラム領域 スタック領域 データ領域 プログラム領域 配列のたし算 10,000,000×8 バイト = 80,000,000 バイトずつ増加
プログラム例 改 #include #pragma comment(lib, "winmm.lib") #define N void sum(int n, double *x, double *y, double *z) {int i; for (i = 0; i < n; i++) z[i] = x[i] + y[i];} int main(void) { int i, t1, t2; static double a[N], b[N], c[N]; for (i = 0; i < N; i++) { a[i] = rand()/100.; b[i] = rand()/100.; } t1 = timeGetTime(); sum(N, a, b, c); t2 = timeGetTime(); printf("elapsed time = %d ms\n", t2-t1); printf("&i=%u, &t1=%u, &t2=%u\n", &i, &t1, &t2); printf("main=%u, sum=%u\n", main, sum); printf("a=%10u, &a[N-1]=%10u\n", a, &a[N-1]); printf("b=%10u, &b[N-1]=%10u\n", b, &b[N-1]); printf("c=%10u, &c[N-1]=%10u\n", c, &c[N-1]); return 0; } 配列のたし算 ex11_2_3a.c 関数 sum の呼び出しは1回 ポインタを使って結果を返す方 法 実行時間は ex11_2_1a.c と同程度
11.3 大域変数( global variable ) 全ての関数からアクセス可能な変数 10.4 で学習した 局所変数( local variable ) とは対立する概念 → 以下の3つのプログラム例を参照 グローバル変数、外部変数 (extern) 静的領域 に確保 スタック領 域に確保 自動変数 (auto)
10.4 関数と変数の可視範囲 より引用 関数内で宣言した変数は、その関数内でのみ可視 アクセス可能 ある関数の中でのみ通用する変数 = 局所変数 変数の可視範囲 = スコープ
int main(void) { int a = 10, b = 30; printf(" 関数呼び出し前 &a=%u:a=%d, &b=%u:b=%d¥n",&a,a,&b,b); scope_rule(); printf(" 関数呼び出し後 &a=%u:a=%d, &b=%u:b=%d¥n",&a,a,&b,b); return 0; } void scope_rule(void) { int a, b; a = 200; b = 400; printf(" 関数内部では &a=%u:a=%d, &b=%u:b=%d¥n",&a,a,&b,b); } この a,b のスコープは scope_rule のみ この a,b のスコープは main のみ main と scope_rule の両方で同じ変数 a,b を用いても良 い 同じ変数名 a,b でも、関数ごとに 別々にスコープが割り当てられる 局所変数の例 ― プログラム例 ―
#include void sum(void); void diff(void); double x, y; int main(void) { x = 12.5; y = 56.7; sum(); diff(); return 0; } void sum(void) { printf("&x=%u, &y=%u, 和は %f¥n", &x, &y, x + y); } void diff(void) { printf("&x=%u, &y=%u, 差は %f¥n", &x, &y, x - y); } 関数 sum や difference に引数は不要 この x,y のスコープは全ての関数 一見、便利! 大域変数の例 ― プログラム例 ― 関数 sum や difference で新たに変数宣言していないので、 x,y といえば、4行目で宣言された変数 x,y を指す
#include void scope_rule(void); int u, v; int main(void) { u = 10; v = 20; printf(" 関数の呼び出し前 &u=%u:u=%d, &v=%u:v=%d¥n", &u,u,&v,v); scope_rule(); printf(" 関数の呼び出し後 &u=%u:u=%d, &v=%u:v=%d¥n", &u,u,&v,v); return 0; } void scope_rule(void) { int u, v; u = 100; v = 200; printf(" 関数内では &u=%u:u=%d, &v=%u:v=%d¥n", &u,u,&v,v); } 関数内での局所変数名に大域変数名と同じ ものを用いても構わない。 この u,v のスコープは scope_rule のみ この u,v のスコープは全ての関数 大域変数を使わないプログラミングを心がけよう ! 混乱を招くので 乱用しない 混在する場合 ― プログラム例 ―
プログラム例 #include double sum(double x, double y); int main(void) { ・ return 0; } double sum(double x, double y) { double z; z = x + y; return z; } プロトタイプ宣言 1行でこれだけの意味を持つ プロトタイプ宣言とは ? (教科書 p.78 参照) sum という関数の定義部 11.4 プロトタイプ宣言 sum という関数を使う その詳細は後で定義 引数は double 型2個 戻り値は double 型
#include 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) {...; } プロトタイプ宣言では、 関数の引数となる変数名は 省略できる 当然だが、関数定義では 仮引数は必要 プログラム例
プロトタイプ宣言の必要性 戻り値の型や引数の型のクロスチェック バグを減らすために有効 引数の変数名は省略可 呼び出す前に必要 先に関数が定義されていれば、なくてもよい 下請けの関数から順に書き、 main 関数を最後にす れば、必要なし 標準ライブラリはヘッダファイル内でプロトタイ プ宣言
指数関数の自作 ( 演習問題 10.5) 指数関数の級数展開と漸化式 n は 100 まででよい |a n /S n | < 10 16 で収束判定 10 < x < 10 で数学ライブラリ関数と比較
指数関数の自作 ( 演習問題 10.5) a ← 1 S ← 1 n = | a ← a x / n S ← S a Exp |a / S| < 10 16 n の出力 break S を返す main i = | Exp(i), exp(i) の出力
#include double Exp(double x) { int n; double a=1, S=1; for (n=1; n<100; n++) { a *= x/n; S += a; if (fabs(a/S) < 1e-16) {printf("%d, ", n); break;} } return S; } int main(void) { int i; for (i=-10; i<=10; i++) printf("%f, %f\n", Exp(i), exp(i)); return 0; } exp.c 指数関数の自作 ( 演習問題 10.5) 収束確認用なので 完成後は削除する 自作 ライブラリ
三角関数の自作 ( 演習問題 10.5) 三角関数の級数展開と漸化式 n は 100 まででよい |a n /S n | < 10 16 で収束判定 10 < x < 10 で数学ライブラリ関数と比較 sin.c
本日のパズル 次のプログラムは何を出力するか #include #define PR(x) printf("%g\t",(double)x) #define NL putchar('\n') #define PRINT4(x1,x2,x3,x4) PR(x1);PR(x2);PR(x3);PR(x4);NL main() { double d; float f; long l; int i; i = l = f = d = 100/3; PRINT4(i,l,f,d); d = f = l = i = 100/3; PRINT4(i,l,f,d); i = l = f = d = 100/3.; PRINT4(i,l,f,d); d = f = l = i = (double)100/3; PRINT4(i,l,f,d); }