第3章:プログラムの流れの分岐
プログラムは、いつも同じことばかりを行うものではない。たとえば、あるキーが押されたら処理Aを行って、別のあるキーが押されたら処理Bを行って... といったように、何らかの条件判定を行った結果をもとに、選択的に処理を実行するのが普通である。
本章では、条件によってプログラムの流れを変えるための基本的な手順を学習する。
3-1 if文
あらかじめ決められた手順でのみ動くプログラムというのは、ほとんどない。本章では、条件に基づいてプログラムの流れを変える手順を学習する。
if文
みなさんは、毎日をどのように過ごしているだろうか。来る日も来る日も、まったく同じパターンで生活しているわけではないだろう。それほど意識的であるかどうかは別として、常に何らかの判断を行って、自分の行動を決めているはずである。たとえば、『今日は雨が降りそうだから、傘をもっていかなくては。』といった具合だ。
プログラムで判断を行うことにする。まずは、次の問題を考えよう。
読み込んだ整数値が5で割り切れなければ、その旨を表示する。
List 3-1 に示すのが、そのプログラムである。
// 読み込んだ整数値は5で割り切れないか
#include <stdio.h>
int main(void)
{
int n;
printf("整数を入力せよ:");
scanf("%d", &n);
if (n % 5)
puts("その数は5で割り切れません。");
return 0;
}
プログラム水色の部分に着目しよう。冒頭の if
は、英語の if とほぼ同じで、『もしも』という意味である。さて、この部分全体は、
の形をしており、if文(if statement)と呼ばれる。
if文は、プログラムの流れを、Fig.3-1(右ページ)のように制御する文である。
【Fig.3-1】if文のプログラムの流れ(その1)
式を評価して、その値が非0であれば(ゼロでなければ)文を実行する。
条件判定のために()内に置かれた式は、制御式(control expression)と呼ばれる。さて、本プログラムの制御式 n % 5 の評価で得られるのは、nを5で割った剰余である。そのため、実行例①のように、nの値が5で割り切れないときにのみ、
の文が実行される。
実行例②のように、整数値 n が5で割り切れるときは、上の文は実行されず、何も表示されない。
奇数の判定
読み込んだ整数値を5ではなく2で割った剰余を求めると、奇数であるかどうかの判定が行える。List 3-2 に示すのが、そのプログラムである。
// 読み込んだ整数値は奇数か
#include <stdio.h>
int main(void)
{
int n;
printf("整数を入力せよ:");
scanf("%d", &n);
if (n % 2)
puts("その数は奇数です。");
return 0;
}
実行例②のように、変数nに読み込んだ値が偶数であれば、何も表示しない。
else付きのif文
List 3-1 のプログラムは、読み込んだ値が5で割り切れるときに何も表示されないため、プログラムを動かす人は、肩透かしを食らってしまう。5で割り切れるときは、その旨を表示するように変更しよう。List 3-3 が、そのプログラムである。
// 読み込んだ整数値は5で割り切れないか割り切れるか
#include <stdio.h>
int main(void)
{
int n;
printf("整数を入力せよ:");
scanf("%d", &n);
if (n % 5)
puts("その数は5で割り切れません。");
else
puts("その数は5で割り切れます。");
return 0;
}
このプログラムは、次の形式のif文を利用している。
もちろん、elseは『~でなければ』という意味である。この形式のif文は、式(制御式)を評価した値が非0であれば(0でなければ)文1を実行し、そうでなければ(0であれば)文2を実行する。すなわち、Fig.3-2 に示すように、選択的な実行を行う。
【Fig.3-2】if文のプログラムの流れ(その2)
実行されるのは、二つの文のいずれか一方である。両方とも実行されない、あるいは、両方とも実行される、といったことはない。
奇数と偶数の判定
ここまで理解できれば、読み込んだ整数値が、奇数であるか偶数であるかを判定・表示するプログラムを作るのは容易である。プログラムをList 3-4 に示す。
// 読み込んだ整数値は奇数であるか偶数であるか
#include <stdio.h>
int main(void)
{
int n;
printf("整数を入力せよ:");
scanf("%d", &n);
if (n % 2)
puts("その数は奇数です。");
else
puts("その数は偶数です。");
return 0;
}
2種類のif文を学習した。まとめよう。
重要
if文は、()の中の制御式の判定結果に応じてプログラムの流れを分岐する。
Example
【演習 3-1】 右に示すように、二つの整数値を読み込んで、後者が前者の約数であれば『BはAの約数です。』と表示し、そうでなければ『BはAの約数ではありません。』と表示するプログラムを作成せよ。
【実行例】
非ゼロの判定
読み込んだ値がゼロであるかどうかを判定するプログラムも作れるようになっているはずである。プログラムをList 3-5に示す。
// 読み込んだ整数値はゼロかどうか
#include <stdio.h>
int main(void)
{
int num;
printf("整数を入力せよ:");
scanf("%d", &num);
if (num)
puts("その数はゼロではありません。");
else
puts("その数はゼロです。");
return 0;
}
if文の制御式が単なるnumであるので、プログラムの流れは次のようになる。
変数numの値が非0 → 先頭側のputs関数の呼出しが実行される。 変数numの値が0 → 後ろ側のputs関数の呼出しが実行される。
if文の構文図
これまでの2種類のif文の形式を一つにまとめて表した構文図(文法上の形式を表す図)をFig.3-3 に示す。
【Fig.3-3】if文の構文図
構文図についての詳細は、右ページのColumn 3-1 で学習する。
注意
この構文にそぐわないものは、決して許されない。たとえば、次のようなものは翻訳時にエラーとなり、実行できない。
Info
【Column 3-1】構文図について(その1)
本書で使用する構文図は、要素を矢印で結んだものである。その要素には、丸囲みのものと、角囲みのものとがある。
- 丸囲み ... "if"などのキーワードや"("などの区切り子は、縦横どおりでなければならず、勝手に"もし" や "[" に変更することはできない。このようなものを丸囲みで表す。
- 角囲み ... "式" や "文" は、"n > 7" や "a = 5;" といった具体的な式や文として記述する。このようなものを角囲みで表す。
■構文図の読み方
構文図を読むときは、矢印の方向にしたがって進む。左端からスタートして、ゴールは右端である。 分岐点は、どちらに進んでも構わない。
【Fig.3C-1】if文の構文図
①は分岐点であるから、if文の構文図を左端から右端までたどるルートには、次の二つがある。
これがif文の形式すなわち構文を表している。たとえば、List 3-1 のif文は、
であし、List 3-3 のif文は、次のようになる。
それでは、Fig.3C-2 の構文図を理解していこう。
【Fig.3C-2】構文図の例
A:先頭から末尾まで行って終了するルートと、分岐点から下におりて{文}を通るルートがある。 『0個の文、または1個の文』を表す。
B:先頭から末尾まで行って終了するルートがあるのはAと同じである。また、分岐点で下におりて{文}を通って先頭に戻れる。いったん戻った後は、末尾まで行って終了することも可能であるし、再び分岐点から{文}を通って、先頭に戻ることもできる。 『0個以上の、任意の個数の文』を表す。
C:この構文図はAと同じである。 『0個の文、または1個の文』を表す。
D:先頭から末尾まで行くルートの途中に{文}がある。また、分岐点で下におりて先頭に戻れる。いったん戻った後は、再び{文}を通過した上で終了することもできるし、再び分岐点から先頭に戻ることもできる。 『1個以上の、任意の個数の文』を表す。
等価演算子
それでは、次の問題を考えよう。
二つの整数値を読み込んで、それらの値が等しいかどうかを判定する。
プログラムをList 3-6 に示す。
// 読み込んだ二つの整数値は等しいか
#include <stdio.h>
int main(void)
{
int n1, n2;
puts("二つの整数を入力せよ。");
printf("整数1:"); scanf("%d", &n1);
printf("整数2:"); scanf("%d", &n2);
if (n1 == n2)
puts("それらの値は同じです。");
else
puts("それらの値は違います。");
return 0;
}
if文の制御式に着目しよう。初登場の==演算子は、左右のオペランドの値が等しいかどうかを判定し、等しければ1、そうでなければ0という値を生成する(生成される1や0の型はint型である)。
本プログラムの場合、if文の挙動は、次のようになる。
n1とn2の値が等しい → n1 == n2を評価した値は1 → ①の文が実行される。 n1とn2の値が等しくない → n1 == n2を評価した値は0 → ②の文が実行される。
==演算子とは逆に、左右のオペランドが等しくないかどうかを判定するのが!=演算子である。
二つの演算子の総称は、等価演算子(equality operator)である(Table 3-1)。
演算子 | 記法 | 説明 |
---|---|---|
==演算子 | a == b | aとbの値が等しければ1、そうでなければ0(その型はint型)。 |
!=演算子 | a != b | aとbの値が等しくなければ1、そうでなければ0(その型はint型)。 |
注意
==や!=は、連続する2文字で1個の単語となる。そのため、=と=のあいだや、!と=のあいだに空白文字を入れて= =や! =などとすることはできない。
本プログラムを、!=演算子を用いて書きかえたのが、右ページのList 3-7 である。
if文の制御式が変更され、puts関数を呼び出す二つの文の順序が反転している。
剰余の判定
次は、読み込んだ整数値の最下位桁の値が5であるかどうかを判定・表示するプログラムを作ろう。List 3-8 に示すのが、そのプログラムである。
// 読み込んだ整数値の最下位桁は5であるか
#include <stdio.h>
int main(void)
{
int num;
printf("整数を入力せよ:");
scanf("%d", &num);
if ((num % 10) == 5)
puts("最下位の桁は5です。");
else
puts("最下位の桁は5ではありません。");
return 0;
}
numを10で割った剰余が5と等しいかどうかで、表示する内容を変えている。
Note
演算子==よりも%のほうが優先順位が高いため(p.22)、num % 10を囲む()は省略可能である。
Info
【Column 3-2】構文図について(その2)
Fig.3C-3 の構文図をよく読んで理解しよう。
【Fig.3C-3】構文図の例
A: XとYのいずれか一方。
B: 空、あるいは、XとYのいずれか一方。
C: 0個あるいは1個のXに続いて、0個あるいは1個のY。
D: 1個以上の任意の個数のXに続いて、 1個以上の任意の個数のY。
関係演算子
これまでのプログラムは、流れを二つに分岐するものであった。三つの分岐にチャレンジしよう。次の問題を考える。
整数値を読み込んで、その符号(0/正/負)を判定する。
そのプログラムがList 3-9 である。
// 読み込んだ整数値の符号を判定
#include <stdio.h>
int main(void)
{
int no;
printf("整数を入力せよ:");
scanf("%d", &no);
if (no == 0)
puts("その数は0です。");
else if (no > 0)
puts("その数は正です。");
else
puts("その数は負です。");
return 0;
}
本プログラムで初登場の>演算子は、左オペランドが右オペランドより大きければ1を、そうでなければ0という値を生成する(生成される0と1の型はint型である)。
そのため、noが0より大きければ、no > 0を評価した値は1となり、そうでなければ0となる。
二つのオペランドの大小関係を判定する演算子は関係演算子(relational operator)と呼ばれ、Table 3-2に示す四つがある。
演算子 | 記法 | 説明 |
---|---|---|
<演算子 | a < b | aがbよりも小さければ1、そうでなければ0(その型はint型)。 |
>演算子 | a > b | aがbよりも大きければ1、そうでなければ0(その型はint型)。 |
<=演算子 | a <= b | aがb以下であれば1、そうでなければ0(その型はint型)。 |
>=演算子 | a >= b | aがb以上であれば1、そうでなければ0(その型はint型)。 |
注意
<=演算子と>=演算子は、等号を左側に置いて=<や=>としたり、<と=のあいだに空白を入れたりすることはできない。
入れ子になったif文
プログラムの水色の部分を理解しよう。
既に学習したように、if文は、右の二つの形式である。
本プログラムには、『else if ... 』とあるが、そのための特別な構文はない。if文は名前のとおり一種の文であるから、elseが制御する文は、当然if文であってもよいのである。
Fig.3-4 に示すように、elseの後ろに置く文が、if文となっている。すなわち、水色のif文の中に赤色のif文が入る入れ子の構造となっているのである。
本プログラムを実行すると、『その数は0である。』『その数は正である。』『その数は負である。』のいずれか1個が表示される。すなわち、3個のどれかを表示しない、あるいは、2個以上が表示される、ということはない。
Example
【演習 3-2】 List 3-9 の最後の else を、else if (no < 0) に変更するとどうなるかを検討せよ。
Example
【演習 3-4】 右に示すように、二つの整数値を読み込んで、それらの値が等しければ『AとBは等しい。』と、Aのほうが大きければ『AはBより大きい。』と、Bのほうが大きければ『AはBより小さい。』と表示するプログラムを作成せよ。
【実行例】
List 3-10 に示すのは、入れ子のif文を利用した、別のプログラム例である。
// 読み込んだ整数値が正であれば偶数/奇数の別を判定して表示
#include <stdio.h>
int main(void)
{
int no;
printf("整数を入力せよ:");
scanf("%d", &no);
if (no > 0)
if (no % 2 == 0)
puts("その数は偶数です。");
else
puts("その数は奇数です。");
else
puts("正でない値が入力されました。\a\n");
return 0;
}
読み込んだ整数値が正であれば、偶数/奇数のいずれであるのかを表示して、そうでなければ、その旨のメッセージを警報とともに表示する。Fig.3-5 に示すのが、if文の構造である。
【Fig.3-5】入れ子になったif文(その2)
if文が入れ子になっている点では前のプログラムと同じであるが、入れ子の構造が異なる。
Note
本プログラムのif文は、p.60で学習する複合文を利用して実現すると、対応関係が明確になって読みやすくなる("chap03/list0310a.c")。
評価
式には、(ごく一部の例外を除くと)値がある。その値は、プログラム実行時に調べられる。式の値を調べることを、評価(evaluation)という。
評価のイメージの具体例を示したのが、Fig.3-6 である。枠内の小さな文字が型で、右側の大きな文字が値である。
【Fig.3-6】式の評価のイメージ
この図では、変数nはint型で値が51であるとしている。
もちろん、変数n、定数135、変数と定数を加算するn + 135 のいずれもが式である。それぞれの評価で得られるのは、51と135と186である(三つの値の型はいずれもint型である)。
重要
式には値がある。式の値は、プログラムの実行時に評価される。
式と評価の例をFig.3-7に示す(いずれもnはint型で、値が51であるとする)。
【Fig.3-7】式と評価の一例
Example
【演習 3-5】 等価演算子や関係演算子が、1あるいは0の値を生成することを確認するプログラムを作成せよ。
2値の最大値を求める
List 3-11 のプログラムを検討する。これは、読み込んだ二つの整数値の大きいほうの値を表示するプログラムである。
// 読み込んだ二つの整数値の大きいほうの値を表示
#include <stdio.h>
int main(void)
{
int n1, n2;
puts("二つの整数を入力せよ。");
printf("整数1:"); scanf("%d", &n1);
printf("整数2:"); scanf("%d", &n2);
if (n1 > n2)
printf("大きいほうの値は%dです。\n", n1);
else
printf("大きいほうの値は%dです。\n", n2);
return 0;
}
さて、表示のメッセージの「大きいほう」を「デッカイほう」に変更しようとすると、二つのprintf関数の呼出しの両方の書きかえが必要である。
大きいほうの値をいったん変数に格納し、それから表示するように変更しよう。そうすると、プログラムはList 3-12 となる。
メッセージを「デッカイほう」に変更するとしても、プログラムの書きかえが1箇所ですむようになった。
Tip
本プログラムでは、if文を1行につめて書いている。短いif文であれば、このような表記でも構わないが、必要以上につめすぎると読みづらくなってしまう。
3値の最大値を求める
次は、三つの整数値を読み込んで、その最大値を表示しよう。右ページのList 3-13に示すのが、そのプログラムである。
// 読み込んだ三つの整数値の最大値を求めて表示
#include <stdio.h>
int main(void)
{
int n1, n2, n3;
puts("三つの整数を入力せよ。");
printf("整数1:"); scanf("%d", &n1);
printf("整数2:"); scanf("%d", &n2);
printf("整数3:"); scanf("%d", &n3);
int max = n1;
if (n2 > max) max = n2;
if (n3 > max) max = n3;
printf("最大値は%dです。\n", max);
return 0;
}
実行例
3値の最大値を求める箇所は、三つのステップで構成されている。Fig.3-8を見ながら理解していこう(変数の値の変化の様子を示している)。
【Fig.3-8】3値の最大値を求める過程での変数の変化
①maxにn1の値を入れる(maxがn1の値で初期化される)。
②そのmaxよりもn2のほうが大きければ、maxにn2を代入する。 ※ n2がmax以下であれば、代入は行われない。
③そのmaxよりもn3のほうが大きければ、maxにn3を代入する。 ※ n3がmax以下であれば、代入は行われない。
この手続きが終了したときには、変数maxにはn1、n2、n3の最大値が格納されている。
Note
本プログラムも、各if文を1行につめて書いている。左ページのプログラムとは異なり、今回は、(変数n2とn3や、maxが縦に並ぶことから)むしろ読みやすくなっている。
Example
【演習 3-6】 三つの整数値を読み込んで、その最小値を求めて表示するプログラムを作成せよ。
Example
【演習 3-7】 四つの整数値を読み込んで、その最大値を求めて表示するプログラムを作成せよ。
条件演算子
List 3-14 に示すのは、読み込んだ二つの整数値の大きいほうの値を求めて表示するプログラム(p.56 の List 3-12)を、別の方法で実現したものである。
【List 3-14】 chap03/list0314.c
// 読み込んだ二つの整数値の大きいほうの値を表示(その3:条件演算子)
#include <stdio.h>
int main(void)
{
int n1, n2;
puts("二つの整数を入力せよ。");
printf("整数1:"); scanf("%d", &n1);
printf("整数2:"); scanf("%d", &n2);
int max = n1 > n2 ? n1 : n2; // 大きいほうの値でmaxを初期化
printf("大きいほうの値は%dです。\n", max);
return 0;
}
実行例
大きいほうの値を求めるために使っているのが、Table 3-3 に示す条件演算子(conditional operator)である。この演算子は、3個のオペランドを必要とする3項演算子である。
Note
3項演算子は、条件演算子?:のみであり、他の演算子は単項演算子か2項演算子である。
【Table 3-3】条件演算子
演算子 | 記法 | 説明 |
---|---|---|
条件演算子 | a ? b : c | aが非0であれば、bを評価した値。そうでなければcを評価した値。 |
条件演算子を用いた条件式(conditional expression)がどのように評価されるのかを示したのが、Fig.3-9 である。この図の解説を、じっくり読むとよい。
【Fig.3-9】条件式の評価
【ⓐ】n1が8でn2が4のとき
【ⓑ】n1が3でn2が5のとき
変数maxに入れられるのが、n1がn2より大きければn1、そうでなければn2であることが分かるであろう。
if文を凝縮した式ともいえる条件式は、熟練プログラマが好んで使う。
重要
条件演算子(? : 演算子)を使うと、式の評価結果に応じて異なる値を作り出す簡潔な条件式が実現できる。
なお、大きいほうの値を入れる変数を使わなければ、プログラムはさらに短くなる。
2値の差を求める
条件演算子を使えば、2値の差を求める式も簡潔に実現できる。List 3-16に示すのが、そのプログラムである。
【List 3-16】 chap03/list0316.c
// 読み込んだ二つの整数値の差を求めて表示(条件演算子)
#include <stdio.h>
int main(void)
{
int n1, n2;
puts("二つの整数を入力せよ。");
printf("整数1:"); scanf("%d", &n1);
printf("整数2:"); scanf("%d", &n2);
printf("それらの差は%dです。\n", n1 > n2 ? n1 - n2 : n2 - n1);
return 0;
}
実行例
本プログラムの条件式は、大きいほうから小さいほうを引いた値となる。
■ n1 > n2 であれば : 式 n1 - n2 を評価した値。 ■ そうでなければ : 式 n2 - n1 を評価した値。
Example
【演習 3-8】 List 3-16 のプログラムを、条件演算子でなくif文を用いて書き換えよ。
Example
【演習 3-9】 演習 3-6(p.57)のプログラムを、if文でなく条件演算子を用いて書き換えよ。
複合文(ブロック)
次は、二つの整数値の大きいほうの値だけでなく、小さいほうの値も求めることにする。それが、List 3-17 のプログラムである。
【List 3-17】 chap03/list0317.c
// 読み込んだ二つの整数値の大きいほうの値と小さいほうの値を求めて表示
#include <stdio.h>
int main(void)
{
int n1, n2;
puts("二つの整数を入力せよ。");
printf("整数1:"); scanf("%d", &n1);
printf("整数2:"); scanf("%d", &n2);
int max, min;
if (n1 > n2) {
max = n1; ←①
min = n2;
} else {
max = n2; ←②
min = n1;
}
printf("大きいほうの値は%dです。\n", max);
printf("小さいほうの値は%dです。\n", min);
return 0;
}
実行例
① 二つの整数を入力せよ。
整数1:8□
整数2:4□
大きいほうの値は8です。
小さいほうの値は4です。
② 二つの整数を入力せよ。
整数1:3□
整数2:5□
大きいほうの値は5です。
小さいほうの値は3です。
本プログラムのif文が、n1がn2より大きければ、①を実行し、そうでなければ②を実行することは分かるであろう。それらは、いずれも{で始まって}で終わっている。
これは、複合文(compound statement)あるいはブロック(block)と呼ばれる文である。
【Fig.3-10】に示すのが、その構文図である。
【Fig.3-10】複合文(ブロック)の構文図
すなわち、ブロックは、次の形式の文である。
{ 0個以上の文または宣言の並び }
Note
文は何個でもよく、宣言も何個でもよいわけである。しかも、その順序も任意である。
たとえば、次に示すものは、すべて複合文である。
{ } { }
{ printf("ABC\n"); } { 文 }
{ int x; x = 5; printf("%d", x); } { 宣言 文 文 }
{ int x; x = 5; printf("%d", x); int y = x; } { 宣言 文 文 宣言 }
複合文は、if文と同様に、文の一種であり、構文上は単一の文とみなされる。そのため、本プログラムのif文は、次のように解釈されるのである。
さて、if文の構文は、右に示す二つの形式であった。すなわち、 ifが制御する文は1個だけである(else以降も1個だけである)。
【if文の構文】
ifあるいはelseで複数の文を制御できるのは、複合文が単一の文とみなされるからである。
重要
単一の文が要求される箇所に、複数の文(や宣言)を置かねばならないときは、それらをまとめて複合文(ブロック)として実現する。
このif文から、両方の { } を削除したらどうなるであろうか("chap03/list0317x.c")。
if文とみなされるのは赤い部分であって、続く min = n2;
は式文である。その後ろのelseはif文と対応していない。そのためエラーとなる。
1個の文を{}で囲んだ複合文も、もちろん単一の文として扱われる。
このことを利用すると、List 3-12(p.56)のif文は、右のように実現できる("chap03/list0312a.c")。
Tip
このように、ifで制御される文が、たとえ単一の文であっても、必ず{}で囲むスタイルには、文の増減によって{}を付けたり外したりしなくてすむ、というメリットがある。
これまでのすべてのプログラムは、Fig.3-11 に示す形式である。
図中の白い部分、すなわち{から}までの本体は、複合文だったわけである。
【Fig.3-11】プログラム中の複合文
複合文について初めて学習したが、最初のプログラムから、ずっと使ってきていたのである。
論理演算子
今度は、次の問題を考える。
読み込んだ月の季節を表示する。
List 3-9(p.52)と同様に、if…else if…をうまく利用すればよさそうである。作成したプログラムをList 3-18に示す。
【List 3-18】 chap03/list0318.c
// 読み込んだ月の季節を表示
#include <stdio.h>
int main(void)
{
int month; // 月
printf("何月ですか:");
scanf("%d", &month);
if (month >= 3 && month <= 5) // 春:3以上かつ5以下
printf("%d月は春です。\n", month);
else if (month >= 6 && month <= 8) // 夏:6以上かつ8以下
printf("%d月は夏です。\n", month);
else if (month >= 9 && month <= 11) // 秋:9以上かつ11以下
printf("%d月は秋です。\n", month);
else if (month == 1 || month == 2 || month == 12) // 冬:1または2
printf("%d月は冬です。\n", month); // または12
else
printf("%d月はありませんよ!!\a\n", month);
return 0;
}
実行例
論理AND演算子
春と夏と秋の判定で使っている&&は、論理AND演算子(logical AND operator)である。Fig.3-12 a に示すように、式 a && b の評価で得られるのは、aとbの両方とも非0であれば1、そうでなければ0という値である(型はint型である)。日本語での『aかつb』に相当する。
Note
非0を真とみなして、0を偽とみなした上で、論理積の論理演算が行われる。
【Fig.3-12】論理AND演算子と論理OR演算子
春の判定を行う式は、month >= 3 && month <= 5 である。monthの値が3以上かつ5以下であれば、その評価値が1となるため、続く『printf("%d月は春である。\n", month);』の文が実行される。夏と秋の判定も同様である。
論理OR演算子
冬の判定で使っている||は、論理OR演算子(logical OR operator)である。
図bに示すように、式 a || b の評価で得られるのは、aとbのいずれか一方でも非0であれば1、そうでなければ0である。日本語での『aまたはb』に相当する。
Note
論理和演算子||は、連続する2個の縦線記号である(小文字のLではない)。また、日本語で、「僕または彼が行くよ。」といった場合、"僕"か"彼"のいずれか一方のみという意味であるが、||演算子は、どちらか一方でもという意味である。
さて、冬の判定の制御式では、演算子||が2回使われている。一般に、加算式 a + b + c が (a + b) + c とみなされるのと同じで、論理式 a || b || c は、(a || b) || c のことである。
そのため、aとbとcのいずれか一つでも非0であれば、式 a || b || c は1となる。
Example
念のために、具体例で検証しよう。
■ monthが1あるいは2のとき month == 1 || month == 2 の評価で1が得られる。⇒ 制御式全体は、1とmonth == 12の論理和を調べる1 || month == 12となる。⇒ その結果として1が生成される。
■ monthが12のとき month == 1 || month == 2 の評価で0が得られる。⇒ 制御式全体は、0とmonth == 12の論理和を調べる0 || month == 12となる。⇒ その結果として1が生成される。
論理AND演算子と論理OR演算子の総称が、論理演算子である(Table 3-4)。
【Table 3-4】論理演算子
演算子 | 記法 | 説明 |
---|---|---|
論理AND演算子 | a && b | aとbの値がいずれも非0であれば1、そうでなければ0(その型はint型)。 |
論理OR演算子 | a || b | aとbの値の一方でも非0であれば1、そうでなければ0(その型はint型)。 |
Note
&&演算子は、aを評価した値が0であればbの評価を行わない。また、||演算子は、aを評価した値が非0であればbの評価を行わない。
以上のことは、短絡評価と呼ばれる。次ページで詳しく学習する。
なお、本プログラムの別解を、本章の『まとめ(p.71)』のプログラムに示している。
Example
【演習 3-10】 右に示すように、三つの整数値を読み込んで、それらの値がすべて等しければ『三つの値は等しいである。』と、どれか二つの値が等しければ『二つの値が等しいである。』と、そうでなければ『三つの値は異なる。』と表示するプログラムを作成せよ。
【実行例】
Example
【演習 3-11】 右に示すように、二つの整数値を読み込んで、それらの値の差が10以下であれば『それらの差は10以下である。』と、そうでなければ『それらの差は11以上である。』と表示するプログラムを作成せよ。 論理OR演算子を利用すること。
【実行例】
短絡評価
if文で最初に行われる《春》の判定に着目する。変数monthの値が2であるとして、次の式の評価を考えよう。
month >= 3 && month <= 5
左オペランドのmonth >= 3を評価した値は0であるから、右オペランドの式month <= 5を調べるまでもなく、この式全体が偽となる(春でない)ことは明らかである。
そのため、&&演算子の左オペランドを評価した値が0すなわち偽であれば、調べる必要のない右オペランドの評価は省略されることになっている。
||演算子も同様である。《冬》の判定に着目しよう。
month == 1 || month == 2 || month == 12
もしmonthが1であれば、2月や12月の可能性を調べるまでもなく、式全体が1すなわち真となる(冬である)ことは明らかである。
そのため、||演算子の左オペランドを評価した値が非0すなわち真であれば、調べる必要のない右オペランドの評価は省略されることになっている。
Note
前ページでは、短絡評価が行われないとして具体例を考えた。実際には短絡評価が行われるため、評価の回数は少なくなる。monthが1であるとして、具体例で検証しよう。
式month == 1 || month == 2は、左オペランドが1であるため、短絡評価によって、右オペランドを調べることなく1と評価される。そのため、制御式全体は、1とmonth == 12の論理和を調べる1 || month == 12となる。この式も、左オペランドが1であるため、短絡評価によって、右オペランドを調べることなく1と評価される。
論理演算式の評価結果が、左オペランドの評価結果のみで明確になる場合に、右オペランドの評価が省略されることは、短絡評価(short circuit evaluation)と呼ばれる。
重要
論理AND演算子&&と論理OR演算子||の評価では、短絡評価が行われる。 すなわち:&&の左オペランドが偽(0)であれば、右オペランドは評価されない。 ||の左オペランドが真(非0)であれば、右オペランドは評価されない。
右オペランドの評価が省略されることは、(わずかではあるものの)処理の高速化に貢献する。
Info
【Column 3-3】初学者が誤りやすいif文
ここでは、初学者が誤りやすいif文の例を示す。
■制御式の囲む)の後ろにセミコロンを置く
次のif文を考えよう。
このif文を実行すると、nがどんな値であっても(正でも負でも0でも)、「その値は正である。」と表示される。そうなる原因は、(n > 0)の後ろに置かれたセミコロン;である。
後の章で学習するが、セミコロンだけの文は、空文と呼ばれる文である(空の文である空文を実行しても、実質的に何も行われない)。そのため、次のように解釈されるのである。
■等価性の判定に=を利用する
等価性(等しいかどうか)の判定に利用する演算子==を=に間違えないように注意しよう。
○ 誤:if (a = 0) 文 // 文は実行されない、しかも、aは0になる ○ 正:if (a == 0) 文
誤った例の場合、変数aに0が代入される。後の章で学習するが、a = 0を評価すると0すなわち偽が得られるため、(代入前のaの値とは関係なく)文は決して実行されない。
■三つの変数の等価性の判定に==を利用する
次に示すのは、変数aと変数bと変数cの値が等しいかどうかを判定する例である。
○ 誤:if (a == b == c) ○ 正:if (a == b && b == c)
等価演算子==は2項演算子であるから、a == b == cでは判定できない。
■二つの条件の判定に&&や||を利用しない
上の例と同様の例である。たとえば、次に示すのは、変数aが3以上でかつ5以下であるかどうかを判定する例である。
○ 誤:if (3 <= a <= 5) ○ 正:if (a >= 3 && a <= 5)
■論理演算子の代わりにビット単位の論理演算子を利用する
上記と同じ、変数aが3以上でかつ5以下であるかどうかを判定する例である。
○ 誤:if (a >= 3 & a <= 5) ○ 正:if (a >= 3 && a <= 5)
論理演算である"かつ"と"または"に利用するのは、&&演算子と||演算子である。よく似て異なる演算子&と|は、第7章で学習する)。
3-2 switch文
if文は、ある条件の判定結果に応じて、プログラムの流れを二つに分岐する文であった。本節で学習するswitch文を用いると、一度に複数に分岐できる。
switch文とbreak文
整数値を3で割った剰余を表示するList 3-19のプログラムを検討しよう。
【List 3-19】 chap03/list0319.c
// 読み込んだ整数値を3で割った剰余を表示(if文)
#include <stdio.h>
int main(void)
{
int no;
printf("整数値:");
scanf("%d", &no);
if (no % 3 == 0)
puts("3で割り切れます。");
else if (no % 3 == 1)
puts("3で割った剰余は1です。");
else
puts("3で割った剰余は2です。");
return 0;
}
実行例
変数noを3で割った剰余を求める演算 no % 3 を2回行っている。このように同じ演算を複数回行うことは、①タイプミスにつながる、②計算時間が余計にかかる、③プログラムが読みづらくなるなどの結果を招く。
単一の式の値に基づいて、プログラムの流れを複数に分岐するときは、if文ではなく、switch文(switch statement)を使うと簡潔に表現できる。
そのswitch文は、Fig.3-13の構文をもつ文であり、()で囲まれた制御式を評価した値によって、プログラムの流れを複数に分岐させる、切替えスイッチのような文である。
【Fig.3-13】switch文の構文図
Note
制御式の型は、整数でなければならない。
switch文を用いて書き直したのが、右ページのList 3-20のプログラムである。
【List 3-20】 chap03/list0320.c
// 読み込んだ整数値を3で割った剰余を表示(switch文)
switch (no % 3) {
case 0 : puts("3で割り切れます。"); break; // no % 3が0であれば…
case 1 : puts("3で割った剰余は1です。"); break; // 1であれば…
case 2 : puts("3で割った剰余は2です。"); break; // 2であれば…
}
ラベル
noが7であるとして、プログラムの流れを考えていこう。no % 3は1であるから、プログラムの流れは『case 1 :』と書かれた目印へと一気に移る(Fig.3-14)。
プログラムの飛び先を示す目印となる『case 1 :』は、ラベル(label)と呼ばれる。
なお、ラベルの値は《定数》でなければならず、変数は許されない。また、複数のラベルが同じ値をもつことは許されない。
Note
図にも示しているように、2と:のあいだは空白を入れても入れなくても構わない。ただし、caseと2のあいだには空白が必要である。空白を入れずにcase2とすることはできない。
【Fig.3-14】List 3-20 のプログラムの流れ
break文
プログラムの流れがラベルに飛んだ後は、その後ろに置かれた文が実行される。この場合、『3で割った剰余は1である。』と表示される。
表示後に実行されるのが、Fig.3-15 の構文をもつbreak文(break statement)である。break文は、それを囲んでいるswitch文を中断・終了させる文である。
【Fig.3-15】break文の構文図
重要
break文を実行すると、プログラムの流れはswitch文から抜け出す。
Note
breakは、『破る』『抜け出る』という意味である。
break文の働きによって、switch文を突き破って抜け出すので、その下に置かれている『puts("3で割った剰余は2である。");』は実行されない。
Note
プログラムの流れが『case 1:』に飛ぶ例を考えた。『case 0:』や『case 2:』も同様である。
複雑なswitch文
それでは、List 3-21 のプログラムを例に、switch文について理解を深めていこう。
【List 3-21】 chap03/list0321.c
// switch文の動作を確認するプログラム
#include <stdio.h>
int main(void)
{
int sw;
printf("整数:");
scanf("%d", &sw);
switch (sw) {
case 1 : puts("A"); puts("B"); break;
case 2 : puts("C");
case 5 : puts("D"); break;
case 6 :
case 7 : puts("E"); break;
default : puts("F"); break;
}
return 0;
}
実行例
switch文の最後に置かれた『default :』は、制御式を評価した値が、どのcaseとも一致しないときにプログラムの流れが飛んでいくラベルである。そのため、このswitch文の流れは、Fig.3-16 のように制御される。
【Fig.3-16】switch文の流れ
プログラムと図をよく見比べよう。break文がない箇所では、プログラムの流れが、次の文へと落ちる(fall through)ことが分かる。
Note
switch文内のラベルの出現順序を変えると、実行結果が変わる。switch文を使うときは、ラベルの順序などが妥当であるかをきちんと吟味しよう。
次は、右に示すswitch文を考える。変数swの値に応じて、3色のいずれかが表示される。
swが4であれば『黒』と表示するように変更したのが、下のswitch文である。
switch (sw) {
case 1 : printf("赤"); break;
case 2 : printf("青"); break;
case 3 : printf("白"); break;
case 4 : printf("黒"); break;
}
『case 4:』以降を追加しているだけではなく、『case 3:』の末尾にbreak文を追加していることに注意しよう。
重要
switch文の最後のcaseの末尾にもbreak文を置いておけば、ラベルの増減に柔軟に対応できる。
switch文とif文
次に示すif文とswitch文は、同じ動作をする。これらの文を対比しながら、if文とswitch文について検討していく。
if (p % 5 == 1) // 左のif文を書き直したswitch文
c = 3; switch (p % 5) {
else if (p % 5 == 2) case 1 : c = 3; break;
c = 5; case 2 : c = 5; break;
else if (p % 5 == 3) case 3 : c = 7; break;
c = 7; default : if (q % 5 == 4) c = 9; break;
else if (q % 5 == 4) }
c = 9;
まずは、if文をじっくり読んでみよう。先頭三つのifはp % 5の値を調べ、最後のifではq % 5の値を調べている。変数cに9が代入されるのは、p % 5が1、2、3のいずれでもなく、かつq % 5が4のときである。
連続したif文において、分岐のための比較対象が必ずしも単一の式であるとは限らない。最後の判定は、if (p % 5 == 4)と読み間違えられたり、if (p % 5 == 4)の書き間違いではないかと誤解されたりする可能性がある。
その点、switch文は、全体の見通しがよいため、プログラムを読む人が、そのような疑念を抱くことが少なくなる。
重要
単一の整数型の式に基づいたプログラムの流れの分岐は、if文よりもswitch文で実現したほうがよい(場合が多い)。
選択文
本章で学習したif文とswitch文は、いずれもプログラムの流れを選択的に分岐させるものであるから、これらをまとめて選択文(selection statement)と呼ぶ。
Example
【演習 3-12】 List 3-4(p.47)のプログラムを、if文でなくswitch文を用いて書き換えよ。
Example
【演習 3-13】 List 3-18(p.62)のプログラムを、if文でなくswitch文を用いて書き換えよ。
まとめ
- 式は、プログラムの実行時に評価される。式を評価すると、その型と値が得られる。
- 0以外の値は真とみなされ、0は偽とみなされる。
- 左右のオペランドの等価性を判定するのが、等価演算子==と!=である。前者は等しいかどうか、後者は等しくないかどうかを判定する。いずれも、判定結果が成立すればint型の1を生成し、そうでなければ0を生成する。
- 左右のオペランドの大小関係を判定するのが、関係演算子<と>と<=と>=である。いずれも、判定結果が成立すればint型の1を生成し、そうでなければ0を生成する。
- 左右のオペランドに対して論理積(両方とも真であれば真)と論理和(一方でも真であれば真)の論理演算を行うのが、論理AND演算子&&と論理OR演算子||である。いずれも、判定結果が成立すればint型の1を生成し、そうでなければ0を生成する。
- 論理演算子は短絡評価を行う。
- 論理AND演算子&&は、左オペランドを評価した結果が偽であれば、右オペランドを評価しない。
- 論理OR演算子||は、左オペランドを評価した結果が真であれば、右オペランドを評価しない。
- ある条件が成立したとき(制御式を評価した値が非0となったとき)にのみ処理を行うことがあれば、elseの付かないif文を利用する。また、ある条件の成立の可否によって異なる処理を行うのであれば、else付きのif文を利用する。
- 単一の文が要求される箇所に、複数の文(や宣言)を置かねばならないときは、それらをまとめて複合文=ブロックとして実現する。
- 条件演算子?:を使うと、if文の働きを単一の式に凝縮できる。第1オペランドの評価結果に基づいて、第2オペランド/第3オペランドの一方のみが評価され、その値が得られる。
- switch文を使うと、単一の整数型の式を評価した値に応じて、プログラムの流れを複数に分岐できる。評価結果に応じて、(整数型の定数で指定された)該当するラベルにプログラムの流れが飛ぶ。該当するラベルがない場合の飛び先は、defaultラベルである。 switch文の中でbreak文が実行されると、switch文の実行は中断されて終了する。なお、break文がない箇所では、プログラムの流れは、次の文へと落ちていく。
- if文とswitch文の総称は、選択文である。
演習問題
演習問題3-1:10以下の差の判定(条件演算子)
問題の説明
二つの整数値を読み込んで、それらの差が10以下であれば「それらの差は10以下です」と表示し、そうでなければ「それらの差は11以上です」と表示するプログラムを作成してください。条件演算子を利用すること。
期待される結果
入力例1:
出力例1:入力例2:
出力例2:ヒント
- 二つの整数の差を求める際、負の値にならないように注意しましょう
- 条件演算子(三項演算子)の構文は
(条件) ? 真の場合の値 : 偽の場合の値
です - カンマ区切りの入力を受け取るためには、scanf関数の書式指定子を
%d,%d
とします - 差を計算した後で、その差が10以下かどうかを判定します
演習問題3-2:成績の判定
問題の説明
0〜100の範囲の整数値(テストの点数)を読み込んで、80点以上を「優」、70〜79点を「良」、60〜69点を「可」、59点以下を「不可」と判定して表示するプログラムを作成してください。
期待される結果
入力例1:
出力例1:入力例2:
出力例2:入力例3:
出力例3:入力例4:
出力例4:入力例5:
出力例5:ヒント
- 条件判定を行う順序が重要です。判定は「より厳しい条件から緩い条件へ」の順で行います
- 入力値が0〜100の範囲内かどうかを最初にチェックしましょう
- 論理演算子「||」(OR演算子)を使って範囲外の条件を一度にチェックできます
- else if文を連続して使うことで、複数の条件を順次チェックできます
演習問題3-3:季節の判定(switch文版)
問題の説明
月を表す整数値(1〜12)を読み込んで、それが春(3〜5月)、夏(6〜8月)、秋(9〜11月)、冬(12、1、2月)のいずれであるかを表示するプログラムをswitch文を用いて作成してください。
期待される結果
入力例1:
出力例1:入力例2:
出力例2:入力例3:
出力例3:入力例4:
出力例4:入力例5:
出力例5:ヒント
- switch文では整数式の値に基づいて分岐します
- 同じ処理を行うケースは連続して書くことができます(breakを書かないとfall-throughします)
- 各分岐の最後にはbreak文を書いて、次のケースに進まないようにしましょう
- 冬は12月、1月、2月であることに注意し、適切にケースを並べましょう
- 入力値の妥当性チェックは、switch文の前に行うのがよいでしょう
演習問題3-4:等値判定
問題の説明
三つの整数値を読み込んで、それらの値がすべて等しければ「三つの値は等しいです」と、どれか二つの値が等しければ「二つの値が等しいです」と、すべて異なれば「三つの値はすべて異なります」と表示するプログラムを作成してください。
期待される結果
入力例1:
出力例1:入力例2:
出力例2:入力例3:
出力例3:ヒント
- 条件の判定順序が重要です。最も厳しい条件(すべてが等しい)から判定します
- 論理演算子「&&」(AND演算子)と「||」(OR演算子)を使って複合条件を作成します 3.「すべての値が等しい」とは、a == b かつ b == c を意味します(これにより a == c も成り立ちます) 4.「二つの値が等しい」は、a == b または b == c または a == c のいずれかが成り立つことを意味します
- カンマ区切りの入力を処理するためには、scanf関数の書式指定子を "%d,%d,%d" とします