【爆速C言語入門】様々な制御文【講義】

スポンサーリンク
パソコンC
スポンサーリンク

はじめに

本講義はプログラミングって何?な初心者の方から、C言語は初めて!という人が対象です。

加えて、メモリなどの説明を多めに取り入れているので、C言語はなんとなく知ってるけどコンピューター上でどういう処理になっているの?という人にもぜひ見てもらいたいと思っています!

今回は以前の記事で触れられなかった制御文についてと、留意事項などについて説明します。

do〜while文

「do~while文」は「while文」と同様に、条件式が真である場合(条件式を満たしている間)、処理を繰り返します。

「do~while文」は、次のように記述します。

do{
	処理
}while(条件式);

条件式の後に「;」が必要です。「;」がなくてエラーが出ているということが多々あるので、注意してください。

「while文」とは条件式を満たしているのか判定するタイミングに次のような違いがあります。

  • 「while文」:条件式を満たしているか判定した後に処理を行う
  • 「do~while文」:処理を行った後に条件式を満たしているか判定する

このため、条件に関わらず1度は実行したい場合には「do~while文」を用いることが多いです。

次のプログラムは、同じ条件、初期値、処理を「while文」と「do~while文」で順に実行します。

#include <stdio.h>

//do-while文

//main関数
int main() {

    //iCount < 0 ではないためどちらの条件も満たさない
    int iCount = 0;

    //while文は実行されない
    while(iCount < 0){
        printf("while文の実行\n");
    }

    //do-while文は一度実行される
    do{
        printf("do~while文の実行\n");
    } while(iCount < 0);

    return 0;
}

実行結果は次のようになります。

do~while文の実行

どちらも条件式は一度も満たしませんが、「do~while文」内に書かれた処理は一度だけ実行されていることが分かりますね。

break文

「break文」は「最も内側の繰り返し処理(for文、while文、do~while文)」と「switch文」から抜けるために用いられる文です。

次の図では、繰り返しの中で「break文」が処理された場合に、「for文」を抜けることを示しています。(「break文」が実行された後、「処理3」が実行されます。)

break文の挙動

次のプログラムは、「配列のデータの中に、ある数(例では7)の倍数が含まれるか判定するプログラム」です。

#include <stdio.h>

//break文

//main関数
int main() {

    int iCount;                                         //ループ処理用

    int iData[10] = { 82,45,21,67,51,66,34,7,90,13 };   //配列データ
    int iDataNum = sizeof(iData) / sizeof(int);         //配列要素数

    int iX = 7;                                         //割る数
    int iModX;                                          //ModX(Xで割ったあまり)

    //配列の中身を順番に確認
    for(iCount = 0; iCount < iDataNum; iCount++){

        //データをXで割ったあまりをiModXに格納
        iModX = iData[iCount] % iX;

        //データをXで割り切れた場合,ループを終了する
        if(iModX == 0){
            printf("配列に%dの倍数を含みます\n", iX);
            iCount++;                                   //要素番号+1が探索回数
            break;                                      //ここでループを抜ける
        }
    }

    printf("%d回探索しました\n終了\n", iCount);

    return 0;
}

実行結果は次のようになります。

配列に7の倍数を含みます
3回探索しました
終了

このプログラムでは「break文」を用いることで、1回でもデータをXで割り切れた場合にすぐに繰り返し処理を終了します(処理から抜けます)。

仮に「break文」がなかった場合、繰り返し処理をすべての要素数分実行するため、次のような結果になります。

配列に7の倍数を含みます
配列に7の倍数を含みます
10回探索しました
終了

多重ループのbreak文

次の図では、「for文」の中に「for文」がある入れ子構造になっている場合に、内側の「for文」の中で「break文」が実行された時、内側の繰り返し処理のみを抜けることを示しています。

(「break文」が実行された後、「処理5」ではなく「処理4」が実行されます。}

多重ループでのbreak文の挙動

1つの「break文」では最も内側の繰り返し処理を抜けることしかできないため、2つの繰り返し処理を抜けるには内側から順番にそれぞれ「break文」が必要です。

次のプログラムは「2次元配列のデータの中に、ある数(例では7)の倍数が含まれるか判定するプログラム」です。

