C++ with Windows API講習/while文とfor文

概要

同じような処理を繰り返し行う反復文の、while文とfor文を解説します。

重要語

input_int.hpp

講習担当者が作ったint値入力ライブラリ

winput::input関数

input_int.hppの入力関数

while文

条件式がtrueである限り文を実行する反復文

do-while文

文を実行してから条件式を評価するwhile文

初期化式

for文の初めに1回実行される式

継続式

for文が文を1回実行するごとに実行する式

for文

初期化式と継続式を備えるwhile文のような反復文

ネスト

同じような構造が繰り返し記述されること

必要語

反復文

while文とfor文の総称

制御文

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

制御文や式文などの総称

複合文

複数の文をまとめたもの

条件式

関係演算子と値の並び

論理演算子

&&,||,!

評価

式が計算されること

入力

講習担当者がint値を入力するためのライブラリを用意しました。
導入方法と使い方を解説します。

導入 - 概要

大まかに以下の手順で導入します。
1.input_int.hppからヘッダファイルをダウンロードする
2.プロジェクトディレクトリにinput_int.hppを移動する
3.input_int.hppをVisual Studioに認識させる
4.ヘッダファイルをインクルードして使えるかを確認する

導入 - 1.ダウンロード

まず、ライブラリ本体であるヘッダファイルをinput_int.hppからダウンロードします。
ファイル名は「input_int.hpp」で、「hpp」はC++のヘッダファイルを示す拡張子です。

導入 - 2.移動

もしプロジェクトのディレクトリがわかっている人は飛ばしてしまっても構いません。
まず、「ソリューション エクスプローラー」の「"プロジェクト名"」を右クリックします。
「ソリューション "ソリューション名" (1/1プロジェクト)」とは違うので注意しましょう。
そして、「エクスプローラーで表示」をクリック、プロジェクトディレクトリが表示されます。
最後に、そこへダウンロードした「input_int.hpp」を移動して完了です。

導入 - 3.認識

次に、ダウンロードしたヘッダをVisual Studioのプロジェクトに紐づけましょう。
まず、エクスプローラーとVisual Studioを2つを同時に開きます。
そしてエクスプローラーにあるinput_int.hppのファイルをドラッグして、
「ソリューション エクスプローラー」の「ヘッダー ファイル」にドロップします。
input_int.hppが「ヘッダー ファイル」の一覧に表示されるか確認しましょう。

導入 - 4.インクルード

インクルードして使います。コードの始めに#include "input_int.hpp"と書きます。
標準ライブラリは<>で囲みますが、今回は""で囲みます。
""は今回のように、プロジェクトディレクトリにヘッダを置いた時に使います。
これでVisual Studioが「ファイルを読み込めません」と出力しなければ導入成功です。

導入 - エラーが出たら

「ファイルを読み込めません」というエラーが出てしまうかもしれません。原因としては、
#include "input_int.hpp"#include <input_int.hpp>になっていること、
・input_int.hppが適切な場所に置かれていないこと、などがあります。
input_int.hppは、「framework.h」や「Resource.h」などがあるディレクトリに置きます。

使い方

それでは使い方です。以下のサンプルコードを実行してみましょう。
使い方
#include <Windows.h>
#include <string>
#include "input_int.hpp"

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    //文字列を渡さない場合はNoneが表示される
    int in = winput::input();
    MessageBoxW(NULL, std::to_wstring(in).c_str(), L"1回目入力結果", MB_OK);

    //文字列を渡す場合は渡した文字列が表示される
    in = winput::input(L"2回目");
    MessageBoxW(NULL, std::to_wstring(in).c_str(), L"2回目入力結果", MB_OK);

    return 0;
}

winput::input関数

input_int.hppの入力は、winput::input関数を用います。返り値が入力された数値です。
また、引数に文字列を渡すと入力のウィンドウに表示されます。
ただし引数は省略することが出来、省略するとNone>が表示されます。

入力方法

winput::input関数が実行されるとウィンドウが表示されます。
そのウィンドウに入力出来る欄があるので、そこに数値を入力します。
決定するには、すぐ下にある「ENTER」と書かれたボタンか、Enterキーを押します。

