制御文(2)

前回に引き続き,さまざまな制御文の使い方について学ぼう.

目次

if() ∼ else if() ∼ else文

単純なif(...)文,二者択一をおこなうif(...) - else文に加えて, より多くの条件分岐・場合分けを行いたい場合には,その間に複数のelse if(...) 文を追加する事によって,多分岐を表現することができる

書き方のルール

この構文を用いれば,任意の数の場合分けが表現可能である.

重要: 条件式の判定は,「式1」,「式2」,...,「式n-1」の順に上から順番に評価され,一つが「真」と判定されると,そのif 文全体が終了し, それ以降の条件式は判定が行わない.
したがって,意図した動作をさせるためには,条件式の記述順序に注意する必要がある.
if(式1) {

    文1;         // 式1が「真」の場合,文1を実行し,<ここ>にジャンプ

} else if(式2){

    文2;         // 式2が「真」の場合,文2を実行し,<ここ>にジャンプ

   ・
   ・             //  else if はいくつあっても良い.
   ・

} else if(式n-1){

    文n-1;       // 式n-1が「真」の場合,文n-1を実行し,<ここ>にジャンプ

} else {

    文n;         // すべての式が「偽」の場合,文nを実行し,<ここ>にジャンプ

} // <ここ>

練習問題

if() ... else if() ... else 構文を用いて,
  1. 整数 n の値をキーボードから入力する.その値が
    • 正の値であれば「positive」
    • 負の値であれば「negative」
    • ゼロであれば「zero」
    と画面に表示してみよう.

  2. 整数 n の値をキーボードから入力し,3, 5 の倍数であればそれぞれ「multiple of 3」「multiple of 5」と表示し,それ以外であれば「Others」と画面に表示せよ.
    ここでは公倍数については考えなくても良い.

    簡単な数で動作を確認したのち,「15」や「30」などの公倍数を入力してみよう.どうなるか?
    (条件式の判定順序を確認.if(), else if()の式の順序を変えて,動作の変化を確認)

  3. 上記の問題で,公倍数の場合を正しく判定できるよう改良し,15, 30の入力時に「common multiple of 3 and 5」と表示してみよう.

if文のネスト(入れ子)

制御文は多重な構造とすることが可能である.
例えば,if 文の中に,別の iffor を入れることもできる.

if 文が,別の ifelse の中に入っている時,外側の ifネストされた ifとか,入れ子の ifなどと呼ぶ.

if( (class == 5)  || (class == 6) )  {  // もしクラスが 5組 or 6組 だったら

    printf("Student of Mechanical Engineering\n");

    if(class == 5) {                     // 入れ子のif

        printf("Room is A201\n");

    } else if(class == 6) {

        printf("Room is A202\n");

    }

}

ネストされた if 文では,一般に if-else if-elseの階層が深くなると,次第に判読が難しくなるため, 以下のようにタブを使って字下げ整形をする.
タブによる字下げは,半角スペース4文字分,または2文字分の場合が多い.
(VSCodeなどのプログラミング用のエディタでは,字下げの範囲を視覚的に表示できるものがあるので,便利である.)

if(式) {

    文;           /* 4文字インデント */

    if(式) {

        文;       /* 8文字インデント */

        if(式) {
            文;   /* 12文字インデント */
        }

    }      /* 2番目の if の終わり */

}          /* 最初の if の終わり */

条件分岐:switch文

if-elseをいくつも並べれば,任意の多分岐を表現できるが,コードが非常に見辛くなることが多い.
そこで,整数値の場合分けに限って,switch-case文を用いることで多分岐をすっきり(?)と記述できる

switch(変数)   // この変数に置けるのは,int, short, char型など,整数型のみ.
{

case 定数1:     // 変数が 定数1 なら,ここから次のbreakまで実行される.
    文;
    break;

case 定数2:     // 変数が 定数2 なら,ここから次のbreakまで実行される.
    文;
    break;

case ...:       // caseは何個でも入れることができる.
    ...

default:        // 上の条件にどれも当てはまらない場合,ここが実行される.
    文;

}

switch文の注意

switchで複数のcaseにまたがる処理を書く

switch文では,case文は単なる処理のジャンプ先を示すラベルであるので,この性質を利用して,複数のcaseに同じ処理を簡単に対応させることができる.
以下の例では,変数 x が 1,3,5の場合に文1を,x=2,4,6の場合に文2を実行する.

