Transcript Document

1
C言語入門
第5週
プログラミング言語Ⅰ(実習を含む。),
計算機言語Ⅰ・計算機言語演習Ⅰ,
情報処理言語Ⅰ(実習を含む。)
2
ツール等の紹介
3
Scratch
• グラフィカルに表現されたロジック使って、ブ
ロックを組む感覚でプログラムが作成出来る
• http://scratch.mit.edu/
• http://techkidscamp.jp/
4
プログラミン
• 文部科学省による
Scratch に似たプログラミング環境
• http://www.mext.go.jp/programin/
5
apt-cyg
• Cygwin用ソフトウェアパッケージ管理ツール
• パッケージが提供されているソフトに関しては
install サブコマンドで自動インストールが
可能(webを探し回る必要がない)。
• 使い方は help サブコマンド
• 例: diff コマンドのインストール
mintty + bash
apt-cyg install diff
6
apt-cyg と proxy の設定
• 学内から利用するにはproxyの設定が必要。apt-cyg
実行前に以下を mintty+bash へコピペして実行。
mintty + bash
export ftp_proxy=http://proxy.cc.yamaguchi-u.ac.jp:8080/
export http_proxy=http://proxy.cc.yamaguchi-u.ac.jp:8080/
export https_proxy=http://proxy.cc.yamaguchi-u.ac.jp:8080/
• 2014-05-06以降のversionはproxy自動検出対応済
upgrade-selfサブコマンドで一度apt-cyg自信をバー
ジョンアップしておけば以降proxyの設定は不要。
mintty + bash
apt-cyg upgrade-self
7
diff のインストール
• mintty+bash から以下のコマンドを実行
mintty + bash
apt-cyg install diff
8
diff
• UNIX系のファイル比較コマンド
mintty + bash
$ diff wavtest.c,20140410_211536.c wavtest.c
56c56
<
double dt = 1.0 / 22050.0;
-->
double dt = 1.0 / SampleRate;
58c58
<
v = INT16_MAX * sin(M_TWOPI * 1 * f * dt * i) * 0.05;
-->
v = INT16_MAX * sin(M_TWOPI * f * dt * i) * 0.05;
9
diff
• 並列表示
• --side-by-sideオプションによる比較
$ diff wavtest.c,20140410_211536.c wavtest.c –-side-by-side
10
fc
• Windows 標準添付のファイル比較コマンド
コマンドプロンプト
>fc wavtest.c,20140410_211536.c wavtest.c
ファイル wavtest.c,20140410_211536.c と WAVTEST.C を比較しています
***** wavtest.c,20140410_211536.c
double f = pow(2.0, (d - 69.0) / 12.0) * 440.0;
double dt = 1.0 / 22050.0;
for (i = 0; i < NumSamples; i++) {
v = INT16_MAX * sin(M_TWOPI * 1 * f * dt * i) * 0.05;
for (j = 0; j < NumChannels; j++) {
***** WAVTEST.C
double f = pow(2.0, (d - 69.0) / 12.0) * 440.0;
double dt = 1.0 / SampleRate;
for (i = 0; i < NumSamples; i++) {
v = INT16_MAX * sin(M_TWOPI * f * dt * i) * 0.05;
for (j = 0; j < NumChannels; j++) {
*****
11
WinMerge
• GUIによるテキストファイルの比較ツール
• 相違点の合成も出来る
• http://www.forest.impress.co.jp/library/software/winmerge/
12
Rekisa
• GUIによる複数テキストファイルの比較ツール
• http://www.forest.impress.co.jp/library/software/rekisa/
13
パイプライン
• 書式: コマンド1 | コマンド2
• コマンド1の出力をコマンド2の入力に繋ぐ
• コマンド2実行時にキーボードを打ってコマンド1
の出力と全く同じ内容を入力したのと同じ効果を
得る。
• 例:
mintty + bash
echo 2014 | ./a
コマンドプロンプト
echo 2014 | is_leap_year_1_1
14
入力のリダイレクト
• 書式: コマンド < ファイル
• ファイルの内容をコマンドの入力に繋ぐ
• コマンド実行時にキーボードを打ってファイルの
内容と全く同じ内容を入力したのと同じ効果を得
る。
• 例:
mintty + bash
test1_input.txt
./a < test1_input.txt
2014
コマンドプロンプト
is_leap_year_1_1 < test1_input.txt
15
出力のリダイレクト
• 書式: コマンド > ファイル
• コマンドの出力をファイルに繋ぐ
• コマンド実行時の出力をファイルに保存出来る。
• 例:
mintty + bash
./a > test1_result.txt
コマンドプロンプト
is_leap_year_1_1 > test1_result.txt
16
ファイル比較コマンドを用いた
動作テストの自動化
1. テスト用の入力と期待する出力を用意する
test1_input.txt
test1_expect.txt
2014
year = 2014 is not leap year.
2. テスト用の入力を与えて出力結果得る
mintty + bash
./a < test1_input.txt > test1_result.txt
コマンドプロンプト
is_leap_year_1_1 < test1_input.txt > test1_result.txt
17
テキスト比較ツールを用いた
動作テストの自動化
3. 期待する出力と実際の出力を比較する
mintty + bash
diff test1_expect.txt test1_result.txt
コマンドプロンプト
fc test1_expect.txt test1_result.txt
18
シェルスクリプト、バッチファイル
• コマンドを入力する代わりに、事前にファイル
に書いておいた一連のコマンドを実行出来る。
test_all.sh
#!/usr/bin/env bash
./a <
./a <
./a <
# ...
./a <
test1_input.txt > test1_result.txt && diff test1_expect.txt test1_input.txt
test1_input.txt > test2_result.txt && diff test2_expect.txt test2_input.txt
test1_input.txt > test3_result.txt && diff test3_expect.txt test3_input.txt
test1_input.txt > test9_result.txt && diff test9_expect.txt test9_input.txt
test_all.bat
@ECHO OFF
is_leap_year_1_1
is_leap_year_1_1
is_leap_year_1_1
REM ...
is_leap_year_1_1
< test1_input.txt > test1_result.txt && fc test1_expect.txt test1_result.txt
< test2_input.txt > test2_result.txt && fc test2_expect.txt test2_result.txt
< test3_input.txt > test3_result.txt && fc test3_expect.txt test3_result.txt
< test9_input.txt > test9_result.txt && fc test9_expect.txt test9_result.txt
19
テキスト比較ツールを用いた
動作テストの自動化
• シェルスクリプトによる一括テスト
test_all.sh
#!/usr/bin/env bash
./a <
./a <
./a <
# ...
./a <
test1_input.txt > test1_result.txt && diff test1_expect.txt test1_input.txt
test1_input.txt > test2_result.txt && diff test2_expect.txt test2_input.txt
test1_input.txt > test3_result.txt && diff test3_expect.txt test3_input.txt
test1_input.txt > test9_result.txt && diff test9_expect.txt test9_input.txt
mintty + bash
sh test_all.sh
mintty + bash
chmod +x
./test_all.sh
chmod コマンドで
実行ビットをセット(+x)すると
直接実行もできる
20
テキスト比較ツールを用いた
動作テストの自動化
• バッチファイルによる一括テスト
test_all.bat
@ECHO OFF
is_leap_year_1_1
is_leap_year_1_1
is_leap_year_1_1
REM ...
is_leap_year_1_1
< test1_input.txt > test1_result.txt && fc test1_expect.txt test1_result.txt
< test2_input.txt > test2_result.txt && fc test2_expect.txt test2_result.txt
< test3_input.txt > test3_result.txt && fc test3_expect.txt test3_result.txt
< test9_input.txt > test9_result.txt && fc test9_expect.txt test9_result.txt
コマンドプロンプト
test_all.bat
21
簡単なゲームの例
22
じゃんけん
日本語
English
• じゃんけんぽん!
• One, two, three, go!
• ぐー
• Stone
• ちょき
• Scissors
• ぱー
• Paper
23
じゃんけん
• 入力
•
•
•
•
キーボードから以下の文字を入力
"m" : グー
"v" : チョキ
"w" : パー
• 出力
• CPU の手を生成して勝敗判定
• 勝ち、負け、引き分けを表示
24
じゃんけん
• CPUの手の生成とプレイヤーの手の入力と
stone-scissors-paper1.c
#define frand() (rand() / (RAND_MAX + 1.0))
char hand_str[] = {'m', 'v', 'w'};
int cpu;
int you;
printf("'m': stone, 'v': scissors, 'w': paper\n");
printf("One, two, three, go!\n");
実行毎に異なる値を
printf("You : ");
得るための
srand(time(NULL));
疑似乱数の初期化
cpu = frand() * 3.0;
you = getchar();
疑似乱数を用いて
printf("CPU : %c\n", hand_str[cpu]);
0以上3未満の
整数を生成
25
列挙定数
stone
= 0
scissors = 1
paper
= 2
となっている
じゃんけん
• プレイヤーの手を数値に変換
stone-scissors-paper1.c
enum hand_enum {stone, scissors, paper};
// ...
switch (you) {
case 'm': you = stone;
break;
case 'v': you = scissors; break;
case 'w': you = paper;
break;
default:
printf("Your hand is invalid.\n");
return EXIT_FAILURE;
}
switch 文では
break が重要。
break がない場合
次の条件用の処理も
続けて実行する。
試に break を
削って実行してみると
どうなるか?
26
じゃんけん
• 勝敗の判定
stone-scissors-paper1.c
switch (cpu) {
case stone:
switch (you) {
case stone:
printf("Draw game.\n");
case scissors: printf("You lose.\n");
case paper:
printf("You win.\n");
}
break;
// ~中略~
case paper:
switch (you) {
case stone:
printf("You lose.\n");
case scissors: printf("You win.\n");
case paper:
printf("Draw game.\n");
}
break;
default:
printf("CPU hand is invalid.\n");
return EXIT_FAILURE;
}
break;
break;
break;
break;
break;
break;
switch 文では
break が重要。
break がない場合
次の条件用の処理も
続けて実行する。
試に break を
削って実行してみると
どうなるか?
27
定数
• リテラル
• 数値リテラル
• 文字列リテラル
• 列挙定数
28
列挙(enumeration)
列挙子(enumerator)
• 書式:
• enum 列挙名 {列挙定数名 [= 定整数値], ...}
• 値を設定しないと
• 0から開始
• 前の値から+1ずつ増加
• #define の代わりに使える
29
列挙(enumeration)
列挙子(enumerator)
• 例:
enum boolean {NO, YES};
enum escape
BELL
BACKSPACE
TAB
NEWLINE
VTAB
RETURN
{
=
=
=
=
=
=
NO = 0
YES = 1
JAN
FEB
MAR
...
DEC
= 1
= 2
= 3
= 12
'\a',
'\b', enum month {
'\t',
JAN = 1, FEB, MAR,
'\n',
APR, MAY, JUN,
'\v',
JUL, AUG, SEP,
'\r'};
OCT, NOV, DEC};
30
列挙(enumeration)
列挙子(enumerator)
• #define で書き変えた場合の例:
#define NO 0
#define YES 1
#define
#define
#define
#define
#define
#define
BELL
BACKSPACE
TAB
NEWLINE
VTAB
RETURN
'\a'
'\b'
'\t'
'\n'
'\v'
'\r'
#define
#define
#define
// ...
#define
JAN
FEB
MAR
1
2
3
DEC 12
31
列挙(enumeration)
列挙子(enumerator)
• stone-scissors-paper1.c の例
stone-scissors-paper1.c
enum hand_enum {stone, scissors, paper};
// ...
switch (you) {
case 'm': you = stone;
break;
case 'v': you = scissors; break;
case 'w': you = paper;
break;
default:
printf("Your hand is invalid.\n");
return EXIT_FAILURE;
}
stone
= 0
scissors = 1
paper
= 2
となっている
32
教科書 p.318, 322.
乱数: ランダムな数値を得る
• rand 関数
• srand 関数
• time 関数
:
:
:
疑似乱数整数の生成
疑似乱数系列の初期化
現在時刻の取得
毎回違う値を得るため
乱数系列初期化に用いる
33
教科書 p.322.
rand 関数
• int rand(void)
• [0:RAND_MAX]の範囲で整数の疑似乱数を返す
• 戻り値:
•
•
•
•
•
疑似乱数の整数を返す
値の範囲は 0 以上 RAND_MAX 以下
RAND_MAX は stdlib.h で定義されている
RAND_MAX は少なくとも 32767 以上である
RAND_MAX + 1 はオーバーフローするかもしれ
ない
JM / rand(3)
34
教科書 p.322.
srand 関数
• void srand(unsigned int seed)
• 疑似乱数系列の初期化を行う
• 引数:
• seed: 疑似乱数の新しい系列の種
初期値は1
• 同じ種からは毎回同じ疑似乱数系列が生成
される。
JM / rand(3)
35
疑似乱数とは?
• 演算で生成する疑似的な乱数
• 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なら
毎回同じ計算になるため
毎回同じ乱数系列が生成される
36
乱数系列の確認
• seed の値で乱数系列がどうなるか確認
randtest.c
int seed, i;
printf("seed = ");
scanf("%d", &seed);
srand(seed);
乱数系列の初期化
printf("RAND_MAX: %.0f\n", (double) RAND_MAX);
for (i = 0; i < 10; i++) {
printf("%.0f\n", (double) rand());
}
37
実行毎に異なる乱数系列に初期化
• stone-scissors-paper1.c の例
stone-scissors-paper1.c
#define frand() (rand() / (RAND_MAX + 1.0))
char hand_str[] = {'m', 'v', 'w'};
int cpu;
int you;
printf("'m': stone, 'v': scissors, 'w': paper\n");
printf("One, two, three, go!\n");
time関数は
printf("You : ");
現在時刻を返す関数なので
srand(time(NULL));
実行毎に異なる
cpu = frand() * 3.0;
seed を与える事が出来る
you = getchar();
printf("CPU : %c\n", hand_str[cpu]);
38
教科書 p.318.
time 関数
• time_t time(time_t *t)
• 現在時刻を UNIX time で得る
• 引数:
• t:
通常はNULLで良い
NULLでない場合*tにも戻り値を格納する
• 戻り値:
• 現在時刻を UNIX time で返す。
JM / time(2)
39
UNIX time (UNIX時間、UNIX時刻)
• UNIX epoch (UNIX 紀元)
EppochConverter
• 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の環境でオーバーフロー
40
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 化等の対応が必要
41
未対策の環境はあるのか?
• SOURCEFORGE.JP MAGAZINE / 2014-05-02:
2038年問題に対応した「OpenBSD 5.5」リリース
• http://sourceforge.jp/magazine/14/05/02/160000
• OpenBSD はセキュリティ面で非常に定評のある OS
• そんな OS でもつい最近になってようやく対応してい
る状況もある。
42
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);
43
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)
44
各環境の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
45
各環境のtime_tの状況
Borland C++ 5.5.1
>bcc32 time_t_test.c && time_t_test
Borland C++ 5.5.1 for Win32 Copyright
time_t_test.c:
Turbo Incremental Link 5.00 Copyright
sizeof(time_t): 4
time_t has sign: YES
2147483647UL: 2147483647L:
2147483648UL: -2147483648L:
4294967295UL:
-1L:
(c) 1993, 2000 Borland
(c) 1997, 2000 Borland
2038-01-19 03:14:07 UTC
2038-01-19 03:14:08 UTC
2106-02-06 06:28:15 UTC
46
各環境の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
VC は long が 32bit だったので
sizeof(time_t): 8
64bit 表示出来てない点には注意
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
47
各環境の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
VC は long が 32bit だったので
sizeof(time_t): 8
64bit 表示出来てない点には注意
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
48
[1] p.205.
[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))
暗黙の算術変換により
全てdoubleに型変換されて
計算される。
[1] pp.52-57, 240-244.
暗黙の算術変換 (概略)
• 二項演算子の両辺が異なる型の場合
以下のルールで変換(符号ありの場合)
•
•
•
•
•
片方がlong double: 他方をlong doubleに変換
片方がdouble: 他方を doubleに変換
片方がfloat: 他方をfloatに変換
char, shortをintに変換
片方がlong: 他方をlongに変換
49
50
[0:N-1]の整数の乱数生成法
• [0:1) の実数の乱数を生成してNを掛けた後
整数に変換する
int x;
x = frand() * N;
[0:N-1] の整数の乱数
= [0:N)
の整数の乱数
なぜ以下の計算方法では駄目か?
x = rand() / RAMD_MAX * N;
x = rand() / RAMD_MAX * (N – 1);
x = frand()
* (N – 1);
ヒント:
• 生成される値の範囲は?
• N が出る確率は?
51
任意の範囲の乱数整数
• stone-scissors-paper1.c の例
stone-scissors-paper1.c
#define frand() (rand() / (RAND_MAX + 1.0))
char hand_str[] = {'m', 'v', 'w'};
int cpu;
int you;
printf("'m': stone, 'v': scissors, 'w': paper\n");
printf("One, two, three, go!\n");
printf("You : ");
srand(time(NULL));
cpu = frand() * 3.0;
[0:2] の乱数整数の生成
you = getchar();
printf("CPU : %c\n", hand_str[cpu]);
52
N面体のサイコロ
• [1:N] の整数が等確率で欲しい
int x;
x = frand() * N + 1;
[1:N]
の整数の乱数
= [0:N-1] + 1 の整数の乱数
= [0:N)
+ 1 の整数の乱数
53
教科書 p.322.
教科書の例
• 実は間違っている
test_p322.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define RANGE_MIN 0
#define RANGE_MAX 10
void main()
{
0~9 までは
(RAND_MAX / 10) / (RAND_MAX + 1)
の確率で出現するので
0以上10以下の乱数を意図したとしても
出現確率のバランスが悪い
ここのコメントも
おかしいが、
0以上10未満でも
0以上10以下でも
やってはいけない実装
rand() は
0 以上 RAND_MAX 以下
の値を返すので、この実装では
1/(RAND_MAX+1) の確率で
10 が出現してしまう
int rand10;
// 0以上未満
srand( (unsigned)time(NULL) );
rand10=(int)(((double) rand() / (double) RAND_MAX)
* RANGE_MAX + RANGE_MIN);
printf("求まった乱数は %d\n", rand10);
}
乱数に関してよく見られる
有名な間違いです。
54
もっと質の良い疑似乱数
• random 関数 (POSIX.1-2001.)
• 非線形加法フィードバック
• JM / random(3)
• drand48 関数 (POSIX.1-2001.)
• 線形合同法+48bit整数
• JM / drand48(3)
• メルセンヌツイスタ
• Wikipedia / メルセンヌツイスタ
55
getchar 関数
• int getchar(void)
• 入力 stream から1文字読み込む
• stream というのはバッファのようなもの
• 通常はENTERが押されるまで入力streamには値
が入って来ない。入力ストリームに値がない場合
は値が入ってくるまで待機する
• 戻り値:
• 入力された文字の文字コード返す
• ファイル終端やエラーの場合はEOFを返す
JM / fgetc(3)
56
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が押されるまで
入力待ちの状態になります。
57
tty_getkey
• ENTER待ちなしのキーボード入力
tty_getkey_example1.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;
}
58
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
59
tty_getkey を利用したプログラムの
コンパイル
• サンプルプログラム
• tty_getkey_example1.c : サンプルプログラム本体
• 必要なファイル
• msleep.h
• tty_getkey.h
• tty_getkey.c
: ミリ秒 sleep 用ヘッダ
: tty_getkey ヘッダファイル
: tty_getkey 本体
mintty + bash + GNU C
$ gcc tty_getkey_example1.c tty_getkey.c -lcurses
コマンドプロンプト + Borland C++
>bcc32 tty_getkey_example1.c tty_getkey.c
gcc では
-lcurses オプションが必要
60
tty_getkey 利用前の準備
Cygwin の場合
• ncurses の開発用ライブラリが必要
• 以下のコマンドを入力してインストール
Cygwin64 mintty + bash
apt-cyg install libncursesw-devel
Cygwin32 mintty + bash
apt-cyg install libncurses-devel
• Borland C++ は、標準添付の conio というライ
ブラリを使っているので前準備は不要
61
Cygwinが何bit版か確認する方法
• uname コマンドに -a オプションを付けて実行
x86_64 なら 64bit 版
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 版
62
tty_getkey 初期化関数
• int tty_begin(void)
• tty_getkey の初期化処理を行います
• int tty_end(void)
• tty_getkey の終了処理を行います
63
tty_getkey キー待ち受け関数
• int tty_iskeyhit(void)
• キー入力の有無を調べます。
• キー入力があれば 1 なければ 0 を返します。
• int tty_getkey(void)
• キー入力を取得します。キー入力がない場合、キー
入力が発生するまで待機します。
• 通常のキーは'a'や'A'等の文字コードを返します。
• 特殊キーの場合はKEY_UPやKEY_DOWN等のマクロで
定義されたキーコードを返します。
64
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' 等の
文字定数リテラルが対応
65
tty_getkey 出力関数
• int tty_printf(char *fmt, ....)
• 書式付の出力を行います。
• 画面制御を伴うためtty_begin()~tty_end()
の間では、通常のprintをは使わないでください。
• int tty_setxy(int x, int y)
• カーソルの座標を(x,y)に移動します。
66
tty_getkey 画面情報関数
• int tty_getx(void)
• カーソルの x 座標を返します。
• int tty_gety(void)
• カーソルの y 座標を返します。
• int tty_getw(void)
• カーソルが移動可能な画面の幅を返します。
• int tty_geth(void)
• カーソルが移動可能な画面の高さを返します。
67
tty_getkey_example2
• カーソルキーで移動、ESC キーで終了
• 移動した場所に * を表示する
68
tty_getkey_example2
tty_getkey_example2.c
while (KEY_ESC !=
switch (c) {
case KEY_UP:
case KEY_DOWN:
case KEY_RIGHT:
case KEY_LEFT:
}
x = x < 1 ? 1 :
y = y < 1 ? 1 :
(c = tty_getkey())) {
y--;
y++;
x++;
x--;
break;
break;
break;
break;
入力された
カーソルキーの方向に応じて
座標を上下左右に移動
w - 2 < x ? w - 2 : x;
h - 2 < y ? h - 2 : y;
tty_setxy(0, 0);
tty_printf("(%2d,%2d) : %#06x", x, y, c);
tty_setxy(x, y);
tty_printf("*");
}
画面から
はみ出さないように
移動範囲を制限
69
tty_getkey_example3
• 6面体サイコロの例
• 開始するとサイコロが転がり始める
• 何かキーを押すと3秒待って終了する
tty_getkey_example3.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
参考文献
• [1] B.W.カーニハン/D.M.リッチー著 石田晴久
訳、プログラミング言語C 第2版 ANSI 規格準
拠、共立出版(1989)