第3回演習

・お約束[コンパイルのやり方]

gcc filename.c -o outputfilename

基本的にこれでできます。
「-o」をくっつけると、コンパイル後にできる
実行可能ファイルの名前を指定した名前に変えられます。
もちろん、そんなややこしい事はせずに、

gcc filename.c

だけでももちろんコンパイルできます。

・課題の提出について

基本的に、次回までの課題は、
次回の授業開始時に各グループごとにチェックします。
そのときにプログラムがしっかり意図した通りに動けばまずOKです。

そして課題の提出として、そのプログラムを提出するのを忘れないで下さい。
提出方法は至極簡単。
毎回指定されたディレクトリに指定されたファイル名で
プログラムを保存して下さい。
あとは自動でこちらが回収します。
(保存するディレクトリ、ファイル名については
 『今週の課題』のところ等に明記されています)

指定されたディレクトリに指定されたファイル名でプログラムを保存する方法

ファイルやディレクトリを作ったときのパーミッションについて


今週やる事

(※)今回の演習用ディレクトリとして「~/sccp/ex03/」を作成し、
今回作成したファイルはすべてその中に保存してください。


・まず、キーボードから入力された文字の受け取り方をやります。
 それからif文という文をつかってプログラムの中に分岐点を仕込んでみます。


1.[キーボードからの文字入力]

やはりプログラムたるもの、
ユーザーからの入力に答えられなくてはいけません。
それを実現するためのものが「getchar()」という関数です。

使い方は至って単純。
「ここでキーボードから1文字だけ文字を入力してほしい」と思ったところで

変数 = getchar();

とするだけです。

では、以下のプログラムをp01.cとして保存し、実行してみましょう。

#include<stdio.h>

main(){

  int c; /* キーボードから受け取った文字を入れておく変数 */

  c = getchar(); /* キーボードから1文字だけ受け取る */
   
  printf("c = %c \n", c); /* [%c]を使って文字を表示 */
  printf("c = %d \n", c); /* [%d]を使って、文字を数値として表示できる */

}

実行結果

実行直後。
とりあえず何か
入力してあげます。
1文字しか受け取れない
はずなのに、4文字入力。
そしてEnterキーを押して確定。
すると、最初の1文字だけ
確かに受け取られ、
あとの文字は捨てられる。

getchar()で確かに1文字だけ受け取ることに成功しています。
そうなんです。getchar()1回に1文字しか受け取れないのです。

また、受け取った文字を表示するために

printf(" c = %c \n", c);

としています。
前回のやつとはまた違いますね。
前回%dだったところが%cになってます。
実はこうすることで、getchar()で受け取った文字を表示できるようになります。

かと思えば、一方で、

printf(" c = %d \n", c);

と、せっかく文字として受け取ったデータを%dを使って表示させることも可能です。
これはプログラム内部ではキーボードから入力された文字ですら
数値として記憶・処理されている事を示しています。

それから、キーボードから入力を受け取る関数の大半が持っている特徴として、
文字を入力したあと、Enterキーが押されて初めて、入力された文字を受け取ることができる、
という大変いらない特徴があります。
ちょっとしたゲームを作るときにもこの特徴が大きな壁となって作る側にのしかかってきます。
一応、暫定的な解決策はp04.cで提示されますが…

次に、getchar()はキーボードから入力を受け取ることができるというのは前述のとおりですが、
実はそれは「abc」とかいった「文字」だけに限ったものではありません。
Enterキーを押したときに送られてくる「改行」とか、
矢印キーを押したときに送られてくる「エスケープシーケンス」とか、
とにかくキーボードから入力されるものなら何でも受け取ってしまうので注意してください。

実行例

何も入力せずに、Enterキーだけ押したときの様子。

最初の「c =」の後に何も表示されていないため、
一見何も入力されなかったかのように見える。

しかし、実は最初の「c =」の後には「改行」が入っている。
Enterキーを押したときに送られてくる「改行」を
getchar()がしっかりと受け取り、
それを変数「c」に代入していたということ。

そのため、%dをつかってcを表示させている2つめの「c =」の部分では、
しっかりと「c = 10」と出ている。

2.[入力を促す]

何をやるかというと、
p01.cを実行したときに、
画面に何も表示されないまま、キーボードから入力待ちになるため、
え?もうなにか入力していいの?」状態に陥る人がいる可能性もあります。

そんなときのために、
getchar()等の関数を使うときには、
その直前のところでprintf()を使い、
「何か入力して下さい」のようなメッセージを表示させるのが一般的です。

