C言語入門
第15週
プログラミング言語Ⅰ(実習を含む。),
計算機言語Ⅰ・計算機言語演習Ⅰ,
乱数
乱数: ランダムな数値を得る
• rand 関数
:
疑似乱数整数の生成
• srand 関数
:
疑似乱数系列の初期化
• time 関数
:
現在時刻の取得
毎回違う値を得るため
乱数系列初期化に用いる
教科書 p.318, 322.
rand 関数
• int rand(void)
• [0:RAND_MAX]の範囲で整数の疑似乱数を返す
• 戻り値:
• 疑似乱数の整数を返す
• 値の範囲は 0 以上 RAND_MAX 以下
• RAND_MAX は stdlib.h で定義されている
• RAND_MAX は少なくとも 32767 以上である
• RAND_MAX + 1 はオーバーフローするかもしれ
ない
JM /
rand
(3)
教科書 p.322.
srand 関数
• void srand(unsigned int seed)
• 疑似乱数系列の初期化を行う
• 引数:
• seed: 疑似乱数の新しい系列の種
初期値は1
• 同じ種からは毎回同じ疑似乱数系列が生成
される。
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なら
毎回同じ計算になるため
毎回同じ乱数系列が生成される
乱数系列の確認
• seed の値で乱数系列がどうなるか確認
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 16Cygwin64 mintty + bash
$ gcc rand_ex1.c && ./a seed = 1 RAND_MAX: 2147483647 1481765933 1085377743 1270216262 1191391529 812669700 553475508 445349752 1344887256 730417256 1812158119
seed が同じなら
毎回同じ乱数系列が生成される
乱数の初期化
実行毎に異なる乱数系列に初期化
• seed の値で乱数系列がどうなるか確認
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 1213 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 で返す。
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の環境でオーバーフロー
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 化等の対応が必要
未対策の環境はあるのか?
• SOURCEFORGE.JP MAGAZINE / 2014-05-02:
2038年問題に対応した「OpenBSD 5.5」リリース
•
http://sourceforge.jp/magazine/14/05/02/160000
• OpenBSD はセキュリティ面で非常に定評のある OS
• そんな OS でもつい最近になってようやく対応してい
る状況もある。
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);
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
JM /
strftime
(3)
JM /
ctime
(3)
各環境のtime_tの状況
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の状況
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 表示出来てない点には注意
各環境の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 表示出来てない点には注意
乱数の範囲調整
[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に型変換されて
計算される。
[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) の整数の乱数
N面体のサイコロ
• [1:N] の整数が等確率で欲しい
int x;
x = frand() * N + 1;
[1:N] の整数の乱数
= [0:N-1] + 1 の整数の乱数
= [0:N) + 1 の整数の乱数
教科書の例
• 実は間違っている
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 /
メルセンヌツイスタ
ファイル操作
標準入出力と標準エラー出力
• 以下の入出力が利用できる
• stdin: STanDard INput:
標準入力
• stdout: STanDard OUTput:
標準出力
• stderr: STanDard ERRor output: 標準エラー出力
• scanf や getchar 等は stdin から入力している
• printf や putchar 等は stdout へ出力している
• stdin, stdout はパイプやリダイレクトの対象だ
が stderr は標準では対象外
標準入出力と標準エラー出力
• パイプやリダイレクトで処理されたくない内容
は stderr へ出力する
• fscanf や fprintf を使うと、入出力先を自由に
選択出来る
[1] pp.196, 199, 218.
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
fprintf 関数
• int fprintf(FILE *fp,
const char *FORMAT, ...);
• printfの結果をfpへ書き出す
• 引数:
• fp:
FILE 構造体へのポインタ
• FORMAT:
書式
• ...:
任意の数の引数
• 戻り値:
• 書き出された文字数
• エラーの場合負の数
教科書 pp.61, 64-66, 98, 300.
参考: [1] pp.305-306.
fscanf 関数
• int fscanf(FILE *fp,
const char *FORMAT, ...);
• fpからデータを読み込む
• 引数:
• fp:
FILE 構造体へのポインタ
• FORMAT:
書式
• ...:
任意の数の引数
値を格納する変数へのポインタ
• 戻り値:
• 変換され代入された入力項目の数
• ファイル終端またはエラーの場合EOF
教科書 pp.80-83, 254.
参考: [1] pp.307-309.
fopen 関数
• FILE *fopen(const char *filename,
const char *mode);
• ファイルを開き、FILE構造体へのポインタを得る
• 引数:
• filename: ファイル名(パス)の文字列
• mode:
ファイルを開くモード
• 戻り値:
• FILE 構造体へのポインタ
• エラーの場合 NULL
教科書 pp.298-305.
参考: [1] pp.194-198.
fopen 関数の mode
mode 読み込み
書き込み
動作
"r"
任意の位置 ×
ファイルを開く、存在しない場合エラー
"w"
×
任意の位置
ファイルを作成し、前の内容は消去する
"a"
×
ファイル末尾 ファイルを開く、または作成
"r+"
任意の位置 任意の位置
ファイルを開く、存在しない場合エラー
"w+"
任意の位置 任意の位置
ファイルを作成し、前の内容は消去する
"a+"
任意の位置 ファイル末尾 ファイルを開く、または作成
教科書 pp.298-305.
参考: [1] pp.194-198.
"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.
ファイル入力の例
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; }
ファイル出力の例
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; }
テスト
ユニットテスト(単体テスト)
• 標準ライブラリヘッダ <assert.h>
• assert マクロの利用
• ユニットテストツールの利用
• CUnit
http://cunit.sourceforge.net/
教科書pp.188-189.
CUnit のインストール (cygwin)
assert マクロ
void assert(int expression)
• expression がゼロの場合以下のメッセージを
stderr に出力し abort する
Assertion failed: expression, file filename, line nnn
• <assert.h>をインクルードする時点で NDEBUG
マクロが定義されていると assert マクロは無
視される
• ユニットテストだけでなくデバッグ時のみ有効に
する不正値のチェック等でも利用される
JM /
assert
(3)
assert マクロ
• 専用のテストルーチンで使用した例
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 マクロ
• 専用のテストルーチンで使用した例
• エラーがなければ何も起きない
• エラーがあるとそこで実行が中断する
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
CUnit
• 専用のテストルーチンを作成して使用
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
• 専用のテストルーチンを作成して使用
• ユニットテストの達成状況がレポートされる
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
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
テストの通過状況の
統計が表示される
変更箇所の管理
diff のインストール
• mintty+bash から以下のコマンドを実行
mintty + bash
diff
• UNIX系のファイル比較コマンド
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オプションによる比較
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 標準添付のファイル比較コマンド
コマンドプロンプト
>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/
UNIX 環境の GUI 版の比較ツールだと meld や tkdiff 等があるRekisa
• GUIによる複数テキストファイルの比較ツール
差分とパッチ
• diff : 差分比較、パッチ作成ツール
• JM /
diff
(1)
• Wikipedia /
diff
• patch : 差分適用(パッチ適用)ツール
• JM /
patch
(1)
• Wikipedia /
patch
単一ファイルのパッチの作成
$ 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
github
人気になっている
の登場で
過去の改変の記録を残したり
複数人で共同で作業する際に
役立つ
テキスト画面の簡易制御
getchar 関数
• int getchar(void)
• 入力 stream から1文字読み込む
• stream というのはバッファのようなもの
• 通常はENTERが押されるまで入力streamには値
が入って来ない。入力ストリームに値がない場合
は値が入ってくるまで待機する
• 戻り値:
• 入力された文字の文字コード返す
• ファイル終端やエラーの場合はEOFを返す
JM /
fgetc
(3)
getchar 関数の動作
• ENTERが押されるまで一気に読み込む
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待ちなしのキーボード入力
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 ライブラリを利用
tty_getkey を利用したプログラムの
コンパイル
• サンプルプログラム
• tty_getkey_ex1.c : サンプルプログラム本体
• 必要なファイル
• msleep.h
: ミリ秒 sleep 用ヘッダ
• tty_getkey.h
: tty_getkey ヘッダファイル
• tty_getkey.c
: tty_getkey 本体
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 オプションが必要
tty_getkey 利用前の準備
Cygwin の場合
• ncurses の開発用ライブラリが必要
• 以下のコマンドを入力してインストール
• Borland C++ では、標準添付の conio というラ
イブラリを使っているので前準備は不要
Cygwin64 mintty + bash
apt-cyg install libncursesw-devel
Cygwin32 mintty + bash
Cygwinが何bit版か確認する方法
• uname コマンドに -a オプションを付けて実行
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 キー待ち受け関数
• int tty_iskeyhit(void)
• キー入力の有無を調べます。
• キー入力があれば 1 なければ 0 を返します。
• int tty_getkey(void)
• キー入力を取得します。キー入力がない場合、キー
入力が発生するまで待機します。
• 通常のキーは'a'や'A'等の文字コードを返します。
• 特殊キーの場合はKEY_UPやKEY_DOWN等のマクロで
定義されたキーコードを返します。
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' 等の
文字定数リテラルが対応
tty_getkey 出力関数
• int tty_printf(char *fmt, ....)
• 書式付の出力を行います。
• 画面制御を伴うためtty_begin()~tty_end()
の間では、通常のprintfは使わないでください。
• int tty_setxy(int x, int y)
tty_getkey 画面情報関数
• int tty_getx(void)
• カーソルの x 座標を返します。
• int tty_gety(void)
• カーソルの y 座標を返します。
• int tty_getw(void)
• カーソルが移動可能な画面の幅を返します。
• int tty_geth(void)
• カーソルが移動可能な画面の高さを返します。
tty_getkey_ex2.c
• カーソルキーで移動、ESC キーで終了
• 移動した場所に * を表示する
tty_getkey_ex2.c
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秒待って終了する
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面体サイコロ
総合実習
tetris.c
• テトリスの簡易版
• 操作方法
• 移動: ← →
• 落下: ↓
• 回転: z x SPACE
• 修了: ESC
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
• フィールド
描画用フィールド
field[0][y][x]
固定ブロック用フィールド
field[1][y][x]
浮動ブロック
b[y][x]
ブロック形状
block[spec][y][x]
仮り配置
選択して回転
落下したら固定
描画用フィールドにコピー
tetris.c の改造
• 以下の改造を考えてみよう
• 「p」でポーズ/解除する
• 点数を表示する
• 次のブロックをランダムに決める
• 次のブロックを表示する
• 上まで積み上がったら
ゲームオーバーにする
• ハイスコアを記録・表示してみる
mintty + bash + GNU C
## ## HISCORE: 001000 ## ## Score : 001000 ## ## ## ## NEXT ## ## ++---++ ## ## ## || || ## ## ## || ## || ## #### ## || #### || ## ## || ## || ## ## ++---++ ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################
tetris.c の改造
ヒント
• 「p」でポーズ/解除する
• ポーズフラグ用の変数が必要
• 「p」キーでポーズフラグを反転する
• ポーズフラグがONなら continue してループを先
頭からやり直す
フラグの反転
// 以下の処理はいずれも
// pause == 0 なら 1
// pause != 0 なら 0
// となる
pause = pause ? 0 : 1;
pause = !pause;
pause = pause == 0;
tetris.c の改造
ヒント
• 点数を表示する
• 点数用の変数が必要
• ブロックの落下や
1列消した場合に
スコアを加算する
• 適当な位置にスコアを表示する
• 位置調整は tty_setxy()
• 表示は tty_printf()
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 で得られる
tetris.c の改造
ヒント
• 次のブロックを表示する
• 次のブロックを記憶するための変数が必要(※1)
• ※1を使って spec を更新する
• spec を更新後 ※1 も更新する
• ※1 を適当な位置に表示する
• 表示位置は tty_setxy() で
調整する
• 枠は "++---++" と
"||" 以外でも
好きな文字を使えば良い
• block[※1][y][x] の値に応じて
" " または "##" を表示する
mintty + bash + GNU C
## ## HISCORE: 001000 ## ## Score : 001000 ## ## ## ## NEXT ## ## ++---++ ## ## ## || || ## ## ## || ## || ## #### ## || #### || ## ## || ## || ## ## ++---++ ## ## ## ## ## ## ## ## ## ## #### ## ## ## #### ## #### #### ## ############## ## ############## ####################