/* * 20190607-01-QQQQ.c * コマンドライン引数で指定した長さのハノイの塔を解く */ #include <stdio.h> #include <stdlib.h> /* exit を利用するために必要 */ /* * ハノイプログラムには、s_hanoi.h が必要 */ #include "s_hanoi.h" /* * hanoi ( char *f, char *t, char *w, char *size ) * 高さ size のハノイの塔を、 * f で表されている棒から * t で表されている棒へ * w で表されている棒を * 作業領域として利用して移動する * w は空っぽか、size より大きな円盤しかない * * * | | | * [1|1] | | * [22|22] | | * [333|333] | | * ----+-----------+-----------+--------- * 1 2 3 * * .... * * | | | * | | | * | | [1|1] * [333|333] | [22|22] * ----+-----------+-----------+--------- * 1 2 3 * * | | | * | | | * | | [1|1] * | [333|333] [22|22] * ----+-----------+-----------+--------- * 1 2 3 * * .... * * | | | * | [1|1] | * | [22|22] | * | [333|333] | * ----+-----------+-----------+--------- * 1 2 3 * * hanoi( "1", "2", "3", "***" ); * => move を 7 回する必要 * 高さが具体的に「3」されれば、問題は解けるが.. * 一般に「n」の場合は ? * * hanoi( "1", "2", "3", "***" ); * => * [1] と [2] を "1" から "3" * [3] を "1" から "2" * [1] と [2] を "3" から "2" * => * 高さ 2 のハノイの塔を "1" から "3" 移動する * s_hanoi_move( "1", "2" ) いま、ここのは [3] がある * 高さ 2 のハノイの塔を "3" から "1" 移動する * => * haoi( "1", "3", "2", "**" ); * s_hanoi_move( "1", "2" ) いま、ここのは [3] がある * haoi( "3", "2", "1", "**" ); * => * haoi( "1", "3", "2", "***" + 1 ); 邪魔なものを取り除く * s_hanoi_move( "1", "2" ) (着目している)一番大きな円盤を移動 * haoi( "3", "2", "1", "***" + 1 ); 除けておいたものを乗せる */ void hanoi ( char *f, char *t, char *w, char *size ) { if ( !strcmp ( size, "" ) ) { /* 空っぽだったら*/ /* する事はない */ } else { /* まだ仕事があるなら */ /* やりたい事は、 「size の大きさの塔を f から t に動かしたい」事 そのためには、最終的に、size の大きさの円盤を動かす必要がある。 そのためには、 size の大きさの上の塔を t を利用して、一旦、f から w に移し size の大きさ円盤を f から t に移し size の大きさの上の塔を f を利用して、w から、t に動かせば良い */ /* f の所にある size の円盤の上の塔を w に移動して退かす (再帰呼出し) */ hanoi ( f, w, t, size + 1 ); /* f の所にある size の円盤を t に移動 */ s_hanoi_move( f, t ); /* 上の円盤がすべて取り除けたので移動可能 */ /* size の円盤の上に w に退かした塔を載せる (再帰呼出し) */ /* w の所にある size + 1 の円盤の上の塔を t に移動して乗せる */ hanoi ( w, t, f, size + 1 ); } } /* * ハノイの塔 プログラム */ int main ( int argc, char *argv[] ) { /* コマンドライン引数を用いるので、 int main ( int argc, char *argv[] ) と、main 関数の引数を宣言する。 */ /* このプログラムでは、ハノイの塔の「高さ」を 一つ目の引数で指定した文字列の「長さ」で決めたい */ /* 少なくても、一つの引数が欲しい */ if ( argc < 2 /* 元は「argc<1」だったが誤り */ ) { /* 引数が指定されていない */ /* argc は「引数の個数 + 1」なので */ /* argc < 2 の時は、ようするに argc == 1 つまり、「引数 0」となる */ printf ( "ハノイの塔の高さを指定する文字列を指定してください\n" ); /* エラーメッセージの出力 */ exit ( -1 ); /* エラーコード : -1 でプログラム終了 */ /* main 関数内であれば return -1; でも可 */ } /* else {} */ /* 「else {}」は省略可 */ /* * 最初は "1" に全ての円盤が置いてある * これを "2" に全ての円盤を移動する */ /* * ハノイで、できること * s_hanoi_init() : ハノイプログラムの開始 : 最初に一度だけ呼び出す * s_hanoi_size ( char *discs ) : ハノイの塔の高さを設定する * s_hanoi_move ( char *from, char *to ) : from にある円盤を to に移す * s_hanoi_clear () : 最初の状態に戻す * s_hanoi_stop() : ハノイ塔プログラムの終了 : return 0 の前に呼び出す */ s_hanoi_init(); /* ハノイの塔のプログラムの初期変化 */ /* コマンドライン引数の一つ目 (argv[1]) で、問題の高さを初期化 */ /* s_hanoi_size を呼び出す */ s_hanoi_size( argv[1] ); /* 引数で指定した文字列の長さで高さを指定 */ printf ( "これから解答を開始します。[Enter] キーを押してください\n" ); putchar ( '>' ); putchar ( getchar() ); /* 開始前に、一旦停止 */ /* 解答開始 */ hanoi ( "1", "2", "3", argv[1] ); /* 解答終了 */ printf ( "プログラムを終了するには [Enter] キーを押してください\n" ); putchar ( '>' ); /* 終了前に確認 */ putchar ( getchar() ); s_hanoi_stop(); return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { /* コマンドライン: プログラムを実行する時に、実行ファイル名を指定して実行している 例 : 「./p-001.exe」 「./p-001.exe」は実行ファイル名 「コマンドライン引数」は、「無い」(これまで存在する事さえか考えてなかたった) が、実は、その後ろに、空白で区切って、複数の「コマンドライン引数」を指定できる 例 : コンパイルする時 「cc -c p-001.c」 「cc」が実行ファイル名 「-c」,「p-001.c」は「コマンドライン引数」 コマンドライン引数は 2 個 例 : リンクする時 「cc -o p-001.exe p-001.o」 「cc」が実行ファイル名 「-o」,「p-001.exe」,「p-001.o」は「コマンドライン引数」 コマンドライン引数は 3 個 の様にできる。 「コマンドライン引数」の参照方法 main 関数の引数である argc = 引数の個数 + 1 (整数) argv[k] = k 番目の引数として指定された「文字列」になる */ printf ( argv[1] ); /* コマンドライン引数の一つ目の引数が表示される */ printf ( "\n" ); printf ( argv[2] ); /* コマンドライン引数の一つ目の引数が表示される */ printf ( "\n" ); printf ( argv[3] ); /* コマンドライン引数の一つ目の引数が表示される */ printf ( "\n" ); /* ここに命令を書く */ return 0; }
#include <stdio.h> #include <stdlib.h> /* exit 関数を利用する場合に入れる */ int main(int argc, char *argv[]) { /* このプログラムは、少なくても三個の引数がある事を想定している => もし、引数が、三個未満の場合は、適切な振る舞いをしない <= 何が起きるかわからない => 不測の事態(引数が足りない)場合に備えて、「エラー処理」を行う */ if ( argc > 3 ) { /* argc >= 4 --> argc - 1 >= 3 --> 「引数の個数 +1」-1 >= 3 --> 「引数の個数」>= 3*/ printf ( "一つ目の引数は" ); printf ( argv[1] ); /* コマンドライン引数の一つ目の引数が表示される */ printf ( "\n" ); printf ( "二つ目の引数は" ); printf ( argv[2] ); /* コマンドライン引数の二つ目の引数が表示される */ printf ( "\n" ); printf ( "三つ目の引数は" ); printf ( argv[3] ); /* コマンドライン引数の三つ目の引数が表示される */ printf ( "\n" ); } else { printf ( "引数は 3 つ以上指定してください\n" ); exit(-1); /* exit : ここで、プログラムを終了 */ /* 同時にエラーコード(-1)を返す */ /* !! エラーコードが「0」の時は「正常/真」*/ /* !! 「正常な状態で終了したい場合」は「exit(0)」とする */ } return 0; }
#include <stdio.h> /* 整数の引数を持つ関数 与えれた引数の個数だけ "*" を出力する */ void subfunc( int times ) { /* 整数の引数を与える場合は、 「int times」の様に、「仮引数宣言では、『char *』でなく『int』」を指定 */ if ( times > 0 ) { putchar ( '*' ); subfunc ( times - 1 ); /* 再帰を利用して繰り返し */ } /* else {} */ /* else で何もしない場合は、 else 節毎省略可能 */ } int main(int argc, char *argv[]) { printf ( "5 個 * を書く\n" ); subfunc( 5 ); printf ( "\n" ); printf ( "15 個 * を書く\n" ); subfunc( 15 ); printf ( "\n" ); return 0; }
#include <stdio.h> /* 整数の引数を持つ関数 与えれた引数の個数だけ "*" を出力する */ void subfunc( int times ) { /* 整数の引数を与える場合は、 「int times」の様に、「仮引数宣言では、『char *』でなく『int』」を指定 */ if ( times > 0 ) { putchar ( '*' ); subfunc ( times - 1 ); /* 再帰を利用して繰り返し */ } /* else {} */ /* else で何もしない場合は、 else 節毎省略可能 */ } int main(int argc, char *argv[]) { printf ( "5 個 * を書く\n" ); subfunc( "5" ); /* 本来は「5」(整数値)なのに、「"5"」(文字列)を指定 */ printf ( "\n" ); printf ( "15 個 * を書く\n" ); subfunc( 15 ); printf ( "\n" ); return 0; }
#include <stdio.h> #include "p-006.h" /* 関数 subfuc のプロトタイプ宣言が書いてある */ /* 整数の引数を持つ関数 与えれた引数の個数だけ "*" を出力する */ void subfunc( int times ) { /* 整数の引数を与える場合は、 「int times」の様に、「仮引数宣言では、『char *』でなく『int』」を指定 */ if ( times > 0 ) { putchar ( '*' ); subfunc ( times - 1 ); /* 再帰を利用して繰り返し */ } /* else {} */ /* else で何もしない場合は、 else 節毎省略可能 */ }
#include <stdio.h> int main(int argc, char *argv[]) { printf ( "5 個 * を書く\n" ); subfunc( "5" ); /* 本来は「5」(整数値)なのに、「"5"」(文字列)を指定 */ printf ( "\n" ); return 0; }
#include <stdio.h> #include "p-006.h" /* 関数 subfuc のプロトタイプ宣言が書いてある */ /* #include によって、p-006.h の中身が、個々に挿入される */ int main(int argc, char *argv[]) { printf ( "5 個 * を書く\n" ); subfunc( "5" ); /* 本来は「5」(整数値)なのに、「"5"」(文字列)を指定 */ printf ( "\n" ); return 0; }
以下は、1,2 限の間の休み時間い整備する CST Portal の今日のページ 本日の課題 [先週(2019/06/14)の内容] 文字型 (小さな整数 : ASCII Code) C 言語では、「文字」を小さな(1 byte : 0 ? 255) の整数値で扱う 「文字」と「数値」の関係 => ASCII Code 表で # 1 byte で区別可能な文字 256 通りなので.. # 半角の英数(大文字/小文字)、いくつかの(算用)記号しか表現できない # => 日本語の漢字など、表現できない # <= 「漢字」を表現するには、2 byte 以上の数値で対応付けが必要 # SJIS : 「全角(漢字・ひらがなを含む)」は 2 byte # UTF-8 : 3 byte 必要 ## 計算機で文字が扱えるのは、 ## この「コード化(情報と数値を1-to-1 対応付けする)」があるから 「文字」に関しては、「半角のみを扱う」 「日本語」に関しては、UTF-8 で「文字列」でのみ扱う 整数型 整数値( - 2^{-31} ? 2^{31} -1 の範囲 ) を扱う int 型 普通に四則、あまりの計算、比較演算など可能 # 他にもいろいろあるが... => 調べてください 分割コンパイル 一つのプログラムが複数の関数から作ら得ているとき 関数単位で、複数のファイルに分けて記述可能 # ファイルを分割する最小単位は、関数より小さくできる.. => 個々にコンパイル(分割コンパイル)して、実行ファイルを作成する時 つまり、リンク時に、まとめて一つにする事ができる <= 再利用が可能(メリット) => 作業が増える(デメリット) # 工学(情報)では、何かを得くすると、何かを失う make と Makefile <= 分割コンパイルの手間を減らしてくれる # 「Makefile を作る」という小さなコスト(代償)で... => Makefile には、いろいろな事がかけるので、積極的に利用するとよい # ライブラリ(他者の作った関数)を利用する場合など... ## 技術や情報:コピーできるとよい ## Makefile は、「作成手順」というノウハウを「コピー」する手段になっている 「鉄則」 「Makefile があったら make する」 [本日の内容] 2019/06/07 の資料 コマンドライン処理 1. コマンドを実行する時に、「コマンドライン」で、「コマンドライン引数」を指定できる 「コマンドライン引数」は、個々の引数は「空白で区切られた『文字列』」 2. main 関数の引数である argc で引数の個数 + 1 を argv[k] で、引数そのものの「文字列」を得る事ができる コマンドラインは、プログラムに対する「(通常は『人間』による)入力」となる !! 常に『人間』は「間違う」可能性がある => コマンドライン引数がいつも適切に指定されるとは限らない => エラー処理(正常でないような入力に対しても、(それなりに)適切に対処)する事を考える コマンドラインでのよくある問題 1. 個数が不適切かも => 多いか少ないかという話なので、典型的な対応が可能 基本 : エラーメッセージを出力して「プログラムを終了」させる 2. 指定した内容が不適切かも => プログラム固有の問題なので、それぞれに個別に対応する エラー処理 プログラムが正常に動かない場合が起きる可能性がある 例 : 割り算をしたいのに、割る数に 0 が指定された 例 : 長さ 3 の文字列に、その長さ以上の数を加えた 文字列から、数を引く その場合に、 事前に、その場合に対して、「問題が起きにくい対処」を行う 特に、「エラーメッセージを出力してプログラムを『異常終了』させる」 事が「最低限度の処理」 C 言語では、exit 関数を用いる事により、 「プログラムの終了とエラーコードを返す事ができる」 # エラーコードは、exit 関数の整数値の引数で、 # 「0」は正常終了(エラーでない)が、「0 以外」は「エラー終了(異常終了)」と # 解釈される ! exit 関数を利用する場合は、 ! #include <stdlib.h> ! も加える 課題 20190607-01 20190607-01-QQQQ.c を 201905031/hanoi に保存 <= hanoi を使うので、「makefile」が使いたい コンパイルの仕方 make clean <= 本来は不要なはずだが、今回はミスで、「必要」らしい make BASE=20190607-01-QQQQ start ./20190607-01-QQQQ.exe 12345 <= 高さ 5 のハノイを解く hanoi ( f, t, w, size ) 高さ k+1 のハノイの塔は hanoi ( f, w, t, size + 1 ); 高さ k のハノイを塔をどかして s_hanoi_move( f, t ); 幅 k の円盤を移動してから hanoi ( w, t, f, size + 1 ); 高さ k のハノイの塔を乗せる 再帰呼び出しの良い ( (単純な) 繰り返しではない .. ) # 「数学的な考え方」の例 (復習) 関数のへの整数引数 「仮引数宣言では、『char *』でなく『int』」を指定 1. 今までは無条件に「char *」だったが、ちゃんと、書く規則がある # 「無条件」ではない => 「条件付き」 2. 整数の引数の場合は「int」を使う # 「文字列」の場合は、「char *」を使う 3. 「仮引数宣言(関数の引数の宣言)」には意味がある # 「いろいろなパターンがある」=> 「意味がある」 # => 他にもパターンがあるかもしれない... 「仮引数宣言」の『意味』 関数の引数に指定されるデータの種類(「型」)を指定してしている why ? エラーチェックなどの「サービス」が受けられる [実験(p-004.c)] 整数の引数の所に(敢えて、誤った..)文字列を指定 p-004.c:25:11: warning: passing argument 1 of ‘subfunc’ makes integer from pointer without a cast [-Wint-conversion] コンパイル時に「warning(警告)」が出る note: expected ‘int’ but argument is of type ‘char *’ [意訳] 本来「整数型(int)もの」が要求されているのだが、 しかしながら、実際には、引数に「文字列型(char *)」のものが指定されている !! subfunc は、引数に「整数型」の値を指定しないと、適切にふるまわない !! 引数の「型」を明示する事により、「誤りを自動検出」している !! # コンパイラが、コンパイル時に「この変数宣言の情報を利用して」エラーチェックする !!!! C 言語が「強い型」の言語である事を意味する !!!! 変数には、その変数の型と同じ型の値しか代入できない(してはいけない) !!!! => コンパイル時にエラーや警告が出る !!!! 「制約」 !!!! cf. python は、「タイプフリー」な言語で、変数にはいろいろなデータがいれらる !!!! => 例 : 同じ変数に、整数や、文字列や他のものも自由にいれられる !!!! !!! 「自由」!!! !!!! !!! <= 自己責任 !!!! => 「制約」がある => 「情報がある」=>「コンピュータがサービス可能」 !!!! => C コンパイラは、「エラーチェック」 C コンパイラは、 関数の仮引数変数の宣言を利用して、「エラーチェック」をしてくれる [実験(p-005.c, p-005-01.c)] 関数の部分を分離した(分割コンパイルしたい..) => p-005-01.c のコンパイルは問題ない p-005.c のコンパイルは、 「note」がなくなった # せっかくのエラーチェック機能が働いていない. エラーチェック(というサービス)を受けるためには、 関数の仮引数宣言の情報が必要 一つのファイルの中で、関数の呼び出し元と呼び出し先があれば、 自動的にチェックされる # 呼び出される側をファイルの先頭に置く必要がある # => 逆だと、呼び出したところで、警告が出る もし、ファイルを分けたい(分割コンパイルしたい)場合は プロトタイプ宣言を、関数を呼び出す側に残す事によって実現 プロトタイプ宣言は関数の型を示したもので、 記述法法は 「関数の宣言の頭部」の前に「extern」(後ろに「;」)を付ける 関数定義(A)と関数呼び出し(B)がある [ A=B をチェックしたい ] 1. 同じファイル内で、定義が呼び出しの前にあれば OK A, B がともに同じファイル内なのでチェック可能 2. 定義と呼び出しが別なら、呼び出しの前に、プロトタイプ宣言(C)をする A, C がともに同じファイルに内なので、チェック可能 # C と B が同じという保証は ? 3. まず、プロトタイプ宣言の記述を単独のファイルに記述 p-006.h (D) 呼び出し側で、#include する A=D チェックは OK # D と B の関係は不明 関数の定義側でも #include する # D と B のチェック ## A=D, D=B --> A=B のチェック可能なる *.h ヘッダファイルには、「関数宣言をまとめてある」 => 分割コンパイル時にも、エラーチェックが可能になる
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20190621-01.c
/* * 20190621-01-QQQQ.c * 2019/05/31 の資料の「砂漠の旅行の問題」の問題を解くプログラムを考えなさい * ただし、N=3 で良い */ #include <stdio.h> #include "s_traveller.h" /* * main */ int main ( void ) { s_traveller_init( 3 ); /* 長さ 3 の道 */ s_traveller_get(); /* スタート地点でできるだけ持つ */ s_traveller_get(); s_traveller_get(); s_traveller_move_right(); /* ** この部分を完成させなさい */ s_traveller_move_right(); s_traveller_move_right(); s_traveller_move_right(); return 0; }
$ ./20190621-01-QQQQ.exe Food Get to 1 (S) --- (D) --- (D) --- (D) --- (G) 0 0 0 @(1) Food Get to 2 (S) --- (D) --- (D) --- (D) --- (G) 0 0 0 @(2) Food Get to 3 (S) --- (D) --- (D) --- (D) --- (G) 0 0 0 @(3) Move right (S) --- (D) --- (D) --- (D) --- (G) 0 0 0 @(2) Food Put to 1 (S) --- (D) --- (D) --- (D) --- (G) 1 0 0 @(1) Move left (S) --- (D) --- (D) --- (D) --- (G) 1 0 0 @(0) Food Get to 1 (S) --- (D) --- (D) --- (D) --- (G) 1 0 0 @(1) Food Get to 2 (S) --- (D) --- (D) --- (D) --- (G) 1 0 0 @(2) Food Get to 3 (S) --- (D) --- (D) --- (D) --- (G) 1 0 0 @(3) Move right (S) --- (D) --- (D) --- (D) --- (G) 1 0 0 @(2) Food Get to 3 (S) --- (D) --- (D) --- (D) --- (G) 0 0 0 @(3) Move right (S) --- (D) --- (D) --- (D) --- (G) 0 0 0 @(2) Move right (S) --- (D) --- (D) --- (D) --- (G) 0 0 0 @(1) Move right (S) --- (D) --- (D) --- (D) --- (G) 0 0 0 @(0) Goal !! $