第 11 章関数について 11.1 標準ライブラリ関数 11. 関数呼び出しのオーバーヘッド 11. 大域変数 11.4 プロトタイプ宣言数学関数の自作 11.1 標準ライブラリ関数 予め定義されており ユーザが定義 作成しなくても使える関数 ヘッダ部に以下のマクロが必要 #iclude <mth.h> pritf, scf 等の入出力関数 sqrt, si 等の数学関数 #iclude <stdlib.h> mlloc, clloc, rd 等の関数 11. 関数呼び出しのオーバーヘッド 関数を呼び出す時 実際の計算以外のことに使われるマシンへの余分な負荷をオーバーヘッドという 関数に渡すデータ ( 引数 ) などのコピー時間や そのためのメモリ消費など 呼び出し回数が多いと無視できなくなる 関数の呼び出し回数が少なくて済むプログラムに プログラム例 11..1 改 #iclude <stdlib.h> #iclude <widows.h> #prgm commet(lib, "wimm.lib") #defie N 1 it i, t1, t; sttic double [N], b[n], c[n]; for (i = ; i < N; i++) [i] = rd()/1.; b[i] = rd()/1.; for (i = ; i < N; i++) c[i] = [i] + b[i]; t = timegettime(); pritf("elpsed time = %d ms ", t-t1); pritf("&i=%u, &t1=%u, &t=%u ", &i, &t1, &t); pritf("mi=%u ", mi); pritf("=%1u, &[N-1]=%1u ",, &[N-1]); pritf("b=%1u, &b[n-1]=%1u ", b, &b[n-1]); pritf("c=%1u, &c[n-1]=%1u ", c, &c[n-1]); retur ; ex11 1.c 1 万個の配列の足し算の実行時間を計測
プログラム例 11.. 改 #iclude <stdlib.h> #iclude <widows.h> #prgm commet(lib, "wimm.lib") #defie N 1 double sum(double x, double y)retur (x + y); it i, t1, t; sttic double [N], b[n], c[n]; for (i = ; i < N; i++) [i] = rd()/1.; b[i] = rd()/1.; for (i = ; i < N; i++) c[i] = sum([i], b[i]); t = timegettime(); pritf("elpsed time = %d ms ", t-t1); pritf("&i=%u, &t1=%u, &t=%u ", &i, &t1, &t); pritf("mi=%u, sum=%u ", mi, sum); pritf("=%1u, &[N-1]=%1u ",, &[N-1]); pritf("b=%1u, &b[n-1]=%1u ", b, &b[n-1]); pritf("c=%1u, &c[n-1]=%1u ", c, &c[n-1]); retur ; ex11.c プログラム例 11..1 改にあえて関数を使ったら 1 万個の配列の足し算の実行時間を計測 実行例 Z: yumo>ex11 1 elpsed time = 5 ms &i=1158, &t1=11588, &t=11584 mi=4198896 = 164488, &[N-1]= 4448 b= 4488, &b[n-1]= 8448 c= 84488, &c[n-1]= 16448 Z: yumo>ex11 elpsed time = 469 ms &i=1158, &t1=11588, &t=11584 mi=419897, sum=4198896 = 164488, &[N-1]= 4448 b= 4488, &b[n-1]= 8448 c= 84488, &c[n-1]= 16448 スタック領域プログラム領域 データ領域 1,, 8バイト = 8,,バイトずつ増加 関数 sum を 1 万回呼び出しているため オーバーヘッドにより時間がかかっている プログラム領域 プログラム例 11.. 改 #iclude <stdlib.h> #iclude <widows.h> #prgm commet(lib, "wimm.lib") #defie N 1 void sum(it, double *x, double *y, double *z) it i; for (i = ; i < ; i++) z[i] = x[i] + y[i]; it i, t1, t; sttic double [N], b[n], c[n]; for (i = ; i < N; i++) [i] = rd()/1.; b[i] = rd()/1.; 関数 sumの呼び出しは1 回 sum(n,, b, c); t = timegettime(); pritf("elpsed time = %d ms ", t-t1); pritf("&i=%u, &t1=%u, &t=%u ", &i, &t1, &t); pritf("mi=%u, sum=%u ", mi, sum); pritf("=%1u, &[N-1]=%1u ",, &[N-1]); pritf("b=%1u, &b[n-1]=%1u ", b, &b[n-1]); pritf("c=%1u, &c[n-1]=%1u ", c, &c[n-1]); retur ; ex11.c ポインタを使って結果を返す方法 実行時間は ex11 1.c と同程度 11. 大域変数 (globl vrible) 全ての関数からアクセス可能な変数 1.4 で学習した グローバル変数 外部変数 (exter) 自動変数 (uto) 局所変数 (locl vrible) とは対立する概念 以下のつのプログラム例を参照 静的領域に確保 スタック領域に確保
1.4 関数と変数の可視範囲より引用 関数内で宣言した変数は その関数内でのみ可視アクセス可能変数の可視範囲 = スコープある関数の中でのみ通用する変数 = 局所変数 局所変数の例 プログラム例 1.4.1 mi と scope_rule の両方で同じ変数,b を用いても良い it = 1, b = ; この,b のスコープは mi のみ pritf(" 関数呼び出し前 &=%u:=%d, &b=%u:b=%d ",&,,&b,b); scope_rule(); pritf(" 関数呼び出し後 &=%u:=%d, &b=%u:b=%d ",&,,&b,b); retur ; 同じ変数名,b でも 関数ごとに別々にスコープが割り当てられる void scope_rule(void) it, b; この,b のスコープは scope_rule のみ = ; b = 4; pritf(" 関数内部では &=%u:=%d, &b=%u:b=%d ",&,,&b,b); 大域変数の例 プログラム例 11..1 void sum(void); void diff(void); double x, y; この x,y のスコープは全ての関数 x = 1.5; y = 56.7; sum(); diff(); retur ; 関数 sum や differece で新たに変数宣言していないので x,y といえば 4 行目で宣言された変数 x,y を指す void sum(void) 関数 sum や differece に引数は不要 pritf("&x=%u, &y=%u, 和は %f ", &x, &y, x + y); void diff(void) pritf("&x=%u, &y=%u, 差は %f ", &x, &y, x - y); 一見 便利! 混在する場合 プログラム例 11.. void scope_rule(void); it u, v; この u,v のスコープは全ての関数 混乱を招くので 乱用しない u = 1; v = ; pritf(" 関数の呼び出し前 &u=%u:u=%d, &v=%u:v=%d ", &u,u,&v,v); scope_rule(); pritf(" 関数の呼び出し後 &u=%u:u=%d, &v=%u:v=%d ", &u,u,&v,v); retur ; 関数内での局所変数名に大域変数名と同じ void scope_rule(void) ものを用いても構わない この u,v のスコープは scope_rule のみ it u, v; u = 1; v = ; pritf(" 関数内では &u=%u:u=%d, &v=%u:v=%d ", &u,u,&v,v); 大域変数を使わないプログラミングを心がけよう!
11.4 プロトタイプ宣言 プロトタイプ宣言とは?( 教科書 p.78 参照 ) プログラム例 1.1.1 double sum(double x, double y); retur ; double sum(double x, double y) double z; z = x + y; retur z; プロトタイプ宣言 sum という関数を使う その詳細は後で定義 引数は double 型 個 戻り値は double 型 1 行でこれだけの意味を持つ sum という関数の定義部 プログラム例 11.4. it foo(it, it, double); double br(double, double); retur ; it foo(it u, it v, double w) double br(double m, double ) プロトタイプ宣言では 関数の引数となる変数名は省略できる 当然だが 関数定義では仮引数は必要 プロトタイプ宣言の必要性 戻り値の型や引数の型のクロスチェック バグを減らすために有効 引数の変数名は省略可 呼び出す前に必要 先に関数が定義されていれば なくてもよい 下請けの関数から順に書き mi 関数を最後にすれば 必要なし 標準ライブラリはヘッダファイル内でプロトタイプ宣言 指数関数の自作 ( 演習問題 1.5) 指数関数の級数展開と漸化式 e x = = = 1, 1 は 1 まででよい x x x x = 1+ + + +...! 1!!! = x / < 1 16 で収束判定, 1 1 < x < 1 で数学ライブラリ関数と比較 =, = +
指数関数の自作 ( 演習問題 1.5) 1 Exp 1 = 1...1 x / + の出力 を返す / < 1 16 brek mi i = 1...1 Exp(i), exp(i) の出力 #iclude <mth.h> double Exp(double x) it ; double =1, =1; for (=1; <1; ++) *= x/; += ; if (fbs(/) < 1e-16) pritf("%d, ", ); brek; retur ; it i; for (i=-1; i<=1; i++) pritf("%f, %f ", Exp(i), exp(i)); retur ; 指数関数の自作 ( 演習問題 1.5) 収束確認用なので完成後は削除する 自作 ライブラリ exp.c 三角関数の自作 ( 演習問題 1.5) 三角関数の級数展開と漸化式 si.c si x = = x, = ( 1) = 1 + 1 x x x x = x + +... ( + 1)!! 5! 7! x, ( + 1) 5 =, 7 = 1 + 本日のパズル次のプログラムは何を出力するか #defie PR(x) pritf("%g t",(double)x) #defie NL putchr(' ') #defie PRINT4(x1,x,x,x4) PR(x1);PR(x);PR(x);PR(x4);NL mi() double d; flot f; log l; it i; は 1 まででよい / < 1 16 で収束判定 1 < x < 1 で数学ライブラリ関数と比較 i = l = f = d = 1/; PRINT4(i,l,f,d); d = f = l = i = 1/; PRINT4(i,l,f,d); i = l = f = d = 1/.; PRINT4(i,l,f,d); d = f = l = i = (double)1/; PRINT4(i,l,f,d); 1 4