制御構造

はじめに

Arduinoで利用するC++/C言語のプログラムの実行の流れの制御方法について説明します。

プログラムの流れの基本

プログラムは関数内に記述します。関数内では、プログラムはスケッチに書いた順に上から下に実行されます。

1
2
3
4
5
6
const int led = 13;      // ledという変数を定義し、13を代入する。

pinMode(led, OUTPUT);    // 13番ピンを出力モードに設定する。
digitalWrite(led, HIGH); // 13番ピンにHIGHを出力する。
delay(1000);             // 1000ミリ秒待つ。
digitalWrite(led, LOW);  // 13番ピンにLOWを出力する。

上記の場合、最初にledという変数を定義した後、3行目から順に4行目、5行目、6行目と処理が進んでいきます。

コンマ演算子

コンマ演算子「,」を利用すると、1行に複数の式を書くことができます。コンマ演算子は左から順番に式が評価されていき、一番右の式の結果が演算子の評価結果となります。

1
a = 2, a += 3, b = 4; // a=2、a += 3、b = 4の順に評価される。aは5、bは4となり、この式の評価結果は4

分岐

上から下にプログラムが実行されるだけでは必要な処理を行うことができないので、プログラムの分岐を行うための仕組みが用意されています。

ある条件が成立したときだけ行いたい処理がある場合には、if文もしくはswitch文を使って制御することができます。無条件に分岐を行うgoto文も使用可能です。

if文

if文では条件を指定し、その条件が成立したときに実行する処理と、その条件が成立しなかったときに実行する処理を記述することができます。if文の形式は以下の2通りがあります。

(1) if (式) 文
(2) if (式) 文1 else 文2

(1)の形式は、「式」が成立したときに「文」を実行します。「式」が成立しなかったときには何も実行しません。(2)の形式は「式」が成立したときに「文1」を実行し、成立しなかったときには「文2」を実行します。

「式」は計算の結果が整数もしくは浮動小数点となる式です。他にポインタ型と呼ばれる型でも記述可能です。式の結果が0以外のときは式が成立(真)、0のときは式が不成立(偽)となります。

if文で条件判定を行うときには、比較演算子や関係演算子を利用する場合が多くあります。C/C++言語では以下のような演算子が用意されています。「&&」や「||」とビット単位の演算子の「&」や「|」とを混同しないようにしてください。

二項演算子 演算子の評価結果が真になる条件
== 左辺と右辺が等しい。
!= 左辺と右辺が等しくない。
< 左辺が右辺より小さい。
> 左辺が右辺より大きい。
<= 左辺が右辺以下。
>= 左辺が右辺以上。
&& 両辺ともに真のとき。
|| 両辺のどちらかが真のとき。
単項演算子 演算子の評価結果が真になる条件
! 偽のとき

条件判定のための式に上記以外の演算子を利用してはいけないというわけではないので注意が必要です。あくまで、式を記述して、その式の評価結果が0以外であれば条件が成立、0であれば条件が不成立と判断されます。これについては、少しあとで例を挙げます。

文には一つの文だけを書く場合と、"{}“を利用して複数の文を書く場合があります。”{}“でくくった範囲をブロックと呼びます。ブロックを用いたときと用いないときには意味が異なるように見える場合があります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 1文だけ書く例
if (a == b)               // aとbが等しければ13番ピンをHIGHにする。
  digitalWrite(13, HIGH);

// ブロックを書く例
if (a == b) {             // aとbが等しければ12番ピンと13番ピンをHIGHにする。
  digitalWrite(12, HIGH); 
  digitalWrite(13, HIGH);
}

// 1文だけを書く例
if (a == b)               // aとbが等しければ12番ピンをHIGHにする。
  digitalWrite(12, HIGH);
  digitalWrite(13, HIGH); // 13番ピンは比較結果と関係なくHIGHにする。

// else を使う例
if (a == b) {             // aとbが等しければ12番ピンをHIGHにし、等しくなければ13番ピンをLOWにする。
  digitalWrite(13, HIGH);
} else {
  digitalWrite(13, LOW);
}

上記の3番目の例では、ブロックを用いていないため、if文の直後の1文だけが条件分岐の対象であることに注意が必要です。C++/C言語でのインデントは人間の読みやすさのためだけにあります。

if文の条件には式を記述するため、正しい値を返す式であれば記述可能です。例えば、代入演算も正しい式です。左辺と右辺が等しいかを検査する演算子「==」と代入のための演算子「=」は間違えやすいため注意が必要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
if (a = b) {  // aにbを代入した結果が真(0でない)のとき13番ピンをHIGHにする。
  digitalWrite(13, HIGH);
}

if (value = analogRead(0)) {  // 0番ピンの電圧を取得しvalueに代入する。valueが0でなければその値をシリアルコンソールに出力する。
  Serial.print(value);
}

