Skip to content

Latest commit

 

History

History
981 lines (755 loc) · 23.1 KB

005-the-restaurant-at-the-end-of-the-branch.md

File metadata and controls

981 lines (755 loc) · 23.1 KB

条件分岐の果てのレストラン

さてC++の勉強に戻ろう。この章では条件分岐について学ぶ。

複合文

条件分岐とループについて学ぶ前に、まず複合文(compound statement)やブロック(block)と呼ばれている、複数の文をひとまとめにする文について学ばなければならない。

C++では(statement)が実行される。については詳しく説明すると長くなるが、';'で区切られたものがだ。

int main()
{
    //
    auto x = 1 + 1 ;
    //
    std::cout << x ;

    // 空文
    // 実は空っぽの文も書ける。
    ;
}

複数の{}で囲むことで、1つの文として扱うことができる。これを複合文という。

int main()
{
    // 複合文開始
    {
        std::cout << "hello\n"s ;
        std::cout << "hello\n"s ;
    } // 複合文終了

    // 別の複合文
    { std::cout << "world\n"s ; }

    // 空の複合文
    { }
}

複合文には';'はいらない。

int main()
{
    // ;はいらない
    { }

    // これは空の複合文に続いて
    // 空文があるだけのコード
    { } ;
}

複合文の中に複合文を書くこともできる。

int main()
{
    {{{}}} ;
}

関数の本体としての一番外側'{}'はこの複合文とは別のものだが、読者はまだ気にする必要はない。

複合文は複数のをひとまとめにして、1つのとして扱えるようにするぐらいの意味しか持っていない。ただし、変数の見え方に影響する。変数は宣言された最も内側の複合文の中でしか使えない。

int main()
{
    auto a = 0 ;

    {
        auto b = 0 ;
        {
            auto c = 0 ;
            // cはここまで使える
        }
        // bはここまで使える
    }
    // aはここまで使える
}

これを専門用語では変数寿命とかブロックスコープ(block-scope)という。

内側のブロックスコープの変数が、外側のブロックスコープの変数と同じ名前を持っていた場合はエラーではない。外側の変数が内側の変数で隠される。

int main()
{
    auto x = 0 ;
    {
        auto x = 1 ;
        {
            auto x = 2 ;
            // 2
            std::cout << x ;
        }
        // 1
        std::cout << x ;
        x = 42 ;
        // 42
        std::cout << x ;
    }
    // 0
    std::cout << x ;
}

慣れないうちは驚くかもしれないが、多くのプログラミング言語はこのような挙動になっているものだ。

条件分岐

すでに読者はさまざまな数値計算を学んだ。読者は12345 + 6789の答えや、8073 * 132 / 5の答えを計算できる上、この2つの答えをさらに掛け合わせた結果だって計算できる。

int main()
{
    auto a = 12345 + 6789 ;
    auto b = 8073 * 132 / 5 ;
    auto sum = a + b ;

    std::cout
        << "a=12345 + 6789=" << a << "\n"s
        << "b=8073 * 132 / 5=" << b << "\n"s
        << "a+b=" << sum << "\n"s ;
}

なるほど、答えがわかった。ところで変数aと変数bはどちらが大きいのだろうか。大きい変数だけ出力したい。この場合は条件分岐を使う。

C++では条件分岐にif文を使う。

int main()
{
    auto a = 12345 + 6789 ;
    auto b = 8073 * 132 / 5 ;


    if ( a < b )
    {
        // bが大きい
        std::cout << b ;
    }
    else
    {
        // aが大きい
        std::cout << a ;
    }
}

if文は以下のように書く。

if ( 条件 )
文1
else2

条件が真(true)のときは文1が実行され、偽(false)のときは文2が実行される。

elseの部分は書かなくてもよい。

if ( 条件 )
文12

その場合、条件が真のときだけ文1が実行される。条件の真偽にかかわらず文2は実行される。

int main()
{
    if ( 2 < 1 )
        std::cout << "sentence 1.\n" ; // 文1
    std::cout << "sentence 2.\n" ; // 文2
}

この例では、21より小さい場合は文1が実行される。文2は必ず実行される。

条件次第で複数の文を実行したい場合、複合文を使う。

int main()
{
    if ( 1 < 2 )
    {
        std::cout << "yes!\n" ;
        std::cout << "yes!\n" ;
    }
}