#include <stdio.h>

//多重ループのbreak文

//main関数
int main() {

    //カウンタ
    int iCount1;
    int iCount2;

    //配列データ
    int iData[5][10] = {
        {8,16,41,65,72,99,93,23,44,58},
        {11,37,69,81,45,62,78,90,56,77},
        {82,45,21,67,51,66,34,0,90,13},
        {19,86,47,80,12,44,32,39,70,16},
        {54,31,27,86,93,27,26,58,61,49}
    };

    //配列の各次元の要素数
    int iDataSize1 = sizeof(iData) / sizeof(iData[0]);
    int iDataSize2 = sizeof(iData[0]) / sizeof(int);

    //計算用
    int iX = 7;
    int iModX;


    //配列の要素を1つずつ確認する
    for(iCount1 = 0; iCount1 < iDataSize1; iCount1++){
        for(iCount2 = 0; iCount2 < iDataSize2; iCount2++){

            //データをXで割ったあまりを計算
            iModX = iData[iCount1][iCount2] % iX;

            //データをXで割り切れた場合,ループを終了する
            if(iModX == 0){
                printf("配列に%dの倍数を含みます\n", iCount1, iX);

                iCount2++;  //要素番号+1がこのループでの探索回数
                break;      //内側のfor文から抜ける
            }
        }

        //データをXで割り切れた場合,ループを終了する
        if(iModX == 0){
            printf("%d回探索しました\n", iCount1 * iDataSize2 + iCount2);

            break;          //外側のfor文から抜ける
        }
    }

    printf("終了\n");

    return 0;
}

実行結果は次のようになります。

配列に1の倍数を含みます
19回探索しました
終了

このプログラムでは2つの「for文」に対して、それぞれ「if文」を用いて「imodX」が「0」であれば「break文」によって繰り返し処理を抜けるようにしています。

実際のプログラムでのbreak文の挙動

必要に応じてどのループを抜ける必要があるのかを見極めて「break文」を使いましょう。

continue文

「continue文」は「最も内側の繰り返し処理(for文、while文、do~while文)」の処理の最後に移動する文です。

次の図では、繰り返しの中で「continue文」が処理された場合に、「処理2」を飛ばして処理の最後に移動していることを示しています。

cotinue文の挙動

次のプログラムは「指定した要素(要素番号で指定)以外の要素の合計値を求めるプログラム」です。

#include <stdio.h>

//continue文

//main関数
int main() {

    int iData[] = { 67,92,41,38,79,96,21,11,26,57 };    //配列データ
    int iDataNum = sizeof(iData) / sizeof(int);         //配列要素数
    
    int iCount;                                         //カウンタ
    int iAdd = 0;                                       //合計値
    int iSkip = 2;                                      //加算しない要素の要素番号

    //配列データを全て順番に処理する
    for(iCount = 0; iCount < iDataNum; iCount++){

        printf("iData[%d] = %d,\t", iCount, iData[iCount]);

        //要素番号が加算しない要素の時
        if(iCount == iSkip){
            printf("continue,\tiAdd = %d\n", iAdd);
            continue;                                   //繰り返し処理の最後に移動する
        }

        iAdd += iData[iCount];                          //データの加算

        printf("iAdd += %d,\tiAdd = %d\n", iData[iCount], iAdd);

    }                                                   //←繰り返し処理の最後

    return 0;
}

実行結果は次のようになります。

iData[0] = 67,  iAdd += 67,     iAdd = 67
iData[1] = 92,  iAdd += 92,     iAdd = 159
iData[2] = 41,  continue,       iAdd = 159
iData[3] = 38,  iAdd += 38,     iAdd = 197
iData[4] = 79,  iAdd += 79,     iAdd = 276
iData[5] = 96,  iAdd += 96,     iAdd = 372
iData[6] = 21,  iAdd += 21,     iAdd = 393
iData[7] = 11,  iAdd += 11,     iAdd = 404
iData[8] = 26,  iAdd += 26,     iAdd = 430
iData[9] = 57,  iAdd += 57,     iAdd = 487

このプログラムでは、加算しない要素の場合に「continue文」を用いて、その後に記述されているデータの加算と「iAdd+=XX」という表示をしないようにしています。