では、次のプログラムをp02.cとして保存し、実行してみましょう。

#include<stdio.h>

main(){

  int c; /* キーボードから受け取った文字を入れておく変数 */

  printf("何か入力してください : "); /* 入力を促すメッセージを表示 */

  c = getchar(); /* キーボードから1文字だけ受け取る */
   
  printf("入力されたのは %c でした。\n", c); /* [%c]を使って文字を表示 */

}

実行結果

入力を促すメッセージが出るようになりました。

p01.cに比べて使う人により優しいプログラムになっている
気がします。

3.[キーボードから2文字まで入力可能にしてみる]

1や2のプログラムではどんなに長い文を入力しようが、
かたくなに最初の1文字しか入力を受け取ってもらえません。
こんなプログラムではかなり役に立ちません。

ではこれを最初の2文字受け取れるようにしてみましょう。

これができれば3文字、4文字……と、さらに大量の文字を受け取らせる方法も簡単に思いつきますよね。

では次のプログラムをp03.cとして保存、実行してみましょう。

#include<stdio.h>

main(){

  int c[2]; /* キーボードから受け取った文字を入れておく変数 */

  printf("何か2文字入力してください : "); /* 入力を促すメッセージを表示 */

  c[0] = getchar(); /* キーボードから1文字だけ受け取る */
  c[1] = getchar(); /* キーボードからもう1文字取ってくる */

  /* [%c]を使って文字を表示 */
  printf("入力されたのは %c と %c でした。\n", c[0], c[1]); 

}

実行結果

しっかりと2文字だけ受け取れるようになりました。

4.[Enterキーを入力しなくても文字を受け取れるようにする]

getchar()等にあるとある特性について、前述したとおり
getchar()等はたとえユーザーが何か文字を入力しても、
最終的にEnterキーが押されるまで、その文字を受け取ることはできません。

時として役に立つ特性ですが、
邪魔になることもあります。

なので、ここではそんなgetchar()の特性を無くしてしまう、
つまり、わざわざEnterキーを押さずとも、なにか文字を入力した時点で
瞬時にそれをgetchar()等に伝えてしまう技を使ってみます。

そうです。「おまじない」です。

では、次のプログラムをp04.cとして保存し、実行してみましょう。

このおまじないにはちょっとしたクセがあることが発覚しました。
"C-Shell"という環境で動かすと、プログラムが終了次第、
キー入力が一切端末上に表示されなくなるというオソロシイものです。
これはおまじないを使いっぱなしにしているために発生するものですので、
おまじないを使い終わったらプログラムの最後に
次の一行を加えるようにしてください。

system("stty echo -icanon min 1 time 0")

見た目はほとんどおまじないと一緒ですが、
これでもう安心です。
実際にサンプルのソースをみて確認しておいてください。
怖いですね〜、おまじない。

#include<stdio.h>

main(){

  int c; /* キーボードから受け取った文字を入れておく変数 */

  /* キーボードからの入力をすぐさま反映するおまじない */
  system("stty -echo -icanon min 1 time 0");  

  printf("何か入力してください : "); /* 入力を促すメッセージを表示 */

  c = getchar(); /* キーボードから1文字だけ受け取る */
   
  printf("入力されたのは %c でした。\n", c); /* [%c]を使って文字を表示 */

  /* おまじないを解除!そうしないとこれ以降、文字が表示されなくなる */
  system("stty echo -icanon min 1 time 0");

}

実行結果

プログラム自体はp03.cとほぼ変わりません。おまじないが入っているだけです。
画像ではイマイチよくわかりませんが、実行してみるときっとその違いがよくわかるかと。

5.[if文で場合分け]

さて、新しいこと目白押しな中、
さらにここで新しく、かつかなり使えるモノを紹介します。

それがif文です。制御構文とかいわれることもあるそうです。
これがないとプログラム書いてらんない、というくらい使える一品です。

肝心の使い道ですが、日本語で書くなら
「もし××ならば●●●をしなさい。さもなくば▲▲▲をしなさい」
のような事をさせたいときに使えます。英語なら
「If ×× then ●●●. Else ▲▲▲」
という具合でしょうか。

つまり、条件による分岐(場合分け)を可能にする文言なのです。

百聞は一見にしかず、ということでやってみましょう。
次のプログラムをp05.cとして保存し、実行させてみましょう。

#include<stdio.h>