条件とか真偽についてはとてもとても深い話があるのだが、その解説はあとの章に回すとして、まずは以下の比較演算子を覚えよう。

演算子 意味


a == b abと等しい a != b abと等しくない a < b abより小さい a <= b abより小さい、もしくは等しい a > b abより大きい a >= b abより大きい、もしくは等しい

真(true)というのは、意味が真であるときだ。正しい、成り立つ、正解などと言い換えてもよい。それ以外の場合はすべて偽(false)だ。正しくない、成り立たない、不正解などと言い換えてもいい。

整数や浮動小数点数の場合、話は簡単だ。

int main()
{
    // 1は2より小さいか?
    if ( 1 < 2 )
    {   // 真、お使いのコンピューターは正常です
        std::cout << "Your computer works just fine.\n"s ;
    }
    else
    {
        // 偽、お使いのコンピューターには深刻な問題があります
        std::cout << "Your computer has serious issues.\n"s ;
    }
}

文字列の場合、内容が同じであれば等しい。違うのであれば等しくない。

int main()
{
    auto a = "dog"s ;
    auto b = "dog"s ;
    auto c = "cat"s ;

    if ( a == b )
    {
        std::cout << "a == b\n"s ;
    }
    else
    {
        std::cout << "a != b\n" ;
    }

    if ( a == c )
    {
        std::cout << "a == c\n" ;
    }
    else
    {
        std::cout << "a != c\n" ;
    }
}

では文字列に大小はあるのだろうか。文字列に大小はある。

int main()
{
    auto cat = "cat"s ;
    auto dog = "dog"s ;

    if ( cat < dog )
    {   // 猫は小さい
        std::cout << "cat is smaller.\n"s ;
    }
    else
    {   // 犬は小さい
        std::cout << "dog is smaller.\n"s ;
    }

    auto longcat = "longcat"s ;

    if ( longcat > cat )
    {   // longcatは長い
        std::cout << "Longcat is Looong.\n"s ;
    }
    else
    {
        std::cout << "Longcat isn't that long. Sigh.\n"s ;
    }
}

実行して確かめてみよう。ほとんどの読者の実行環境では以下のようになるはずだ。ほとんどの、というのは、そうではない環境も存在するからだ。読者がそのような稀有な環境を使っている可能性はまずないだろうが。

cat is smaller.
Longcat is Looong.

なるほど。"cat"s"dog"sよりも小さく(?)、"longcat"s"cat"sよりも長い(大きい?)ようだ。なんだかよくわからない結果になった。

これはどういうことなのか。もっと簡単な文字列で試してみよう。

int main()
{
    auto x = ""s ;

    // aとbはどちらが小さいのだろうか?
    if ( "a"s < "b"s )
    {   x = "a"s ; }
    else
    {   x = "b"s ; }
 
    // 小さい方の文字が出力される
    std::cout << x ;
}

これを実行するとaと出力される。すると"a"s"b"sより小さいようだ。

もっと試してみよう。

int main()
{
    auto x = ""s ;
    if ( "aa"s < "ab"s )
    { x = "aa"s ; }
    else
    { x = "ab"s ; }

    // 小さい文字列が出力される
    std::cout << x ;
}

これを実行すると、aaと出力される。すると"aa"s"ab"sより小さいことになる。

文字列の大小比較は文字単位で行われる。まず最初の文字が大小比較される。もし等しい場合は、次の文字が大小比較される。等しくない最初の文字の結果が、文字列の大小比較の結果となる。

条件式

条件とは何だろう

if文の中で書く条件(condition)は、条件式(conditional expression)とも呼ばれている(expression)の一種だ。というのは例えば"1+1"のようなものだ。の中に書くことができ、これを式文(expression statement)という。

int main()
{
    1 + 1 ; // 式文
}

"a==b""a\<b"のような条件なので、として書くことができる。

int main()
{
    1 == 1 ;
    1 < 2 ;
}

C++では多くの式には型がある。たとえば"123"int型で、"123+4"int型だ。

int main()
{
    auto a = 123 ; // int
    auto b = a + 4 ; // int
    auto c = 1.0 ; // double
    auto d = "hello"s ; // std::string
}

とすると、"1==2""3!=3"のような条件式にも型があるのではないか。型があるのであれば変数に入れられるはずだ。試してみよう。

