C++ on MSVC講習/条件分岐1 - if ( いふ ) 文
あらすじと概要
前回は整数と小数について少し詳細に解説しました。 今回はいかにもなプログラミングが出来るようになる、if文について解説します。
重要語
複合文
複数の文を1つとして扱うことが出来る文
ネスト
ある構造を再帰的に記述すること
スコープ
識別子を使用できる範囲についてのコード上での範囲
ライフタイム
変数自体が使える範囲
true/false
真/偽、あるいは正しい/正しくない
std::boolalpha ( ぶーるあるふぁ )
true/falseを文字列として出力するためのマニピュレータ
==
いこーる、比較演算子の1つ
!=
のっといこーる、比較演算子の1つ
<
しょうなり、比較演算子の1つ
<=
しょうなりいこーる、比較演算子の1つ
>
だいなり、比較演算子の1つ
>=
だいなりいこーる、比較演算子の1つ
!
論理否定演算子
&&
論理積演算子
||
論理和演算子
短絡評価
論理積/論理和演算子で右辺の評価が省略されること
if文
条件によって実行する文を選択できる文
,
コンマ演算子
複合文
if文の前にいくらか説明をしたいので、次のサンプルコードを実行してみてください。
複合文
COPY
#include < iostream>
int main()
{
int a = 1 ;
std:: cout << "1\n"
<< " a " << a << "\n\n" ;
{
std:: cout << "2\n"
<< " a " << a << "\n\n" ;
int a = 2 ;
int b = 4 ;
{
std:: cout << "3\n"
<< " a " << a << "\n"
<< " b " << b << "\n\n" ;
}
std:: cout << "4\n"
<< " a " << a << "\n"
<< " b " << b << "\n\n" ;
}
std:: cout << "5\n"
<< " a " << a << "\n" ;
}
実行結果
COPY
1 a 1 2 a 1 3 a 2 b 4 4 a 2 b 4 5 a 1
解説
少し出力の量が多いので困惑してしまうかもしれません。
複合文
main関数内の{}
が、複数の文を1つとして扱うことが出来る、複合文です。 複合文の中に複数文を書くことが出来、それらをまとめて1つの文と扱えるのです。 複合文は複文やブロックとも呼ばれますが、文であり、複合文はネスト出来ます。 main関数の{}
と複合文は別なものですが、どちらもスコープを形成します。
ネスト
ネストは「入れ子」などとも言われ、ある構造を再帰的に記述することです。 あるいは、あるものの中に、それと似たようなものが記述されている状態のことです。 複合文なら、{ { } }
のように、複合文の中に複合文があることを指します。
スコープ
スコープとは、識別子を使用できる範囲についてのコード上での範囲のことです。 スコープは関数や複合文、他にもいくつかの構文で形成され、ネスト出来ます。 実は、識別子は宣言をすることで、宣言をしたスコープへ導入されていたのです。 そして、導入された識別子は、導入先とネストするスコープで使用できるのです。 サンプルコードだと、変数b
が分かりやすいかと思います。
スコープと識別子
識別子について、同じスコープに同じ識別子を複数導入することは出来ないのです。 これは、同じスコープに同じ識別子が複数あると、どれがどれだか区別できなくなるからです。 ただし、スコープがネストしている場合は、それぞれのスコープで同じ識別子を導入できます。 その時、より外側のスコープの識別子は、より内側のスコープの識別子によって隠されます。 サンプルコードだと、変数a
においてまさにその状態になっています。
スコープと変数のライフタイム
変数の識別子もやはり識別子なので、変数が使用できる範囲はスコープで制限されます。 変数自体が使える範囲のことを変数のライフタイム、あるいは変数のスコープと呼びます。 ライフタイムの始まりは、やはり宣言をした後からということになります。 一方、ライフタイムの終わりは、変数を宣言したスコープの終わりと一致するのです。
解説1 - スコープを考える
以上のことから、今回考えるべきなのは、main関数、main関数の中の複合文、 そして、そのmain関数の中の複合文の複合文の、3つが持つスコープになります。 これら3つのスコープを、便宜的にA
/B
/C
として表すことにします。
解説2 - 変数のライフタイム
A
とB
で、変数a
が2つ、B
で変数b
が宣言されていることがわかるでしょうか。b
については、B
での宣言以降と、C
で使用できることになります。 そして、A
のa
は、A
での宣言以降、B
ではB
のa
が宣言されていない部分で、 一方、B
のa
は、B
での宣言以降とC
で使用できるということになります。 ですから、a
の出力は順番に、1, 1, 2, 2, 1
となるのです。
真偽値とbool
次に、数学でも出てくる真偽の値について解説していきます。
真偽値とbool
COPY
#include < iostream>
#include < string>
int main()
{
std:: string s = "string" ;
std:: cout
<< "1 : " << true << " " << false << "\n\n"
<< std:: boolalpha
<< "2 : " << true << " " << false << "\n"
<< "3 : " << ( bool ) 1 << " " << ( bool ) 0 << "\n"
<< "4 : " << ( int ) true << " " << ( int ) false << "\n\n"
<< "5 : " << ( s == s) << " " << ( s != s) << "\n"
<< "6 : " << ( 0 < 1 ) << " " << ( 1 <= 0 ) << "\n"
<< "7 : " << ( true > false ) << " " << ( false >= true ) << "\n\n"
<< "8 : " << (! true ) << " " << (! false ) << "\n"
<< "9 : " << ( true && true ) << " " << ( true && false ) << " " << ( false && true ) << " " << ( false && false ) << "\n"
<< "10: " << ( true || true ) << " " << ( true || false ) << " " << ( false || true ) << " " << ( false || false ) << "\n"
;
}
実行結果
COPY
1 : 1 0 2 : true false 3 : true false 4 : 1 0 5 : true false 6 : true false 7 : true false 8 : false true 9 : true false false false 10: true true true false
解説
なにやら知らない文字がたくさんありますが、実際は簡単です。
boolとリテラル(1-4, 7-10)
bool ( ぶーる )
というのは、変数の型です。bool
はtrue ( とぅるー )
とfalse ( ふぉるす )
という2つの値のみ取ります。true
が真/false
が偽で、平易に言えば、正しい/正しくないという感じです。bool
は、真偽型ですが、同時に整数型にも分類され、大抵は8bitの整数型です。 なお、理論上は1bitでいいのですが、規格の制限で「大抵8bit」になっています。
bool値と整数小数の関係(1,3-4,7)
実は、C++ではbool値と整数小数は、暗黙の型変換が行われる関係にあります。 boolから変換される時は、false
が0
、true
が1
に変換されます。 逆の場合は、0
がfalse
、非0
がtrue
に変換されます。 この規則は、キャストを行ったときも同様の規則で変換されます。 一部を除き、bool値は計算されるときは数値に変わってしまうことに注意しましょう。
boolとstd::coutとstd::boolalpha ( ぶーるあるふぁ ) (1-10)
std::coutを用いて出力する際、標準だと整数になって出力されてしまいます。 当然、整数に変換される際は暗黙の型変換と同様の規則で整数に変換されます。true
やfalse
という文字列で出力したいときは、std::boolalpha
を使います。std::boolalpha
もマニピュレータで、純粋にこれを出力すればよいです。std::boolalpha
は、#include <iostream>
のみで使用することが出来ます。
等値演算子と比較演算子(5-7)
さて、数学でおなじみの= ≠ < ≦ > ≧
は、== != < <= > >=
と対応します。 これらの演算子の結果は、boolになり、式が正しければtrue
、そうでなければfalse
です。 これらの6種類の演算子は、まとめて比較演算子と呼ばれることがあります。 また、前者2つを等値演算子、後者4つを比較演算子と呼ぶ場合もあります。 それはともかく、代入と==
を間違えないように細心の注意を払いましょう。
比較演算子(上数学記号、読み、下C++記号、意味)
=
いこーる
==
両辺が等しいか
≠
のっといこーる
!=
両辺が等しくないか
<
しょうなり
<
左辺より右辺が大きいか
≦
しょうなりいこーる
<=
左辺より右辺が大きいか、あるいは等しいか
>
だいなり
>
右辺より左辺が大きいか
≧
だいなりいこーる
>=
右辺より左辺が大きいか、あるいは等しいか
文字列に対しての比較演算子(5)
サンプルコードでは、std::stirng
に対しても比較演算子を適用していますが、 実は文字列リテラル同士だと、文字列の内容で比較することは出来ないのです。 なので、文字列の内容を比較したい場合には、右辺左辺どちらかをstd::stirng
にしましょう。std::string
は文字列リテラルをキャストする形式でも変換できるのでそれでもいいでしょう。
論理演算子(8-10)
比較演算子で条件式を立てられますが、組み合わせる時には論理演算子を使います。 論理演算子は! && ||
の3つがあり、それぞれ、論理否定/論理積/論理和演算子と言います。 論理否定は前置の単項演算子なので、! bool値に評価される値
と使います。 論理積と論理和はtrue
とfalse
を1
と0
の計算だとして考えると理解できるでしょう。 なお、その演算結果から、「かつ」/「または」の意味をもち、そう呼ばれることがあります。
論理演算子(上C++記号、意味、下効果)
!
論理否定
true
とfalse
を否定(反転) !true
がfalse
、!false
がtrue
&&
論理積 「かつ」とも
true
とtrue
のみtrue
、それ以外はfalse
||
論理和 「または」とも
false
とfalse
のみfalse
、それ以外はtrue
論理積の演算結果
true && true
true
true && false
false
false && true
false
false && false
false
論理和の演算結果
true && true
true
true && false
true
false && true
true
false && false
false
論理積と論理和の短絡評価(9,10)
論理積と論理和の演算結果を見て勘の鋭い人がもう気付いたかもしれません。 論理和/論理積はそれぞれ、左辺がfalse
/true
だと結果はfalse
/true
であるのです。 右辺に関わらず結果が決まるため、右辺を評価しません。これを短絡評価と言います。
解説
サンプルコードの順に、必要な内容を解説しています。 サンプルコードを上から順に確認して暗算して出力と照らし合わせて下さい。 なお、タイトルにある番号は、出力の番号と対応しています。
if文
それではif文を扱ってみましょう。 注意点があるので、まずそれを確認してからコードを実行してみましょう。
言語規格のバージョンについての注意
C++の規格もバージョンがあり、現在3年ごとに改訂されています。 Visual Studioは2021/7/31現在、C++14/C++17/Working Draftが選べます。 そして、標準設定だとC++14になっているので、C++17に変更してください。プロジェクト>○○のプロパティ>構成プロパティ>C/C++>言語>C++ 言語標準
を確認してISO C++17 標準(/std:c++17)
かそれよりも新しいのを選択します。 これ以降も上記と同様に、最も新しい完全に実装された言語標準を選択してください。
少し詳しい人向け
Visual Studioの設定をあまり変更してないので、cmd.exeとWindows-31Jを使っています。 なので/utf-8
付けたり、system("chcp 65001")
したりしないでください。
if文
COPY
#include < iostream>
#include < string>
int main()
{
// 常に真文が実行される
if ( int num{ 1 }) std:: cout << "数学の定期テスト何点でした?\n" ; else int dummy = 0 ;
if ( int num; ( std:: cin >> num), num < 0 || num > 100 )
{
std:: cout << "受けていないですね?\n" ;
}
else
{
if ( num >= 95 ) /*評価 10 */ std:: cout << "素晴らしい!\n" ;
else if ( num >= 75 ) /*評価 9,8*/ std:: cout << "よくできました。\n" ;
else if ( num >= 65 ) /*評価 7 */ { std:: cout << "よくがんばりました。\n" ; }
else if ( num >= 55 ) /*評価 6 */ { std:: cout << "もうすこしがんばりましょう。\n" ; }
else
{
std:: cout << "中学生ですか?中学生ならyを入力してください。\n" ;
std:: string s; std:: cin >> s;
if ( bool flag{ s == "y" })
{
if ( num >= 45 )
{ /*評価 5 */
std:: cout << "ぎりぎりですね。がんばりましょう。\n" ;
}
else
{ /*赤点 */
std:: cout << "もっとがんばりましょう。\n" ;
}
}
else
{
if ( num >= 45 )
{ /*評価 5 */
std:: cout << "もうすこしがんばりましょう。\n" ;
}
else if ( num >= 35 )
{ /*評価 4 */
std:: cout << "ぎりぎりですね。がんばりましょう。\n" ;
}
else
{ /*赤点 */
std:: cout << "もっとがんばりましょう。\n" ;
}
}
}
}
}
実行結果について
実行例を見ていくのは大変なので、下の表を見て下さい。 なお、54点以下で中学生かどうかのチェックが入ります。 中学生ならy
、高校生ならそれ以外を入力します。
点数と出力の関係(中学生)
100~95, 評価 10
素晴らしい!
94~75, 評価 9~8
よくできました。
74~65, 評価 7
よくがんばりました
64~55, 評価 6
もうすこしがんばりましょう。
54~45, 評価 5
ぎりぎりですね。がんばりましょう。
44~0, 評価 4~0, 赤点
もっとがんばりましょう。
上記以外
受けていないですね?
点数と出力の関係(高校生)
100~95, 評価 10
素晴らしい!
94~75, 評価 9~8
よくできました。
74~65, 評価 7
よくがんばりました
64~45, 評価 6~5
もうすこしがんばりましょう。
44~35, 評価 4
ぎりぎりですね。がんばりましょう。
34~0, 評価 3~0, 赤点
もっとがんばりましょう。
上記以外
受けていないですね?
実行結果例1
COPY
数学の定期テスト何点でした? $ -1 受けていないですね?
実行結果例2
COPY
数学の定期テスト何点でした? $ 100 素晴らしい!
実行結果例3
COPY
数学の定期テスト何点でした? $ 80 よくできました。
実行結果例4
COPY
数学の定期テスト何点でした? $ 70 よくがんばりました。
実行結果例5
COPY
数学の定期テスト何点でした? $ 60 もうすこしがんばりましょう。
実行結果例6
COPY
数学の定期テスト何点でした? $ 50 中学生ですか?中学生ならyを入力してください。 $ y ぎりぎりですね。がんばりましょう。
実行結果例7
COPY
数学の定期テスト何点でした? $ 50 中学生ですか?中学生ならyを入力してください。 $ n もうすこしがんばりましょう。
実行結果例8
COPY
数学の定期テスト何点でした? $ 40 中学生ですか?中学生ならyを入力してください。 $ y もっとがんばりましょう。
実行結果例9
COPY
数学の定期テスト何点でした? $ 40 中学生ですか?中学生ならyを入力してください。 $ n ぎりぎりですね。がんばりましょう。
実行結果例10
COPY
数学の定期テスト何点でした? $ 30 中学生ですか?中学生ならyを入力してください。 $ n もっとがんばりましょう。
解説
解説行きましょう。
if文
if文は、条件
によって、真文
と偽文
の実行を選ぶことが出来ます。 これによって、実行時に実行する内容を変えることが出来るようになります。
if
COPY
if
(
初期化文(opt) 条件 )
真文if
(
初期化文(opt) 条件 )
真文 else
偽文
初期化文
C++17で追加された初期化文では、;
で終わるような1つの文を書くことが出来ます。 具体的には、変数宣言や、式文、即ち式に;
を付けたもの、などが記述できます。 なお、ここで宣言された変数は、if文全体をスコープとして使用することが出来ます。 すなわち、else以下が無ければ真文
まで、あれば偽文
までをスコープに持つことになります。
条件
条件
は、boolに評価される式、又は変数の宣言を書くことが出来ます。 宣言の場合、;
は必要なく、型は配列ではなく、boolに変換できるものになります。 配列は、今後の記事で説明する、変数を複数並べて用意する機能のことです。 そして、初期化は=/{}/={}
による形式のみで、()
の形式は許可されません。 なお、ここで宣言された変数も、初期化文の場合と同様、if文全体をスコープに持ちます。
よくある間違い1
条件
のうち、「boolに評価される式」に間違えやすいところが隠れています。 解説したように、intやdoubleなどの値は暗黙のうちにboolに変換されてしまいます。 例えば、a
をintとした時a == 1
のつもりがa = 1
としたとしましょう この時、a = 1
は1
になったa
がtrue
になり、エラーは発生しません。 このように、意図しない動作になってしまう時があるので注意しましょう。
真文
と偽文
真文
と偽文
は、共に単一の文を必要とし、おおむね複合文を使用します。 また、複合文でなくとも、1文で複合文であるようにスコープを持ちます。
if文の実行のされ方
if文はelse ( えるす ) 以下があるかで実行のされ方が少しだけ変わります。 else以下がが無い場合、条件
がtrueの時に真文
が、falseの時にはif文は終了します。 else以下がある場合は、条件
がtrueの時に真文
が、falseの時には偽文
を実行します。
よくある間違い2
例えば、if(b) a = 1; a = 2
(aはint, bはbool)と書いたとしましょう。 この時、if文に含まれるのはa = 1;
のみで、a = 2;
は含まれません。 つまり、b
の真偽によらず、a = 2;
が実行されてしまうのです。 この観点からも真文
と偽文
には複合文を使用するべきだと言えます。
else-if文
特別に名前を付けるほどではないですが、else-if文という言い方があります。 これは、偽文
にif文を書き、if(){} else if(){}
のような形になる書き方です。偽文
を複合文にし、その中にif文を書く時であれば、if() {} else { if(){} }
のようになるので、それに比べてネストしないため、きれいな形になります。
コンマ演算子
(std::cin >> num), num < 0 || num > 100
の,
は、コンマ演算子です。 これは、左辺を評価し、次に右辺を評価して、右辺の評価結果を、全体の評価結果とします。 今回は、入力して、条件式を立てる、という2つのことを条件
の中でするために使用しました。
練習問題
練習問題になります。
問題文
1行の計算式が与えられるので、その結果を出力してください。 与えられる計算式のパターンと対応する出力は以下の表の通りです。(原文ママ) 出典:EX6 - 電卓をつくろう
制約
COPY
0 <= A,B <= 100 A,Bは整数 opは+
,-
,*
,/
,?
,=
,!
のいずれか
出力
COPY
以下の表に従って出力(ただし、出力の最後に改行が必要)
入力(上)と出力(下)
A + B
A + Bの計算結果を出力
A - B
A - Bの計算結果を出力
A * B
A * Bの計算結果を出力
A / B
A / Bの計算結果を出力、小数点以下切り捨て、ゼロ除算はerror
と出力
A ? B
error
と出力
A = B
error
と出力
A ! B
error
と出力
回答例
回答例
COPY
#include < iostream>
#include < string>
int main()
{
int a, b;
std:: string op;
std:: cin >> a >> op >> b;
if ( op == "+" )
{
std:: cout << a + b;
}
else if ( op == "-" )
{
std:: cout << a - b;
}
else if ( op == "*" )
{
std:: cout << a * b;
}
else if ( op == "/" )
{
if ( b == 0 )
{
std:: cout << "error" ;
}
else
{
std:: cout << a / b;
}
}
else
{
std:: cout << "error" ;
}
std:: cout << "\n" ;
}
参照、出典
参照や出典です
参照
[stmt.pre]
https://timsong-cpp.github.io/cppwp/n4861/stmt.pre
[stmt.select]
https://timsong-cpp.github.io/cppwp/n4861/stmt.select#stmt.if
文 - cppreference.com
https://ja.cppreference.com/w/cpp/language/statements
スコープ - cppreference.com
https://ja.cppreference.com/w/cpp/language/scope
基本型 - cppreference.com
https://ja.cppreference.com/w/cpp/language/types
暗黙の変換 - cppreference.com
https://ja.cppreference.com/w/cpp/language/implicit_conversion
比較演算子 - cppreference.com
https://ja.cppreference.com/w/cpp/language/operator_comparison
論理演算子 - cppreference.com
https://ja.cppreference.com/w/cpp/language/operator_logical
if 文 - cppreference.com
https://ja.cppreference.com/w/cpp/language/if
if文とswitch文の条件式と初期化を分離 - cpprefjp C++日本語リファレンス
https://cpprefjp.github.io/lang/cpp17/selection_statements_with_initializer.html