Download : sample-001.c
/* * 2017/06/30 sample-001.c * * [コンパイル] * cc -I ~/include -c sample-001.c * [リンク] * cc -o sample-001.exe sample-001.o * [実行] * ./sample-001.exe */ /* * 入力を一度だけ処理 */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * main 関数 */ int main ( int argc, char *argv[] ) { s_print_string ( "整数値を一つ入力してください、一つ増やした値を出力します:" ); s_print_int ( s_input_int() + 1 ); /* 整数値を入力し、1 を増やし、出力 */ s_print_newline(); return 0; }
123
$ ./sample-001.exe < sample-001.in 整数値を一つ入力してください、一つ増やした値を出力します:123 124 $
Download : sample-002.c
/* * 2017/06/30 sample-002.c * * [コンパイル] * cc -I ~/include -c sample-002.c * [リンク] * cc -o sample-002.exe sample-002.o * [実行] * ./sample-002.exe */ /* * 入力を複数回処理 * 入力関数(s_input_int())が呼び出される度に、入力が行われる */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * main 関数 */ int main ( int argc, char *argv[] ) { /* * 一回目 */ s_print_string ( "一回目:整数値を一つ入力してください、一つ増やした値を出力します:" ); s_print_int ( s_input_int() + 1 ); s_print_newline(); /* * ニ回目 */ s_print_string ( "ニ回目:整数値を一つ入力してください、一つ増やした値を出力します:" ); s_print_int ( s_input_int() + 1 ); s_print_newline(); /* * */ return 0; }
123 456
$ ./sample-002.exe < sample-002.in 一回目:整数値を一つ入力してください、一つ増やした値を出力します:123 124 ニ回目:整数値を一つ入力してください、一つ増やした値を出力します:456 457 $
Download : sample-003.c
/* * 2017/06/30 sample-003.c * * [コンパイル] * cc -I ~/include -c sample-003.c * [リンク] * cc -o sample-003.exe sample-003.o * [実行] * ./sample-003.exe */ /* * 一つの入力を複数回処理 * 入力関数の呼出し(入力)は一回だが、値(入力値)は複数利用したい * 関数(変数)を利用するしかない */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * 引数で与えらえた値を複数回利用する(関数/引数変数の特権 !!) */ void print_add_one_result ( int value ) { s_print_string ( "整数値 " ); s_print_int ( value ); /* 一度目の利用 */ s_print_string ( " に 1 を加えた値は " ); s_print_int ( value + 1 ); /* 二度目の利用 */ s_print_string ( "これを 2 倍した値は " ); s_print_int ( value * 2 ); /* 三度目の利用 */ s_print_string ( " になります。\n" ); } /* * main 関数 */ int main ( int argc, char *argv[] ) { /* * 一回目 */ print_add_one_result ( s_input_int() ); /* 「入力(機能)」は「ここ」で一回 */ /* 「入力値」は、関数内で、何度も利用可能 */ /* * ニ回目 */ print_add_one_result ( s_input_int() ); /* 二度目の入力 */ /* * */ return 0; }
123 456
$ ./sample-003.exe < sample-003.in 123 整数値 123 に 1 を加えた値は 124これを 2 倍した値は 246 になります。 456 整数値 456 に 1 を加えた値は 457これを 2 倍した値は 912 になります。 $
Download : sample-004.c
/* * 2017/06/30 sample-004.c * * [コンパイル] * cc -I ~/include -c sample-004.c * [リンク] * cc -o sample-004.exe sample-004.o * [実行] * ./sample-004.exe */ /* * インプットループ * 入力を繰り返す関数 */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * '$' が入力されるまで、文字を入力し、それを二度ずつ出力する */ void print_twice_char ( char ch ) { s_print_char ( ch ); /* とりあえず二度出力 */ s_print_char ( ch ); if ( ch == '$' ) { /* '$' だった / もうこれ以上処理しない */ /* 何もしない */ } else { /* まだ処理する必要がある */ print_twice_char ( s_input_char() ); /* 入力をして、再帰 */ } } /* * main 関数 */ int main ( int argc, char *argv[] ) { /* * 最初の呼出しは main で.. */ print_twice_char ( s_input_char() ); /* * */ return 0; }
abc$
$ ./sample-004.exe < sample-004.in a aa b bb c cc $ $$$
Download : sample-005.c
/* * 2017/06/30 sample-005.c * * [コンパイル] * cc -I ~/include -c sample-005.c * [リンク] * cc -o sample-005.exe sample-005.o * [実行] * ./sample-005.exe */ /* * 乱数 */ #include <stdio.h> #include <stdlib.h> /* rand を利用する場合 */ #include "s_print.h" /* * main 関数 */ int main ( int argc, char *argv[] ) { /* * */ s_print_string ( "乱数を出力します : " ); s_print_int ( rand() ); s_print_newline(); /* * */ return 0; }
$ ./sample-005.exe 乱数を出力します : 1804289383 $
/* * 20170623-03-QQQQ.c * * 二つの整数の最大公約数を返す * * コンパイル : * cc -I../include -c 20170623-03-QQQQ.c * cc -o 20170623-03-QQQQ.exe 20170623-03-QQQQ.o * 実行 : * ./20170623-03-QQQQ.exe * */ #include <stdio.h> #include "s_print.h" /* s_print_int を利用するので.. */ /* * int euclid ( int a, int b ) * int a : 非負の整数 * int b : 非負の整数 * 返り値 : a と b の最大公約数を返す */ int euclid ( int a, int b ) { if ( b == 0 ) { /* 任意の数 a と 0 の最大公約数 (a,0) は a となる */ return a; } else { /* b が 0 でない時 (a,b) = (b,a%b) となる */ return euclid ( b, a%b ); /* a % b は a を b で割った余りを計算する */ } } /* * main */ int main( void ) { s_print_int ( 12 ); s_print_string ( " と、 " ); s_print_int ( 18 ); s_print_string ( " の最大公約数は " ); s_print_int ( euclid( 12, 18 ) ); s_print_string ( " です。\n" ); s_print_int ( 1111 ); s_print_string ( " と、 " ); s_print_int ( 111111 ); s_print_string ( " の最大公約数は " ); s_print_int ( euclid( 1111, 111111 ) ); s_print_string ( " です。\n" ); return 0; }
/* * CDATE FILENAME * * 数当てを行う */ #include <stdio.h> #include "s_print.h" /* s_print_int を利用するので.. */ #include "s_input.h" /* s_input_int を利用するので.. */ /* * prompt * メッセージを出力し、キーボードから整数値を読み込んで、 * その値を返す関数 */ int prompt(void) { s_print_string ( "私が選んだ数を予想して入力してください : " ); return s_input_int(); } /* */ void game ( int input, int answer ) { if ( input == answer ) { /* 入力と答えが一致した */ s_print_string ( "お見事です。あたりました。\n" ); } else { if ( input > answer ) { /* 入力が、答えより大きい */ s_print_string ( "その数は大きすぎます。\n" ); } else { /* 入力が、答えより小さい */ s_print_string ( "その数は小さすぎます。\n" ); } /* 未だ、答えが、当っていないので、ゲームを続ける.. */ /* 再帰呼び出しをする */ /* インプット・ループなので、入力からして再帰 */ game(prompt(),answer); /* prompt() で、入力をしてから、ゲームを続ける */ } } /* * main */ int main( void ) { /* 乱数系列の変更 */ srand((unsigned) time(NULL)); /* これによって、毎回異なる問題が作られる [参考文献] http://www9.plala.or.jp/sgwr-t/lib/srand.html */ s_print_string ( "私が考えた 1 〜 100 数を予想して当ててみてください。\n" ); game ( prompt(), (rand()%100) + 1 ); /* ゲームを始めるまえに、最初の入力を済ませる必要がある ゲームの問題は、rand()を使って行う rand() -- 整数値の乱数を返す %100 する事によって、この数は 0 ? 99 になるので、 これに 1 を加えることにより、 1 ? 100 になる */ return 0; }
#include <stdio.h> /* "Hello, World" を無限に繰り返す 引数を指定いない 「終わり」が判断できない 再帰呼び出しをすると、無限ループ 実行を開始すると、止まらない 止めるには、Ctrl-C ( [Ctrl] + [c] ) で、強制終了 最悪は、terminal を閉じれば、自動的に強制終了になる */ void infinity_loop_print_hello(void) { printf ( "Hello, World\n" ); infinity_loop_print_hello(); /* 無条件に自分を再帰呼び出し */ } int main(int argc, char *argv[]) { infinity_loop_print_hello(); return 0; }
#include <stdio.h> /* "Hello, World" を n 回繰り返す 繰り返し回数を引数で指定 */ void ntimes_loop_print_hello( int n ) { if ( n <= 0 ) { /* 繰り返し回数は 0 以下.. */ /* なにもしなくてよい */ } else { /* 繰り返す必要がある */ printf ( "Hello, World\n" ); /* とりあえず 1 回 */ ntimes_loop_print_hello( n - 1 ); /* 残りは再帰呼び出し */ /* 引数である n の値がどんどん減ってゆく */ /* n の値が 0 以下になったら「終了」する */ /* 引数 n によって、実行が「制御」されている */ } } int main(int argc, char *argv[]) { ntimes_loop_print_hello( 10 ); /* 丁度 10 回、出力する */ return 0; }
#include <stdio.h> #include "s_print.h" int f(int x) { return x * x; /* 引数 x の二乗を返す関数 */ } int main(int argc, char *argv[]) { s_print_string ( "3 の二乗は " ); s_print_int ( f(3) ); s_print_string ( " になります。\n" ); return 0; }
#include <stdio.h> #include "s_print.h" int f(int x) { return x * x; /* 引数 x の二乗を返す関数 */ } int main(int argc, char *argv[]) { s_print_string ( "3 の二乗の 2 倍は " ); s_print_int ( 2 * f(3) ); s_print_string ( " になります。\n" ); s_print_string ( "3 の二乗の 2 倍は " ); s_print_int ( f(3) + f(3) ); s_print_string ( " になります。\n" ); return 0; }
#include <stdio.h> #include "s_print.h" int f(int x) { s_print_string ( "[[f が呼び出されました]]" ); /* 純粋な計算をしているだけでなく、 「出力」という「副作用」をもつ */ return x * x; /* 引数 x の二乗を返す関数 */ } int main(int argc, char *argv[]) { s_print_string ( "3 の二乗の 2 倍は " ); s_print_int ( 2 * f(3) ); s_print_string ( " になります。\n" ); s_print_string ( "3 の二乗の 2 倍は " ); s_print_int ( f(3) + f(3) ); s_print_string ( " になります。\n" ); return 0; }
/* * 2017/06/30 sample-001.c * * [コンパイル] * cc -I ~/include -c sample-001.c * [リンク] * cc -o sample-001.exe sample-001.o * [実行] * ./sample-001.exe */ /* * 入力を一度だけ処理 */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * main 関数 */ int main ( int argc, char *argv[] ) { s_print_string ( "整数値を一つ入力してください、一つ増やした値を出力します:" ); s_print_int ( s_input_int() + 1 ); /* 整数値を入力し、1 を増やし、出力 */ s_print_newline(); return 0; }
/* * 2017/06/30 sample-002.c * * [コンパイル] * cc -I ~/include -c sample-002.c * [リンク] * cc -o sample-002.exe sample-002.o * [実行] * ./sample-002.exe */ /* * 入力を複数回処理 * 入力関数(s_input_int())が呼び出される度に、入力が行われる */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * main 関数 */ int main ( int argc, char *argv[] ) { /* * 一回目 */ s_print_string ( "一回目:整数値を一つ入力してください、一つ増やした値を出力します:" ); s_print_int ( s_input_int() + 1 ); s_print_newline(); /* * ニ回目 */ s_print_string ( "ニ回目:整数値を一つ入力してください、一つ増やした値を出力します:" ); s_print_int ( s_input_int() + 1 ); s_print_newline(); /* * */ return 0; }
/* * 2017/06/30 sample-003.c * * [コンパイル] * cc -I ~/include -c sample-003.c * [リンク] * cc -o sample-003.exe sample-003.o * [実行] * ./sample-003.exe */ /* * 一つの入力を複数回処理 * 入力関数の呼出し(入力)は一回だが、値(入力値)は複数利用したい * 関数(変数)を利用するしかない */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * 引数で与えらえた値を複数回利用する(関数/引数変数の特権 !!) */ void print_add_one_result ( int value ) { s_print_string ( "整数値 " ); s_print_int ( value ); /* 一度目の利用 */ s_print_string ( " に 1 を加えた値は " ); s_print_int ( value + 1 ); /* 二度目の利用 */ s_print_string ( "これを 2 倍した値は " ); s_print_int ( value * 2 ); /* 三度目の利用 */ s_print_string ( " になります。\n" ); } /* * main 関数 */ int main ( int argc, char *argv[] ) { /* * 一回目 */ print_add_one_result ( s_input_int() ); /* 「入力(機能)」は「ここ」で一回 */ /* 「入力値」は、関数内で、何度も利用可能 */ /* * ニ回目 */ print_add_one_result ( s_input_int() ); /* 二度目の入力 */ /* * */ return 0; }
/* * 2017/06/30 sample-004.c * * [コンパイル] * cc -I ~/include -c sample-004.c * [リンク] * cc -o sample-004.exe sample-004.o * [実行] * ./sample-004.exe */ /* * インプットループ * 入力を繰り返す関数 */ #include <stdio.h> #include "s_input.h" #include "s_print.h" /* * '$' が入力されるまで、文字を入力し、それを二度ずつ出力する */ void print_twice_char ( char ch ) { s_print_char ( ch ); /* とりあえず二度出力 */ s_print_char ( ch ); if ( ch == '$' ) { /* '$' だった / もうこれ以上処理しない */ /* 何もしない */ } else { /* まだ処理する必要がある */ print_twice_char ( s_input_char() ); /* 入力をして、再帰 */ } } /* * main 関数 */ int main ( int argc, char *argv[] ) { /* * 最初の呼出しは main で.. */ print_twice_char ( s_input_char() ); /* * */ return 0; }
/* * 2017/06/30 sample-005.c * * [コンパイル] * cc -I ~/include -c sample-005.c * [リンク] * cc -o sample-005.exe sample-005.o * [実行] * ./sample-005.exe */ /* * 乱数 */ #include <stdio.h> #include <stdlib.h> /* rand を利用する場合 */ #include "s_print.h" /* * main 関数 */ int main ( int argc, char *argv[] ) { /* * */ s_print_string ( "1:乱数を出力します : " ); s_print_int ( rand() ); s_print_newline(); s_print_string ( "2:乱数を出力します : " ); s_print_int ( rand() ); s_print_newline(); s_print_string ( "3:乱数を出力します : " ); s_print_int ( rand() ); s_print_newline(); /* * */ return 0; }
前回 「関数」の復習をした その中で再帰も復習 当初は、「関数」=「命令列に名前を付ける」 再帰を考えると、「再帰関数」は、「複数(原理的には無限)」の長さの命令に相当する 「『可変長』の命令列」に名前をつける 自由な形ではなくて、「『同じ命令』の繰り返し」の形 再帰プログラムの基本構造 関数名(制御変数) { if ( 制御変数が最低値かどうか ) { なにもしない } else { 「何か」一つして(繰り返す命令) 関数名 (制御変数の値を小さくする) 再帰呼び出し } } この再帰的関数は、「何か」を繰り返す関数になっている 値を返す事ができる return 命令 引数に型が指定できる ! 最初は「文字列」だけだったので「char *」がおまじない 仮引数変数の前には「その変数が保持するデータの型」を書く 整数値の引数の場合は int 文字の引数の場合は char 「文字列」の引数の場合は char * とそれぞれ型名を記述する 関数の型も同じ 三つの基本的な制御構造 順接 二つの命令 P, Q から関数 f() を作る f() { P;Q; ( P をやってから、 Q を行う ) } # P;Q; と Q;P; は異なる 条件分岐 二つの命令 P, Q と条件 C(x) からから関数 f(x) を作る f(x) { if ( C(x) ) { 条件が成立したとき P; } else { 不成立の場合は Q; } } もし C(x) が成立したら P, そうでなければ Q を実行 条件 C によって P と Q どちらか一方を実行 再帰 制御変数 x と、最低値を判定する C(x),と、 値を小さくする φ(x) ( φ(x)は x をより小さくし、 これを繰り返し適用すると、いつかは C(x) が成立する) 繰り返す命令 P があったとき f(x) { if ( C(x) ) { 最低値 なにもしない } else { P; f(φ(x)); 再帰呼び出し 引数に関数φを適用する事によい 引数の値が「小さく」なる } } 前回の課題の 2 階乗の計算をする factrial(int n) : n が自然数の時に n! を計算 # n = 0, 1, 2,... とし「0! = 1」と決める 1 (n=0 の時) fcatrial(n) = { n * fcatrial(n-1) (n>0 の時) # 計算機(C 言語)では、「再帰的定義」 # 数学では、「帰納的定義」 int factrial(int n) { if ( n <= 0 ) { /* なぜ == でなく <= かというと安全性を取った */ return 1; /* n <= 0 の時は n! = 1 とする */ } else { return n * factrial ( n - 1 ); } /* ここにくることはない */ } この問題のポイント 作成したい「階乗の関数」が、最初から「帰納的」に定義されている そのまま、「再帰」で実現できる /* 数学証明表現は機械的にその問題を解くプログラムにできる */ /* 二次方程式の解の存在証明を読むと、実は、解の公式が導出できる 解の公式=解を求めるプログラム(アルゴリズム) と考えてよい */ もし、作りたい関数が、帰納的に定義されていなかったら ? -> 帰納的に定義しなおす必要がある <= これは、関数の値の存在証明とほぼ同じ -> 数学の人間は、問題を「証明」で解く事により、プログラムが作れる 再帰の考え方 : 帰納法の考え方 全体を、一つと残り(残りも全体と同じ構造を持つ)に分ける ユークリッドの互除法 a,b : 自然数 ( 0 以上の整数 ) の最大公約数を (a,b) で表現する事にする [R.1] (a,0) = a [R.2] (a,b) = (b,r) # r は a を b で割ったときの余り 上記の性質を利用すると、最大公約数を求める手続き 0.(a,b) を求めたい ( a, b >= 0 ) 1.もし、b が 0 なら答えは a である (R.1) ので a を答えとして終了 2. そうでなければ、a を b で割ったあまり(r とする)を求め、 (b,r) を求めればよい (R.2:帰納的) a (b=0 の時) gcm(a,b) = { gcm(b,a%b) (b>0 の時) # a%b : a を b で割った余り C 言語の関数としては、帰納法で実現可能 int gcm(int a, int b) { if ( b == 0 ) { return a; } else { return gcm(b,a%b); /* a>b の時に、 b と a%b a<b の時は、 a と b%a とあるが、プログラムでは ?? 常に、b と a%b だけど大丈夫 ?? a>b の時は、もちろん、 a と a%b で OK a<b の時は、どうなるか ? b と a%b b と a 再度 gcm を呼び出すと a と b%a ( a>b が保証されている ) gcm(12,16) -> gcm(16,12%16) -> gcm(16,12) -> gcm(12,16%12) -> gcm(12,4) -> gcm(4,12%4) -> gcm(4,0) -> 4 */ } } gcm(a,b) -> gcm(b,a%b) a>b の時 a%b は、b より小さくなる a,b の小さい方を取ると、 a,b の場合 b b,a%b の場合は a%b 「小さく」なる a<b の時 a, b%a (gcm が二度呼ばれた後に) a,b の小さいほうは a a, b%a の小さいは b%a 「小さく」なる [副作用] 入出力を伴う関数は、「副作用」をもっている 純粋な関数とはいえない 特に入力に関しては、入力関数を呼び出すたびに、プログラムは 入力待ちになり、そのたびに「データの入力を促す」事になる もし、入力結果を再度利用したい場合はどうするか ? 入力関数を二度呼び出すと、二度入力作業が必要になり、 (同じ値をしていないと..) 異なった値になる 「入力値を二度利用する場合」は、「入力関数を二度呼んではいけない」 同じ値を、何度も利用する場合 : 変数を利用する 変数に値が設定されていれば、同じ変数を利用する事により、同じ値を何度も参照可能 ところが、「変数を利用するには関数を作る必要がある」 なので、入力値は、関数の引数に与える事になる [乱数] rand() ライブラリ関数 乱数値を返す関数 呼び出すたびに、(一般には..)異なる値を返す その値は(一応..)予想できない(事になっている) 数当てゲーム 一方が、一定の範囲(例 : 1 から 100)の数を決める 他方が、それを予想する 予想値を相手に伝える あっていれば、「あたり」いう あってなければ、「おきいかちいさいか」を答える さらに、予想を続ける
Download : 20170630-01.c
/* * CDATE FILENAME * * 数当てを行う */ #include <stdio.h> #include "s_print.h" /* s_print_int を利用するので.. */ #include "s_input.h" /* s_input_int を利用するので.. */ /* * prompt * メッセージを出力し、キーボードから整数値を読み込んで、 * その値を返す関数 */ int prompt(void) { s_print_string ( "私が選んだ数を予想して入力してください : " ); return s_input_int(); } /* */ void game ( int input, int answer ) { if ( input == answer ) { /* 入力と答えが一致した */ s_print_string ( "お見事です。あたりました。\n" ); } else { if ( input > answer ) { /* 入力が、答えより大きい */ /* ** この部分を完成させなさい */ } else { /* 入力が、答えより小さい */ s_print_string ( "その数は小さすぎます。\n" ); } /* 未だ、答えが、当っていないので、ゲームを続ける.. */ /* ** この部分を完成させなさい */ } } /* * main */ int main( void ) { s_print_string ( "私が考えた 1 〜 100 数を予想して当ててみてください。\n" ); game ( prompt(), (rand()%100) + 1 ); return 0; }
50 75 83 90 85 84
$ ./20170630-01-QQQQ.exe 私が考えた 1 〜 100 数を予想して当ててみてください。 私が選んだ数を予想して入力してください : 50 その数は小さすぎます。 私が選んだ数を予想して入力してください : 75 その数は小さすぎます。 私が選んだ数を予想して入力してください : 83 その数は小さすぎます。 私が選んだ数を予想して入力してください : 90 その数は大きすぎます。 私が選んだ数を予想して入力してください : 85 その数は大きすぎます。 私が選んだ数を予想して入力してください : 84 お見事です。あたりました。 $
Download : 20170630-02.c
/* * CDATE FILENAME * * 与えられた自然数の素因数を出力する * * 12 は.. * * * * * * */ #include <stdio.h> #include "s_print.h" /* s_print_int を利用するので.. */ /* * void print_prime_factor_sequence ( int n, int p ) * 機能 : p 以上の n の素因数を小さい順に並べで表示する * 条件 : n には p 未満の素因数を含まれていないものとする * int n : p 未満の素因数を含まれていない自然数 * 返り値 : なし */ void print_prime_factor_sequence ( int n, int p ) { if ( p > n ) { /* もう n には p 以上の素因数を含まない */ /* 何もしなくてよい */ /* 本来、ここにくるのは n = 1 の時だが、念の為 */ } else if ( n % p == 0 ) { /* n が p を素因数として含む */ s_print_char ( ' ' ); /* 隙間を空け */ s_print_int ( p ); /* 素因数を表示し */ /* もう一度 p の要素があるかもしれないので、そこから試す [再帰] */ /* ** この部分を完成させなさい */ } else { /* n が p を素因数として含まない */ /* 本来は p の次の素数を試すべきだが.. */ /* その代りに p + 1 を試す (のは無駄だが、正く動く) [再帰] */ /* ** この部分を完成させなさい */ } } /* * void print_prime_factor_of ( int n ) * 機能 : n の素因数を出力する * int n : 素因数を表示したい自然数 * 返り値 : なし */ void print_prime_factor_of ( int n ) { if ( n < 1 ) { /* 与えらた数が自然数ではない */ s_print_int ( n ); s_print_string ( "は、自然数ではありません\n" ); } else if ( n == 1 ) { /* 与えられた数が 1 の場合 */ s_print_int ( n ); s_print_string ( "の素因数はありません\n" ); } else { s_print_int ( n ); s_print_string ( "の素因数分解は" ); /* ** この部分を完成させなさい */ s_print_string ( " となります。\n" ); } } /* * main */ int main( void ) { print_prime_factor_of ( 12 ); return 0; }
$ ./20170630-02-QQQQ.exe 12の素因数分解は 2 2 3 となります。 $