Subsections

3章 プログラムの流れの分岐 if, switch

3.1 学習のポイント

プログラムの流れの制御は, if, switch, for, while, do ... while があれば十分であり, C言語にはこれらが備わっている. ここでは, そのうちの2つ ifswitchについて学ぶ.

  1. ifを理解する. 特に, 入れ子にした複雑な条件分岐を記述できるように する.
  2. ブロックを理解する. ブロックは文を1つにまとめる機能であり, if のみでなく, C言語全般に関係する機能である.
  3. 条件式を一通り理解する. 条件式は, if のみでなく, for, while, do ... while においても条件を記述するのに用いる.

  4. switchを理解する. とくに, break があるところまで switch{ }内が順に評価されること (フォールスルー)に注意すること.

3.2 本日の課題

if および switch を用いたプログラムを作成する. 3.5を参考にしてもよい.

作成したプログラム(.cが付くファイル)をメールで提出する. 本文に, 学籍番号, 氏名を明記すること.

3.3 if

3.3.1 if の構文

if の構文
if によりプログラムの分岐ができる.
if ( condition ) {
    文A1;
    文A2;
    文A3;
} else {
    文B1;
    文B2;
    文B3;
}
において 条件式 condition が真のとき, if に続く{ } で囲まれたブロック内の文 文A1, 文A2, 文A1 が順に実行される. このとき 文B1, 文B2, 文B3 を含むelse に続くブロックは実行されない. 逆に 条件式 condition が偽のとき, else 以降の { } で囲まれたブロック内の文 文B1, 文B2, 文B3 が実行される. このとき 文A1, 文A2, 文A3 は実行しない.

ブロック内の文は複数であっても構わないし, 1文のみでも構わない. (さらに ;のみからなる何も実行しない文 空文 でも構わない.)

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a > 3) {
        puts("aは3より大きい.");
    } else {
        puts("aは3と等しいか小さい.");
    }

    return 0;
}

文法的には以下のように { および } を配置しても構わない. 慣れないうちはこちらの方が見通しがいいかも知れないが, 慣習としては上の書き方が多用されている.

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a > 3)
    {
        puts("aは3より大きい.");
    }
    else
    {
        puts("aは3と等しいか小さい.");
    }

    return 0;
}

elseの省略
次のように else 以降はなくてもよい.
if ( condition ) {
    文A1;
    文A2;
    文A3;
}

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a > 3 ) {
        puts("aは3より大きい.");
    }

    return 0;
}

3.3.2 入れ子

1つ目の else の後ろに

if ( a < 0 ) {
        puts("a は負です.");
} else {
        puts("aは0です.");
}
を接続し, 第一の条件式 a>0 が偽のときさらに 条件式 a<0 により分岐できる.

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a > 0 ) {
        puts("a は正です.");
    } else if ( a < 0 ) {
        puts("a は負です.");
    } else {
        puts("aは0です.");
    }
}

次のようにさらに if を加えることもできる.

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a > 10 ) {
        puts("a は10より大きい.");
    } else if ( a > 0 ) {
        puts("a は正です.");
    } else if ( a < 0 ) {
        puts("a は負です.");
    } else {
        puts("aは0です.");
    }
}

3.3.3 ブロック: 複数の文をまとめる

if の条件式が真のとき, それに続く1文または1ブロックが実行される. ブロックは, {} で囲まれた複数の文の集まりで, 前から順に評価・実行される.

ブロック
条件が成り立つとき複数の文を実行するには これらの文を{} で1つの「ブロック」にまとめる. 以下の例では, 2つの文がブロック内にあり, if の条件式が真のときブロック内が実行される.
/*
   このプログラムでは, aが3のとき, puts("aは3です");と puts("hello.");
   が実行される.
 */

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a==3 ) {
        puts("aは3です.");
        puts("hello.");
    }

    return 0;
}

ブロックは1つの文のみを含んでもよい. 次の例では, puts("hello.") はブロックに含まれていないので a == 3 の条件に関係なく常に実行される.

/*
   このプログラムでは, aが3のとき, puts("aは3です."); が実行される.
   また a の値に関係なく常に puts("hello."); が実行される.
 */

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a==3 ) {
        puts("aは3です.");
    } 
    puts("hello.");

    return 0;
}

1文のみ実行したい時は, 次のようにブロックにしないでその文だけを記述することもできる. 次の例では, a==3 の真偽に関係なく, puts("hello.") が実行される. puts("hello."); は if の影響が及ばない位置に記述されているからである.

/*
   このプログラムでは, aが3のとき, puts("aは3です."); が実行される.
   また a の値に関係なく常に puts("hello."); が実行される.
 */

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a==3 )
        puts("aは3です.");
    puts("hello.");

    return 0;
}

