C++ with Windows API講習/if文とswitch文

概要

プログラムの流れを分岐させる選択文の、if文やswitch文を解説します。

重要語

値と演算子の並び

選択文

if文とswitch文の総称

反復文

while文とfor文の総称

制御文

選択文や反復文などの総称

式文

式にセミコロンがついたもの

空文

セミコロンのみの文

制御文や式文などの総称

複合文

複数の文をまとめたもの

関係演算子

<,>,<=,>=,==,!=

条件式

関係演算子と値の並び

論理演算子

&&,||,!

評価

式が計算されること

短絡評価

理論積と理論和で右辺が評価されないこと

条件演算子

条件式 ? 値A : 値B

if文

条件式に応じて文を実行する選択文

switch文

整数などに応じて文を順次実行する選択文

フォールスルー

他のラベルを跨ぐこと

break文

選択文や反復文の実行をやめる文

今回の必要語はありません。

式と文

まず、式と文について細かい定義について学びましょう。
以下のサンプルコードを実行してみましょう。
式と文
#include <Windows.h>
#include <string>

int r10() {
    return 10;
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
   int z = 0;
 
 //セミコロンを付けているので純粋な値ではない
    //値と見做せるもの
    0;     //リテラル
    z;     //変数
    r10;   //アドレス
    r10(); //関数呼び出し

 //セミコロンを付けているので実際は式文
    //式と見做せるもの
    z = 10 + r10();

    //複合文
    {
        MessageBoxW(NULL, std::to_wstring(z).c_str(), L"wWinMainのz", MB_OK);

        //複合文のzがwWinMainのzを隠す
        int z = 10;
        MessageBoxW(NULL, std::to_wstring(z).c_str(), L"複合文のz", MB_OK);
    }

    return 0;
}

何気なく使っている式の定義は、値と演算子の並びです。
値には、コード上の値や変数、式や関数呼び出しなどが属します。

C++の文は選択文や反復文、変数宣言、式文、空文などの総称です。
文は実行の順番を指定するもので、セミコロンで区切られていると文です。
選択文は今回のif文とswitch文、反復文は次回のwhile文とfor文です。
式文は式の末尾にセミコロンを付けたものです。
空文はセミコロンのみのことで、その名の通り何も処理されません。

複合文

複数の文を波括弧で囲うと、複数の文を1つの文として扱う複合文になります。
複合文の終わりはセミコロンが不要です。付けると空文が続く事になります。
複合文は複数の文をまとめる機能が主ですが、変数の見え方や寿命に影響します。
変数は宣言された複合文と、その複合文に含まれる複合文の中でしか使用できないのです。

式と文

コード「式と文」の解説です。

値と式

0;0は整数リテラルです。リテラルはソースコード上の値のことです。
z;zは変数名です。直前で宣言したint型の変数zのことなので値です。
r10;r10は関数名です。関数名はアドレスにされるので値です。
r10();r10()は関数呼び出しです。関数は値を返却するので値と見做せます。
z = 10 + r10();z = 10 + r10()は式です。
ただし、セミコロンを付けると式文になるので、 全体としては式文です。

変数z

変数zwWinMain関数と、それに含まれる複合文で宣言されています。
wWinMainの変数zは、複合文で宣言される変数zに隠されます。
すなわち複合文の中での識別子zは、変数z宣言前はwWinMainの変数zを指します。
そして変数zの宣言後には、複合文で宣言した方の変数zを指すことになるのです。

条件式とbool

制御文に入る前に条件式とbool型について学びます。
以下のサンプルコードを実行してみましょう。
条件式とbool
#include <Windows.h>
#include <string>

