class: title, smokescreen, shelf, no-footer # 関数化しよう<br>2022/07/22 version <div class=footnote> <small> (脚注) 不安がある人は教科書を読み直しましょう。 なおプロトタイプ宣言は一旦わすれてok </small> </div> --- class: compact # 関数のふんわりした話(たとえ話) - だいたいのプログラムは「入力」「計算する(プログラムの実体)」「出力」 - 今までは**main(プログラム全体)だけ**だったので、 **入力は「ユーザがキーボードから文字や数字を入れる」**、 **出力は「モニタへ文字や数字が出てくる」**ことでした - (長い)プログラムは「mainから**下請け(関数**)に仕事を依頼する」作業の連続です - 関数への依頼は、mainから関数へデータを**渡す**(関数へ**データを入力**とも言えます) - 関数からの納品はmainへデータを**返す**こと(mainへデータを**出力**と言えます) ``` // プログラム(全体) (キーボードから)入力 -> プログラム -> (モニタへの)出力 // 関数(プログラムの一部の動作); 「渡す/返す」と「入力/出力」なじめる表現でok(問題文は前者の表現なので慣れてね) mainから渡す -> 関数 -> mainへ返す mainから入力 -> 関数 -> mainへの出力 ``` --- class: compact # 関数のふんわりした話(たとえ話) <div class=footnote> <small> (脚注) 発注時じつは(変数の)コピーを下請け(関数)に渡し、 下請け(関数)からの納品(返り値)もコピーです </small> </div> ``` // プログラム(全体) (キーボードから)入力 -> プログラム -> (モニタへの)出力 // 関数(プログラムの一部の動作); 「渡す/返す」と「入力/出力」なじめる表現でok(問題文は前者の表現なので慣れてね) mainから渡す -> 関数 -> mainへ返す mainから入力 -> 関数 -> mainへの出力 mainから発注 -> 関数 -> mainへ納品 mainから発注 -> 下請 -> mainへ納品 ``` - (長い)プログラムは「mainから**下請け(関数**)に仕事を依頼する」作業の連続です        --- class: img-right,compact # プログラムたとえ話「学食」 <div class=footnote> <small> </small> </div>  1. 食券を買う 1. 食券を渡す 1. 該当する食事をもらう 1. 食べる 1. 食器を返す --- class: img-right,compact # 関数たとえ話「食券を買う」 <div class=footnote> <small> (脚注) ちなみに右の写真は昔の学食(-2019)の券売機です。よ〜くみるとメニューが違う </small> </div>  - だいたいのプログラムは「入力」「計算する(プログラムの実体)」「出力」 - 学食の券売機も同じでしょ? 1. お金を入れる(入力) 1. メニューボタンが押される 1. (裏側で)なにか計算(仕事)している - たぶん裏で -> 印刷 -> 切る -> 食券を下へ落とす 1. 食券とおつりが出てくる(出力) --- class: col-2,compact # 擬似コード「食券を買う」 <div class=footnote> <small> </small> </div> 簡単化のため、メニューが一つしかない(メニュー番号の指定がない)場合、 食券を買う動作は次のようになるでしょう 1. おかねをいれる 1. (裏側で印刷したり準備) 1. 食券が出てくる ``` // 擬似コードで書くと、こんな感じでしょうか // おかね(IN) -> 券売機 -> 食券(OUT) 食券 食券を買う (おかね) { 裏側で行われる食券販売機の実体を書くところ; return 食券; } ``` <wbr> 正しくは、関数名の前(左側のこと)に書くものは「**食券の型**」です。 また「おかね」の型も書かないといけません ``` // OUT型 関数名(IN型 IN) { 実体; return OUT;} 食券の型 食券を買う(型 おかね) { 裏側で行われる食券販売機の実体を書くところ; return 食券; } int 食券を買う(int okane) { int shokken; // 裏側で行われる食券販売機の実体を書くところ; return shokken; } ``` --- class: col-2,compact # 食券の型?その実体はメニューの番号じゃないかな? <div class=footnote> <small> (脚注) [発展] この仕様では忙しい時間帯に間違えそうです。よりよい仕様を考えなさい </small> </div> - お金は整数型 int でいいですよね? - 食券の型は?と言うと、 **実世界を、どうデジタル表現するか?はプログラム設計者しだいだから (仕様書しだい)**が答え - (いまメニューが一つだけどさ:-)調理場に渡す情報はメニュー番号(int)かな? ``` // わりとハリボテ版, 食券を変数名menu変更版 int 食券を買う(int okane) { int menu = 1; // メニュー1つだけ,決め打ち return menu; } ``` <wbr> 全体を書くとこんな感じかな? それぞれの関数(いわば下請け)に指示を与える情報は、これでok? 例えば「食器を返す」関数はメニュー関係ないので引数不要ですよね? ``` int main() { int okane, menu; menu = 食券を買う(okane); 調理場に発注(menu); // menuを発注 受け取る(menu); // menuを受け取る 食べる(menu); // menuを食べる 食器を返す(); // 引数なし return(0); // OSへの礼儀作法 } ``` --- class: title, smokescreen, shelf, no-footer # じゃんけんを関数化しよう <div class=footnote> <small> 課題1000番台用のワークシートを再配布するので、それを使ってください </small> </div> --- class: compact # 課題1010-1040: じゃんけんを関数化しよう <div class=footnote> <small> </small> </div> - 課題010,020,030,040の関数化は、課題090関数化の一部です。次ページから課題090の関数化を進めますが、不安な人は、課題010-040を関数化から始めてみてください 課題010-040の流れの復習 1. コンピュータの手を決める 1. キーボードから(ユーザ=jibun)の手を入力 1. じゃんけんの判定を計算 1. 出力 --- class: compact # [例題]課題1090: じゃんけんの関数化 <div class=footnote> <small> (脚注) 自信のある人は課題群をとばして課題1210(課題210「条件文、繰り返し」の関数化)へ進んでokです </small> </div> 課題090の「じゃんけん」(条件文なし、繰り返しなし)を例にします。 課題090の流れを復習すると、こうですね? 1. 乱数を初期化 1. コンピュータの手を決める 1. キーボードから(ユーザ=jibun)の手を入力 1. じゃんけんの判定を計算 1. 出力 --- class: compact # 課題1090: じゃんけん関数化の改造元,課題090 ``` #include <stdio.h> // 最初の行に必ず書く呪文 #include <stdlib.h> // 乱数(srand,rand)のため #include <time.h> // time()を使うため int main () { // 最後にreturn(0)つまり数字を返すからint int aite, jibun; // 整数変数を宣言 srand(time(NULL)); // 乱数の初期化,一回だけ実行 aite = rand() % 3; // コンピュータの手を乱数で決める scanf("%d", &jibun); // キーボードから入力した数字を変数jibunに代入 kekka = (3 + jibun - aite) % 3; printf("jibun = %d, aite = %d, kekka = %d\n", jibun, aite, kekka); return(0); // 礼儀作法(OSへstatus = 0を返す) } ``` --- class: compact # 課題1090: じゃんけんを関数化: 出力(1) - 出力の本体は、この行です。これを関数shuturyokuに移します ``` printf("jibun = %d, aite = %d, kekka = %d\n", jibun, aite, kekka); ``` - 返り値つまりmainに伝えたい情報はありますか?と考えると、実はありません。 本当はモニタ出力(printf)に「成功したよ」「失敗したよ」を伝えるべきですが、 printfは失敗しないだろ!とサボることが多いです - 渡すべき情報つまり仮引数に必要な変数たちは何ですか? printf行にある変数たちが必要ですね? これらをすべて書きます - では、次の関数宣言行を考えてください ... しんきんぐたいむ ... ``` 返り値無し 関数名 (仮引数) { printf("jibun = %d, aite = %d, kekka = %d\n", jibun, aite, kekka); } ``` --- class: compact # 課題1090: じゃんけんを関数化: 出力(2) - 出力の本体は、この行です。これを関数shuturyokuに移します ``` printf("jibun = %d, aite = %d, kekka = %d\n", jibun, aite, kekka); ``` - 返り値は無し、このときは void というキーワードを書きます。 コンパイラに「mainに返す情報は無い、心配すんな」とつたえてあげます。 伝えないとコンパイラが「まちがってるんじゃね?」と思って警告してくれることがあります - 渡すべき情報つまり仮引数に必要な変数はprintf行にある変数たち、つまりjibunとaiteそしてkekkaです。 これらをすべて書いてください - 関数定義の解答は次のとおりです(仮引数の型も書くことを忘れずに) ``` void shuturyoku (int jibun, int aite, int kekka) { printf("jibun = %d, aite = %d, kekka = %d\n", jibun, aite, kekka); } ``` --- class: compact # 課題1090: じゃんけんを関数化: 出力(3) <div class=footnote> <small> (脚注) main側を先に関数宣言書いて、そのあと関数宣言を書く派? main側と関数宣言のどちらを先に書くか?は人によりますかね〜 書いていくうちに両方を修正していくことは普通だしなぁ。 main側が先がよいとかいう理屈はないかもしれない </small> </div> - main側です。関数の定義は次のとおりでしたね? - 返り値なし ... `変数 = 関数`の`変数 =`部分が無いということです - jibunとaiteそしてkekkaを渡します。 `()`の中に関数宣言と同じ順序で書いてください - 解答: つまり ``` printf("jibun = %d, aite = %d, kekka = %d\n", jibun, aite, kekka); ``` を ``` shuturyoku(jibun, aite, kekka); ``` に差し替えます。 --- class: compact # 課題1090: じゃんけんを関数化: 出力(4) ``` #include <stdio.h> // 最初の行に必ず書く呪文 #include <stdlib.h> // 乱数(srand,rand)のため #include <time.h> // time()を使うため int main () { // 最後にreturn(0)つまり数字を返すからint int aite, jibun; // 整数変数を宣言 srand(time(NULL)); // 乱数の初期化,一回だけ実行 aite = rand() % 3; // コンピュータの手を乱数で決める scanf("%d", &jibun); // キーボードから入力した数字を変数jibunに代入 kekka = (3 + jibun - aite) % 3; shuturyoku(jibun, aite, kekka); // !!!関数になった!!! return(0); // 礼儀作法(OSへstatus = 0を返す) } ``` --- class: compact # 課題1090: じゃんけんを関数化: 入力(1) - 同じように「入力部」(scanfの行)を関数nyuuryokuにしてみましょう - 返り値と渡す情報はなんでしょうね? - 注意: 変数jibunの値を、このあとmainで使うよね? ... しんきんぐたいむ ... ``` int main () { // 最後にreturn(0)つまり数字を返すからint int aite, jibun; // 整数変数を宣言 srand(time(NULL)); // 乱数の初期化,一回だけ実行 aite = rand() % 3; // コンピュータの手を乱数で決める 変数 = 入力(引数); // ???(new) <- ここは、どうなりますか? kekka = (3 + jibun - aite) % 3; shuturyoku(jibun, aite, kekka); // !!!関数になった!!! return(0); // 礼儀作法(OSへstatus = 0を返す) } ``` --- class: compact # 課題1090: じゃんけんを関数化: 入力(2) - キーボードから読み込んだ情報を返します。 だから返り値はあります。型は整数(int) - 一方、mainから渡す情報はありません -> 引数はありません - では、考えて見てください ... Thinking Time --- class: compact # 課題1090: じゃんけんを関数化: 入力(3) - キーボードから読み込んだ情報(整数値,int)を返す - 一方、mainから渡す情報はありません。引数がないときは void - 解答(関数側) ``` int nyuuryoku (void) { scanf("%d", &jibun); // キーボードから入力した数字を変数jibunに代入 (コピペ) return jibun; // 返す! (new) } ``` --- class: compact # 課題1090: じゃんけんを関数化: 入力(4) <div class=footnote> <small> (脚注) 入力の関数化は単純な置き換え作業ではない例です </small> </div> - nyuuryokuから、キーボードから読み込んだ情報(整数値,int)が返る - その情報は、このあとも使うので、変数jibunに代入します - 一方、mainから渡す情報はありません ... 関数呼び出しは単に`()`です ``` int main () { // 最後にreturn(0)つまり数字を返すからint int aite, jibun; // 整数変数を宣言 srand(time(NULL)); // 乱数の初期化,一回だけ実行 aite = rand() % 3; // コンピュータの手を乱数で決める jibun = nyuuryoku(); // !!!関数になった!!! (new) kekka = (3 + jibun - aite) % 3; shuturyoku(jibun, aite, kekka); // !!!関数になった!!! return(0); // 礼儀作法(OSへstatus = 0を返す) } ``` --- class: img-right,compact # 課題1090: じゃんけんを関数化: 判定部分を関数化 <div class=footnote> <small> </small> </div> - 関数 hantei を定義してください。まず考えること、次の2つは何ですか? - 入力 = 関数に渡す情報 - 出力 = mainに返す情報 - 課題090の入力部分を hantei を使い、書き直してください - main側を先に書き換えると、こう書き換えるでしょう。関数hanteiを考えてください ``` kekka = hantei(引数); // 引数のところは何を書けばよいでしょうか? ``` - (解答は3つまとめて) --- class: img-right,compact # 課題1090: じゃんけんを関数化: aiteの手を関数化 - 関数 saikoro3 (3面サイコロ)を定義してください。次の2つは何ですか? - 入力 = 関数に渡す情報 - 出力 = mainに返す情報 - 課題090の入力部分を saikoro3 を使い、書き直してください - main側を先に書き換えると、こう書き換えるでしょう。関数hanteiを考えてください ``` kekka = saikoro3(引数); // 引数のところは何を書けばよいでしょうか? ``` - (解答は3つまとめて) --- class: img-right,compact # 課題1090: じゃんけんを関数化: 初期化部分を関数化 <div class=footnote> <small> (脚注) 変数宣言を関数に移しちゃダメだよ!?(な〜んでだ?) </small> </div> - (初期化といっても)乱数の初期化部分を関数 shokika にしてみてください - 入力 = 関数に渡す情報 - 出力 = mainに返す情報 - じゃんけんプログラムだと短いので意味が無い気もしますが、 もぐらたたきのshokika関数は、そこそこ長いので、それの練習台だと思ってください - main側を先に書き換えると、こう書き換えるでしょう。関数hanteiを考えてください ``` shokika(引数); // 引数のところは何を書けばよいでしょうか? ``` - (解答は3つまとめて) --- class: img-right,compact # 課題1090: じゃんけん関数化 最終版: 宣言+関数定義(1) ``` #include <stdio.h> // 最初の行に必ず書く呪文 #include <stdlib.h> // 乱数(srand,rand)のため #include <time.h> // time()を使うため void shokika (void) { srand(time(NULL)); // 乱数の初期化,一回だけ実行 } int saikoro3 (void) { int me; me = rand() % 3; return me; } ``` --- class: img-right,compact # 課題1090: じゃんけん関数化 最終版: 関数定義(2) ``` int nyuuryoku (void) { int jibun; scanf("%d", &jibun); // キーボードから入力した数字を変数jibunに代入 (コピペ) return jibun; // 返す! (new) } int hantei(int jibun, int aite) { int kekka; kekka = (3 + jibun - aite) % 3; return kekka; } void shuturyoku (int jibun, int aite, int kekka) { printf("jibun = %d, aite = %d, kekka = %d\n", jibun, aite, kekka); } ``` --- class: img-right,compact # 課題1090: じゃんけん関数化 最終版 main <div class=footnote> <small> (脚注) 読みやすくなりましたよね? コメントが無くても、変数名と関数名だけ読めば、ほぼ分かるよね? </small> </div> ``` int main () { // 最後にreturn(0)つまり数字を返すからint int aite, jibun, kekka; // 整数変数を宣言 shokika(); // 乱数の初期化,一回だけ実行 aite = saikoro3(); // コンピュータの手を乱数で決める jibun = nyuuryoku(); // キーボードから入力した数字を変数jibunに代入 kekka = hantei(jibun, aite); // 判定 shuturyoku(jibun, aite, kekka); // 結果をモニタに出力 return(0); // 礼儀作法(OSへstatus = 0を返す) } ``` --- class: img-right,compact # 課題1110: 課題110を関数化してください <div class=footnote> <small> (脚注) 簡単だと思う人は、この課題をとばして課題210「条件文あり、繰り返しあり」の関数化へ進んでok </small> </div> - 課題110(090のじゃんけん+条件文)は、判定結果をkachi,make,aikoと表示してくれる課題ですね。これを関数化してみてください - 課題1090と同じように、意味のある単位で関数に分けていきます - そもそも課題1090と、ほぼ同じですよね? - 出力するところに条件文がくるところだけ異なります - `shuturyoku()`の定義だけ変更すればいいよね? --- class: img-right,compact # 課題1210: 課題210を関数化してください <div class=footnote> <small> (脚注) [発展] じゃんけんの発展課題も関数化してみましょう。 発展課題はノーヒント,がんばってください:-) </small> </div> - 課題210は、3回くりかえして遊べる「じゃんけん」プログラムの一応の完成形です。 これを関数化してください - コードは課題1090, 1110と、ほとんど同じです。 異なるのは「どこからどこまでを繰り返すのか?」の部分だけです、 そこを考えてください。 mainの一部を修正するだけです(3行くらい?)。 だいぶ読みやすくなってるし簡単じゃないかな? --- class: title, smokescreen, shelf, no-footer # もぐらたたきを関数化しよう <div class=footnote> <small> このへんまできたら、ほぼノーヒントで行けますかね? ただ、これポインタなんですよね〜 </small> </div> --- class: compact # 課題1310-1590: もぐら叩きを関数化 <div class=footnote> <small> (脚注1) [発展] もぐらたたきの発展課題も関数化してみよう <br> (脚注2) [発展] shuturyoku関数が「判定」と「表示」の2つの機能を持つのはよくないです。 1機能1関数であるべき。書き直してみましょう </small> </div> - 課題1310と1320: 課題310と320を関数化 - 関数 shokika ... もぐらの位置を決める - 関数 hyouji ... もぐらの位置を表示 - 課題1330と1340: 課題330と340を関数化 - 関数 shokika ... もぐらの位置を決める - 関数 nyuuryoku ... 叩くもぐらの位置を入力 - 関数 shuturyoku ... atari,hazureを表示 - 課題1390〜1490 (課題390,410,420,430,490の関数化) - たぶん上で作成した関数をそのまま使うか少し修正するだけだと思うので、 ノーヒントで行ってみよう - 課題1510,1590 (課題510,590 2次元もぐら叩きの関数化) - 2次元でも必要な関数群は1次元と同じだと思います。 適宜なおして2次元版をTRY --- class: compact # 課題1340: もぐら叩きを関数化(途中まで) <div class=footnote> <small> (脚注) C言語の配列はポインタ(の syntax sugar)です。 関数の引数にはmoguraと書きますが、 さて関数側の仮引数の宣言をどうすればよいか?気をつけてください (考えましょう) </small> </div> main側が次のとき、関数群を定義し、このプログラムが動くようにしてください ``` int main () { int jibun; // 自分の手を入れる変数 int mogura[4] = {}; // もぐら配列を宣言と同時にを0で初期化 shokika(mogura); // もぐらの位置を設定 jibun = nyuuryoku(); // 叩く場所の指定を入力 shuturyoku(mogura,jibun); // 判定して出力 // (注:ここで引数の変数moguraはポインタ) return(0); } ``` --- class: compact # 課題1510: 2次元もぐら叩きの関数化(途中まで)(1) ヒントとして宣言とmain部分だけ書いておきます。完成させてください ``` #include <stdio.h> #include <stdlib.h> #include <time.h> // もぐらたたき(2次元)総集編 // - 2次元配列 #define TATE 9 // もぐら配列の大きさ #define YOKO 9 // もぐら配列の大きさ #define SAIDAI 3 // 挑戦できる回数の最大値 int main () { int mogura[TATE][YOKO]; // 2次元配列を宣言 int i, tate, yoko, kekka; int tensuu = 0; // 点数,0で初期化 ``` --- class: compact # 課題1510: 2次元もぐら叩きの関数化(途中まで)(2) ``` shokika(mogura); // もぐらを配置 for (i = 0; i < SAIDAI; i++) { tate = nyuuryoku(); // 入力関数を2回呼ぶ yoko = nyuuryoku(); // 関数は値を一つしか返せないため kekka = hantei(mogura, tate, yoko); // 判定 1/0で返す if (kekka == 1) { printf("atari\n"); tensuu++; // 合計点数 } else { printf("hazue\n"); } } printf("tensuu = %d\n", tensuu); // 合計点数を出力 return(0); // 礼儀作法(OSへstatus = 0を返す) } ```