慣れないうちは 1文だけでもブロックにしておいた方が誤りが少なくてよい.

3.3.4 条件式

等価演算子 (教科書 Table 3-1)
名称 C言語における表記 数学における表記
==演算子 a == b $a=b$
!=演算子 a != b $a\ne b$

関係演算子 (教科書 Table 3-2)
名称 C言語における表記 数学における表記
<演算子 a < b $a < b$
>演算子 a > b $a > b$
<=演算子 a <= b $a \leqq b$
>=演算子 a >= b $a \geqq b$

論理演算子 (教科書 Table 3-4, Table 4-1) 条件式を組み合わせる時に用いる.

名称 C言語における表記 意味
論理AND演算子 A && B 『条件式Aが真「かつ」条件式Bが真』のとき真
論理OR演算子 A || B 『条件式Aが真「または」条件式Bが真』のとき真
論理否定演算子*1 ! A Aの否定. 条件式Aが真のとき偽, 偽のとき真.
*1) 厳密には文法上算術演算子に分類されています. !A は int型で, Aが0のとき1, Aが0以外のとき0.

注意1: = と ==
数学では, 「代入」と「等しい(等価)」に同じ記号 =を用いる. これに対してC言語では, 「代入」には=, 「等しい(等価)」には ==を用い, 両者を区別する必要がある.

注意2: &&&, |||
論理演算では & や | を2つ続けた &&|| を用いる. これに対しビット演算では, & や | が1つだけの &| を用いる. (ビット演算については, 7章基本型を参照のこと.)

注意3:!
論理否定では !を用いる. これに対して, ビット演算(ビットごとの反転)には ~を用いる.

3.3.5 条件演算子

? : 教科書参照のこと.

3.3.6 条件式の値

条件式は int 型の整数値となる. 条件式が真のとき 「0 以外の整数」, 偽のとき 「0」 になる.

例えば

#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( a==1 )
        puts("true");

    return 0;
}
においては, a==1 は偽であるので 「a==1」 は 「0」 である. したがって, 次のプログラムと等価になる.
#include <stdio.h>

int main(void)
{
    int a = 3;

    if ( 0 )
        puts("true");

    return 0;
}

規格では真の時条件式がいくつであるかは定められていないが, 次のようにして使用している環境でいくつになるかを調べられる.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int a = 1, condition;

	condition = (a==3);

	printf("condistion=%d\n", condition);
	
	 return EXIT_SUCCESS;
}

このように条件式を入れる部分に整数を入れて, プログラムの制御を行うこともある. 次のプログラムでは, 無限にputs("hello"); を実行し続ける.

#include <stdio.h>

int main(void)
{
    while ( 1 ) {
        puts("hello");
    }
}

3.4 switch

case から入り break; までを実行することに注意すること.

#include <stdio.h>

int main(void)
{
    int month = 3;

    switch (month) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
		printf("%d月は31日まであります.\n", month);
		break;
    case 4:
    case 6:
    case 9:
    case 11:
		printf("%d月は30日まであります.\n", month);
		break;
    case 2:
		printf("%d月は28日または29日まであります.\n", month);
		break;
    default:
		printf("%d月はありません.\n", month);
		break;
    }

    return 0;
}

#include <stdio.h>

int main(void)
{
	int i;

	i = 10;
	
	switch (i % 4) {
	case 0:
		printf("%dは4で割り切れます.\n", i);
		break;
	case 1:
		printf("%dを4で割ると1余ります.\n", i);
		break;
	case 2:
	case 3:
		printf("%dを4で割ると1以外の余りが出ます.\n", i);
		break;
	default:
		printf("この行が実行されることはありません.\n");
		return 1;
		break;
	}
	
	return 0;
}

/*
  return 1; について

  main関数中で return 文を実行すると, その文でプログラムの実行を終了する.
  このプログラムのdefault での処理のように プログラムを異常終了させる場合は
  0 以外の値を OS に return する場合が多い.

  どの値が正常終了か異常終了かは環境に依存するので,
  初めに #include <stdlib.h> としておいて stdlib.h の中で定義されている
  EXIT_SUCCESS や EXIT_FAILURE を用いると どの環境でも動作するプログラムと
  なる. すなわち

  #include <stdlib.h>
  正常終了のときは return EXIT_SUCCESS;
  異常終了のときは return EXIT_FAILURE;
*/


3.5 if や switch を用いたプログラム

たとえば 次のようなif や switch を用いたプログラムを作成し, if や switch を理解する.

#include <stdio.h>
#include <math.h>