std::wstring to_wstring(bool b) {
    return b ? L"true" : L"false";
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    bool b = MessageBoxW(NULL, L"こんにちは", L"どうも", MB_YESNO) == IDYES;
    MessageBoxW(NULL, to_wstring(b).c_str(), L"「はい」だったか", MB_OK);

    std::wstring str, str2;
    str = to_wstring(10 < 20) + L", "
        + to_wstring(10 > 20) + L", "
        + to_wstring(10 <= 10) + L", "
        + to_wstring(20 >= 20) + L", "
        + to_wstring(30 == 30) + L", "
        + to_wstring(39 != 39);
    str2 = L"10<10, 10>20, 10<=10, 20>=20, 30==30, 39!=39";
    MessageBoxW(NULL, str.c_str(), str2.c_str(), MB_OK);

    int result
        = MessageBoxW(
            NULL,
            L"「はい」か「いいえ」を押さないとtrue",
            L"ばつ",
            MB_ICONWARNING | MB_YESNOCANCEL
        );
    b = result != IDYES && result != IDNO;
    MessageBoxW(NULL, to_wstring(b).c_str(), L"「はい」か「いいえ」を押さなかったか", MB_OK);

    b = !b;
    MessageBoxW(NULL, to_wstring(b).c_str(), L"論理否定", MB_OK);
    return 0;
}

bool型

bool型は値にfalsetrueを持つ型です。falseを0、trueを1として値を持っています。
falseは偽のことで「正しくない」、trueは真のことで「正しい」という意味を持ちます。
bool型は整数型とほぼ同じもので、ほとんどの場合で1バイトの整数として実装されます。
ただし、どんな整数を代入してもbool型が持つ値はfalsetrueです。

int型とbool型

C言語との互換のため、0はfalse、0以外はtrueに暗黙のうちにキャストされます。
その逆も同様に、falseは0に、 true1に暗黙のうちにキャストされます。

条件式と関係演算子

条件式は式の一種ですが、演算子には関係演算子を用いります。
条件式はbool値を結果として持ちます。
比較できる型の値値A,値Bを用いて、値A 演算子 値Bと用います。
std::wstringは、辞書順での大小が比較されます。

関係演算子一覧

<

右辺が左辺よりも大きいとtrue

>

左辺が右辺よりも大きいとtrue

<=

右辺が左辺と等しいか大きいとtrue

>=

左辺が右辺と等しいか大きいとtrue

==

左辺と右辺が等しいとtrue

!=

左辺と右辺が等しくないとtrue

論理演算子

複数のbool値を組み合わせたり、否定をするときには論理演算子を使います。
&&を論理積演算子、||を論理和演算子、!を論理否定演算子と呼びます。
論理積、論理和演算子はbool値値A,値Bを用いて、値A 演算子 値Bと用います。
論理否定演算子はbool値のを用いて、!値と用います。

論理演算子一覧

&&

両辺がtrueだとtrue

||

どちらか一辺がtrueだとtrue

!

falseならtruetrueならfalse

評価と短絡評価

式や値は評価されて値を持ちます。すなわち、評価されると演算されます。
そして、論理積と論理和演算子は短絡評価が行われることがあります。
これは右辺を評価しなくとも結果が変わらない場合に、右辺を評価しないことです。
すなわち、論理積では左辺がfalse、論理和では左辺がtrueであると短絡評価されます。
短絡評価された時、右辺で変数などに変更を伴う式を書いていても実行されません。

条件演算子

条件演算子は条件式 ? 値A : 値Bという構文で、条件式と値を二つ取ります。
値A,値Bは、条件式がtrueであると値Aが、falseであると値Bが選択されます。
そして、選択された値が条件演算子を用いた式の値になります。
ほとんどの場合、値A,値Bは同じ型の値を用いります。
違う型の場合、変換によって同じ型に出来ないとエラーになります。

コード「条件式とbool」解説

コード「条件式とbool」の解説です。

to_wstring関数

std::to_wstring関数と別に、bool値を文字列に変換するto_wstring関数を定義しています。
条件演算子を用いて、bool値がfalseならL"false"を、trueならL"true"を選択します。
選択した文字列は、std::wstringに変換されて返却されます。
なお、std::to_wstringではboolは用意されておらず、整数として変換されてしまいます。
すなわち、false0true1に変換されてしまうということです。

1、2個目のメッセージボックス

メッセージボックスを表示し、その返り値によって「はい」が押されたかを表示しています。
返り値とIDYESが等しいかを判定し、変数bに代入します。
そして、to_wstring関数を用いてstd::wstringにして表示しています。
なお、MB_YESNOのメッセージボックスの返り値は、IDYESIDNOです。