int main()
{
    if (  1 == 1 )
    { std::cout << "1 == 1 is true.\n"s ; }
    else
    { std::cout << "1 == 1 is false.\n"s ; }

    auto x = 1 == 1 ;
    if ( x )
    { std::cout << "1 == 1 is true.\n"s ; }
    else
    { std::cout << "1 == 1 is false.\n"s ; }
}

"if(x)""if(1==1)"と書いた場合と同じように動く。

変数に入れられるのであれば出力もできるのではないだろうか。試してみよう。

int main()
{
    auto a = 1 == 1 ; // 正しい
    auto b = 1 != 1 ; // 間違い
    std::cout << a << "\n"s << b ;
}
1
0

なるほど、条件が正しい場合"1"になり、条件が間違っている場合"0"になるようだ。

ではif文の中に10を入れたらどうなるのだろうか。

// 条件が正しい値だけ出力される。
int main()
{
    if ( 1 ) std::cout << "1\n"s ;
    if ( 0 ) std::cout << "0\n"s ;
    if ( 123 ) std::cout << "123\n"s ;
    if ( -1 ) std::cout << "-1\n"s ;
}

実行結果は以下のようになる。

1
123
-1

この結果を見ると、条件として1, 123, -1は正しく、0は間違っているということになる。ますます訳がわからなくなってきた。

bool型

そろそろ種明かしをしよう。条件式の結果は、bool型という特別な型を持っている。

int main()
{
    auto a = 1 == 1 ; // bool型
    bool A = 1 == 1 ; // 型を書いてもよい
}

int型の変数には整数の値が入る。double型の変数には浮動小数点数の値が入る。std::string型の変数には文字列の値が入る。

すると、bool型の変数にはbool型の値が入る。

bool型には2つの値がある。条件が正しいことを意味するtrueと、条件が間違っていることを意味するfalseだ。

int main()
{
    bool correct = true ;
    bool wrong = false ;
}

bool型にこれ以外の値は存在しない。

bool型の値を正しく出力するには、std::boolalphaを出力する。

int main()
{
    std::cout << std::boolalpha ;
    std::cout << true << "\n"s << false ;
}
true
false

std::boolalpha自体は何も出力をしない。一度std::boolalphaを出力すると、それ以降のbool値がtrue/falseで出力されるようになる。

元に戻すにはstd::noboolalphaを使う。

int main()
{
    std::cout << std::boolalpha ;
    std::cout << true << false ;
    std::cout << std::noboolalpha ;
    std::cout << true << false ;
}

以下のように出力される。

truefalse10

すでに学んだ比較演算子は、正しい場合にbool型の値trueを、間違っている場合にbool型の値falseを返す。

int main()
{
    // true
    bool a = 1 == 1 ;
    // false
    bool b = 1 != 1 ;

    // true
    bool c = 1 < 2 ;
    // false
    bool d = 1 > 2 ;
}

先に説明したif文条件が「正しい」というのはtrueのことで、「間違っている」というのはfalseのことだ。

int main()
{
    // 出力される
    if ( true )
        std::cout << "true\n"s ;

    // 出力されない。
    if ( false )
        std::cout << "false\n"s ; 
}

bool型の演算

bool型にはいくつかの演算が用意されている。

論理否定: operator !

"!a"atrueの場合falseに、falseの場合trueになる。

int main()
{
    std::cout << std::boolalpha ;

    // false
    std::cout << !true << "\n"s ;

    // true
    std::cout << !false << "\n"s ;
}

論理否定演算子を使うと、falseのときのみ実行されてほしい条件分岐が書きやすくなる。

// ロケットが発射可能かどうかを返す関数
bool is_rocket_ready_to_launch()
{
    // まだだよ
    return false ;
}

int main()
{

    // ロケットが発射可能ではないときに実行される
    if ( !is_rocket_ready_to_launch() )
    {   // もうしばらくそのままでお待ちください
        std::cout << "Standby...\n" ;
    }
}

この例では、ロケットが発射可能でない場合のみ、待つようにアナウンスする。

同じように、trueのときに実行されてほしくない条件分岐も書ける。

// ロケットが発射可能かどうかを返す関数
bool is_rocket_ready_to_launch()
{
    // もういいよ
    return true ;
}