/* 
   2次方程式の解を数値的に求めるプログラム.
   判別式Dの値により異なる計算を行い, それぞれに適した形式で出力する.

   sqrt は平方根を求める関数. math.h の中で定義されている.
   gcc でコンパイルする場合は このプログラムのファイル名を a.c とすると
   gcc a.c -lm
   のように -lm オプションをつけ ライブラリ libm の中の sqrt 関数を使用する.

   Dは常に有効桁に起因する誤差を含むので,
   条件式の中で D==0.0 のように 0.0 と等しいかどうかを調べることはできない.
   (D > -0.0001) && (D<0.0001) のように一定の範囲内で「0」とみなせるかどうかを
   調べる必要がある.

   この例では 「D>+1.0e-20でなく」かつ「D<-1.0e-20でない」場合を,
   Dが「0」であるとした.

 */

int main(void)
{
    double a, b, c, D, x1, x2;
	
    a =  1.0;
    b = -5.0;
    c =  6.0;
	
    D = b*b - 4.0*a*c;
	
    if ( D > +1.0e-20 ) {
		x1 = (-b+sqrt(D)) / (2.0*a);
		x2 = (-b-sqrt(D)) / (2.0*a);
		printf("x1=%g, x2=%g\n", x1, x2);
    } else if ( D < -1.0e-20 ) {
		printf("虚数解です.\n");
		printf("x1 = %g + %g i, ", -b/(2.0*a), sqrt(-D)/(2.0*a));
		printf("x2 = %g - %g i\n", -b/(2.0*a), sqrt(-D)/(2.0*a));
    } else {
		printf("重解です.");
		printf("x=%g\n", -b/(2.0*a));
    }

    return 0;
}

3.6 べき乗

数学で$x$の2乗は$x^2$と表記されるが, C言語では x$\hat{\mbox{}}$2という表現はない. 代わりに, x*x で代用する. もしくは, pow関数を用いて, y=pow(x, 2.0); のようにして求めることもできる. pow関数を用いる場合は, 冒頭に #include <math.h> が必要であることに注意すること.

3.7 実数が等しい

C言語で実数を扱う場合 実数の有効桁に注意する必要がある. 数学では 1.0/3.0 は無限小数であるが, C言語ではそのような実数を扱うことはできない. また, 実数は内部では2進数で表現されているため, 10進数で切りがよい実数でも, 2進数では無限小数になる場合がある.

例えば, 次のプログラムは, 数学的には $a$$b$は等しいが, C言語としては等しいと判別されるであろうか. さらに, a, b にいろいろな演算結果を代入してみるとよい.

#include <stdio.h>

int main(void)
{
    double a, b, x, y, z;

    x = 0.1;
    y = 0.01;
    z = 0.09;

    a = x;
    b = y+z;

    if ( a == b ) {
	puts("条件式は真です.");
    } else {
	puts("条件式は偽です.");
    }

    printf("a=%30.20e\n", a);
    printf("b=%30.20e\n", b);

    return 0;
}

C言語では実数に誤差が含まれるので, $\vert a-b\vert < \epsilon$ であるときすなわち ab の差が十分小さいとき, $a$$b$ が等しいとみなすことにする.

#include <stdio.h>

int main(void)
{
    double a, b, x, y, z, d;

    x = 0.1;
    y = 0.01;
    z = 0.09;

    a = x;
    b = y+z;

    // d = |a-b|
    if ( a > b )
		d = a - b;
    else
		d = b - a;

    // |a-b| < 十分小さい数
    if ( d < 1.0e-15 ) {
		puts("aとbは等しいとみなせる.");
    } else {
		puts("aとbは等しくない.");
    }

    printf("a=%30.20e\n", a);
    printf("b=%30.20e\n", b);

    return 0;
}

double 型実数の絶対値を求める関数fabsを用いると 簡潔に表現できる. fabs関数はmath.hで定義されているので, math.h をインクルードする必要がある.

#include <stdio.h>
#include <math.h>

int main(void)
{
    double a, b, x, y, z;

    x = 0.1;
    y = 0.01;
    z = 0.09;

    a = x;
    b = y+z;

    if ( fabs(a-b) < 1.0e-15 ) {
		puts("aとbは等しいとみなす.");
    } else {
		puts("aとbは等しくない.");
    }

    printf("a=%30.20e\n", a);
    printf("b=%30.20e\n", b);

    return 0;
}

同様にa0.1と等しいかどうかを調べるときは, 次のようになる.

#include <stdio.h>
#include <math.h>

int main(void)
{
    double a, x, y;

    x = 0.01;
    y = 0.09;

    a = x + y;

    if ( fabs(a-0.1) < 1.0e-15 ) {
	puts("aは0.1にほぼ等しい.");
    } else {
	puts("aは0.1に等しくない.");
    }

    printf("a=%30.20e\n", a);

    return 0;
}

(c)1999-2013 Tetsuya Makimura