[前回(2019/07/05)の復習 前回(2019/07/05)の内容 (二週目に入った) 知識も技術も習得のためには、繰り返しが必要 講義 => 演習 => 復習をする 知識 : 繰り返しにコストがかからない(頭の中でできる) 技術 : 作業を伴うので、繰り返すのは結構大変 => 講義(演習)の中で、繰り返しを強制する => 繰り返し学習をする <= 先週から、繰り返しの二週目に入る 「Hello, World」 again (お呪いが減った) [hello.c] -- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< -- #include int main(int argc, char *argv[]) { printf ( "Hello, World\n" ); return 0; } -- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< -- 初回 : いろいろなお呪いがあった => お呪いはお呪いのまま、printf の部分に着目して議論 前回 : お呪いの解説 #incldue => 他のファイルを読み込んでいる stdio.h => 標準入出力関数の関数宣言(等)が入っている # 少なくても printf 関数のプロトタイプ宣言が入っている # => 標準入出力関数(printf,getchar,putchar)を使う時には必要 main 関数 C のプログラムには必ず必要 # C プログラムは、関数の集まりとして作成できる # <= その中の一つが main 関数であり、そこからプログラムがスタート # => main 以外の関数は、main から直接あるいは間接的に呼び出される 先頭の int (main の前) と、最後の return 0; は、このプログラムが、 システムに「整数型の値(エラーコード)を返す」事意味している その値(エラーコード)が 0 (エラーでない事を表す)である 「システムにエラーコードを返す : main 関数」 引数の argc, argv それぞれ、コマンドライン引数の個数とその値(文字列)を意味する 御呪いでない部分 C 言語で作られた完全なプログラム => これを雛形にして、いろいろなプログラムを作成する事ができる C 言語の最初のサンプルプログラム => プログラムのコンパイルやリンク、実行の方法を学ぶ printf 関数の機能(引数に与えられた文字列を画面に表示する) => C 言語で利用できる基本機能(標準ライブラリ)の API を学ぶ最初の例 順接 「printf 関数を呼ぶ」(命令を実行する)と、メッセージが表示されるという「効果」がある(hello.c) この printf を並べる(命令をならべる)と、その命令が、「書かれた順に実行」される => C 言語が持つ「順接」という「表現方法」の効果 # 関数の定義順は、関数の実行順序とは無関係(関数定義は命令じゃない) => 有限の結果を成したい場合は、 その結果を導くような命令をならべる(呼び出す関数の選択と順接)だけで実現可能 cf. 自分の名前を 100 回書く => 今 : 再帰(繰り返し)で行う => 当時 : 命令を 100 回書けば、実現できる 「命令を組み合わせる(制御構造)」:プログラムの基本 順接は(基本となる三つの)制御構造の一つ => 残りの制御構造(条件分岐[if 構文]/繰り返し[再帰])と合わせて、万能になる == [今日の内容] 関数の作り方と使い方 again 関数とは [数学(入門 A)] 定義域と値域があり、それぞれの集合の要素間の対応関係で、定義域の値が一つ定まれば、 必ず、値域の値が定まるもの C 言語でも基本、関数の考え方は同じ 例 : y = x^2 を計算する関数 square => p-001.c C 言語でも「関数」が定義できる 定義域 : 引数の型 値域 : 関数の値型 対関係 : return の所に値となる計算式を書く 一般に y=f(x) [f(x) は x の式] となる C 言語での関数の定義 !!! function => 関数 / 機能 値域型 関数名(定義域の型 その値を持つ変数) { return 値を計算する式; } 引数が無い場合は、定義域がないので、引数は void と宣言して、呼び出し時は、 引数を記述しない。 一方、「値を持たない『関数』」も作れて、その場合は、「値の型」の所に「void」と書く また、値を書かない関数は、他の関数の引数にできないので、直接「関数名(引数);」の形 で呼び出す(必要がある..) 一般に、値を返さない関数は、「関数(何らかの計算を行い、その結果を値として返す)」として 主作用は、存在しないが、副作用を記述できるので。それを目的として、 作る意味がある cf. 関数の導入 printf ( .. ) => 副作用を行う「関数」 副作用に対して、名前を付ける事が可能 => 値を返さない関数(void 型)を導入した 「関数」の定義の仕方 1. (純粋に..)数学的な関数 ( y = f(x) を計算する関数 ) を定義する場合 値域の型 関数名(定義域の型 x) { return f(x); } s_prnit_xxx( 関数名(数値) ) => 数値から関数の値を計算した結果が表示される 2. 副作用を持つ命令列に名前をつけて(その副作用を利用するために)関数とする場合 void 関数名(void) { 副作用を持つ命令列 } !! プログラムのまとまった一部に「名前」をつけ、 !! その名前を指定する事により、その部分を「再利用」できる !! 事が大事 !! C 言語における「関数」は、一般の言語の「モジュール(部品)」に相当 [拡張] 引数は、複数指定する事が可能で、 個々の引数には、別の名前(仮引数変数名)を付け、それぞれに型を指定する 関数としては、 n 引数関数 or n 個の直積集合の要素を一つが定義域になる 条件分岐 : 条件によって、異なる複数の命令のいずれかを実行する「構文(命令の組み合わせを表現する方法)」 C 言語では、条件分岐(の表現方法の一つ)として「if 構文」がある if ( 条件 ) { 条件が成立した時に実行する命令列 } else { 条件が不成立の時に実行する命令列 } !!! 後期 : if 構文以外の条件分岐の記述方法がある... !!! 但し、任意の条件分岐は、if 構文の組み合わせで実現可能 繰り返し : 同じ命令を何度も繰り返し実行する構文 !!! 「何度も繰り返す」.. 「具体的は何回 ??」 !!! => 実際に「繰り返し」を行う場合は、「繰り返しが終わった」という条件がわかる必要がある C 言語では(実は普通に繰り返しを行う構文が他にあるが..)、再帰呼び出しで、実現する 例: n! = n * (n-1) * .. * 2 * 1 => n の値によって、掛け算の個数が異なる 例 : 3! = 3 * 2 * 1 => 2 回 ( 3-1 ) 回の掛け算 5! = 5 * 4 * 3 * 2 * 1 =. 4 ( 5-1 ) 回の掛け算 <= 記述された命令の長さと、実行される命令の個数(長さ)が異なる cf. 順接は、記述された命令の長さと、実行される命令の個数(長さ)が同じ => 原理的に、有限の記述で無限の種類の命令が実行できる cf. n! は n の値によって、異なる個数の命令が実行されている 再帰の場合は、基本、繰り返せる命令の種類は一定 関数 f() を、終了条件になるまで、繰り返すための再帰的関数定義 rec_f(n) { if ( n に関する終了条件 ) { 終了処理 } else { f() ; 繰り返したい事 rec_f ( next (n) ); n を次の状態にして再帰呼び出し # next n は、n が終了条件を満たすまで、「減らす」作業 # 自然数なら -1 する => 1 (終了条件を満たす) に近づける # 文字列なら +1 する => "" (終了条件を満たす) に近づける } } 与えられた問題の解を、再帰的定義に変換できれば、 それは、C 言語の再帰呼び出しで実現できる