• 検索結果がありません。

/* do-while */ #include <stdio.h> #include <math.h> int main(void) double val1, val2, arith_mean, geo_mean; printf( \n ); do printf( ); scanf( %lf, &v

N/A
N/A
Protected

Academic year: 2021

シェア "/* do-while */ #include <stdio.h> #include <math.h> int main(void) double val1, val2, arith_mean, geo_mean; printf( \n ); do printf( ); scanf( %lf, &v"

Copied!
18
0
0

読み込み中.... (全文を見る)

全文

(1)

情報処理演習

関数

その1

システム科学科 生物工学コース 佐々木耕太

http://www7.bpe.es.osaka-u.ac.jp/~kota/classes/jse.html kota@fbs.osaka-u.ac.jp

(2)

/* ふたつの非負の数の相加平均と相乗平均を計算する(do-whileの例) */

#include <stdio.h>

#include <math.h>

int main(void) {

double val1, val2, arith_mean, geo_mean;

printf(“ふたつの非負の数の相加平均と相乗平均を計算します\n”); do { printf(“ひとつめの値を入力してください:”); scanf(“%lf”, &val1); } while (val1 < 0.0); do { printf(“ふたつめの値を入力してください:”); scanf(“%lf”, &val2); } while (val2 < 0.0);

arith_mean = (val1 + val2) / 2.0; /* 相加平均 */ geo_mean = sqrt(val1 * val2); /* 相乗平均 */

printf(“相加平均は%f、 相乗平均は%fです\n”, arith_mean, geo_mean); return 0; } ほとんど同じ手続きが複数ある 関数を作る 冗長だし、変更が必要になったら、 似たような手続きのところをすべて 変更しなくてはいけない(離れた場 所にあったら変更し忘れるかもしれ ない。もっとたくさんあったら変更 は面倒だし、どこか変更し忘れた り、変更し間違えるおそれもある。)

(3)

関数のつくりどころ

似たような手続きがいくつかあるとき ある程度汎用的に使用できるように関数を作成する

プログラムの構造(アルゴリズム)を明確にしたいとき 煩雑な計算が関数になっていたり、必要に応じて手続 きの段階ごとに関数にわかれていたりする方が、プロ グラムの構造がわかりやすい。特に、自作の関数を利 用せずに、mainのブロックだけが数千行にもわたる プログラムはとても読みづらく、わかりづらい。

いろいろなプログラムで共通に使用(再利用)できる ように、ライブラリを作成するとき

HandyGraphicや各種SDK(software development kit) 例:

関数: 一連の手続き(作業)をまとめたもの

もしsin(x)が決まったxの値についてしか正弦の値を計算

できないなら、汎用性がなく、使い勝手がよくない 例:

(4)

関数の利点

同じことを何度も書かなくてよい 同じように関数を呼び出せばよい(1文ですむ) 似たようなことを何度も書かなくてよい 引数を変えて関数を呼び出せばよい(1文ですむ) 関数が実際に何をしているのか必ずしも知らなくてよい 煩雑な手続きを隠せる(ブラックボックス化 できる)ので、プログラムがわかりやすくなる 正弦の値を得るための面倒くさい計算を毎度見る ことも、いちいち気にすることもなく、sin関数 を使えば正弦の値が得られる 例:

(5)

関数の利点

関数ごとに作成し、デバッグできる(部品化) 関数だけを独立に作成、デバッグ(、コンパイル)し、 完成させたら、他のプログラムから(再)利用したり、 他の人に配布したりすることもできる プログラムを作成(開発)する効率が上がる http://www.beembee.com/2010/lego-cities (規格化された)単純な部品を(再)利用して  “すごい” ものを作る

(6)

関数の仕様

sin関数を例に

double

sin

(

double x

)

{

/* 実際にすること */

return

double型の(変)数

;

}

戻り値の型

戻り値がないときはvoid型

戻り値はあってもひとつだけ

void型のとき、returnの文はreturn;とするか、書かない

引数

引数の数、それぞれの型

引数が複数あるときは,で区切る

引数をとらないときはvoid型

関数の名前

(7)

/* ふたつの非負の数の相加平均と相乗平均を計算する */

#include <stdio.h>

#include <math.h>

double getNonNegativeDouble(void); int main(void)

{

double val1, val2, arith_mean, geo_mean;

printf(“ふたつの非負の数の相加平均と相乗平均を計算します\n”); val1 = getNonNegativeDouble();

val2 = getNonNegativeDouble();

arith_mean = (val1 + val2) / 2.0; /* 相加平均 */ geo_mean = sqrt(val1 * val2); /* 相乗平均 */

printf(“相加平均は%f、 相乗平均は%fです\n”, arith_mean, geo_mean); return 0;

}

double getNonNegativeDouble(void)

/* キーボードから入力された非負の実数を返す関数の定義 */ { double val; do { printf(“非負の実数の値を入力してください:”); scanf(“%lf”, &val); } while (val < 0.0); return val; } 関数のプロトタイプ宣言 関数の定義 (関数が実際に行う手続き) なんという名前で、どういう引数を取って、 どんな戻り値を返すかという、関数の簡単な 紹介 ;を忘れずに 関数の戻り値 関数の呼び出し 何をする関数なのか、 必ず説明を書く