また、「continue文」も「break文」と同様に多重ループの中で記述された場合は最も内側の繰り返し処理の最後に移動するので気を付けてください。

switch文

「switch文」は条件に応じて分岐させる文で、「式」の値と等しい「定数式」が定義されている場所から処理を行います。

分岐後は、上から順(C言語の実行順通り)に「break文」もしくは「switch文」の末尾に到達するまで全ての処理を実行します。

「switch文」は、一般的に次のように記述します。

switch(式){
    case 定数式1: //式の値が定数式1と一致した場合、ここから処理を行う
        処理1
        ・
        ・
        ・
        break;  //これがなければ処理2以下も実行する
    case 定数式2: //式の値が定数式2と一致した場合、ここから処理を行う
        処理2
        ・
        ・
        ・
        break;  //これがなければ処理3以下も実行する
    case 定数式3: //式の値が定数式3と一致した場合、ここから処理を行う
        処理3
        ・
        ・
        ・
        break;   //これがなければ以下も実行する
    ・
    ・
    ・
    default:     //式の値がどの定数式とも一致しない場合、ここから処理を行う
        処理
        break;   //下に処理がないため無くても良い
}

「case 定数式:」は、「式」が「定数式」の値と一致した場合にそこから処理を進めるだけなので、「break文」がなければそのまま次の処理も実行するということに注意してください。

「break文」がないために、上手く動かない例を示します。

#include <stdio.h>

//switch文

//main関数
int main() {

    int iData[] = { 1,2,3,4,5 };            //中身を自由に変えてみてください
    int iNum = sizeof(iData) / sizeof(int); //iDataの要素数を取得
    int iCount;
    
    //for文で配列の要素を順に確認
    for(iCount = 0; iCount < iNum; iCount++){

        //配列の要素を2で割った結果を式として分岐
        switch(iData[iCount]%2){

            case 0:     //2で割り切れる場合
                printf("%dは偶数です\n",iData[iCount]);

            case 1:     //1余る場合
                printf("%dは奇数です\n", iData[iCount]);
                break;

            default:    //上記以外
                printf("奇数でも偶数でもない\n");
                break;
        }
    }

    return 0;
}

実行結果は次の通りです。

1は奇数です
2は偶数です
2は奇数です
3は奇数です
4は偶数です
4は奇数です
5は奇数です

このプログラムの目的は、配列に格納された要素を先頭から順番に偶数か奇数か判定して、「~は偶数です」もしくは「~は奇数です」と出力することです。

しかし、2で割り切れる(偶数)の場合に「~は偶数です」「~は奇数です」の両方を出力してしまっています。

これは「case 0」と「case 1」の間に「break文」がないため、「式」の値が「0」の時に「case 1」以下の処理も行ってしまっていることが原因です。

case 0:     //2で割り切れる場合
    printf("%dは偶数です\n",iData[iCount]);
          //←ここに「break文」がない
case 1:     //1余る場合

このため、「break文」を追加して次のようにします。

#include <stdio.h>

//switch文

//main関数
int main() {

    int iData[] = { 1,2,3,4,5 };            //中身を自由に変えてみてください
    int iNum = sizeof(iData) / sizeof(int); //iDataの要素数を取得
    int iCount;
    
    //for文で配列の要素を順に確認
    for(iCount = 0; iCount < iNum; iCount++){

        //配列の要素を2で割った結果を式として分岐
        switch(iData[iCount]%2){

            case 0:     //2で割り切れる場合
                printf("%dは偶数です\n",iData[iCount]);
                break;

            case 1:     //1余る場合
                printf("%dは奇数です\n", iData[iCount]);
                break;

            default:    //上記以外
                printf("奇数でも偶数でもない\n");
                break;
        }
    }

    return 0;
}

実行結果は次のように、想定通りの動きになります。

1は奇数です
2は偶数です
3は奇数です
4は偶数です
5は奇数です

goto文

「goto文」はラベル名を指定することで、そのラベル名の場所へ移動(間の処理は一切されない)します。

次の図では、「goto文」が処理されたときに指定したラベルの位置に移動することを示しています。

goto文の挙動