コード「使い方」解説

コード「使い方」の解説です。

winput::input

先述した通りです。1回目は引数を渡さないため、「None」が表示されます。
2回目は引数に「2回目」と渡したため、「2回目」が表示されます。
そして、1回目も2回目も入力された値をメッセージボックスで表示しています。

予告 - input_int.hppについて

input_int.hppは、大方Windows API講習の方で学ぶことを使って実装されています。
ということで、Windows APIのエディットコントロールを扱う回に書いてもらいます。

while文

繰り返し文を実行するための反復文、while文について解説します。
以下のサンプルコードを実行してみましょう。
while文
#include <Windows.h>
#include <string>
#include "input_int.hpp"

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    int sum = 0;
    while (int in = winput::input(L"0を入力すると終了")) {
        sum += in;
        MessageBoxW(NULL, std::to_wstring(sum).c_str(), L"現在の合計", MB_OK);
    }
    MessageBoxW(NULL, (std::to_wstring(sum) + L"です。").c_str(), L"総合計は", MB_OK);

    return 0;
}

while文

while文は、while (条件式) 文の構文を取ります。
条件式がtrueである限り、文を実行し続けるという効果を持ちます。
なおif文の時と同様に、複数の文を扱いたい場合には複合文を用います。

コード「while文」解説

入力を足し続けるプログラムです。while文を用いてループをして実現しています。
while文の条件式で入力をし、それを条件式にしています。
0はfalseに、0以外はtrueに変換されることを利用した条件式です。
0以外が入力されると、while文の条件式がtrueになり、文が実行されループします。
0が入力されるとwhile文の条件式がfalseとなり、ループを抜けることになります。

インデックスループ

N回ループしたい、という時がこの先よく出てきます。
このような時には、整数の変数を用いて条件式を作ります。
以下のサンプルコードを実行してみましょう。
インデックスループ
#include <Windows.h>
#include <string>

#include <algorithm>

#include "input_int.hpp"

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    int min = winput::input(L"範囲1"), max = winput::input(L"範囲2");
    if (min > max) std::swap(min, max);

    std::wstring str = L"";
    {
        int i = min;
        while (i <= max) {
            if (i % 2 == 0) {
                str += std::to_wstring(i) + L" ";
            }

            i++;
        }
    }
    MessageBoxW(NULL, str.c_str(), L"その範囲の2の倍数は", MB_OK);

    return 0;
}

std::swap関数

std::swap関数は、#include <algorithm>と書くと使えるようになる関数です。
組み込み型や標準ライブラリの型において、二つの変数の値を交換する機能を持ちます。

コード「インデックスループ」解説

コード「インデックスループ」の解説です。

概要

2つの整数の値を受け取り、その2つの整数の範囲において昇順に2の倍数を列挙します。

1.minmaxに整数の値を入力させます。min<=maxであることを期待します。
2.minよりもmaxの方が小さな値が入力された場合には、std::swapで交換しています。
3.minからmaxまで、N回ループを回します。Nは、max-min+1回のことです。
4.それぞれについて2の倍数か判定し、2の倍数を文字列に追加していきます。
5.結果を出力します

解説 - 3

別に変数を用意してminで初期化してmaxまで増やしていけばよいでしょう。
+=でも良いですが、インクリメント++を使うとより簡潔に書けます。
インクリメントは、++変数名もしくは変数名++で変数の値を1増やす演算子です。
なお、インクリメントは前者の前置か、後者の後置かで計算結果が異なうことがあります。
ただし、それはインクリメント単体では起こりえないので問題はあまりないでしょう。

解説 - 4

2の倍数かを判定するのは、2で割った余りが0かを判定すれば良いでしょう。
なので、変数名 % 2 == 0になります。文字列には空白区切りで追加しています。

補足

