C言語入門 第15週
プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
1
乱数
サイコロを振るようにランダムな値を得る
2
乱数: ランダムな数値を得る
• rand 関数 : 疑似乱数整数の生成
• srand 関数 : 疑似乱数系列の初期化
• time 関数 : 現在時刻の取得 毎回違う値を得るため 乱数系列初期化に用いる
3 教科書 p.318, 322.
rand 関数
• int rand(void) • [0:RAND_MAX]の範囲で整数の疑似乱数を返す
• 戻り値: • 疑似乱数の整数を返す
• 値の範囲は 0 以上 RAND_MAX 以下
• RAND_MAX は stdlib.h で定義されている
• RAND_MAX は少なくとも 32767 以上である
• RAND_MAX + 1 はオーバーフローするかもしれない
4
JM / rand(3)
教科書 p.322.
srand 関数
• void srand(unsigned int seed)
• 疑似乱数系列の初期化を行う
• 引数:
• seed: 疑似乱数の新しい系列の種 初期値は1
• 同じ種からは毎回同じ疑似乱数系列が生成される。
5
JM / rand(3)
教科書 p.322.
疑似乱数とは?
• 演算で生成する疑似的な乱数
• POSIX 1003.1-2003 で挙げられている実装例
static unsigned long next = 1; /* RAND_MAX を 32767 と仮定 */ int myrand(void) { next = next * 1103515245 + 12345; return((unsigned)(next/65536) % 32768); } void mysrand(unsigned int seed) { next = seed; }
計算式は決まっているので、 同じseedなら 毎回同じ計算になるため 毎回同じ乱数系列が生成される
6
乱数系列の確認
• seed の値で乱数系列がどうなるか確認
7
rand_ex1.c int seed, i; printf("seed = "); scanf("%d", &seed); srand(seed); printf("RAND_MAX: %d¥n", RAND_MAX); for (i = 0; i < 10; i++) { printf("%d¥n", rand()); }
乱数系列の初期化
6 7 8 9
10 11 12 13 14 15 16
Cygwin64 mintty + bash $ gcc rand_ex1.c && ./a seed = 1 RAND_MAX: 2147483647 1481765933 1085377743 1270216262 1191391529 812669700 553475508 445349752 1344887256 730417256 1812158119
seed が同じなら 毎回同じ乱数系列が生成される
乱数の初期化
実行毎に異なる乱数を得る
8
実行毎に異なる乱数系列に初期化
• seed の値で乱数系列がどうなるか確認
9
rand_ex2.c int i; srand(time(NULL)); printf("RAND_MAX: %d¥n", RAND_MAX); for (i = 0; i < 10; i++) { printf("%d¥n", rand()); }
6 7 8 9
10 11 12 13
Cygwin64 mintty + bash $ gcc rand_ex2.c && ./a RAND_MAX: 2147483647 408068090 654635880 1819541412 1080013827 1356279002 1536746152 352225876 1197042546 1830476305 459739427
毎回 seed が異なるため 毎回違う乱数系列が生成される
time関数は 現在時刻を返す関数
time 関数
• time_t time(time_t *t)
• 現在時刻を UNIX time で得る
• 引数:
• t: 通常はNULLで良い NULLでない場合*tにも戻り値を格納する
• 戻り値:
• 現在時刻を UNIX time で返す。
10
JM / time(2)
教科書 p.318.
UNIX time (UNIX時間、UNIX時刻)
• UNIX epoch (UNIX 紀元) • 1970-01-01 00:00:00 UTC
• UNIX time • UNIX epoch からの経過秒数
• 2038年問題 • 2038-01-19 03:14:07 UTC
= UNIX time: 2,147,483,647秒 = UNIC time: 0x7fffffff秒
• time_tが符号付き32bitの環境でオーバーフロー
11
EppochConverter
2038年問題
• 2038-01-19 03:14:07 UTC = UNIX time: 2,147,483,647秒 = UNIC time: 0x7fffffff秒
• time_t が符号付き 32bit の環境
• time_t がオーバーフロー
• 以降、正しい日時が処理できなくなる!
• 対策
• time_t の 64bit 化等の対応が必要
12
未対策の環境はあるのか?
• SOURCEFORGE.JP MAGAZINE / 2014-05-02: 2038年問題に対応した「OpenBSD 5.5」リリース
• http://sourceforge.jp/magazine/14/05/02/160000
• OpenBSD はセキュリティ面で非常に定評のある OS
• そんな OS でもつい最近になってようやく対応している状況もある。
13
time_t の確認
• 0x7fffffff秒,0x80000000秒,-1秒を確認
time_t_test.c char buf[1024]; time_t t = 0x7fffffff; struct tm *tm; printf("sizeof(time_t): %d¥n", sizeof(time_t)); printf("time_t has sign: %s¥n", (~(time_t) 0) < (time_t) 0 ? "YES" : "NO"); tm = gmtime(&t); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", tm); printf("%20luUL: %11ldL: %s¥n", (unsigned long) t, (long) t, buf); t++; tm = gmtime(&t); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", tm); printf("%20luUL: %11ldL: %s¥n", (unsigned long) t, (long) t, buf); t = -1; tm = gmtime(&t); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", tm); printf("%20luUL: %11ldL: %s¥n", (unsigned long) t, (long) t, buf);
14
strftime 関数
• size_t strftime(char *s, size_t max, const char *format, const struct tm *tm) • 日付と時刻を文字列に変換する
• 引数: • s: 変換結果の格納先(通常はchar型配列) • max: sのサイズ • format: 変換の書式 • tm: time_t 型の値をlocaltime関数または gmtime関数を用いて変換した日付と時刻情報
• 戻り値: • 終端文字列'¥0'を含めた変換結果のサイズ • 格納先のサイズが不足していた場合は0
15
JM / strftime(3) JM / ctime(3)
各環境のtime_tの状況
16
Cygwin64 + GNU C $ gcc time_t_test.c && ./a sizeof(time_t): 8 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: 2147483648L: 2038-01-19 03:14:08 UTC 18446744073709551615UL: -1L: 1969-12-31 23:59:59 UTC
Cygwin32 + GNU C
$ gcc time_t_test.c && ./a sizeof(time_t): 4 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: -2147483648L: 1901-12-13 20:45:52 UTC 4294967295UL: -1L: 1969-12-31 23:59:59 UTC
各環境のtime_tの状況
17
Borland C++ 5.5.1 >bcc32 time_t_test.c && time_t_test Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland time_t_test.c: Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland sizeof(time_t): 4 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: -2147483648L: 2038-01-19 03:14:08 UTC 4294967295UL: -1L: 2106-02-06 06:28:15 UTC
各環境のtime_tの状況
Visual Studio 2013 Express Desktop Windows 32bit版 >cl time_t_test.c && time_t_test Microsoft(R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86 Copyright (C) Microsoft Corporation. All rights reserved. time_t_test.c Microsoft (R) Incremental Linker Version 12.00.21005.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:time_t_test.exe time_t_test.obj sizeof(time_t): 8 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: -2147483648L: 2038-01-19 03:14:08 UTC 4294967295UL: -1L: 1969-12-31 23:59:59 UTC
VC は long が 32bit だったので 64bit 表示出来てない点には注意
18
各環境のtime_tの状況
Visual Studio 2013 Express Desktop Windows 64bit版 >cl time_t_test.c && time_t_test Microsoft(R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x64 Copyright (C) Microsoft Corporation. All rights reserved. time_t_test.c Microsoft (R) Incremental Linker Version 12.00.21005.1 Copyright (C) Microsoft Corporation. All rights reserved. /out:time_t_test.exe time_t_test.obj sizeof(time_t): 8 time_t has sign: YES 2147483647UL: 2147483647L: 2038-01-19 03:14:07 UTC 2147483648UL: -2147483648L: 2038-01-19 03:14:08 UTC 4294967295UL: -1L: 1969-12-31 23:59:59 UTC
VC は long が 32bit だったので 64bit 表示出来てない点には注意
19
乱数の範囲調整
任意の範囲の乱数を得る
20
[0:1) の実数の乱数生成法
• rand(): [0:RAND_MAX]の整数の乱数を生成
• [0:1) を得るには?
• 実数にして RAND_MAX + 1 で割れば良い
• RAND_MAX って幾つ?
• RAND_MAX + 1 だとオーバーフローするかも?
• RAND_MAX + 1.0 なら大丈夫
#define frand() (rand() / (RAND_MAX + 1.0))
[1] p.205.
暗黙の算術変換により 全てdoubleに型変換されて 計算される。
21
[0:N-1]の整数の乱数生成法
• [0:1) の実数の乱数を生成してNを掛けた後 整数に変換する
int x; x = frand() * N;
なぜ以下の計算方法では駄目か? x = rand() / RAMD_MAX * N; x = rand() / RAMD_MAX * (N – 1); x = frand() * (N – 1); ヒント: • 生成される値の範囲は? • N が出る確率は?
[0:N-1] の整数の乱数 = [0:N) の整数の乱数
22
N面体のサイコロ
• [1:N] の整数が等確率で欲しい
int x; x = frand() * N + 1;
[1:N] の整数の乱数 = [0:N-1] + 1 の整数の乱数 = [0:N) + 1 の整数の乱数
23
教科書の例
• 実は間違っている
24
test_p322.c #include <stdio.h> #include <stdlib.h> #include <time.h> #define RANGE_MIN 0 #define RANGE_MAX 10 void main() { int rand10; // 0以上未満 srand( (unsigned)time(NULL) ); rand10=(int)(((double) rand() / (double) RAND_MAX) * RANGE_MAX + RANGE_MIN); printf("求まった乱数は %d¥n", rand10); }
0~9 までは (RAND_MAX / 10) / (RAND_MAX + 1) の確率で出現するので 0以上10以下の乱数を意図したとしても 出現確率のバランスが悪い
rand() は 0 以上 RAND_MAX 以下 の値を返すので、この実装では 1/(RAND_MAX+1) の確率で 10 が出現してしまう
ここのコメントも おかしいが、 0以上10未満でも 0以上10以下でも やってはいけない実装
教科書 p.322.
乱数に関してよく見られる 有名な間違いです。
もっと質の良い疑似乱数
• random 関数 (POSIX.1-2001.)
• 非線形加法フィードバック
• JM / random(3)
• drand48 関数 (POSIX.1-2001.)
• 線形合同法+48bit整数
• JM / drand48(3)
• メルセンヌツイスタ
• Wikipedia / メルセンヌツイスタ
25
ファイル操作
ファイルに対する入出力
26
標準入出力と標準エラー出力
• 以下の入出力が利用できる
• stdin: STanDard INput: 標準入力
• stdout: STanDard OUTput: 標準出力
• stderr: STanDard ERRor output: 標準エラー出力
• scanf や getchar 等は stdin から入力している
• printf や putchar 等は stdout へ出力している
• stdin, stdout はパイプやリダイレクトの対象だが stderr は標準では対象外
[1] pp.196, 199, 218. 27
標準入出力と標準エラー出力
• パイプやリダイレクトで処理されたくない内容は stderr へ出力する
• fscanf や fprintf を使うと、入出力先を自由に選択出来る
[1] pp.196, 199, 218. 28
stdiotest.c printf("output to stdout with printf¥n"); fprintf(stdout, "output to stdout with fprintf¥n"); fprintf(stderr, "output to stderr with fprintf¥n");
mintty + bash $ ./stdiotest > redirect.txt output to stderr with fprintf $ cat redirect.txt output to stdout with printf output to stdout with fprintf
標準入出力と標準エラー出力
• stdin,stdout,stderrはstdio.hで定義されている
• stdio.h は standard input / output header
[1] pp.196, 199, 218. 29
fprintf 関数
• int fprintf(FILE *fp, const char *FORMAT, ...);
• printfの結果をfpへ書き出す
• 引数: • fp: FILE 構造体へのポインタ
• FORMAT: 書式
• ...: 任意の数の引数
• 戻り値: • 書き出された文字数
• エラーの場合負の数
教科書 pp.61, 64-66, 98, 300.
参考: [1] pp.305-306.
30
fscanf 関数
• int fscanf(FILE *fp, const char *FORMAT, ...); • fpからデータを読み込む
• 引数: • fp: FILE 構造体へのポインタ • FORMAT: 書式 • ...: 任意の数の引数
値を格納する変数へのポインタ
• 戻り値: • 変換され代入された入力項目の数 • ファイル終端またはエラーの場合EOF
教科書 pp.80-83, 254.
参考: [1] pp.307-309.
31
fopen 関数
• FILE *fopen(const char *filename, const char *mode); • ファイルを開き、FILE構造体へのポインタを得る
• 引数:
• filename: ファイル名(パス)の文字列
• mode: ファイルを開くモード
• 戻り値:
• FILE 構造体へのポインタ
• エラーの場合 NULL
教科書 pp.298-305.
参考: [1] pp.194-198.
32
fopen 関数の mode
mode 読み込み 書き込み 動作
"r" 任意の位置 × ファイルを開く、存在しない場合エラー
"w" × 任意の位置 ファイルを作成し、前の内容は消去する
"a" × ファイル末尾 ファイルを開く、または作成
"r+" 任意の位置 任意の位置 ファイルを開く、存在しない場合エラー
"w+" 任意の位置 任意の位置 ファイルを作成し、前の内容は消去する
"a+" 任意の位置 ファイル末尾 ファイルを開く、または作成
教科書 pp.298-305.
参考: [1] pp.194-198.
33
"r", "w", "a", "r+", "w+", "a+" はテキストモードで読み書きする テキストモードでは改行コード(¥n)の扱いが環境によって異なる • Windows: CR LF (0xd 0xa) • Mac: CR (0xd) • UNIX: LF (0xa) バイナリモードにするには、"rb", "wb", "ab", "r+b", "w+b", "a+b" のように "b" を追加する
fclose 関数
• int fclose(FILE *fp);
• ファイルを閉じます
• 引数:
• fp: FILE構造体へのポインタ
• 戻り値:
• 0 を返す
• エラーの場合 EOF を返す
教科書 pp.298-305.
参考: [1] pp.194-198.
34
ファイル入力の例
35
fprintf_ex1.c #include <stdio.h> #include <stdlib.h> int main() { FILE *fp; // ファイル入出力用のポインタ int value = 123; fp = fopen("sample.txt", "w"); // 書き込みモードでファイルを開く if (fp == NULL) { // エラー処理 fprintf(stderr, "Error: fopen: sample.txt¥n"); exit(EXIT_FAILURE); } fprintf(fp, "%d¥n", value); // fp に value の値を出力 fclose(fp); // 使い終わったファイルを閉じる return EXIT_SUCCESS; }
ファイル出力の例
36
fscanf_ex1.c #include <stdio.h> #include <stdlib.h> int main() { FILE *fp; // ファイル入出力用のポインタ int value; fp = fopen("sample.txt", "r"); // 読み込みモードでファイルを開く if (fp == NULL) { // エラー処理 fprintf(stderr, "Error: fopen: sample.txt¥n"); exit(EXIT_FAILURE); } fscanf(fp, "%d", &value); // fp から符号付き整数の文字列を読み込む printf("%d¥n", value); // 読み込んだ値を表示 fclose(fp); // 使い終わったファイルを閉じる return EXIT_SUCCESS; }
テスト
プログラムの動作を検証する
37
ユニットテスト(単体テスト)
• 標準ライブラリヘッダ <assert.h>
• assert マクロの利用
• ユニットテストツールの利用
• CUnit http://cunit.sourceforge.net/
38 教科書pp.188-189.
CUnit のインストール (cygwin) apt-cyg install CUnit
assert マクロ
void assert(int expression) • expression がゼロの場合以下のメッセージを stderr に出力し abort する
Assertion failed: expression, file filename, line nnn
• <assert.h>をインクルードする時点で NDEBUG マクロが定義されていると assert マクロは無視される
• ユニットテストだけでなくデバッグ時のみ有効にする不正値のチェック等でも利用される
39
JM / assert(3)
assert マクロ
• 専用のテストルーチンで使用した例
40
is_leap_year_assert.c void test_leap_year() { assert(is_leap_year(-400) == 1); assert(is_leap_year(- 56) == 1); assert(is_leap_year(- 4) == 1); assert(is_leap_year( 0) == 1); assert(is_leap_year( 4) == 1); assert(is_leap_year( 56) == 1); assert(is_leap_year( 400) == 1); assert(is_leap_year(1996) == 1); assert(is_leap_year(2000) == 1); assert(is_leap_year(2004) == 1); }
is_leap_year_assert.c void test_normal_year() { assert(is_leap_year(-300) == 0); assert(is_leap_year(-200) == 0); assert(is_leap_year(-100) == 0); assert(is_leap_year(- 3) == 0); assert(is_leap_year(- 2) == 0); assert(is_leap_year(- 1) == 0); assert(is_leap_year( 1) == 0); assert(is_leap_year( 2) == 0); assert(is_leap_year( 3) == 0); assert(is_leap_year( 100) == 0); assert(is_leap_year( 200) == 0); assert(is_leap_year( 300) == 0); assert(is_leap_year(1900) == 0); assert(is_leap_year(1997) == 0); assert(is_leap_year(1998) == 0); assert(is_leap_year(1999) == 0); assert(is_leap_year(2001) == 0); assert(is_leap_year(2002) == 0); assert(is_leap_year(2003) == 0); }
is_leap_year_assert.c int main() { test_leap_year(); test_normal_year(); return EXIT_SUCCESS; }
assert マクロ
• 専用のテストルーチンで使用した例
• エラーがなければ何も起きない
• エラーがあるとそこで実行が中断する
41
mintty + bash + GNU C
mintty + bash + GNU C
$ gcc is_leap_year_assert.c is_leap_year_func_ex4_2.c && ./a
$ gcc is_leap_year_assert.c is_leap_year_func_practice1.c && ./a assertion "is_leap_year(-300) == 0" failed: file "is_leap_year_assert.c", line 21, function: test_normal_year Aborted (コアダンプ)
CUnit
• 専用のテストルーチンを作成して使用
42
is_leap_year_cunit.c void test_leap_year() { CU_ASSERT_EQUAL(is_leap_year(-400), 1); CU_ASSERT_EQUAL(is_leap_year(- 56), 1); CU_ASSERT_EQUAL(is_leap_year(- 4), 1); CU_ASSERT_EQUAL(is_leap_year(1996), 1); CU_ASSERT_EQUAL(is_leap_year(2000), 1); CU_ASSERT_EQUAL(is_leap_year(2004), 1); }
is_leap_year_cunit.c void test_normal_year() { CU_ASSERT_EQUAL(is_leap_year(-300), 0); CU_ASSERT_EQUAL(is_leap_year(-200), 0); CU_ASSERT_EQUAL(is_leap_year(-100), 0); CU_ASSERT_EQUAL(is_leap_year(- 3), 0); CU_ASSERT_EQUAL(is_leap_year(2002), 0); CU_ASSERT_EQUAL(is_leap_year(2003), 0); }
is_leap_year_cunit.c static CU_TestInfo test_is_leap_year[] = { {"leap year", test_leap_year}, {"normal year", test_normal_year}, CU_TEST_INFO_NULL, }; static CU_SuiteInfo suites[] = { {"is_leap_year test", NULL, NULL, test_is_leap_year}, CU_SUITE_INFO_NULL, }; int main() { CU_initialize_registry(); CU_register_suites(suites); CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); CU_cleanup_registry(); return EXIT_SUCCESS; }
CUnit
• 専用のテストルーチンを作成して使用
• ユニットテストの達成状況がレポートされる
43
mintty + bash + GNU C $ gcc is_leap_year_cunit.c is_leap_year_func_ex4_2.c -lcunit && ./a CUnit - A unit testing framework for C - Version 2.1-2 http://cunit.sourceforge.net/ Suite: is_leap_year test Test: leap year ...passed Test: normal year ...passed Run Summary: Type Total Ran Passed Failed Inactive suites 1 1 n/a 0 0 tests 2 2 2 0 0 asserts 29 29 29 0 n/a Elapsed time = 0.000 seconds
テストの通過状況の 統計が表示される
CUnit
44
mintty + bash + GNU C $ gcc is_leap_year_cunit.c is_leap_year_func_practice1.c -lcunit && ./a CUnit - A unit testing framework for C - Version 2.1-2 http://cunit.sourceforge.net/ Suite: is_leap_year test Test: leap year ...passed Test: normal year ...FAILED 1. is_leap_year_cunit.c:21 - CU_ASSERT_EQUAL(is_leap_year(-300),0) 2. is_leap_year_cunit.c:22 - CU_ASSERT_EQUAL(is_leap_year(-200),0) 3. is_leap_year_cunit.c:23 - CU_ASSERT_EQUAL(is_leap_year(-100),0) 4. is_leap_year_cunit.c:30 - CU_ASSERT_EQUAL(is_leap_year( 100),0) 5. is_leap_year_cunit.c:31 - CU_ASSERT_EQUAL(is_leap_year( 200),0) 6. is_leap_year_cunit.c:32 - CU_ASSERT_EQUAL(is_leap_year( 300),0) 7. is_leap_year_cunit.c:33 - CU_ASSERT_EQUAL(is_leap_year(1900),0) Run Summary: Type Total Ran Passed Failed Inactive suites 1 1 n/a 0 0 tests 2 2 1 1 0 asserts 29 29 22 7 n/a Elapsed time = 0.000 seconds
テストの通過状況の 統計が表示される
変更箇所の管理
比較、差分、パッチ、バージョン管理等
45
diff のインストール
• mintty+bash から以下のコマンドを実行
46
mintty + bash apt-cyg install diff
diff
• UNIX系のファイル比較コマンド
47
mintty + bash $ diff is_leap_year_func_practice1.c is_leap_year_func_ex4_2.c 5c5 < return year % 4 == 0; --- > return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
上記の例では ファイル間の相違点を 一覧として表示している
diff
• 並列表示
• --side-by-sideオプションによる比較
48
mintty + bash $ diff is_leap_year_func_practice1.c is_leap_year_func_ex4_2.c --side-by-side #include "is_leap_year_func.h" #include "is_leap_year_func.h" int is_leap_year(int year) int is_leap_year(int year) { { return year % 4 == 0; | return (year % 4 == 0 && year % 100 != 0) || year % 400 == } }
fc
• Windows 標準添付のファイル比較コマンド
49
コマンドプロンプト >fc is_leap_year_func_practice1.c is_leap_year_func_ex4_2.c ファイル is_leap_year_func_practice1.c と IS_LEAP_YEAR_FUNC_EX4_2.C を比較しています ***** is_leap_year_func_practice1.c { return year % 4 == 0; } ***** IS_LEAP_YEAR_FUNC_EX4_2.C { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; } *****
WinMerge
• GUIによるテキストファイルの比較ツール
• 相違点の合成も出来る • http://www.forest.impress.co.jp/library/software/winmerge/
50
UNIX 環境の GUI 版の比較ツールだと meld や tkdiff 等がある
Rekisa
• GUIによる複数テキストファイルの比較ツール • http://www.forest.impress.co.jp/library/software/rekisa/
51
差分とパッチ
• diff : 差分比較、パッチ作成ツール
• JM / diff (1)
• Wikipedia / diff
• patch : 差分適用(パッチ適用)ツール
• JM / patch (1)
• Wikipedia / patch
52
単一ファイルのパッチの作成 $ diff -c myfile.orig myfile > myfile.patch
パッチの適用 $ patch < myfile.patch
ディレクトリ以下のパッチの作成 $ diff -crN mydir.orig mydir > mydir.patch
カレントディレクトリへのパッチの適用 $ patch -p0 -d. < mydir.patch
ファイル間の差異を パッチ(=絆創膏)として取り出し 適用することで変更箇所を反映させる
バージョン管理ツール
• RCS • Wikipedia / Revision Control System
• CVS • Wikipedia / Concurrent Version System
• Subversion • Wikipedia / Apache Subversion
• Mercurial • Wikipedia / Mercurial
• Bazaar • Wikipedia / Bazaar
• git • Wikipedia / git
53
github の登場で 人気になっている
過去の改変の記録を残したり 複数人で共同で作業する際に 役立つ
テキスト画面の簡易制御
tty_getchar.c
54
getchar 関数
• int getchar(void) • 入力 stream から1文字読み込む
• stream というのはバッファのようなもの
• 通常はENTERが押されるまで入力streamには値が入って来ない。入力ストリームに値がない場合は値が入ってくるまで待機する
• 戻り値: • 入力された文字の文字コード返す
• ファイル終端やエラーの場合はEOFを返す
55
JM / fgetc(3)
getchar 関数の動作
• ENTERが押されるまで一気に読み込む
56
getchartest.c #include <stdio.h> #include <stdlib.h> int main() { int c; while ((c = getchar()) != EOF) { printf("%#04x¥n", c); } return EXIT_SUCCESS; }
バッファリングと言います。 読み込み処理を 高速化するための仕組みです。
バッファリングに溜めてある 入力文字を1文字ずつ取り出します。 バッファが空になると ENTERが押されるまで 入力待ちの状態になります。
tty_getkey
• ENTER待ちなしのキーボード入力
57
tty_getkey_ex1.c #include "tty_getkey.h" #include "msleep.h" int main() { int c; tty_begin(); // 開始処理 while(tty_iskeyhit() == 0) { // 打鍵待ちループ msleep(1); // CPU に負荷をかけずに 1 msec 待つ // tty_ishitkey() は即座に値を返すので空ループだと CPU に負荷がかかる } c = tty_getkey(); // 打鍵キーの取得 tty_printf("%#x key was hit.¥n", c); // tty 用の printf tty_printf("Hit ESC key to exit.¥n"); while(KEY_ESC != tty_getkey()) { // 打鍵待ちループ ; // tty_ketkey() はキー入力があるまで待機するため空ループでも CPU に負荷をかけない } tty_end(); // 終了処理 return EXIT_SUCCESS; }
tty_getkey
• ENTER待ちなしのキーボード入力
• Windows 系の環境
• conio.h ライブラリを利用
• embarcadero / RAD Studio / conio.h
• MSDN / Console and Port I/O
• UNIX 系の環境
• curses ライブラリを利用
• http://ja.wikipedia.org/wiki/Curses
58
tty_getkey を利用したプログラムの コンパイル
• サンプルプログラム
• tty_getkey_ex1.c : サンプルプログラム本体
• 必要なファイル
• msleep.h : ミリ秒 sleep 用ヘッダ
• tty_getkey.h : tty_getkey ヘッダファイル
• tty_getkey.c : tty_getkey 本体
59
mintty + bash + GNU C $ gcc tty_getkey_ex1.c tty_getkey.c -lcurses
コマンドプロンプト + Borland C++ >bcc32 tty_getkey_ex1.c tty_getkey.c
gcc では -lcurses オプションが必要 これには ncurses ライブラリが必要
tty_getkey 利用前の準備 Cygwin の場合
• ncurses の開発用ライブラリが必要
• 以下のコマンドを入力してインストール
• Borland C++ では、標準添付の conio というライブラリを使っているので前準備は不要
60
Cygwin64 mintty + bash apt-cyg install libncursesw-devel
Cygwin32 mintty + bash apt-cyg install libncurses-devel
Cygwinが何bit版か確認する方法
• uname コマンドに -a オプションを付けて実行
61
Cygwin64 mintty + bash $ uname -a CYGWIN_NT-6.1 EX58EXTREME 1.7.27(0.271/5/3) 2013-12-09 11:54 x86_64 Cygwin
Cygwin32 mintty + bash $ uname -a CYGWIN_NT-6.1-WOW64 EX58EXTREME 1.7.27(0.271/5/3) 2013-12-09 11:57 i686 Cygwin
i686 なら 32bit 版
x86_64 なら 64bit 版
tty_getkey 初期化関数
• int tty_begin(void)
• tty_getkey の初期化処理を行います
• int tty_end(void)
• tty_getkey の終了処理を行います
62
tty_getkey キー待ち受け関数
• int tty_iskeyhit(void)
• キー入力の有無を調べます。
• キー入力があれば 1 なければ 0 を返します。
• int tty_getkey(void)
• キー入力を取得します。キー入力がない場合、キー入力が発生するまで待機します。
• 通常のキーは'a'や'A'等の文字コードを返します。
• 特殊キーの場合はKEY_UPやKEY_DOWN等のマクロで定義されたキーコードを返します。
63
tty_getkey() が返すキーコード
• KEY_INSERT
• KEY_DELETE
• KEY_HOME
• KEY_END
• KEY_PAGEUP
• KEY_PAGEDOWN
• KEY_UP
• KEY_DOWN
• KEY_LEFT
• KEY_RIGHT
• KEY_ESC
• KEY_TAB
• KEY_SPACE
• KEY_BS
• KEY_ENTER
• KEY_F1 ~ KEY_F48
通常のキーは 'a', 'A' 等の文字定数リテラルが対応
64
tty_getkey 出力関数
• int tty_printf(char *fmt, ....)
• 書式付の出力を行います。
• 画面制御を伴うためtty_begin()~tty_end()の間では、通常のprintfは使わないでください。
• int tty_setxy(int x, int y)
• カーソルの座標を(x,y)に移動します。
65
tty_getkey 画面情報関数
• int tty_getx(void)
• カーソルの x 座標を返します。
• int tty_gety(void)
• カーソルの y 座標を返します。
• int tty_getw(void)
• カーソルが移動可能な画面の幅を返します。
• int tty_geth(void)
• カーソルが移動可能な画面の高さを返します。
66
tty_getkey_ex2.c
• カーソルキーで移動、ESC キーで終了
• 移動した場所に * を表示する
67
tty_getkey_ex2.c
68
tty_getkey_ex2.c while (KEY_ESC != (c = tty_getkey())) { switch (c) { case KEY_UP: y--; break; case KEY_DOWN: y++; break; case KEY_RIGHT: x++; break; case KEY_LEFT: x--; break; } x = x < 1 ? 1 : w - 2 < x ? w - 2 : x; y = y < 1 ? 1 : h - 2 < y ? h - 2 : y; tty_setxy(0, 0); tty_printf("(%2d,%2d) : %#06x", x, y, c); tty_setxy(x, y); tty_printf("*"); }
入力された カーソルキーの方向に応じて 座標を上下左右に移動
画面から はみ出さないように 移動範囲を制限
tty_getkey_ex3.c
• 6面体サイコロの例
• 開始するとサイコロが転がり始める
• 何かキーを押すと3秒待って終了する
69
tty_getkey_ex3.c while(tty_iskeyhit() == 0) { d = frand() * 6 + 1; tty_setxy(0, 0); tty_printf("%d", d); msleep(1); } msleep(3000);
値域 [1:6] の乱数生成 = 6面体サイコロ
総合実習
プログラムで遊んでみる
70
tetris.c
• テトリスの簡易版
• 操作方法
• 移動: ← →
• 落下: ↓
• 回転: z x SPACE
• 修了: ESC
71
mintty + bash + GNU C $ gcc tetris.c tty_getkey.c -lcurses && ./a
コマンドプロンプト + Borland C++ >bcc32 tetris.c tty_getkey.c && tetris
mintty + bash + GNU C ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
tetris.c
• フィールド
72
描画用フィールド field[0][y][x]
固定ブロック用フィールド field[1][y][x]
浮動ブロック b[y][x]
ブロック形状 block[spec][y][x]
仮り配置
選択して回転
落下したら固定
描画用フィールドにコピー
tetris.c の改造
• 以下の改造を考えてみよう
• 「p」でポーズ/解除する
• 点数を表示する
• 次のブロックをランダムに決める
• 次のブロックを表示する
• 上まで積み上がったら ゲームオーバーにする
• ハイスコアを記録・表示してみる
73
mintty + bash + GNU C ## ## HISCORE: 001000 ## ## Score : 001000 ## ## ## ## NEXT ## ## ++--------++ ## ## ## || || ## ## ## || ## || ## #### ## || #### || ## ## || ## || ## ## ++--------++ ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
tetris.c の改造 ヒント
• 「p」でポーズ/解除する
• ポーズフラグ用の変数が必要
• 「p」キーでポーズフラグを反転する
• ポーズフラグがONなら continue してループを先頭からやり直す
74
フラグの反転 // 以下の処理はいずれも // pause == 0 なら 1 // pause != 0 なら 0 // となる pause = pause ? 0 : 1; pause = !pause; pause = pause == 0;
tetris.c の改造 ヒント
• 点数を表示する
• 点数用の変数が必要
• ブロックの落下や 1列消した場合に スコアを加算する
• 適当な位置にスコアを表示する
• 位置調整は tty_setxy()
• 表示は tty_printf()
75
mintty + bash + GNU C ## ## HISCORE: 001000 ## ## Score : 001000 ## ## ## ## NEXT ## ## ++--------++ ## ## ## || || ## ## ## || ## || ## #### ## || #### || ## ## || ## || ## ## ++--------++ ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
例えばこの位置に表示するなら tty_printf() の前に tty_setxy(W*2+2,1); を入れる
tetris.c の改造 ヒント
• 次のブロックをランダムに決める
• spec の値を乱数で決定すれば良い
• 前述の frand() マクロを使うと [0:7) の乱数は frand()*7 で得られる
76
tetris.c の改造 ヒント
• 次のブロックを表示する • 次のブロックを記憶するための変数が必要(※1) • ※1を使って spec を更新する • spec を更新後 ※1 も更新する • ※1 を適当な位置に表示する
• 表示位置は tty_setxy() で 調整する
• 枠は "++--------++" と "||" 以外でも 好きな文字を使えば良い
• block[※1][y][x] の値に応じて " " または "##" を表示する
77
mintty + bash + GNU C ## ## HISCORE: 001000 ## ## Score : 001000 ## ## ## ## NEXT ## ## ++--------++ ## ## ## || || ## ## ## || ## || ## #### ## || #### || ## ## || ## || ## ## ++--------++ ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
tetris.c の改造 ヒント
• 上まで積み上がったらゲームオーバーにする
• y <= 0 でブロックが固定されたら、上まで積み上がっている
• ゲームオーバーになるとどうするか?
• GAME OVER と表示する?
• その後、再度ゲームをスタートするか?プログラムを終了するか?
• 一般に流通しているゲームはどうしているか参考にすると良い?
78
tetris.c の改造
• ハイスコアを記録・表示してみる
• ハイスコア用の変数が必要
• ハイスコアは画面の適当な場所に表示する
• スコアがハイスコアを超えたらハイスコアを更新する必要がある
• ハイスコアはファイルに保存し、ゲーム開始時に読み込み終了時に保存する必要がある
• fopen, fclose でファイルのオープン、クローズが行える
• fprintf, fscanf で値の読み書きが行える
79
まとめ
やったこと、やらなかったこと
80
この授業で扱った事
• C言語の基礎
• 演算、式、変数、制御構造、関数、ポインタ等
• 簡単なプログラムの作成
• 奇数偶数の判定
• 閏年の判定
• 行列の演算
• シーザー暗号
• 等々
この授業で扱わなかったこと
• 構造体と共用体: struct, union
• 型定義: typedef
• 可変長引数: <stdarg.h>
• va_list, va_start, va_arg, va_end
• 非局所ジャンプ: <setjmp.h>
• シグナル処理: <signal.h>
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準拠、共立出版(1989)
83
おしまい
おつかれさまでした。
来週は試験です。しっかり復習しておきましょう。
お
か
だ
Top Related