int main()
{
    // ロケットが発射可能なときに実行される
    if ( !is_rocket_ready_to_launch() )
    {   // カウントダウン
        std::cout << "3...2...1...Hallelujah!\n"s ;
    }

}

この2つの例では、ロケットの状態が実行すべき条件ではないので、正しく何も出力されない。

同値比較: operator ==, !=

bool型の値の同値比較はわかりやすい。truetrueと等しく、falsefalseと等しく、truefalseは等しくない。

int main()
{
    std::cout << std::boolalpha ;
    auto print = [](auto b)
    { std::cout << b << "\n"s ; } ;

    print( true  == true  ) ; // true
    print( true  == false ) ; // false
    print( false == true  ) ; // false
    print( false == false ) ; // true

    print( true  != true  ) ; // false
    print( true  != false ) ; // true
    print( false != true  ) ; // true
    print( false != false ) ; // false
}

比較演算子の結果はbool値になるということを覚えているだろうか。"1 \< 2"trueになり、"1 \> 2"falseになる。

bool値同士も同値比較ができるということは、"(1 \< 2) == true"のように書くことも可能だということだ。

int main()
{
    bool b = (1 < 2) == true ;
}

"(1\<2)"trueなので、"(1\<2)==true""true==true"と同じ意味になる。この結果はもちろん"true"だ。

論理積: operator &&

"a && b"abがともにtrueのときにtrueとなる。それ以外の場合はfalseとなる。これを論理積という。

表にまとめると以下のようになる。

式 結果


false && false false false && true false true && false false true && true true

さっそく確かめてみよう。

int main()
{
    std::cout << std::boolalpha ;
    auto print = []( auto b )
    { std::cout << b << "\n"s ; } ;

    print( false && false ) ; // false
    print( false && true  ) ; // false
    print( true  && false ) ; // false
    print( true  && true  ) ; // true
}

論理積は、「AかつB」を表現するのに使える。

例えば、人間の体温が平熱かどうかを判断するプログラムを書くとする。36.1℃以上、37.2℃以下を平熱とすると、if文を使って以下のように書くことができる。

int main()
{
    // 体温
    double temperature = 36.6 ;

    // 36.1度以上
    if ( temperature >= 36.1 )
        if ( temperature <= 37.2 )
        { std::cout << "Good.\n"s ; }
        else
        { std::cout << "Bad.\n"s ; }
    else
    { std::cout << "Bad.\n"s ; }
}

このコードは、operator &&を使えば簡潔に書ける。

int main()
{
    double temperature = 36.6 ;

    if ( ( temperature >= 36.1 ) && ( temperature <= 37.2 ) )
    { std::cout << "Good.\n"s ; }
    else
    { std::cout << "Bad.\n"s ; }
}

論理和: operator ||

"a || b"abがともにfalseのときにfalseとなる。それ以外の場合はtrueとなる。これを論理和という。

表にまとめると以下のようになる。

式 結果


false || false false false || true true true || false true true || true true

さっそく確かめてみよう。

int main()
{
    std::cout << std::boolalpha ;
    auto print = []( auto b )
    { std::cout << b << "\n"s ; } ;

    print( false || false ) ; // false
    print( false || true  ) ; // true
    print( true  || false ) ; // true
    print( true  || true  ) ; // true
}

論理和は、「AもしくはB」を表現するのに使える。

例えば、ある遊園地の乗り物には安全上の理由で身長が1.1m未満、あるいは1.9mを超える人は乗れないものとする。この場合、乗り物に乗れる身長かどうかを確かめるコードは、if文を使うと以下のようになる。

int main()
{
    double height = 1.3 ;

    if ( height < 1.1 )
    { std::cout << "No."s ; }
    else if ( height > 1.9 )
    { std::cout << "No."s ; }
    else
    { std::cout << "Yes."s ; }
}

論理和を使うと以下のように簡潔に書ける。

int main()
{
    double height = 1.3 ;

    if ( ( height < 1.1 ) || ( height > 1.9 ) )
    { std::cout << "No."s ; }
    else
    { std::cout << "Yes."s ; }
}

短絡評価

論理積と論理和は短絡評価と呼ばれる特殊な評価が行われる。これは、左から右に最小限の評価をするという意味だ。

論理積では、"a && b"とある場合、abがともにtrueである場合のみ、結果はtrueになる。もし、afalseであった場合、bの結果如何にかかわらず結果はfalseとなるので、bは評価されない。