main(){

  /* キーボードから受け取った文字を入れておく変数 */
  int c;

  /* 問題文を表示 */
  printf("突然質問。\n");
  printf("Q.正しい円周率はどっち?\n");
  printf("a:3.1415926535... b:3.1415925357...\n");
  /* 入力を促す */
  printf("aかbでお答えください : ");

  /* キーボードから1文字だけ受け取る */
  c = getchar(); 

  /* ifを使って正解しているかどうかチェックする */
  if(c == 'a'){

    printf("その通りです。\n");         /* aが入力されていたとき */

  }else if(c == 'b'){

    printf("そうではありません。\n");   /* bが入力されていたとき */

  }else{

    printf("何入力してるんですか…\n"); /* それ以外の文字だったとき */

  }
   
  /* おまじないを解除!そうしないとこれ以降、文字が表示されなくなる */
  system("stty echo -icanon min 1 time 0");

}

実行結果

getchar()if文のコラボレーション。

問題文を表示させ、
その解答をgetchar()で受け取り、
if文で正解、不正解、意味不明の
判定を行っているわけである。

少しプログラムが
それっぽくなってきた予感

なんとなく「正解、不正解、意味不明」で場合分けできていることに納得してもらえれば成功です。
ここでif文の簡単な使い方を説明しておきます。

