/*
* 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 !! $