#include <stdio.h>

int main(void)
{
    int n;
    printf("Saikoro(1 to 6) = ?");
    scanf("%d", &n);

    switch(n) {
    case 1:
    case 3:
    case 5:
        printf("%d is ODD.\n", n);     // n = 1,3,5の時に実行される
        break;

    case 2:
    case 4:
    case 6:
        printf("%d is EVEN.\n", n);     // n = 2,4,6の時に実行される
        break;

    default:
        printf("%d is out of range.\n", n);   // 範囲外の数値が入力された場合
    }
    return 0;
}


練習問題

  1. 上記のswitch-case 文の動作を確認してみよう.
  2. case文を入れ替えて,入力された整数(1∼20とする)が素数の場合は prime number と,合成数の場合は non-prime number と表示するよう変更してみよ.

ループのネスト

while()forなどのループ処理の中に,別のループ処理を置くことができ,ネストされたループ,またはループの入れ子と呼び,行列計算や画像処理など多次元の計算処理において非常によく使われる.
一般的には,ループ処理それぞれに対してカウンタ変数を別々に用意し(例えば,i, j, k...),それぞれのforループでインクリメントする.

二重ループ処理を用いて画面に三角形状の文字を出力.
    #include <stdio.h>

int main(void)
{
    int i, j;  // 二重ループなので,カウンタ変数を2つ用意

    for(j=0; j<10; j++) {

        for(i=0; i<j+1; i++) {  // 反復回数は定数でなくても良い.

            printf("@");

        }      // 変数 i のループの終了

        printf("\n");

    }  // 変数 j のループの終了

    return 0;
}

もうひとつの例,2変数の和の計算処理を行いたい場合

#include <stdio.h>

int main(void)
{
    for(int j=1; j<=3; j++) {          // j は外側のloop.ここで変数を宣言しても良い.

        for(int i=1; i<=3; i++) {      // i は内側のloop.ここで変数を宣言しても良い.

            printf("%2d + %2d = %2d  ", i, j, i + j);

        }

        printf("\n");    // 1行ごとに改行
    }
    return 0;
}

いずれの場合も,カウンタ変数の取り扱いに注意すること.
つまり,どのカウンタ変数がどの(内側か外側)のループのカウントをしているかを間違えないこと.

練習問題

上記のループのネストを使って,i , j の範囲を変更してみよ.
例:

画面表示が崩れる場合は,ターミナルのウインドウを大きくすると良い.

break, continue

breakによるループからの脱出

break文は大変便利なもので,ループの反復処理中に,いつでもループから脱出できる.
以下は,while(1) の部分が無限ループに見えるが,i が11になるとif文が真となりbreakでループを終了するため,printf文は10回,実行される.

プログラム例.
画面に数値がいくつまで表示されるでしょうか...
#include <stdio.h>

int main(void)
{
    int i=0;

    while(1) {   // 無限ループに見えるが...

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

        if(i > 10) {
            printf("i = %d, break!\n" , i);
            break;    // i が 10より大きくなると,ループ終了...
        }

        i++;

    }

    // breakによりループを抜けだし,ここ以降の処理続行.
    printf("End.\n");
    return 0;
}

continueによるループのスキップ

ループを抜け出したくはないが,ある条件ではその回のそれ以降の処理をスキップしたいこともある.
この様なとき continue文を用いる.
例えば以下の例では,1から10までのうち,偶数のみ表示される.

プログラム例.
画面にどのような値が表示されるでしょうか...
#include <stdio.h>
	
int main(void)
{
    int i;

    for(i=1; i<=10; i++) {     // i は 1 から 10 まで増加

        if(i % 2 == 1) {     // iが奇数だったら...
            continue;        // 次のループ処理に進む.
        }

        printf("i = %d \n", i);      // ここに到達すると数値を表示

    }

    printf("End.\n");
    return 0;
}

練習問題

上記のソースコードを実行し,break, continueの動作を確認しよう.
(注:break, continue は,ループ処理の中に書かないとエラーになる!)

禁断のgoto文

C言語では,文と呼ばれる「指定したソースコードの位置に無条件でジャンプする」機能を持つ構文も備えている.
しかし,goto文を用いるとプログラム処理の流れが破壊され,どのような処理が行われているのか理解し難いプログラムになる.
また,goto 文を用いなければ実現不可能な処理は存在しないことがわかっている.