(8)

関数のプロトタイプ宣言

double getNonNegativeDouble(void); getNonNegativeDoubleという名前で、引数を取らずに、 double型の戻り値を返す関数 例 解説

double power(double x, int n);

powerという名前で、ひとつめの引数にdouble型、ふたつめの引

数にint型の変数を取り、double型の戻り値を返す関数

void swap(int x, int y);

swapという名前で、int型の引数をふたつ取り、戻り値はない関数

int fact(int n);

factという名前で、int型の引数をひとつ取り、int型の戻り値を 返す関数

(9)

/* 実数のべき乗を求める関数を使う */

#include <stdio.h>

#include <math.h>

double power(double x, int n); /* power関数のプロトタイプ宣言 */ int main(void)

{

int n; double x;

printf(“実数xを何乗か(自然数乗)します\n”);

printf(“実数xの値を入力してください:”); scanf(“%lf”, &x);

printf(“何乗しますか(自然数)?:”); scanf(“%d”, &n);

printf(“%.3fの%d乗は%.3fです\n”, x, n, power(x, n)); return 0;

}

double power(double x, int n)

/* 実数xのn乗を返す関数powerの定義 */ { int i; for (i = 1; i < n; i++) { x *= x; } return x; } 引数や戻り値があるなら、 それらの説明を怠らない この関数は期待通りには 動作しない。訂正せよ。

(10)

変数のスコープ(有効範囲)

変数は、それを宣言してから、そのブロックの中でのみ 使うことができる(変数のスコープ(有効範囲)は、変 数を宣言したブロックに限られる)

よって、main関数の中の変数はmain関数の中でしか使 えないし、自分で作った関数の中の変数はその関数の中 でしか使えない

すなわち、自分で作った関数の中の変数に、main関数 の中の変数と同じ名前をつけたとしても、それらの変数 はまったく別のものである

また、引数として渡された変数の値を関数の中で変更し ても、呼び出し元の変数の値はもとのまま保存されてお り、破壊されない(変数の値渡し)

(11)

変数のスコープ(有効範囲)

main関数内の変数xの値が、foo関数

の中で宣言された、別の変数xに渡さ

れる(変数の値渡し)

/* #include <stdio.h>など */

int main(void) { int i, n; double x, y; /* xへの値の代入など */ y = foo(x); /* yの値の出力など */ return 0; }

double foo(double x) /* foo関数の定義 */

{ int i; double y; /* なにか計算する */ return y; } foo関数の中で宣言された変数 は、main関数の変数と(名前が同 じでも)まったく別のもので、 foo関数の中でしか使えない foo関数の中の変数yの値が、 main関数内の別の変数yに渡される

(12)

ふたつの変数の値を入れ替えたいが、

以下の関数はうまくいかない。なぜ?

/* うまくいかないswap関数... */

#include <stdio.h>

void swap(int x, int y); /* swap関数のプロトタイプ宣言 */ int main(void)

{

int x, y; x = 5; y = 3;

printf(“swap関数を呼び出す前:x = %d, y = %d\n”, x, y);

swap(x, y);

printf(“swap関数を呼び出した後:x = %d, y = %d\n”, x, y); return 0;

}

void swap(int x, int y)

/* 受け取った2つの引数の値を入れ替える関数swapの定義 */ {

int tmp;

printf(“swap関数に入ってきたとき:x = %d, y = %d\n”, x, y); tmp = y;

y = x; x = tmp;

printf(“swap関数を出るとき:x = %d, y = %d\n”, x, y); }

戻り値がないとき、 returnの文は

(13)

引数として与えた変数の値を

変更するには

授業の範囲外(で、かつ、C言語でおそらく一番 つまづくところ)なので説明しないが、実はだい ぶ前からお世話になっている scanf(“%d”, &n);

授業の範囲外といっても、次次回にあつかう「関 数に引数として配列を渡す」のは、そう説明しな いだけで、アドレス渡しに他ならない ポインタ引数へのアドレス渡し 参考: キーボードから入力された値をnに代入 してほしいのに、「nは別の変数だから 値は変更されない」では役に立たない &はアドレス(コンピュタの メモリ上で変数がある場所) を得る演算子

(14)

関数の再帰呼び出し

/* 1からNまで階乗を計算する */

#include <stdio.h>

#define N 10

int fact(int n); /* fact関数のプロトタイプ宣言 */ int main(void) { int i; double x; printf(“1から%dまで階乗を計算します\n”, N); for (i = 1; i <= N; i++) {

printf(“%3d! = %d\n”, i, fact(i)); }

return 0; }

int fact(int n)

