今回は「式」と「式の評価」という考え方を習得して、while構文と比較演算子について理解を深めます。
比較「演算」子
前回の記事では「何かと何かが等しい」などの比較をする記号を習得しました。
!=
<
<=
>
>=
これらは比較する記号です。意識高い系の場合、「記号」は「子」と呼ぶんでしたよね。ということは「比較する記号」は「比較子」ですよね。
ここで疑問が出てきてしまいました。「比較する記号」のことを「比較子」ではなく「比較演算子」と呼んでいます。つまり「比較を計算する記号」というわけですが、なぜ計算(演算)するのでしょうか。
今回の記事では、なぜ「演算」という言葉が使われているかという疑問から出発して、比較演算子とwhile構文について理解を深めていきます。
Arduinoの計算能力
ちょっと驚くかもしれませんが、Ardunoボードを始め、最新のPCやスマホも含めて多くのコンピュータは数字を直接比較することができないんです。とはいっても、前回のスケッチで
while( digitalRead(SWITCH) == 1 ) {
}
と書いて、スイッチの状態である「digitalRead(SWITCH)」と「1」が等しいかどうか比較していましたよね。これって、どこからどうみても比較しています。
一般的なコンピュータ、つまり計算機は、当然ながら計算は得意なのですが、できることはかなり限られています。Arduinoの場合、計算と言ってもできることはこれぐらいなんです。
- 整数の足し算
- 整数の引き算
- 計算結果が0か0以外か判定する
- 計算結果がプラスかマイナスか判定する
これだけといっても、実際には比較とか、掛け算、割り算、さらには少数の計算をしているのですが、一体どうしてるんでしょうか。
実は、Arduinoボードの内部では、足し算、引き算、結果が0かマイナスかプラスかの判定をうまく組み合わせて、スケッチに書かれた比較や掛け算割り算などの複雑な計算をやっているんです。
そこで今回の記事では、Arduinoボードが比較をどのように実現しているのか一緒に詳しくみていきましょう。
比較演算子の処理
これから以下のスケッチのArduinoボード内部処理のようすを詳しくみていきます。
digitalRead(SWITCH) == 1
これはスイッチの状態を調べて、その値が1に等しいかどうか調べる、という動作内容のスケッチです。Arduinoボードはこのスケッチをどのように処理しているか、順番にみていきましょう。
この比較をするには、最初にdigitalRead(SWITCH)を処理する必要があります。digitalRead(SWITCH)は以下のような値になるんでしたよね。
スイッチの状態 | digitalRead(SWITCH)の値 |
---|---|
OFF | 1 |
ON | 0 |
ここで、スイッチがOFFのケースを考えます。スイッチOFFの場合はdigitalRead(SWITCH)の値は1ですので、先ほどの比較のスケッチは「digitalRead(SWITCH)」の部分が「1」になるので、Arduinoボードは内部で以下のように変換します。
1 == 1
次に、Arduinoボードは「==」の左側の数字から右側の数字を引き算します。つまり「1 – 1」を計算します。
結果はどうなるかというと「0」ですよね。Arduinoボードは結果が0の場合はこの比較が成立している、0以外の場合は比較が成立していない、と判定します。
このように、Arduino内部では比較を行う場合、引き算をしてその結果がどうなるかを判定しています。つまり「==」などの比較の記号は「比較するための計算の記号」つまり「比較演算子」だったわけです。
ここで説明した例は等しいケースですが、例えば「○ > □」の比較を行う場合、Arduinoボードは最初に「 ○ -
□」という引き算を行い、結果がプラスであれば成立している、プラスでなければ成立していない、と判断します。
他の比較の記号も全て、左側の数字から右側の数字を引いて、その結果がどうなるか判定しているわけです。
ここまでが、Arduinoボードが比較演算子のスケッチが成立しているか、成立していないか判定するまでの処理です。
この先、さらにややこしくなります。
真と偽
先ほどの比較演算子のように、コンピュータ内部では、何かが成立しているか成立していないか、という判定が頻繁に行われます。
例えば比較演算子で、以下のような場合、
1 + 2 == 3
比較演算子の左側は3、右側も3ですので成立しています。また、以下のような場合、
2 + 3 == 8
比較演算子の左側は5、右側は8ですので、成立していません。
このように成立している状態、正しい状態を「真」(しん)と呼びます。英語では「true」(トゥルー)と呼んでいます。
また、成立していない状態、正しくない状態を「偽」(ぎ)と呼びます。英語では「false」(フォルス)と呼んでいます。
このように成立している状態、成立していない状態は、日本語を話す人であれば「真」か「偽」、英語を話す人であれば「true」「false」ですが、コンピュータ内部では数字で表しています。
なんともわかりづらいですが、C/C++言語に限らず、他のプログラミング言語でも、真は0以外の数字、偽は0で表すことになっています。真の場合は0以外の数字ですが、一般的には数字の1が利用されています。
これまでのことをまとめると、以下のようになります。
状態 | 日本語 | 英語 | コンピュータ語 |
---|---|---|---|
成立している 正しい |
真 | true | 0以外の数字 (一般的には数字の1) |
成立していない 正しくない |
偽 | false | 数字の0 |
Arduinoボードは比較演算子を処理するとき、計算をして計算結果を見て成立しているか成立していないか判定しました。Arduinoボードの内部ではその判定結果を真か偽かを意味する0か1かに置き換えています。
といってもさっぱりわからないですよね。それでは、Arduinoボードの中で、以下の比較がどのように行われているか、もう一度最初から順を追って見ていきましょう。
digitalRead(SWITCH) == 1
Arduinoボードは最初にdigitalRead(SWITCH)を処理します。この時、スイッチがOFF、つまりdigitalRead(SWITCH)の結果が1とします。Arduinoボードは上のスケッチを以下のように変えます。
1 == 1
これで比較ができるようになったので、Arduinoボードは「==」の左側の数字から右側の数字を引き算した結果を求めます。「1 – 1 = 0」ですので、結果は0です。「==」の場合、結果が0であれば成立している、0以外であれば成立していないという判定になりますので、この比較は計算結果が0ということで成立している、と判定します。
成立している、つまり真(true)ということがわかりましたので、Arduinoボードはこの比較を真を表す「1」に置き換えて処理が完了します。
1
まとめると、Arduinoボードの中では以下の比較を処理すると、
digitalRead(SWITCH) == 1
成立している場合は、
1
に変換する、というわけです。なんだかわかったようなわからないような、という感じですので、実際にArduinoボードの動きをシリアルモニタで確認してみましょう。
真か偽の判定
それでは、新規スケッチを作成してください。スケッチ名は「true_false」にしておきましょう。
このスケッチでは、比較演算子が最終的に「1」(真)か「0」(偽)のどちらかに変換される様子をシリアルモニタで確認します。
確認用に以下のスケッチを作成しました。
/*
* 内容: 比較演算子の結果を表示する
* 変更履歴:
* 2019.8.17: 新規作成
*/
void setup() {
Serial.begin(9600); // シリアルモニタの設定(通信速度は9600)
}
void loop() {
// 1秒に1回比較演算の結果をシリアルモニタに表示する
Serial.println( 1 + 2 == 3 );
delay(1000);
}
この内容でスケッチを作成したら、ArduinoボードをPCに接続して、シリアルモニタウィンドウを開きます(ツール→シリアルモニタ)。準備ができたらスケッチをArduinoボードに送ります。
しばらく待っていると、以下のように「1 + 2 == 3」の結果が表示されます。
Arduinoボードは、「1 + 2 == 3」を演算して、その結果真であると判定したため「1」(真)と表示しています。それでは、比較が成立しないように書き換えて、その結果がどうなるか確認してみてください。例えば「1 + 3 == 5」は成立しませんので、この結果を表示するとどうなるか確認してみてください。
成立しない場合は「0」(偽)が表示されますよね。
C/C++言語に限らず、プログラミングの世界では「真」は0以外の数字(一般的には数字の1)、「偽」は数字の0で表現する、ということは非常に重要ですので、十分理解しておきましょう。
「式」と「式の評価」
Arduinoボード内部ではdigitalRead(SWITCH) == 1
にしても1 + 2 == 3
にしても、左側から右側を引き算してその結果が0か0以外かでこの比較が成立するか判定していました。
ここで、新しい言葉を覚えておきましょう。
digitalRead(SWITCH) == 1
や1 + 2 == 3
などの計算や比較を「式」と呼びます。数学でも式が出てきますので特に違和感はないですよね。
またArduino内部では比較の式を、左側から右側を引き算してその結果が0か0以外かで成立するか判定しています。この判定する処理のことを「評価する」と呼びます。
言い方としては「『1 + 2 == 3』という式を評価すると結果は真(1)だ」などと言います。
特に「評価」というと、何かランク付けするイメージが強いかもしれませんが、プログラミングの世界では「評価」をこのような意味合いで使用しています。この言い方はC/C++言語に限らず、全てのプログラミング言語で共通です。
while構文の本当の意味
前回の記事で、while構文は以下のように書きます、と説明しました。
while(条件)
命令;
実はこの説明はあまり正確ではないんです。while構文は以下がより正確な説明になります。
while構文は、whileの括弧の中の式を評価した結果が「真」の間、「命令」を繰り返す、という動作をする構文なんです。
あまりおすすめできない書き方ですが、スイッチを押すとLEDの点滅を開始するスケッチは以下のようにも書けます。
while構文では比較演算子ではなく、直接digitalRead(SWITCH)を書いています。digitalRead(SWITCH)は、スイッチがOFFの時は1、ONの時は0になります。つまり、スイッチOFFの時は「1」=「真」、スイッチONの時は「0」=「偽」になるため、わざわざ「digitalRead(SWITCH) == 1」と書いて、1と比較する必要はないわけです。
ただ、このようなスケッチはわかりづらいので、書かないようにしてください。
なお、他の人が書いたスケッチやC/C++言語で書かれたプログラムでは、この仕組みを利用した書き方をする場合もあります。while構文の仕組みを十分理解しておくようにしてください。
while構文をわかりやすく書く
while構文の説明の最後に、スケッチをわかりやすく変更します。
と言われても、どこがどうわかりにくいか、って感じですよね。まずわかりにくい背景を説明します。
わかりにくい部分はここです。
while(digitalRead(SWITCH) == 1){
}
何がどうわかりにくいのか、ちょっと???ですよね。
今まで一緒にスケッチを作ってきましたので、私たちはこのスケッチの意味がわかっています。
でも他の人が急にこのスイッチを見た場合を考えてみてください。
「digitalRead(SWITCH)」という部分は、読み取っている端子はおそらくスイッチだろう、ということが想像できます。でも「1」ってなんなんでしょうか。スイッチがONなのかOFFなのか、直感的にわかりづらいですよね。
このように式の中に数字を直接書くとわかりづらいことが多々あります。そこで、スイッチONの時とスイッチOFFの時で、digitalRead(SWITCH)命令がどのような値になるのか、#defineで明確にしておきます。
#define SWITCH_OFF 1
#define SWITCH_ON 0
このように定義しておくと、先ほどのwhile構文は以下のように書けます。
while(digitalRead(SWITCH) == SWITCH_OFF){
}
while構文は、式が真の間繰り返す、という処理をしますので、これを初めてみた人は「スイッチの値を読んで、その値がスイッチOFFの間、何もしないんだな」ということが想像できます。
今後、スケッチの中に数字を直接書く場合は、もう少しわかりやすく書ける方法があるか一度考えてみるようにしてください。
/*
* キッチンタイマー
*
* 内容: スイッチ、LED、スピーカーを使ったキッチンタイマー
* 変更履歴:
* 2019.8.11: 新規作成
* 2019.8.15: スタートスイッチ処理を追加
* 2019.8.17: スイッチ関連の#define追加
*/
// 秒を表現するLED関連(青色LED)
#define BYOU_LED 12 // 秒を表現する青色LEDの端子番号
#define BYOU_ON 50 // 秒を表現するLEDをつけている時間 (単位:ミリ秒)
#define BYOU_OFF 1000 - BYOU_ON // 秒を表現するLEDを消している時間 (単位:ミリ秒)
// スタートスイッチ関連
#define SWITCH 23 // スイッチを接続している端子番号
#define SWITCH_OFF 1 // スイッチOFFの時のdigitalReadの値
#define SWITCH_ON 0 // スイッチONの時のdigitalReadの値
void setup() {
// 端子の設定
pinMode(BYOU_LED, OUTPUT); // 青色LED接続端子設定
pinMode(SWITCH, INPUT_PULLUP); // スイッチ接続端子の設定
// スイッチが押されるまで待つ
while(digitalRead(SWITCH) == SWITCH_OFF) {
}
}
void loop() {
// 1秒に1回青色LEDを点滅する
digitalWrite(BYOU_LED, HIGH);
delay(BYOU_ON);
digitalWrite(BYOU_LED, LOW);
delay(BYOU_OFF);
}
【補足】ずっと繰り返す処理の書き方
電子工作のプログラミングでは、ずっと何かの処理を繰り返す、という動作をすることがいろいろな場面で出てきます。他の人が書いたスケッチでもずっと繰り返すという処理が出てきます。そのようなスケッチを読めるように、いくつか書き方を覚えておきましょう。
ずっと繰り返す、ということは、whileの式が常に真であればOKです。真は「1」ですので以下のように書くと「処理」をずっと繰り返します。
while(1){
処理;
}
ただ、「1」と書くのは数字自体に意味があるのか単に真を表すために0以外の数字ということで「1」と書いてあるのかよくわかりませんよね。
実は、Arduino IDEではよく出てくる真と偽について、裏で以下のように定義してくれています。
#define true 1
#define false 0
「true」は「真」、「false」は「偽」の意味でしたよね。この#defineは見えないところで定義してくれているので、スケッチの中でいつでも使えます。
そのため、他の人のスケッチを見ると、ずっと繰り返す場合は以下の書き方をよく見かけます。
while(true){
処理;
}
これでスタートスイッチの機能が実現できましたので、次回からいよいよ時間を数えるスケッチを作成します。
ミニチャレンジ課題
while構文は、何かの処理を繰り返す時に利用できます。
ちょっと無理やり感のある課題ですが、以下のスケッチを作成してみてください。
すでにLEDを1秒間に1回「ピカッ、ピカッ」とずっと繰り返し光らせるスケッチを作成しました。LED制御はずっと繰り返しますので、loopの中に命令を書きました。
今回は、loopの中に何も命令を書かない、つまりsetupだけを使って同じ制御をするスケッチを作成してみてください。
次に、スイッチを押している間、点滅を繰り返す、というスケッチを作成してみてください。
スイッチを押し続けている間、LEDは点滅を繰り返し、スイッチを離している状態のときはLEDは消えたまま、という動作になるようにしてみてください。
この動作はloopに書いてください。
ミニチャレンジ課題の解答例
スケッチの書き方は唯一の正解があるわけではありませんので、以下の解答例は参考にしていただければと思います。
どんなスケッチでも思った通りに動けば正解です!
ミニチャレンジ課題1の解答例
ずっと処理を繰り返す処理はloopに書くべきですが、この課題は今回のテーマの理解を深めるための無理矢理感漂う課題です。
loopには何も書かず、電源投入後に最初に1度だけ実行されるsetupだけでずっとLEDの点滅を繰り返す処理が必要です。
「ずっと繰り返す」場合、プログラミングの定石の「while(true){…}」を使えば実現できますので、次のようなスケッチにしてみました。
/*
* ミニチャレンジ課題1スケッチ例
* これが唯一の正解ではありませんが、参考にしていただければと思います。
* このスケッチと違っても動けばそれが正解です!
*/
// LEDの接続端子
#define BYOU_LED 12
void setup() {
// setupはArduino電源投入直後に1度実行されるだけなので、
// ここでずっと繰り返す処理をwhile文を使って書く
// LED接続端子の設定
pinMode(BYOU_LED, OUTPUT);
// 1秒に1回青色LEDを点滅する処理をずっと繰り返す
// (LEDの点灯時間は50msで固定)
while(true) {
digitalWrite(BYOU_LED, HIGH);
delay(50);
digitalWrite(BYOU_LED, LOW);
delay(950);
}
}
void loop() {
// loop内には何も処理を書かない
}
ミニチャレンジ課題2の解答例
課題1に続いて、こちらもちょっと無理矢理感が出ている内容です。
今までの知識では「〇〇の間繰り返す」という処理は、while文が使えます。
スイッチが押されていたらLED点滅を繰り返す、という処理をしたいので、whileの条件式でスイッチがONだったら繰り返す、という処理にしてみました。
/*
* ミニチャレンジ課題2スケッチ例
* これが唯一の正解ではありませんが、参考にしていただければと思います。
* このスケッチと違っても動けばそれが正解です!
*/
// LEDの接続端子
#define BYOU_LED 12
// スイッチ関連の定義
#define SWITCH 23 // スイッチを接続している端子番号
#define SWITCH_OFF 1 // スイッチOFFの時のdigitalReadの値
#define SWITCH_ON 0 // スイッチONの時のdigitalReadの値
void setup() {
// 端子の設定
pinMode(BYOU_LED, OUTPUT); // 青色LED接続端子設定
pinMode(SWITCH, INPUT_PULLUP); // スイッチ接続端子の設定
}
void loop() {
// スイッチがONの間、点滅を繰り返す
// スイッチがOFFの時、このwhile文を終了しますが、
// loop内なので再度while文が実行され、
// 再度スイッチがONの間、点滅を繰り返す、という処理がされます
while( digitalRead(SWITCH) == SWITCH_ON ) {
// 青色LEDを点滅する
digitalWrite(BYOU_LED, HIGH);
delay(50);
digitalWrite(BYOU_LED, LOW);
delay(950);
}
}
なお、今回の記事ではwhile文がテーマなので、上のようなスケッチを解答例としました。
ただ、本来であればこのような処理はこのシリーズ記事のあとで出てくる「if文」を使う方がより良いです。以下のスケッチでも動作しますので参考にしてみてください。
/*
* ミニチャレンジ課題2スケッチ例
* このような処理の場合、while文よりはif文を使うべきですが、
* if文の解説はこの後出てきますので、以下のスケッチは参考となります。
*/
// LEDの接続端子
#define BYOU_LED 12
// スイッチ関連の定義
#define SWITCH 23 // スイッチを接続している端子番号
#define SWITCH_OFF 1 // スイッチOFFの時のdigitalReadの値
#define SWITCH_ON 0 // スイッチONの時のdigitalReadの値
void setup() {
// 端子の設定
pinMode(BYOU_LED, OUTPUT); // 青色LED接続端子設定
pinMode(SWITCH, INPUT_PULLUP); // スイッチ接続端子の設定
}
// スイッチがONの間、点滅を繰り返す
void loop() {
// loopは何度も繰り返しますので、
// loop内ではスイッチがONの場合、LEDを点滅する、という処理をif文で書きました。
// if文はシリーズ後半で解説していますが、動作としてはif(条件)で条件が成立していれば
// その後の処理を実行する、という制御になります
if( digitalRead(SWITCH) == SWITCH_ON ) {
// 青色LEDを点滅する
digitalWrite(BYOU_LED, HIGH);
delay(50);
digitalWrite(BYOU_LED, LOW);
delay(950);
}
}
更新履歴
日付 | 内容 |
---|---|
2019.8.17 | 新規投稿 |
2021.8.22 | 新サイトデザイン対応 |
2022.2.13 | ミニチャレンジ課題追加 |
2024.10.24 | ミニチャレンジ課題解答例追加 |
ミニチャレンジの回答スケッチが欲しいです。
コメントどうもありがとうございました。
ご参考にミニチャレンジ課題の解答例を追加しました。
ただ、スケッチ(プログラム)は唯一の正解はありませんので、他のスケッチでも動作すれば正解です。
ただスケッチに慣れていないと「これでもいいのかな?」と思うこともあるかと思います。その時はコメント欄からご相談いただければと思います!