PowerPoint プレゼンテーション

第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


n0
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←Sa
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 

n0
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