/* nの階乗を返す関数factの定義 */ { if (n == 1) { return 1; } else { return n * fact(n - 1); } } 関数の中で、その関数自身を呼び 出すことを再帰呼び出しという 例えば、fact(3)を実行すると… 3 * fact(2) 2 * fact(1) 4.fact(1)の 戻り値の代入 2.fact(2)の 呼び出し 1 3.fact(1)の 呼び出し 5.fact(2)の 戻り値の代入 6.fact(3)の戻り値が得られる 1.fact(3)の 呼び出し

(15)

printf関数などのプロトタイプ宣言は?

printfやscanfも関数なので、これらを使用するた めにはプロトタイプ宣言が必要である。

このため、こうしたよく使う関数(標準Cライブラリ の関数)を使用するための変数宣言やプロトタイプ宣 言が書かれたヘッダーファイルが用意されている。

プログラム冒頭の#include <stdio.h>というおま じないは、(printfなどの)標準入出力(standard input/output)関数のプロトタイプ宣言などをstdio.h というヘッダーファイルから読み込むために書いてい た(同様に、#include <math.h>は…)。

コンパイルのときに、関数の使われ方(引数の型や数 など)がプロトタイプ宣言に違反していないか確認さ れる。このとき、関数が定義されているかどうかはと りあえず気にしない。

(16)

では、標準

Cライブラリの

関数の定義はどう

“解決”する?

ソース ファイル オブジェクト ファイル 実行可能 ファイル 標準Cライブラリの ライブラリ ファイル プリプロセス と コンパイル リンクによる 関数定義の解決 • #で始まる命令(includeやdefineなど)を、プリプロセッサ指令(ディレク ティブ)といい、これらはコンパイルを行う前に処理される(プリプロセス) • ライブラリファイルとは、いろいろなプログラムで共通に使用(再利用)でき るように、いくつもの関数のオブジェクトファイルをアーカイブにしたもの • gccコマンドを使用するときに-vをつけると(verbose)、実行可能ファイルが 生成されるまでの過程をひとつひとつ表示してくれる

実行可能ファイル生成の概略 このライブラリファイルに 関数の定義がまとめられている

(17)

gccのリンカオプション-lmと

undefined reference to ‘***’

標準Cライブラリの関数の定義は、ライブラリファイルにまとめ られている。

GCC(授業で使用しているコンパイラ。コンパイルだけでなく、 リンクを含め、実行可能ファイルを生成するために必要なことを すべて行ってくれる。)は、標準入出力関数をはじめとするほと んどの関数について、ライブラリファイルから必要なものをリン クし、関数定義を解決してくれる。

しかし、数学関数のライブラリファイルは上記のライブラリファ イルとは別になっており、 GCCは自動的にリンクしない。した がって、関数の定義を解決するために必要なライブラリファイル をリンクするよう、gccコマンドを使用するときにリンカオプ ション-­‐lmを明示しなくてはいけない。

プログラムの中で数学関数を使用しているのにgccコマンドで -lmを忘れたときのundefined reference to ‘***’という エラーは、「***という関数っぽいものを呼ぼうとしてるけど、 定義されていないからどうしたらいいかわからない (ノω・、)ウゥ・・・」というコンパイラの嘆き。

(18)

「おまじない」の役割を

説明できる?

/* ふたつの非負の数の相加平均と相乗平均を計算する(do-whileの例) */ #include <stdio.h> #include <math.h> int main(void) {

double val1, val2, arith_mean, geo_mean;

printf(“ふたつの非負の数の相加平均と相乗平均を計算します\n”); do { printf(“ひとつめの値を入力してください:”); scanf(“%lf”, &val1); } while (val1 < 0.0); do { printf(“ふたつめの値を入力してください:”); scanf(“%lf”, &val2); } while (val2 < 0.0);

arith_mean = (val1 + val2) / 2.0; /* 相加平均 */ geo_mean = sqrt(val1 * val2); /* 相乗平均 */

printf(“相加平均は%f、 相乗平均は%fです\n”, arith_mean, geo_mean); return 0; } 何のため? ここのintとvoidは何? これは何をしている?

プログラムはmainから 実行されるという点に おいてのみ特殊で、 mainも関数の体裁に のっとっている

参照

関連したドキュメント

これはつまり十進法ではなく、一進法を用いて自然数を表記するということである。とは いえ数が大きくなると見にくくなるので、.. 0, 1,

と言っても、事例ごとに意味がかなり異なるのは、子どもの性格が異なることと同じである。その

しかし , 特性関数 を使った証明には複素解析や Fourier 解析の知識が多少必要となってくるため , ここではより初等的な道 具のみで証明を実行できる Stein の方法

LF/HF の変化である。本研究で はキャンプの日数が経過するほど 快眠度指数が上昇し、1日目と4 日目を比較すると 9.3 点の差があ った。

るものの、およそ 1:1 の関係が得られた。冬季には TEOM の値はやや小さくなる傾 向にあった。これは SHARP

断するだけではなく︑遺言者の真意を探求すべきものであ

いてもらう権利﹂に関するものである︒また︑多数意見は本件の争点を歪曲した︒というのは︑第一に︑多数意見は

□ ゼミに関することですが、ゼ ミシンポの説明ではプレゼ ンの練習を主にするとのこ とで、教授もプレゼンの練習