if ((value = analogRead(0)) > 512) {  // 0番ピンの電圧を取得しvalueに代入する。valueが512より大きければその値をシリアルコンソールに出力する。
  Serial.print(value);
}

if (! digitalRead(0)) {        // digitalRead(0)の結果が0のとき13番ピンをHIGHにする。
  digitalWrite(13, HIGH);
}

上記の例ではプログラムは問題なくコンパイルされます。最初の例の場合は、aにbを代入した結果が評価されます。つまりbが0以外であれば真、0であれば偽となります。

elseの後にさらにifを続けることも可能です。

1
2
3
4
5
6
7
if (a == 1) {            // aが1であれば1番ピンをHIGHにする。
  digitalWrite(1, HIGH);
} else if (a == 2) {     // aが2であれば2番ピンをHIGHにする。
  digitalWrite(2, HIGH);
} else {                 // そうでなければ3番ピンをHIGHにする。
  digaitalWrite(3, HIGH);
}

ifの条件が成立あるいは不成立の場合の文にもif文を書くことができます。ただし、コンパイラによってどのように解釈されるのかがわかりづらいという問題があります。

1
2
3
4
5
if (a == 1)                   // aが1であれば次のif文を実行する。
  if (b == 2)                 // (aが1かつ)bが2であれば13番ピンをHIGHにする。
    digitalWrite(13, HIGH);
  else                        // (aが1かつ)bが2でなければ13番ピンをLOWにする。
    digitalWrite(13, LOW);

上記の例では、最後のelseは直前のifに対応します。最後のelseを最初のifに対応させるには以下のようにします。

1
2
3
4
5
if (a == 1) {                 // aが1であれば次のif文を実行する。
  if (b == 2)                 // (aが1かつ)bが2であれば13番ピンをHIGHにする。
    digitalWrite(13, HIGH);
} else                        // aが1でなければ13番ピンをLOWにする。
  digitalWrite(13, LOW);

演算子を組み合わせて判定させることもできます。

1
2
3
if ((a == 1) && (b == 1)){            // aが1かつbが1であれば1番ピンをHIGHにする。
  digitalWrite(1, HIGH);
}

switch文

switch文は、ある式の値により実行する処理を決定するための機構です。

switch (式) {
case 定数式: 文1
case 定数式: 文2

case 定数式: 文m
default: 文n
}

式を評価した結果に一致する定数式の値を持つcaseラベルに制御が移行されます。式と定数式は整数型である必要があります。定数式の結果が同一となるcaseラベルが同一のswitch文内にあってはいけません。defaultラベルはあってもなくても構いません。文中にbreak文が現れるとその時点でswitch文を抜け出します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
switch (value) {
case 0:  // valueが0だったら0番ピンをHIGH、1番ピンをLOWにする。
  digitalWrite(0, HIGH);
  digitalWrite(1, LOW);
  break; // ここでvalueが0のときの処理は終了。
case 1:  // valueが1だったら0番ピンをLOW、1番ピンをHIGHに、2番ピンをHIGHにする。
  digitalWrite(0, LOW);
  digitalWrite(1, HIGH);
case 2:  // valueが2だったら2番ピンをHIGHにする。
  digitalWrite(2, HIGH);
  break; // ここでvalueが1と2のときの処理は終了。
case 3:  // valueが3もしくは4だったら0番ピンをHIGH、1番ピンをHIGHに、2番ピンをHIGHにする。
case 4:
  digitalWrite(0, HIGH);
  digitalWrite(1, HIGH);
  digitalWrite(2, HIGH);
  break; // ここでvalueが3と4のときの処理は終了。
default: // それ以外の場合は0番ピンをLOW、1番ピンをLOWに、2番ピンをLOWにする。
  digitalWrite(0, LOW);
  digitalWrite(1, LOW);
  digitalWrite(2, LOW);
  break; // ここでdefaultのときの処理は終了。
}

valueが0のときは、case 0: の部分に制御が移行し、3行目と4行目を実行します。5行目にbreak文があるので処理はそこで中断されます。

valueが1のときは、7行目と8行目、10行目の文を実行し、11行目のbreak文で処理は中断されます。caseラベルは処理の開始地点を規定するだけであり、終了地点を規定するのではありません。

12行目と13行目のように同一地点に複数のcaseラベルをつけることもできます。caseラベルには範囲を記述する能力はないので複数の値に対応させるためには複数のcaseラベルを記述する必要があります。

valueに該当するcaseラベルがない場合は、defaultラベルに制御が移行します。defaultラベルはあってもなくても構いません。

goto文

goto文は、そのgoto文を含む同一の関数内のある地点(ラベル)に無条件で制御を移します。

goto ラベル;

ラベルはプログラム中に「ラベル名:」という形で記述します。

一般にgoto文を利用することは、プログラムの可読性を悪くするのでよくないことと言われています。よく利用されるケースは後述するネストしたfor文の内側から抜け出すというものです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
for (i = 0; i < 10; i++) {
  for (j = 0; j < 10; j++) {
    
   if (エラー) {
      goto error:  // error:にジャンプする
    }
  }
}