この例を見てもわかるように「goto文」はラベルの位置によって自由に移動することができます。このため、可読性の観点から原則使用禁止と言われることが多い文です。

しかし、「goto文」を用いた方が簡潔に書ける場合も存在しています。

「多重ループのbreak文」で説明したプログラムと同様のプログラムを「goto文」を使って記述すると次のようになります。

#include <stdio.h>

//多重ループのbreak文

//main関数
int main() {

    //カウンタ
    int iCount1;
    int iCount2;

    //配列データ
    int iData[5][10] = {
        {8,16,41,65,72,99,93,23,44,58},
        {11,37,69,81,45,62,78,90,56,77},
        {82,45,21,67,51,66,34,0,90,13},
        {19,86,47,80,12,44,32,39,70,16},
        {54,31,27,86,93,27,26,58,61,49}
    };

    //配列の各次元の要素数
    int iDataSize1 = sizeof(iData) / sizeof(iData[0]);
    int iDataSize2 = sizeof(iData[0]) / sizeof(int);

    //計算用
    int iX = 7;
    int iModX;


    //配列の要素を1つずつ確認する
    for(iCount1 = 0; iCount1 < iDataSize1; iCount1++){
        for(iCount2 = 0; iCount2 < iDataSize2; iCount2++){

            //データをXで割ったあまりを計算
            iModX = iData[iCount1][iCount2] % iX;

            //データをXで割り切れた場合,ループを終了する
            if(iModX == 0){
                printf("配列に%dの倍数を含みます\n", iX);

                iCount2++;  //要素番号+1がこのループでの探索回数
                goto DONE;  //データをXで割り切れた場合,2つのループから抜ける
            }
        }
    }

DONE:
    printf("%d回探索しました\n", iCount1 * iDataSize2 + iCount2);

    printf("終了\n");

    return 0;
}

どうでしょう。「if文」が2つあるよりもよっぽど可読性が高いように感じませんか?

このような「goto文」を用いた方が可読性が高いような場合もいくつもあります。

ただし、「goto文」が必要なプログラムは存在しないため(「goto文」が無くてもプログラムが書ける)、なるべく避けた方がいいかと思います。

無限ループ

無限ループとは、次のように条件式を常に真にすることで処理を無限に繰り返すことです。

while(1){
	処理
}
for(;;){
	処理
}

ただし、殆どの場合で無限ループは不要です。

「プログラムの実行後、無限ループを行い、強制終了されるまで繰り返し処理を行う」のであれば無限ループである意味があります。

しかし大抵の場合は無限ループをしている意味があまりない上に可読性が悪いプログラムになってしまいます。

よく見るのが、無限ループの中で「break文」を用いてループ処理を終了するプログラムです。

この場合、終了の条件が決まっているのでそれをループ処理の条件にすべき(もちろん工夫が必要なこともある)です。

「無限ループである必要があるか」をよく考えてから使いましょう。

演習

最後に演習です。今回も講義を網羅できるように3問用意しました。

頑張りましょう!

演習1

ある正の整数Xに対して、「1,2,3,…」と1つずつ大きくなる値を順番に引いていく。

Xが負の数になる時、「最後に引いた数」はいくつになるか出力するプログラムを「do~while文」を用いて作成してください。

尚、正の整数Xはプログラム中の変数に用意して構いません。

出力例

Xが100だった場合、次のような出力が得られます(最終行以外は無くても良い)。

正の整数X = 100
100 - 1 = 99
99 - 2 = 97
97 - 3 = 94
94 - 4 = 90
90 - 5 = 85
85 - 6 = 79
79 - 7 = 72
72 - 8 = 64
64 - 9 = 55
55 - 10 = 45
45 - 11 = 34
34 - 12 = 22
22 - 13 = 9
9 - 14 = -5
最後に引いたのは14
#include <stdio.h>

//演習1

//main関数
int main() {

    int iXNum = 100;            //正の整数X

    int iCalcNum = iXNum;       //減算結果を格納する変数
    int iSubNum = 0;            //引く数

    printf("正の整数X = %d\n", iXNum);

    //do-while文で繰り返し処理
    do{
        printf("%d", iCalcNum);

        iSubNum++;              //引く数を1つ大きくする
        iCalcNum -= iSubNum;    //減算して結果を格納
        
        printf(" - %d = %d\n", iSubNum, iCalcNum);

    } while(iCalcNum >= 0);     //減算結果が正の数であれば繰り返し継続

    printf("最後に引いたのは%d\n", iSubNum);

    return 0;
}