従って,ここでは goto 文の構文を解説することはしないので,興味があれば各自で調べてみよう.
goto 文は百害あって一利なしであり,使用しない方が良い.

条件式の「値」

条件分岐や繰り返し文には必ず条件式が登場するが,この条件式には,関係演算子だけではなく,あらゆる値を置くことができる.
その理由は,関係演算子は「演算子」であるため,演算結果は「値」となるからであるが,ここであらためて条件式の「値」について確認してみよう.

C言語では,falseの値は 0 である,と定義されている.
逆に,trueの値は,非ゼロ値( = ゼロではない値)となる.(計算機は数値しか認識しないことを思い出すと良い)

次の例題で,論理式の具体的な値を確かめてみよう.

#include <stdio.h>

int main(void)
{
    printf("3>2 = %d \n", 3>2);
    printf("3<2 = %d \n", 3<2);

    return 0;
}

一般に,関係演算子による演算結果は,真の場合,非ゼロ値(多くの場合は1),偽の場合はゼロとなる.

上記のプログラムを実行すると,とある処理系では,

関係演算子の演算結果: 3>2 = 1 
関係演算子の演算結果: 3<2 = 0

と表示される. 処理系によっては異なる結果が出る場合もある. 自分のPC環境で試してみよう.

練習問題(授業時間内に実施)

Q1

試験の成績から平均点を計算するプログラムを作りたい.

ユーザーにキーボードから整数(0-100の点数とする)を繰り返し入力させ,-1 が入力されたら入力処理を終了, それまで入力した(最後の -1 以外の)数値の個数と平均値を表示するプログラムを作成せよ.
入力間違いには,エッラーメッセージを表示する.もちろん点数には加算しないこと.

ヒント:

実行例:
Input:10
Input:20
Input:300
Error!
Input:30
Input:40
Input:-1
---- Data input done. ----

count = 4
average = 25.0

Q2

1∼1000までの値について,

という動作をするプログラムを作成せよ.

実行例:
[4] [5] [8] [10] [12] [15] [16] (20) [24] ... 

手計算で倍数の計算するのではなく,プログラムに計算処理をさせること.

ダメな例:
#include <stdio.h>

int main(void)
{
    printf("[4] [5] [8] [10] [12] [15] [16] (20) [24] ... \n");
    return 0;
}

Q3

for 文の2重ループを使用して,以下に示すような縦横それぞれ1-3の数値の積を表示するプログラムを作りなさい.
ヒント:行,列の添え字の扱いに注意.

実行例:

1*1=1 1*2=2 1*3=3
2*1=2 2*2=4 2*3=6
3*1=3 3*2=6 3*3=9

Q4

定期預金や投資信託などにおいて,複利計算により資産額が当初の 2 倍を超えるのは,何年後になるかを計算するプログラムを作成しなさい.
例えば,初期の預入金額(元金,がんきん)は10,000円とする.

複利計算とは,前年度までの(元金+利息)に対して,さらに利息が付く計算方法である.
実際の金利水準などは,金融機関のWebなどで調べてみよう.

金利(年率)をキーボードから実数で入力すること.(例えば,3% のときは 0.03 を入力)
計算は実数で行うこと.表示は整数でも少数でも良い.

ヒント:1年経過するごとに,金額が (1.0 + 金利) 倍となる指数関数である
この計算を,ループ処理を用いて,n 年後に「もし金額が元の 2 倍を超えたら break; 」のような処理を行う. 「何回繰り返したか=何年経ったか」を表示する.
プログラムでは,無限ループと,break 文を使用すること.

実行例.(出力された数値が正しいとは限らない.)

r=? 0.03  (←キーボード入力)

0 year : 10000 yen
1 year : 10300 yen
2 year : 10609 yen
.
.
.
?? year : 20100 yen

Your assets will double in ?? years.
// コード例
#include <stdio.h>

int main(void)
{
    float r;    // 金利
    printf("r=?\n");
    scanf(???);

    float A  = 10000;  // 当初の資産額.
    float An = A;      // n年後の資産額.これをループで計算

    int n = 0;         // 経過年数.0で初期化.

    // 初期の金額を表示
    printf("0 year: ?? yen.\n");

    while(1) {     // 無限loop
        An = ...
        
        if(...)
            break;
    }

    printf("Your assets will double in ?? years.\n");

    return 0;
}