3個目のメッセージボックス

全ての関係演算子を用いた条件式の結果をまとめて表示しています。
どの関係演算子も数学と似ているので期待通りになったと思います。
また、std::wstringは辞書順で比較されるのでした。

4、5、6個目のメッセージボックス

4個目のメッセージボックスの値をresultに代入しています。
そして、resultIDYESでも、IDNOでもなければtrueという条件式を立てています。
条件式の結果は変数bへ代入し、それを5個目のメッセージボックスで表示しています。
更に、変数bの値を否定した結果を6個目のメッセージボックスで表示しています。
bool値を否定すると、falseならtruetrueならfalseになるのでした。

if文とswitch文

それでは、if文とswitch文を学んでいきます。以下のコードを実行してみましょう。
if文とswitch文
#include <Windows.h>
#include <string>

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    if (true)
        MessageBoxW(NULL, L"trueなので実行される", L"if文", MB_OK);

    if (MessageBoxW(NULL, L"こんにちは", L"どうも", MB_YESNO) == IDYES) {
        MessageBoxW(NULL, L"はいが押されました", L"結果 - 文A", MB_OK);
    }
    else {
        MessageBoxW(NULL, L"いいえが押されました", L"結果 - 文B", MB_OK);
    }

    int result = MessageBoxW(NULL, L"something", L"switch", MB_CANCELTRYCONTINUE);

    switch (result) {
    case IDCANCEL:
        MessageBoxW(NULL, L"が押されました", L"キャンセル", MB_OK);
        break;
    case IDTRYAGAIN: {
        std::wstring str = L"が押されました";
        MessageBoxW(NULL, str.c_str(), L"再実行", MB_OK);
        break;
    }
    default:
        MessageBoxW(NULL, L"が押されました", L"続行", MB_OK);
        break;
    }

    return 0;
}

if文

if文は、if (条件式) 文Aもしくはif (条件式) 文A else 文Bという構文です。
なお、後者の構文をif-else文と呼び、区別することがあります。
条件式trueであれば文Aが、falseでかつif-else文であれば文Bが実行されます。
ただし、文A,文Bどちらも1文のみ許容されるので、複数の文を扱うときは複合文を使います。

補足 - if文のネスト

また、当然文A,文Bにif文を入れる、すなわちネストをすることも出来ます。
よって、if (条件式1) 文A1 else if (条件式2) 文A2などというような形も出来ます。
この形だと条件式1falseの時に、else ifのif文へ実行が進みます。
なお、else ifという記述を構文と見做すことがあり、else-if文と言います。

コラム - if文の条件式の落とし穴

if文の条件式に、==を使った条件式を指定するときは注意が必要です。
なぜなら、===とタイプミスし、代入にしてもintなどではエラーが出ないからです。
というのも、前述したようにintとboolは暗黙のうちに変換されてしまうからです。

switch文

switch文はswitch (ラベル) { 処理部 }という構文です。
ラベルには整数などが指定できます。Windows APIの定数も実体は整数なので指定できます。
処理部では、caseラベルとdefaultラベルを用いて分岐をします。

switch文の処理部

caseラベルはcase ラベル:、defaultラベルはdefault:という構文を用います。
ラベルに指定した値と対応するcaseラベルがあるとcaseラベルに処理が移ります。
対応するcaseラベルが無く、defaultラベルがある場合はdefaultラベルに処理が移ります。
いずれかのラベルに飛んだ後は、ラベル以降の文を順次実行していきます。

フォールスルーとbreak文

switch文で気を付けたいのが、順次実行はラベルを超えても続くことです。
つまり、他の条件で実行したい文をも実行してしまうということです。
これをフォールスルーと言います。フォールスルーを防ぐには、break文を記述します。
break文はbreak;という構文で、順次実行をやめ、switch文から抜けます。

コード「if文とswitch文」解説

コード「if文とswitch文」の解説です。

if文、if-else文

1個目のif文は条件式にtrueを指定しているので、文Aは常に実行されます。
2個目のif文は、if-else文です。条件式は「メッセージボックスの返り値がIDYESか」です。
すなわち、メッセージボックスで「はい」を押すと文A、「いいえ」だと文Bが実行されます。