int main()
{
    auto a = []()
    {
        std::cout << "a\n"s ;
        return false ;
    } ;
    auto b = []()
    {
        std::cout << "b\n"s ;
        return true ;
    } ;

    bool c = a() && b() ;
    std::cout << std::boolalpha << c ; 
}

これを実行すると以下のようになる。

a
false

関数呼び出し"a()"の結果はfalseなので、"b()"は評価されない。評価されないということは関数呼び出しが行われず、当然標準出力も行われない。

同様に、論理和では、"a || b"とある場合、abのどちらか片方でもtrueであれば、結果はtrueとなる。もし、atrueであった場合、bの結果如何にかかわらず結果はtrueとなるので、bは評価されない。

int main()
{
    auto a = []()
    {
        std::cout << "a\n"s ;
        return true ;
    } ;
    auto b = []()
    {
        std::cout << "b\n"s ;
        return false ;
    } ;

    bool c = a() || b() ;
    std::cout << std::boolalpha << c ; 
}

結果、

a
true

"b()"が評価されていないことがわかる。

boolの変換

bool型の値と演算はこれで全部だ。値はtrue/falseの2つのみ。演算は==, !=, !&&||の5つだけだ。

読者の中には納得のいかないものもいるだろう。ちょっと待ってもらいたい。boolの大小比較できないのだろうか。boolの四則演算はできないのか。"if(123)"などと書けてしまうのは何なのか。

好奇心旺盛な読者は本書の解説を待たずしてすでに自分でいろいろとコードを書いて試してしまっていることだろう。

boolの大小比較はどうなるのだろうか。

int main()
{
    std::cout << std::boolalpha ;

    bool b = true < false ;
    std::cout << b ;
}

このコードを実行すると、出力は"false"だ。"true \< false"の結果が"false"だということは、truefalseより大きいということになる。

四則演算はどうか?

int main()
{
    auto print = [](auto x)
    { std::cout << x << "\n"s ; } ;

    print( true  + true  ) ;
    print( true  + false ) ;
    print( false + true  ) ;
    print( false + false ) ;
}

結果、

2
1
1
0

不思議な結果だ。"true+true""2""true+false""1""false+false""0"。これはtrue1false0ならば納得のいく結果だ。大小比較の結果としても矛盾していない。

すでに見たように、std::boolalphaを出力していない状態でboolを出力するとtrue1false0となる。

int main()
{
    std::cout << true << false ;
}

結果、

10

これはbool型整数型が変換されているのだ。

異なる型の値が変換されるというのは、すでに例がある。整数型浮動小数点数型だ。

int main()
{
    // 3
    int i = 3.14 ;
    std::cout << i << "\n"s ;

    // 123.0
    double d = 123 ;
    std::cout << d << "\n"s ;
}

浮動小数点数型整数型に変換できる。その際に小数部は切り捨てられる。整数型浮動小数点数型に変換できる。小数部はない。

これと同じように、bool型整数型と変換ができる。

bool型のtrue整数型に変換すると1になる。false0になる。

int main()
{
    // 1
    int True = true ;
    // 0
    int False = false ;
}

同様に、整数型のゼロをbool型に変換するとfalseになる。非ゼロはtrueになる。

int main()
{
    // false
    bool Zero = 0 ;

    // すべてtrue
    bool One = 1 ;
    bool minus_one = -1 ;
    bool OneTwoThree = 123 ;  
}

したがって、"if (0)""if (false)"と等しく、"if (1)""if(-1)"など非ゼロな値は"if (true)"と等しい。

int main()
{
    // 出力されない
    if ( 0 )
        std::cout << "No output.\n"s ;

    // 出力される
    if ( 1 )
        std::cout << "Output.\n"s ;
}

大小比較は単にboolを整数に変換した結果を比較しているだけだ。"true \< false""1 \< 0"と書くのと同じだ。

int main()
{
    std::cout << std::boolalpha ;

    // 1 < 0
    std::cout << (true < false) ;
}

同様に四則演算もbool型を整数型に変換した上で計算をしているだけだ。"true + true""1 + 1"と書くのと同じだ。

int main()
{
    // 1 + 1
    std::cout << (true + true) ;
}

C++では、bool型整数型の変換は暗黙に行われてしまうので注意が必要だ。