C プログラミング入門 基幹7 (水5) 08: 関数 Linux にログインし、以下の講義ページ を開いておくこと http://www-it.sci.waseda.ac.jp/ teachers/w483692/CPR1/ 2016-06-01 1 例題:一般角の正規化を複数回行う 角度の正規化 (第5回参照) を 2 つの角に対し て行いたい a の正規化 a と b の正規化 ... ... int main(void) int main(void) { { 変数 b について も正規化したい double a = 1030; double a = 1030, b = -703; while(a >= 360) { a -= 360; } while(a >= 360) { a -= 360; } while(a < 0) while(a < 0) { a += 360; } { a += 360; } while(b >= 360) { b -= 360; } printf("%f\n", a); ... while(b < 0) ほぼ同じコードを何度も 書かなければいけない ... { b += 360; } 2 関数化 処理の固まりを新しい関数にする コードの重複が減り、読みやすくなる a と b の正規化 関数版 a と b の正規化 ... ... int main(void) int main(void) { { double a = 1030, b = -703; 変数 b について も正規化したい double a = 1030, b = -703; a = NormalizedAngle(a); while(a >= 360) { a -= 360; } b = NormalizedAngle(b); while(a < 0) { a += 360; } while(b >= 360) { b -= 360; } printf("%f, %f\n", a, b); ... while(b < 0) こういう関数を自分で作る ... ことができればいい { b += 360; } 3 関数の定義 関数定義の構文 任意の個数指定可能 引数がない場合はキー ワード void を与える 戻り値の型 関数名(型1 仮引数名1, ...) {ブロック} 異なる引数の同名関数を定義することはできない 関数を定義するために使う変数を 仮引数 (かりひきすう) という /// 一般角 angle [deg] を /// [0, 360) に正規化した値を返す double NormalizedAngle(double angle) { while(angle >= 360) { angle -= 360; } while(angle < 0) return angle; } { angle += 360; } 関数の実行結果(評価 値)は double 型の値 関数の仕様をコメントで 説明する double 型の値を一つ受 け取る関数で、その仮引 数名を angle としてい る (名前は任意) 仮引数は変数と同じなの で、変更してもよい。 4 戻り値を持たない関数 戻り値の型を void とする ブロック末尾の return を省略可能 /// Hello, world を単に表示するだけの関数 void printHelloWorld(void) { printf("Hello, world!\n"); return; } 末尾にある場合は、書かなくてもよい 省略することはできない 5 引数の不要な関数 仮引数に void を書く 省略はできない /// Hello, world を単に表示するだけの関数 void printHelloWorld(void) { printf("Hello, world!\n"); return; 引数を取らない関数を表す。 省略することはできない } C++ では引数のない関数は (void) の代わりに () だけで定義 してもよい。 6 main 関数 main() も関数の一つ OS から呼び出されたと考える 戻り値はプログラムの実行がどのように終了 したかを OS に報告するステータスコード 0 が正常終了、それ以外は異常終了 int main(void) 引数は無し { ... int main(int ac, char** av); ... return 0; } main 関数の引数は仕様で決められてい る。 void 以外に、以下のものが許され ている。これは、今後の講義で解説する 正常終了 <stdlib.h> には, EXIT_SUCCESS, EXIT_FAILURE というマク ロ定数が用意されていて、それぞれ 0 と 1 である。 return 0; の代わりに return EXIT_SUCCESS; などとして使うこと ができる。 7 例題のコード全体 最初の例題を書き換えた完全なコード 呼び出し時に与え プログラムは main() から始まる る値を実引数とい う #include <stdio.h> double a = 1030, b = -703; /// 一般角 angle [deg] を main 関数とは別の場所 double に関数定義を書く na, nb; // 正規化後の角度 /// [0, 360) に正規化した値を返す na = NormalizedAngle(a); double NormalizedAngle(double angle) nb = NormalizedAngle(b); { while(angle >= 360) { angle -= 360; } printf("Angle %f -> %f\n", a, na); while(angle < 0) printf("Angle %f -> %f\n", b, nb); { angle += 360; } return angle; } return 0; } int main(void) { 計算結果として返され る戻り値型 double の 値を代入 あとで説明する通り、この例は意図的に main の前にユーザー定義関数を書いている 8 関数の呼び出しと値渡し (call by value) 値渡し:引数の値が仮引数にコピーされる ... /// 一般角 angle [deg] を /// [0, 360) に正規化した値を返す double NormalizedAngle(double angle) 関数の自動変数は呼び出しの時に作られ、 終了時点で削除される NormalizedAngle NormalizedAngle angle angle { while(angle >= 360) { angle -= 360; } while(angle < 0) { angle += 360; } return angle; } int main(void) 1030 310 自動変数は削除 される コピー コピー -703 新しく自動 変数が作成 される main { double a = 1030, b = -703, na, nb; 310 1030 na = NormalizedAngle( a ); nb = NormalizedAngle( -703 b ); ... a b 1030 -703 na 310 ?? nb ?? 9 変数名の重複 関数ごとに変数名は独立している スコープはそれぞれの関数ブロック #include <stdio.h> /// 一般角 a [deg] を /// [0, 360) に正規化した値を返す double NormalizedAngle(double a) 他の関数のことを気にせず、自由に変 数名を使える。 ただし、大域変数の名前はスコープが全体なので 隠蔽してしまう(詳細略) { if(a >= 360) ... } int main(void) { 関数 NormalizedAngle の変数 a 関数 main の変数 a 互いに何の影響 も及ぼさない int a = 1030, na; na = NormalizedAngle(a); ... 10 関数の再帰呼び出し 関数が自分自身を呼び出すことを再帰呼び出 し (recursive call) という 呼び出しの度に、局所変数は別のものとして 作られる 階乗の漸化式 𝑛 ⋅ 𝑛 − 1 !, 𝑛 > 1 𝑛! = 1, 𝑛 = 1 /// 階乗 int fact(int n) { if(n == 1) return 1; else } 再帰呼び出しをしない条件が必ず存在する return n*fact(n-1); 再帰呼び出し 再帰呼び出しが関数の末尾 にある場合は、必ずループ による計算に等価変換でき る。詳しくは末尾再帰など で調べるとよい。 11 標準ライブラリ関数 printf() などの関数プロトタイプはリファレ ンスサイトで調べることが可能 関数プロトタイプを書いていないのに、なぜ コンパイルエラーにならないのか…? ヘッダファイルに書かれている 標準ライブラリ関数の例 ... は省略ではなくて、 C の文 法の一部であり、可変個の引数を 指定できることを表す 関数プロトタイプ 宣言されているヘッダファイル int abs(int j); stdlib.h double sin(double x); math.h int printf(const char *format, ...); stdio.h int rand(void); stdlib.h 12 関数のスコープ 変数と同様、関数の宣言からファイルの終わ りまでがスコープ (=使用できる範囲) #include <stdio.h> 関数 Normaliz edAngle のス コープ /// 一般角 angle [deg] を /// [0, 360) に正規化した値を返す double NormalizedAngle(double angle) { ... } int main(void) ファイル終端 { ... na = NormalizedAngle(a); } main を最後に 書いて、ほかの 関数をすべて前 に書くスタイル のコードは多い 13 関数プロトタイプ宣言 (prototype) 前方宣言によりスコープを広げる 関数プロトタイプ宣言 #include <stdio.h> #include <stdio.h> ここでは未定義 エラーとなる double NormalizedAngle(double a); int main(void) int main(void) { { 定義ブロックを 書かずにセミコ ロンで終わる ... ... na = NormalizedAngle(a); na = NormalizedAngle(a); } } /// 一般角 a [deg] を /// 一般角 a [deg] を /// [0, 360) に正規化した値を返す /// [0, 360) に正規化した値を返す double NormalizedAngle(double a) double NormalizedAngle(double a) { ... } 呼び出しより後 ろに定義がある スコープ 問題なく呼び出せる { ... } 14 コンパイラの気持ちになって考える コンパイラは、プログラムが文法通りに正しくかかれている かをチェックする。 チェックは、ソースコードにかかれている順で1度のみ行わ れる。 関数の呼び出しをチェックする際に、その関数がどういうも のかを知る必要がある。 関数を使う前に定義されていれば問題ない。 自分で作った関数の定義が使う場所より後にある場合は、プロトタイプ 宣言を書く。 標準ライブラリの関数 (printf) のことは知らないので、 <stdio.h> ファイルを読み込んで、教える。このファイルにはプロトタイプ宣言が 列挙してある。 もちろん、自分で調べて自分で書いてもよいが、それは大変面倒である。 ヘッダファイルはこの作業を簡単にするためにある。 15 ヘッダファイル 関数プロトタイプなどをまとめたもの 普通、関数の定義そのものは含まれていない #include によって, その位置に内容が展開される 各種ライブラリ(標準でないものも含む)は、 必ずヘッダファイルが提供される 関数の使い方等の情報が書かれていることも多い 関数のリファレンスにはどのヘッダか必ず書かれ ている stdio.h などを必ず読み込まなければいけないというわけではない。その代わりに、使おうとする関数の プロトタイプ宣言を自分で書いてもよい。ただし、ヘッダファイルに書かれる内容は環境により変わる可 能性があるので、自分で正しいものが書ける保証はない。 16 これまでの知識でできないこと 配列を引数として渡す, 戻り値として返す 文字列を扱う 呼び出し元の変数を直接書き換える 例: 2 つの変数の値を入れ替える関数 例: scanf() はそのようなことを行う関数の一つ ⇒ポインタにより実現(次回以降説明) 17
© Copyright 2024 ExpyDoc