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.min
とmax
に整数の値を入力させます。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
関数は、lhs
、rhs
、answer
に値をセットする、すなわち式を用意する関数です。
そして、用意した式を表示して正解か不正解かを判定して、不正解なら出題をやめています。
後半では、11R~16Rの1番から5番までを列挙して表のような形式で表示しています。
前半 - set_random
関数
lhs
はleft-hand side
、rhs
はright-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;
}