if文のサンプル 特別な語句 簡単な説明
if( 条件1 ){
 条件1が満たされている時に
 実行したいプログラム(処理)

}else if( 条件2 ){
 最初の条件1が満たされてはいないけど、
 条件2は満たされている時に
 実行したいプログラム(処理)

}else if( 条件3 ){
 ...
 else ifはいくつでも連ねることが可能。
 ...

}else{
 最後に、どの条件も満たされていないときに
 実行したいプログラム(処理)

}
条件1,条件2,
条件3,……
if文は与えられた条件が満たされている時に限って、
その条件のすぐ下から始まるプログラム(処理)を実行する。
条件の書き方は下のテーブルを参照して下さい。
先頭のif() if文のうち、一番最初にここの条件が満たされているかどうか
チェックされて、満たされていれば
if()直後にある中カッコ{から次の中カッコまでの間の
プログラム(処理)が実行されることになる。
満たされていなかったら
次の「else if()」に飛んでいく。
ちなみに、「else if()」や「else」は無くてもOK。

つまり、

if( 条件 ){
 実行したいプログラム(処理)
}

だけでも立派なif文として機能する。
else if() if文を日本語で表現した

もし××なら●●を。
そうでなくて、もし◇◇なら▲▲を……

のうちの、二行目、「そうでなくて、もし」が
ズバリ「else if()」の役割そのもの。
つまり、これより上の条件が全て満たされなかったときに、
さらにまたif文を使いたいときに使われるもの。
いくつでも書くことができる。
最後のelse ifおよび、else ifの、その全ての条件が満たされていないときに
このelseから下のプログラム(処理)が実行される。

if文で使う条件にはある一定の書き方があります。
たとえば、int型の変数aがあって、もしaが10ならば……と、書きたいとすると、

if(a == 10){

 ...

}

のように、条件のところは「a == 10」となります。
これで「変数aが10ならば」という条件を書いている事になるんですね。

ココで補足ですが、
等しい」という条件をよく凡ミスで「=」として
「if( a = 10){ ...」とやってしまうことが多々あります。
普通にC言語でプログラム書き慣れている人でもやってしまうことがあります。
しかし、普通にコンパイルできてしまいます。(オソロシヤ
しかし、時としてこのミスがプログラム全体に与える影響は
プログラムを暴走させるほどになります。
くれぐれも「等しい」という条件としての「==」を「=」と書き間違えないように注意です。

そして、もちろん、「○○が▲▲と等しければ」という「==」で表すことのできる「等しい」という条件だけでは
とうていプログラムを制御しきれませんから、
条件には他に様々なものがあります。

条件の種類 書き方
等しい a == b
等しくない a != b
〜より大きい a > b
〜以上 a >= b
〜未満 a < b
〜以下 a <= b

あと、よりごちゃごちゃしたプログラムを書くときには、
条件同士をくっつける必要が出てくることがあります。

たとえば、「aが10でかつbが1のとき」のような感じで。
このときは「aが10」という条件と「bが1」という条件が「かつ」でくっつけられてるわけです。
このようなときに使われるのが不思議な記号「&&」です。アンパサンド(平たくいえば「アンド」のこと)2個です。
これで「かつ」という条件を作れるようになります。

if(a == 10 && b == 1){

こんな具合ですね。
またこれと対をなす存在として、「または」があります。
使う記号は「||」です。パイプ2本です。(パイプはShift+\キーで出せます)

if(a == 10 || b == 1){

とすれば、これはつまり「aが10またはbが1のとき」ということになります。

条件同士の
くっつけ方
書き方
〜かつ… &&
〜または… ||

ちなみに、getchar()で受け取ってきた文字の判定にはシングルクォーテーション「’」で囲んだ文字が使えます。
たとえば、変数cがあって、

c = getchar();

としたあとで、もしcが「f」という文字ならば、というif文を作りたいときは、

if(c == 'f'){

と、やります。

6.[カーソルキーを入力として受け取ってみる]

長かった第3回もそろそろ終盤です。

ずっと前に書いたように、
getchar()では普通の「文字」だけでなく、
「改行」やカーソルキーを受け取る事も可能です。

そこで、ここではgetchar()によってカーソルキーからの入力を受け取り、
どのカーソルキーが押されたのかを表示してみます。

カーソルキーが押されると、実は一度に3つの文字が発生します
エスケープシーケンス(ESC)」「左大カッコ([)」「A〜Dのうちのどれか
です。

つまり、たとえば上キーが押されると、『(ESC)[A』というのが一気に押し寄せてくるということです。
ここで(ESC)はエスケープシーケンスと呼ばれる特殊な文字のことです。

さらにずっと前に書いた、「たとえ文字といえども、全て数値で管理されている」ことを思い出してもらうと、
実は上の3つの文字は「27」「91」「65」という数字で表されることになります。

受け取るカーソルキー 受け取る文字 数値に置き換えたモノ
(ESC)[A 27,91,65
(ESC)[B 27,91,66
(ESC)[C 27,91,67
(ESC)[D 27,91,68

つまり、こんな関係にあるわけです。
では、これらを考慮してプログラムを作ってみます。

以下のプログラムをp06.cとして保存してから、実行してみましょう。

#include<stdio.h>

main(){

  /* キーボードから受け取った文字を入れておく変数 */
  int c[3];

  /* キーボードからの入力をすぐさま反映したいので */
  /* おまじないをいれておく。 */
  system("stty -echo -icanon min 1 time 0");  

  /* 入力を促す */
  printf("カーソルキー(矢印の描かれてるキー)を押してください\n");

  /* 実はカーソルキーによる入力を識別するためには、 */
  /* キーボードから3文字受け取る必要がある。 */
  c[0] = getchar();
  c[1] = getchar();
  c[2] = getchar();

  /* ifを使ってそれがカーソルキーかどうかチェックする。 */
  /* 入力されたのがカーソルキーであるための条件は、 */
  /* 一文字目が27(文字で表すことはできません)、 */
  /* 二文字目が91(文字で表すと'['になる)、 */
  /* 三文字目が65〜68のどれか(文字で表すと'A'〜'D'のどれかになる)、 */
  /* 以上の3つとなる。 */

  /* まず一文字目が27で、さらに二文字目が91かどうかチェック */
  if(c[0] == 27 && c[1] == 91){

    /* 次に三文字目の値が65〜68のどれになっているかチェックする */
    if(c[2] == 65){
      
      printf("上が押されました\n");

    }else if(c[2] == 66){
      
      printf("下が押されました\n");

    }else if(c[2] == 67){
      
      printf("右が押されました\n");

    }else if(c[2] == 68){
      
      printf("左が押されました\n");

    }else{

      printf("結局変なキーが押されました\n");

    }

  }else{

    printf("あからさまにカーソルキー以外のキーが押されました\n");

  }
   
  /* おまじないを解除!そうしないとこれ以降、文字が表示されなくなる */
  system("stty echo -icanon min 1 time 0");

}

何かカーソルキーを押すと、見事に反応します。

今週の課題 保存箇所
~/sccp/ex03/p07.c

・今週は、前回、枠および数字が表示できるようになったプログラムを改良して、
 一回だけカーソルキーを受け取って空白パネル(黒い四角「■」の部分のこと)を動かせるプログラムを書いてみましょう。

目標物

空白パネルが一つ左に動いています。

しかし、たとえ前回のソースを元にして作るとはいえ、
かなり大変な作業が予想されますので、
一応サンプルを用意しておきました。

前回と同じく、「_______」で埋められた部分に、正しいモノを書き入れていって下さい。


第3回は以上で終了です。
大変お疲れ様でした〜。