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