switch文

swtich文のラベルにはメッセージボックスの返り値を代入した、変数labelを指定しています。
メッセージボックスのスタイルにはMB_CANCELTRYCONTINUEを指定しています。
そのため、変数labelが取る値はIDCANCELIDTRYAGAINIDCONTINUEのいずれかです。
今回は、IDCANCELIDTRYAGAINはcaseラベルで、IDCONTINUEはdefaultラベルで対応します。
また今回は、フォールスルーは必要ないのでbreak文を忘れないようにします。

switch文と変数

swtich文の中では、原則として変数を使用することが出来ません。
ただし、caseラベルやdefaultラベルを跨がない複合文では、変数を使用出来ます。
今回は、case IDTRYAGAIN:に複合文を置き、std::wstringを宣言しています。

コラム - switch文の中で変数が使えないわけ

前述した通り、原則としてswitch文の中では変数を使用することが出来ません。
これは、switch文はcaseラベルかdefaultラベルに処理が飛ぶという処理に起因します。
すなわち、処理が飛んだ際に変数の宣言が飛ばされてしまう可能性があるのです。
それによって、変数を宣言していないのに破棄をすることになるのでエラーになるのです。

練習問題

Windows APIにはPOINT構造体が用意されています。
POINT構造体はLONGという型の変数をx,yと2つ持っています。
LONGは、int型と同様に整数を扱うlong型のエイリアスです。

POINT構造体変数のペアa,bx,yについて、同一の座標か判定してください。
関数に切り出しても、wWinMain関数に直接書いても問題ありません。

また、0から5までの値を発生させるrandom関数が用意されています。
random関数の返り値に対応する表のメッセージを表示してください。

対応表

タイトル

本文

1

せかいせいふく

2

しぼう

3

にっぽん

4

いみふめい

default

ないです
解答の一部
#include <Windows.h>
#include <string>

#include <random>

/*POINT構造体の定義**********
* typedef struct tagPOINT *
* {                       *
*    LONG  x;             *
*    LONG  y;             *
* } POINT;                *
**************************/

int random() {
    std::random_device seed_gen;
    std::default_random_engine engine(seed_gen());
    return std::uniform_int_distribution<>(0, 4)(engine);
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    POINT a{ 50,100 }, b{ 10,50 }, x{ 10,20 }, y{ 10,20 };

    int random_number = random();

    return 0;
}
解答例
練習問題解答
#include <Windows.h>
#include <string>

#include <random>

/*POINT構造体の定義**********
* typedef struct tagPOINT *
* {                       *
*    LONG  x;             *
*    LONG  y;             *
* } POINT;                *
**************************/

int random() {
    std::random_device seed_gen;
    std::default_random_engine engine(seed_gen());
    return std::uniform_int_distribution<>(0, 4)(engine);
}

//変数名の表示は問題文に記載がないのでする必要はありません
void is_same(POINT* a, POINT* b, std::wstring* str) {
    if (a->x == b->x && a->y == b->y) {
        MessageBoxW(NULL, L"等しいです", str->c_str(), MB_OK);
    }
    else {
        MessageBoxW(NULL, L"等しくないです", str->c_str(), MB_OK);
    }
    return;
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    POINT a{ 50,100 }, b{ 10,50 }, x{ 10,20 }, y{ 10,20 };

    is_same(&a, &b, &(std::wstring)L"aとbは");
    is_same(&x, &y, &(std::wstring)L"xとyは");

    int random_number = random();
    switch (random_number) {
    case 1:
        MessageBoxW(NULL, L"せかいせいふく", L"1", MB_OK);
        break;
    case 2:
        MessageBoxW(NULL, L"しぼう", L"2", MB_OK);
        break;
    case 3:
        MessageBoxW(NULL, L"にっぽん", L"3", MB_OK);
        break;
    case 4:
        MessageBoxW(NULL, L"いみふめい", L"4", MB_OK);
        break;
    default:
        MessageBoxW(NULL, L"ないです", L"default", MB_OK);
        break;
    }

    return 0;
}