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