演習2

ある月の1日は日曜日でした。

「Xの曜日」を出力するプログラムを「switch文」を用いて作成してください。

尚、任意の日X(範囲は1-31)はプログラム中の変数に用意して構いません。

出力例

Xが16だった場合、次のような出力が得られます。

16日は月曜日です
#include <stdio.h>

//演習2

//main関数
int main() {

    int iDay = 16;          //任意の日
    int iMod7 = iDay % 7;   //日を7で割ると0-6で各曜日に対応する

    printf("%d日は", iDay);

    //対応する曜日を出力
    switch(iMod7){
        case 0:
            printf("土");
            break;
        case 1:
            printf("日");
            break;
        case 2:
            printf("月");
            break;
        case 3:
            printf("火");
            break;
        case 4:
            printf("水");
            break;
        case 5:
            printf("木");
            break;
        case 6:
            printf("金");
            break;
        default:
            break;
    }

    printf("曜日です\n");

    return 0;
}

演習3

任意の長さの配列を任意の値の要素で初期化する。

このとき、全ての要素を乗算した結果を出力するようなプログラムを作成してください。

ただし、次の2つの条件を満たすようにしてください。

  • 配列の先頭から乗算を行い負の値の場合は乗算をしないこと
  • 0を見つけた場合はそこまでの乗算結果を出力すること

出力例

次のような配列に対してプログラムを実行した場合。

19,23,-1,-43,2,98,0,76,51,24

次のような出力が得られます(最終行以外は無くても良い)。

iTimes = 19
*= 23
iTimes = 437
*= 2
iTimes = 874
*= 98
iTimes = 85652
乗算結果 : 85652
  • 19,23は正の数なので乗算する
  • -1,-43は負の数なので乗算しない
  • 2,98は正の数なので乗算する
  • 0のため乗算を終了して結果を出力
#include <stdio.h>

//演習3

//main関数
int main() {

    //初期値が格納された配列を用意する
    int iData[] = { 19,23,-1,-43,2,98,0,76,51,24 }; //演算対象の配列
    int iDataNum = sizeof(iData) / sizeof(int);     //配列の要素数

    int iNumCnt;                                    //演算を行う要素の添え字
    int iCntData;                                   //iNumCntの要素の値

    int iTimes = 0;                                 //配列の要素の乗算結果

    //配列のすべての要素を取り出す
    for(iNumCnt = 0; iNumCnt < iDataNum; iNumCnt++){
        
        iCntData = iData[iNumCnt];

        //要素が負の値の場合は乗算しない
        if(iCntData < 0){
            continue;
        }
        //要素が0の場合、処理を終了する
        else if(iCntData == 0){
            break;
        }

        //乗算する
        //乗算結果が0の場合はiTimesに値を代入
        if(iTimes == 0){
            iTimes = iCntData;
        }
        else{
            iTimes *= iCntData;

            //計算過程の表示
            printf("*= %d\n", iCntData);
        }

        //計算過程の表示
        printf("iTimes = %d\n", iTimes);
    }

    //結果出力
    printf("乗算結果 : %d\n", iTimes);

    return 0;
}

最後に

どうですか?制御文そのものはとても簡単でしたよね?

慣れればうまく使えるようにもなると思います。

これで「めっちゃ初心者!」状態は卒業です!おめでとうございます!

とはいえまだまだ続くのでお楽しみに!

おすすめの参考書

参考書を数冊載せておきます。人によって好みはあると思うので、書店や試し読みで確認してから購入することをおすすめします。

スッキリわかるC言語入門

新品価格
¥2,970から
(2021/4/25 22:34時点)

たった1日で基本が身に付く! C言語 超入門

新品価格
¥2,266から
(2021/4/25 22:39時点)

新・明解C言語 入門編 (明解シリーズ)

新品価格
¥2,530から
(2021/4/25 22:40時点)

コメント

タイトルとURLをコピーしました