これはインデックスループであるのは確かですが、1番多い書き方とは違う所があります。
普通、初期化式で宣言した変数は0で初期化すること。
条件式初期化式で宣言した変数 < ループしたい回数となること。
の2つです。「なぜ0から始めるのか」というと、「慣習である」というのが1つの理由です。
また、今後扱う機能には0から始めた方が都合がいいことがあることも理由の1つです。
なお条件式の方は、0から始めた時、ループしたい回数を直接書けるのがこうだからです。

for文

for文を使うと、コード「インデックスループ」はもっと簡潔に書くことが出来ます。
コードの内容はそのまま、while文をfor文に書き換えた以下のコードを実行してみましょう。
for文でのインデックスループ
#include <Windows.h>
#include <string>

#include <algorithm>

#include "input_int.hpp"

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    int min = winput::input(L"範囲1"), max = winput::input(L"範囲2");
    if (min > max) std::swap(min, max);

    std::wstring str = L"";
    for (int i = min; i <= max; i++) {
        if (i % 2 == 0) {
            str += std::to_wstring(i) + L" ";
        }
    }
    MessageBoxW(NULL, str.c_str(), L"その範囲の2の倍数は", MB_OK);

    return 0;
}

for文の構造

for文はfor (初期化式; 条件式; 継続式) 文という構文を取ります。
初期化式はfor文に入る前に実行される式ですが、変数を宣言することもできます。
初期化式で宣言した変数は、そのfor文の中でのみ使うことができます。

条件式はwhile文と全く同じで、trueになるとループしを実行します。
そしてfalseになるとループをやめ、を実行するのも終了します。

継続式は、を実行した後に実行される式で、ループ1回ごとに実行する式を書きます。
インデックスループでは、用意した変数をインクリメントすることがほとんどです。

for文の実行

for文の実行についてサンプルコードで細かく見ていきましょう。
for(int i = min; i <= max; i++)ですね。わかりやすく、min=0,max=3にします。
まず、int i = minが実行されます。変数iは0になりました。
次に、i <= maxが実行されます。0 <= 3なのでtrueです。
次に、が実行されます。が、ここはループの処理とは関係ないので割愛します。
次に、i++が実行されます。変数iは1になりました。
次に、i <= maxが実行されます。1 <= 3なのでtrueです。
ここからは同じことの繰り返しなので、省略します。
はい、i++が実行されました。変数iは4になりました。
はい、i <= maxが実行されました。今回の仮定では4 <= 3なのでfalseです。
そしてここで条件式falseになったので実行は終了です。

for文

for文の他の機能についても見ていきましょう。
for文
#include <string>
#include <random>
#include <Windows.h>
#include "input_int.hpp"

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

void set_random(int* lhs, int* rhs, int* answer) {
    (*lhs) = random();
    (*rhs) = random();
    (*answer) = (*lhs) * (*rhs);
    return;
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    int lhs, rhs, answer;
    set_random(&lhs, &rhs, &answer);

    for (std::wstring s;; set_random(&lhs, &rhs, &answer)) {

        s = L"次の答えを入力:"
            + std::to_wstring(lhs)
            + L"*" + std::to_wstring(rhs);

        if (answer == winput::input(s)) {
            MessageBoxW(NULL, L"正解!", L"結果", MB_OK);
        }
        else {
            MessageBoxW(NULL, L"不正解", L"結果", MB_OK);
            break;
        }
    }

    std::wstring s = L"";
    for (int i = 11; i < 17; i++) {
        for (int j = 1;; j++) {
            s += std::to_wstring(i * 100 + j) + L" ";
            if (j < 6) {
                continue;
            }
            else {
                break;
            }
            s = L"ここは実行されません!";
        }
        s += L"\n";
    }
    MessageBoxW(NULL, s.c_str(), L"", MB_OK);

    return 0;
}

break/continue文

反復文ではbreak文に加えて、continue文という文も使うことが出来ます。
break文はswitch文と同様に、実行された時点で反復文を終了させます。
continue文は、実行された時点での一番最後へ処理が飛びます。
すなわち、残りのの実行を飛ばし、条件式の評価へと移ります。

for文の式の省略

