2014年4月5日土曜日

C/C++ 処理の流れ その2 条件分岐

ユーザーの入力や、様々な要因によって処理の流れを変えたい場合があります。その場合には条件分岐を使用します。

if文


一番主に使われる条件分岐はif文で使用文法は以下です。まずは一番基本的な文法です。条件式が正しい場合(真,trueの場合)、処理1を行い、条件式が違う場合(偽,falseの場合)に処理2を行います。


    if (条件式)
        処理1
    else
        処理2

基本的な文法からelseを省いた形です。条件式の正しい場合に処理1を行います。

    if (条件式)
        処理1

この形はよく使うのですが、安易に使用すると問題になることが多いです。elseの偽の場合の処理が本当に不要か考えたほうが良いです。

条件式の書き方


C/C++言語では0が偽で、0以外が真と見なされます。条件式は比較演算以外でも、どんな演算式でも許されます(例えばif (1)のように数値のみでも大丈夫です)。そのため、以下の2つは間違えやすいので注意が必要です。

    if (a = 10) {
        :
    }

    if (a == 10) {
        :
    }

上側の"="はaに10を代入する演算です。代入演算の結果は代入された値のため演算結果は10です。そのため、このif文の処理は必ず真で実行されます。

下側の"=="はaと10を比較する演算です。比較演算の結果はaの値が10であれば1であり、10でなければ0となります。そのため、このif文の処理はaの値が10であれば真の処理を実行し、10でなければ偽(else)の処理を実行します。

通常は下側の比較演算子を使いますが、間違えないようにするため、以下のように右辺と左辺を逆にする記述を薦める場合もあります(代入演算の場合はエラーになる)。

    if (10 == a) {
        :
    }

しかし、最近のコンパイラでは警告が表示されますし、感覚的にはわかりにくいと思うため、私はお薦めしません。

また条件式には関数を入れることもできますが、&&演算など演算子によっては、関数が実行されないこともあるので、注意が必要です。

処理の書き方


処理の書き方としては分岐後の処理は段を下げて書くのが一般的ですが、段を下げることで処理が変わるわけではありません。それとif分の後に書ける処理は1つのみです。複数の処理を行いたい場合は{}で処理を囲みます。

色々な書き方がありますが、以下のA, B, C, D, E, F, Gの処理を見てください。

    /* A */
    if (a == 10) {
        printf("Hello");
    }
    printf("World");

    /* B */
    if (a == 10)
    {
        printf("Hello");
    }
    printf("World");

    /* C */
    if (a == 10) ;
    { 
        printf("Hello");
    }    
    printf("World");

    /* D */
    if (a == 10) {
        printf("Hello");
    printf("World");
    }

    /* E */
    if (a == 10) 
        printf("Hello");
    printf("World");

    /* F */
    if (a == 10) 
        printf("Hello");
        printf("World");

    /* G */
    if (a == 10) printf("Hello");
    printf("World");

この中で、CとDのみ動作が違い、後のA, B, E, F, Gは同じ動作をします。

Cは条件式の後ろにセミコロン(;)が付いているので、そこで条件が真の時の処理の記述が終了してしまい、"Hello"と"World"は常に表示されます。

Dは条件式が真の時だけ"Hello"と"World"がされます。

その他は条件式が真のときに"Hello"が表示され、"World"は常に表示されます。

Eの書き方は、処理の見やすさとしては問題ないのですが、Fの書き方の問題を起こしやすく、Appleのgotofail問題など、大きな問題となったことがあります。そのため、Eの書き方よりもAのように1つの処理でも{}を使用するAの書き方が奨励されます。

Bの書き方はAの書き方より空白が多く見やすいですが、Cの間違いが起きることがあるため、Aの方が良いという意見があります。

Gの書き方は横に伸びすぎたり、処理部が見にくくなる問題があるかもしれません。

複数の条件式と複雑度