error:

goto文の後ろだけではなく、前にも制御を移行することも可能です。

1
2
3
4
5
L1:



goto L1;  // L1にジャンプする

繰り返し実行(ループ)

繰り返し実行(ループ)を実現する方法は、forとwhile、do whileの3種類の方法が用意されています。

for

for文の構文は以下の通りです。またif文のときと同様文は一つの文でもいいし、ブロックでも構いません。

for (式1; 式2; 式3) 文

実行順序は以下の通りです。

  1. 式1
  2. 式2
    1. 式2が真(0でない)であれば文を実行
    2. 式2が偽(0)であれば終了
  3. 式3
  4. 2(式2)にもどる

式1は必ず1回実行されます。式2は1回以上実行されます。式3と文は0回以上実行されます。

全ての式は省略可能です。式2を省略した場合は常に真として扱われます。すなわち必ず文が実行されることになります。

以下では、デジタルピンの0番から9番までを出力モードに設定します。

1
2
3
for (int i = 0; i < 10; i++) {  // iは0から9まで変化する。
  pinMode(i, OUTPUT);
}

この例のように式1の中で変数を宣言することもできます。この変数の有効期間はforループの中だけです。ただし、.cファイルでは式1の中で変数を宣言することはできません。C言語ではこの機能はC99という規格で取り入れられましたが、コンパイル時のオプションにC99を有効にするオプションが指定されていないためです。

以下の例はArduinoのmain()関数の中に記述されているもので、式をすべて省略しています。loop()関数を無限回呼び出しています。

1
2
3
4
for (;;) {
  loop();
  if (serialEventRun) serialEventRun();
}

while

while文の構文は以下の通りです。

while (式) 文

式が真であれば文を実行します。文を実行後、式の評価を行います。最初に式を評価した結果が偽であれば文は一度も実行されません。

以下の例では、3番ピンの値がLOWである間は何も実行しません(正確には空文(セミコロンだけの文)を実行し続けます)。

1
2
while (digitalRead(3) == LOW)
  ;

for文は以下のwhile文と同等です。

式1;
while (式2) {

式3;
}

do while

do while文の構文は以下の通りです。式の後にセミコロンが必要です。

do 文 while (式);

文を実行した後式を評価し、評価した結果が真であれば文を実行します。文は最初に1回必ず実行されます。

以下の例では、シリアルコンソールから送信された文字が「y」か「Y」でない場合は、シリアルコンソールからの値を読み続けます。

1
2
3
4
5
6
7
8
9
int c;
Serial.begin(9600);
do {
  if (Serial.available() > 0) {
    c = Serial.read();
  }
} while ((c != 'y') && (c != 'Y'));

Serial.println("OK!!");

繰り返し実行の中での分岐

for文やwhile文内で繰り返し実行している途中で繰り返しをやめることができます。

break

breakは実行中の繰り返しを終了し、当該のfor文やwhile文から抜け出します。

1
2
3
4
5
6
for (int i = 0; i < 6) {
  if (digitalRead(i) == LOW) {  // digitalRead()の結果がLOWだったらループを終了する。
    break;
  }
  
}

continue

continueは実行中の繰り返しの一番最後に制御を移します。結果として、繰り返し実行を行うかを判定する式の評価に制御が移ります。

1
2
3
4
5
6
for (int i = 0; i < 6) {
  if ((value = analogRead(i)) < 512) {  // analogRead()の値が512未満のときは処理を行わないで、次のループに移る。
    continue;
  }
  
}

breakとcontinueはともに、一番内側のループを終了するための機構です。ループの中にループがあるような場合の、内側のループでbreakやcontinueを行っても、外側のループを脱出するわけではありません。以下に簡単な例を示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void setup() {
  Serial.begin(9600);
  
  for (int i = 0; i < 5; i++) {
    Serial.println("-----------");
    Serial.print("Outer: i = ");
    Serial.println(i);
    
    for (int j = 0; j < 5; j++) {
      if (j == 3) {
        break;
      }
      Serial.print("Inner: j = ");
      Serial.println(j);
    }
  }
}

void loop() {
}

上記のスケッチの実行結果を以下に示します。内側のループではjが3になるとbreakしています。このため内側のループは途中で終わります。外側のループは最後まで実行されていることがわかります。

関数の終了

return文を使うと実行中の関数を終了し、制御を呼び出しもとに移します。関数の返却値に合わせた式を指定します。return文の書式は以下の通りです。

return; return 式;

関数の返却値の型がvoidのときは式なしのreturn文を、それ以外のときは関数の返却値に適合した型をもつ式をもつreturn文を使います。一つの関数の中に複数のreturn文があってもかまいません。

最終更新日

February 11, 2021

inserted by FC2 system