for文には、初期化式、条件式、継続式があると説明していました。
これら3つの式は、空文のように何も書かないことで省略することが出来ます。
特筆すべきなのが条件式を省略すると、常にtrueとして実行されることです。
while文では条件式は省略出来ないので、ここが唯一の条件式の違いでしょう。

制御文や複合文のネスト

制御文と複合文は自身の中に、更に制御式や複合文を入れることが出来ます。
このように、同じような構造が繰り返し記述されることをネストと呼びます。
ネストは、条件や処理が複雑にならないように、最小限に留めるのが良いでしょう。
ただ、反復文ではネストした方がコードが短く、わかりやすくできることがしばしばです。
今回ではルームの番号を外側、出席番号を内側で、としたことでforを2回で済ましています。

ネストされた文の実行

文をネストした時も、上から順次実行されていくことには変わりありません。
つまり、反復文では外側の1回のループごとに、内側は始めから最後までループします。
なおbreak文とcontinue文は、効果が与えられる文のうち一番内側の文に対して実行されます。
疑似的にはfor{if{break}}ならfor、for1{for2{}for3{break}}ならfor3に効果が及びます。

コード「for文」解説

コード「for文」の解説です。このコードは長いですが、重要なのはwWinMain関数内だけです。

概要

前半では、まずrandom関数、set_random関数で掛け算の式を用意します。
random関数は-1000から1000までの乱数を発生させる関数ですが、気にしなくてよいです。
set_random関数は、lhsrhsanswerに値をセットする、すなわち式を用意する関数です。
そして、用意した式を表示して正解か不正解かを判定して、不正解なら出題をやめています。
後半では、11R~16Rの1番から5番までを列挙して表のような形式で表示しています。

前半 - set_random関数

lhsleft-hand siderhsright-hand size、つまり右辺と左辺という意味です。
今回は掛け算のみを出すということにしているので、この2つの変数に値をセットしています。
answerは名前通り、式の結果を代入しています。
ポインタで受け取った引数はアスタリスクで間接参照して、渡された変数を操作するのでした。

前半 - for文

このfor文では条件式が省略されています。つまり、break文が実行されるまで止まりません。
今回break文が実行されるのは変数answerと入力値が違う、すなわち回答を間違えた時です。
前述していますが、このbreak文は1番内側のfor文を終了させます。
なおwinput::input関数に問を代入したstd::wstringを引数に渡し、表示させています。

後半 - ネスト

前述した通りfor文をネスト、外側でルームを、内側で出席番号を考えています。
もしこれをルームだけ、出席番号だけfor文にしたらどうなるでしょう。
前者だと同じのfor文を6回、後者だと出席番号の1~6をそれぞれ書かなければいけません。
なお「何度も書く」というのは、変更に弱くなり、保守性が下がるという問題もあります。

後半 - ネストの実行

前述した通り、上から順次実行されます。実行順としては、
外側1回目始まり、i=11  内側1-6回目  外側1回目終わり
外側2回目始まり、i=12  内側1-6回目  外側2回目終わり
...
外側6回目始まり、i==16  内側1-6回目  外側6回目終わり
のような感じになります。

後半 - break/continue文

continu文はの実行を最後まで飛ばし、break文はfor文自体を終了するのでした。
そのため、全く実行されない場所があります。すぐにわかりますね。
また、break文はどちらとも1番内側のfor文を終了させるのでした。
なので、外側のループも同時に終了することはありません。

練習問題

九九表を作成して表示するプログラムを作りましょう。
つまり、以下のような9*9の表を表示してください。横は空白区切りで問題ありません。
1 2 ... 9
2 4 ... 18
... ... ... ...
9 18 ... 81
解答の一部
#include <string>
#include <random>
#include <Windows.h>

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, 
    std::wstring s = L"";

    MessageBoxW(NULL, s.c_str(), L"九九表", MB_OK);

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

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    std::wstring s = L"";

    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            s += std::to_wstring(i * j) + L" ";
        }
        s += L"\n";
    }

    MessageBoxW(NULL, s.c_str(), L"九九表", MB_OK);

    return 0;
}