プログラムが大きくなってくると、分岐処理がどんどん多くなっていき、条件分岐の中でさらに条件分岐という形になっています。

    if (条件式1) {
       :
        if (条件式2){
  :
            if (条件式3) {

このようになってくると、プログラムが見にくくなり、複雑度がまし、不具合が多くなることになります。条件分岐は不具合が最も多く出る所と言ってもよいでしょう。

そのために条件分岐を少しでも減らす工夫が必要です。

まず、書き方の工夫ですが、条件により処理を切り替える場合のif文の書き方としては、以下のような書き方があります。

    if (条件式1)
        処理1
    else if (条件式2)
        処理2
    else if (条件式3)
        処理3
         :
         :
    else
    どの条件にも当てはまらない場合の処理

この書き方であれば、ネストが深くなることもありません。

switch文


前述のような else ifのような複数の処理の分岐において、条件式が単純な変数と整数値の一致比較であれば、switch文を使うことができます。switch文の文法は以下です。

    switch (整数の変数){
    case 整数値1:
        処理1
        break;
    case 整数値2:
        処理2
        break;
    case 整数値3:
        処理3
        braek;
         :
         :
    default:
        どの整数値も一致しなかった場合の処理
        break;
    }

整数の変数と整数値1が一致したときに、その処理1が行われます。 break;という行でbreak文がありますが、これはswich文を抜けることを意味します。break文がない場合は、次のcase文の処理も行われます。例えば以下のように、複数の値に一致した場合の処理が同じ場合にbreak文を書かない書き方が使用できます。

    switch (整数の変数){
    case 整数値1:
    case 整数値2:
    case 整数値3:
        処理1,2,3
        braek;
         :
         :
    default:
        どの整数値も一致しなかった場合の処理
        break;
    }

しかし、break文を記述し忘れて問題になることもありますので、注意が必要です。

またdefault:は無くてもエラーにはなりません。しかし、if文においてelseを考えないと問題を起こすことがあるのと同様に、条件に合わなかった部分を考えることは重要ですので、defaultは必ず書くようにした方が良いです。

言語によっては、整数値以外がswitch文で使用できますが、C/C++言語では整数値のみです。


その他の条件分岐 三項演算子を利用する


if文はC/C++言語では文となっていますが、他のプログラミング言語(特に関数型言語と言われる言語)では、式となっていて、条件分岐後の値を返す場合もあります。

C/C++言語でもそのような使い方はできて、三項演算子という演算子があり、以下のような文法です。

    条件式 ? 値1 : 値2

条件式が真の場合には値1を返し、条件式が偽の場合には値2を返します。例えば、以下の2つは同じ意味になります。

    if (a == 0) {
        b = 10;
    } else {
        b = -10;
    }

    b = (a == 0 ? 10 : -10);

if文のように条件式を必ず()で括る必要がないので、注意が必要です。三項演算子全体を()で括っていますが、これは見やすさのためです。

たまに三項演算子の使用をルールで禁止するとこもあるようですが、式であることから色々な使い方ができ、場合によってはプログラムをより簡潔に見やすくすることができます。例えば、以下のような書き方もできます。

    printf(a == 0 ? "Hello" : "World"); 

aが0の場合に"Hello"を表示し、0でない場合に"World"を表示します。

その他の条件分岐 論理演算子(使用しないほうがいい)


論理演算子を使って、条件分岐を行うことも可能です。Perlと言う言語で使われているのを見たことがありますが、C/C++言語では見たことがないです。使わない方がいいでしょう。

    a == 0 || printf("Hello");
    a == 0 && printf("World"); 

上の文は、a == 0が偽のときだけ、||の後のprintfの処理が実行されます。下の文はa == 0が真のときだけ、&&の後のprintfの処理が実行されます。論理演算子の動作を利用したものです。

使うつもりは無くても、条件式の中で使ってしまうこともあるので注意してください。


その他の条件分岐 配列を利用する


配列とはデータの集合のことです。例えば、0〜6の数値を得て、その数値に対する曜日の文字列を返す関数get_week()を作成するとします。

その場合、以下のようにif文を使って書くことができます(さぼって、水曜日までにしています)。

const char *get_week(int num)
{
        if (num == 0) {
                return "Monday";
        } else if (num == 1) {
                return "Tuesday";
        } else if (num == 2) {
                return "Wednesday";
        } else {
                return "";
        }
}

配列を使うと、以下の様に書くこともできます。

const char *get_week(int num)
{
        static const char week[][10]= {"Monday", "Tuesday", "Wednesday" };

        if (num < 0 || num > 2) {
                return "";
        }

        return week[num];
}

この配列を使うということには色々な利点があります。
  1. コード量を少なく、見やすくすることができる。
  2. 関数の配列にすれば処理も自在に変更することができる。
  3. 配列の中身を変更することで、分岐処理自体を変更することができる。 
常に配列が良いとは限りませんが、配列を利用できることは案外と多く、条件分岐を使用せずに、配列で代用できないかを考えることができるようになれると良いのではないかと思います。

その他の条件分岐 多態性を利用する


多態性とは、オブジェクト指向言語などにおいて、プログラミングの型に持たせる性質のことです。ポリモーフィズムや、ポルモルフィズムなど色々と呼ばれています。型によって異なった動作しますので、条件分岐の代わりとして使うことができます。

あまり良い例ではありませんが、以下がC++での多態性の例です。

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

class Iclass {
public:
        virtual void print() const = 0;
};

class Aclass : public Iclass
{
public:
        void print() const {
                printf("Hello");
        }
        int test;
};

class Bclass : public Iclass
{
public:
        void print() const {
                printf("World");
        }
        int test;
};

void time3(const Iclass &ic)
{
        ic.print();
        ic.print();
        ic.print();
}

int main(int argc, char *argv[])
{
        if (argc != 2) {
                return 1;
        }

        if (atoi(argv[1]) == 0) {
                time3(Aclass());
        } else {
                time3(Bclass());
        }
        return 0;
}


time3()関数で、3回print()の関数を呼び出していますが、このとき引数の型がAclassかBclassかで、time3()関数で表示される文字列が"Hello"か"World"かが変わります。

time3()関数内はif文が使われていないことに着目してください。つまり3回print()を実行していますが、3回のif文が必要無いのです。このように分岐を使わず、型によって処理を分けることで、if文の量を減らすことができます。

上記はC++言語ですが、C言語でも関数ポインタなどを利用すれば多態性と同じことをすることができます。

オブジェクト指向言語では、よいプログラムの書き方をパターン化したデザインパターンという設計手法がありますが、これらでは多態性や配列を使用することによってif文があまり無いようになっています。


0 件のコメント:

コメントを投稿