C++/制御構造
はじめに
編集制御構造の重要性
編集制御構造はプログラミングの基本であり、プログラムの流れを制御するための枠組みです。これにより、条件に応じた処理の分岐や繰り返し処理が可能になります。制御構造を理解することは、効率的でエラーの少ないプログラムを書くための第一歩です。
C++では、豊富な制御構造が提供されています。基本的な制御構造から、C++特有の高度な機能まで幅広くカバーされています。本章では、以下の制御構造について詳しく解説します。
- ループ構造
- 同じ処理を複数回実行する。
- C++特有の制御構造
-
- if constexpr
- コンパイル時に条件分岐を行うことができ、コンパイル時の最適化を支援します。C++17で導入されました。
- 範囲for文 (range-based for loop)
- コンテナや配列の全要素に対して処理を行うための簡潔な方法です。C++11で導入されました。
これらの制御構造を使うことで、プログラムの可読性が向上し、効率的なコーディングが可能になります。本章では、それぞれの制御構造の使い方と注意点について具体的な例を交えて解説します。
条件分岐
編集条件分岐はプログラムの制御構造の一つで、特定の条件に基づいて異なる処理を実行することができます。C++では、主にif
文、else
文、else if
文を使用して条件分岐を実現します。
if文
編集if
文は、条件式が真の場合にのみブロック内のコードを実行します。
基本構文
編集if (条件式) 文
複文(ブロック { ... }
)も文であり、条件実行の対象が短文の場合も
if (条件式) { // 条件式が真のときに実行されるコード }
のようにブロックとすることが推奨され、しばしばコーディング規約で規定されます。
例えば、ある変数が10より大きいかどうかを確認するコードは次のようになります。
- コード例
int number = 15; if (number > 10) { std::cout << "Number is greater than 10." << std::endl; }
この場合、number
が10より大きいので、メッセージが表示されます。
else節
編集else
節は、前のif
の条件が偽の場合に実行されるコードブロックを定義します。
if (条件式) { // 条件式が真のときに実行されるコード } else { // 条件式が偽のときに実行されるコード }
else if文
編集else if
文は、複数の条件を順にチェックし、条件が真の場合にのみ対応するコードブロックを実行します。
else if
は実際にはelse
節に別のif
文をネストした形であり、便宜上else if
文と呼んでいます。
if (条件式1) { // 条件式1が真のときに実行されるコード } else if (条件式2) { // 条件式2が真のときに実行されるコード } else { // すべての条件式が偽のときに実行されるコード }
例えば、数値が正、負、ゼロのいずれであるかを判定するコードは次のようになります。
int number = -5; if (number > 0) { std::cout << "Number is positive." << std::endl; } else if (number < 0) { std::cout << "Number is negative." << std::endl; } else { std::cout << "Number is zero." << std::endl; }
この場合、number
は負の数なので、"Number is negative."が表示されます。
ネストしたif文
編集if
文は他のif
文の内部にネスト(入れ子)することができます。これにより、複雑な条件分岐を実現できます。
if (条件式1) { if (条件式2) { // 条件式1および条件式2が真のときに実行されるコード } else { // 条件式1が真で条件式2が偽のときに実行されるコード } } else { // 条件式1が偽のときに実行されるコード }
例えば、数値が正の偶数か正の奇数かを判定するコードは次のようになります。
int number = 8; if (number > 0) { if (number % 2 == 0) { std::cout << "Number is a positive even number." << std::endl; } else { std::cout << "Number is a positive odd number." << std::endl; } } else { std::cout << "Number is not positive." << std::endl; }
この場合、number
は正の偶数なので、"Number is a positive even number."が表示されます。
if-else
文において発生します。この問題は、条件分岐が複雑になると、else
句がどのif
文に対応するのかが曖昧になる場合に生じます。
以下のコードを見てください:
if (condition1) if (condition2) // Do something else // Do something else
この場合、else
句がcondition2
のif
文に対応するのか、それともcondition1
のif
文に対応するのかが問題になります。C++の仕様では、else
は常に直前のif
文に対応します。
- 正しい解釈
- 上述のコードは次のように解釈されます:
if (condition1) { if (condition2) { // Do something } else { // Do something else } }
- 問題の解決策
この問題を解決するためには、コードの意図を明確にするために中括弧 {}
を使ってブロックを明示的に指定します。
- 明示的なブロック構造を使用
if (condition1) { if (condition2) { // Do something } else { // Do something else } }
または、もしelse
が外側のif
文に対応する場合は:
if (condition1) { if (condition2) { // Do something } } else { // Do something else }
とします。
- インデントの役割
インデントはコンパイラの解釈には影響を与えませんがが、コードの可読性を向上させるために重要です。適切なインデントは、他のプログラマがコードの意図を理解しやすくするための視覚的な助けとなります。しかし、インデントだけでは問題を解決できないため、明示的なブロック構造が必要です。
- まとめ
- 懸垂else問題は、ネストされた
if-else
文においてelse
句がどのif
文に対応するのかが曖昧になる問題です。 - 解決策は、ブロックを明示的にするために中括弧
{}
を使用することです。これにより、else
句がどのif
文に対応するかが明確になります。 - インデントはコードの可読性を向上させるための視覚的ツールですが、コンパイラはインデントを無視して文法通りにコードを解釈します。
サンプルコードと解説
編集以下に、if文、else節、else if文、ネストしたif文を含むサンプルコードを示します。
#include <iostream> auto main() -> int { int number; std::cout << "Enter a number: "; std::cin >> number; if (number > 0) { if (number % 2 == 0) { std::cout << "Number is a positive even number." << std::endl; } else { std::cout << "Number is a positive odd number." << std::endl; } } else if (number < 0) { std::cout << "Number is negative." << std::endl; } else { std::cout << "Number is zero." << std::endl; } return 0; }
このプログラムは、ユーザーから入力された数値が正の偶数か正の奇数か、負の数か、ゼロかを判定して適切なメッセージを表示します。
このように、if
文、else
文、else if
文、およびネストしたif
文を組み合わせることで、複雑な条件分岐を実現し、プログラムの制御を柔軟に行うことができます。
if constexpr
編集if constexpr
はC++17で導入された新しい条件分岐の形式で、コンパイル時に条件を評価するために使用されます。この機能は、コンパイル時に分岐を解決できるため、メタプログラミングやテンプレートプログラミングで特に有用です。
コンパイル時条件分岐
編集従来のif
文は、実行時に条件を評価しますが、if constexpr
はコンパイル時に条件を評価します。そのため、if constexpr
を使用することで、コンパイル時に条件に基づいたコードの最適化が可能になります。これにより、不要なコードが排除され、コンパイルエラーも未然に防ぐことができます。
#include <iostream> template <typename T> void foo(T value) { if constexpr (std::is_integral_v<T>) { // Tが整数型の場合の処理 std::cout << "Integer: " << value << std::endl; } else { // Tが整数型でない場合の処理 std::cout << "Non-integer: " << value << std::endl; } }
使用例と解説
編集次に、if constexpr
の具体的な使用例とその解説を示します。
#include <iostream> #include <type_traits> template <typename T> void print_type_info(T value) { if constexpr (std::is_integral_v<T>) { // Tが整数型の場合の処理 std::cout << "Value is an integer: " << value << std::endl; } else if constexpr (std::is_floating_point_v<T>) { // Tが浮動小数点型の場合の処理 std::cout << "Value is a floating-point number: " << value << std::endl; } else { // その他の型の場合の処理 std::cout << "Value is of an unknown type" << std::endl; } } auto main() -> int { print_type_info(42); // 整数型の場合 print_type_info(3.14); // 浮動小数点型の場合 print_type_info("Hello"); // 文字列リテラルの場合 return 0; }
上記の例では、print_type_info
関数が異なる型の引数を受け取ります。if constexpr
によって、引数の型に応じた処理がコンパイル時に決定されます。
std::is_integral_v<T>
が真の場合、整数型に対する処理が実行されます。std::is_floating_point_v<T>
が真の場合、浮動小数点型に対する処理が実行されます。- どちらの条件も満たさない場合は、その他の型に対する処理が実行されます。
このように、if constexpr
を使用することで、コンパイル時に条件を評価し、最適化されたコードを生成することができます。これにより、実行時のオーバーヘッドが減り、プログラムの効率が向上します。また、コンパイル時にエラーを検出できるため、バグの発生を未然に防ぐことができます。
switch文
編集switch文は、変数の値に基づいて複数の分岐を実行するための制御構造です。if文よりも分岐が多い場合や、値に応じて明確に異なる処理を行う場合に使用されます。
基本構文
編集switch文の基本構文は以下の通りです。
switch (variable) { case value1: // variable が value1 の場合に実行されるコード break; case value2: // variable が value2 の場合に実行されるコード break; // 必要に応じて他の case を追加 default: // variable がいずれの case にも一致しない場合に実行されるコード }
caseとdefault
編集- case
- それぞれのcaseキーワードは、
variable
が特定の値(value1
、value2
など)と一致する場合に実行されるコードを指定します。 - default
- defaultキーワードは、
variable
がいずれのcaseとも一致しない場合に実行されるコードを指定します。この部分はオプションですが、全ての可能性をカバーするために使われることが多いです。
breakの重要性
編集switch文内で使用されるbreak
ステートメントは、現在のcaseブロックの終わりを示し、switch文の外へ制御を移します。break
がないと、次のcaseブロックに流れ込み、予期しない動作を引き起こすことがあります。
switch (variable) { case value1: // value1 の場合の処理 break; case value2: // value2 の場合の処理 break; default: // どの case にも一致しない場合の処理 }
サンプルコードと解説
編集以下に具体的なサンプルコードを示します。このコードは、ユーザーの入力に基づいて曜日を表示する例です。
#include <iostream> auto main() -> int { int day; std::cout << "曜日を数字で入力してください (1-7): "; std::cin >> day; switch (day) { case 1: std::cout << "月曜日" << std::endl; break; case 2: std::cout << "火曜日" << std::endl; break; case 3: std::cout << "水曜日" << std::endl; break; case 4: std::cout << "木曜日" << std::endl; break; case 5: std::cout << "金曜日" << std::endl; break; case 6: std::cout << "土曜日" << std::endl; break; case 7: std::cout << "日曜日" << std::endl; break; default: std::cout << "無効な入力です。1から7の数字を入力してください。" << std::endl; break; } return 0; }
このプログラムでは、ユーザーに1から7までの数字を入力させ、その数字に対応する曜日を表示します。switch
文を使うことで、各曜日に対応する処理を簡潔に記述できます。break
ステートメントを使用することで、各caseブロックの後にswitch文を終了させ、次のcaseブロックに流れ込まないようにしています。
範囲を取る case
編集C++では、switch文で複数のcaseをまとめることができるため、範囲を取るような動作を実現できます。これは、いくつかの値が同じ処理を必要とする場合に便利です。
switch (variable) { case 1 ... 3: std::cout << "variable は 1, 2, 3 のいずれかです。" << std::endl; break; case 4 ... 5: std::cout << "variable は 4 または 5 です。" << std::endl; break; default: std::cout << "variable は 1 から 5 の範囲外です。" << std::endl; break; }
この例では、variable
が1、2、または3の場合に同じ処理が実行され、4または5の場合に別の処理が実行されます。これにより、コードがシンプルで読みやすくなります。
enum と switch の組み合わせのメリット
編集列挙型(enum)とswitch文を組み合わせることで、コードの可読性と保守性が向上します。enumを使用することで、意味のある名前で値を管理でき、switch文でそれらの値に基づく処理を記述することができます。
#include <iostream> enum Color { RED, GREEN, BLUE }; auto main() -> int { Color color = GREEN; switch (color) { case RED: std::cout << "赤です。" << std::endl; break; case GREEN: std::cout << "緑です。" << std::endl; break; case BLUE: std::cout << "青です。" << std::endl; break; } return 0; }
この例では、Color
というenum型を定義し、それを使ってswitch文で色に基づく処理を行っています。enumを使うことで、コードがより直感的で理解しやすくなり、定数を使う場合のような魔法の数字(マジックナンバー)を避けることができます。また、default:
を書かないことでswicthのenumに対する全称性を担保することが出来ます。
- メリット
- 可読性の向上
- 意味のある名前を使用することで、コードの意図が明確になります。
- 保守性の向上
- enumの定義を変更するだけで、関連するすべてのコードに反映できます。
- エラーの減少
- 定数の値を直接使用する場合の入力ミスを防げます。
switch
文を適切に使用することで、特定の値に基づく分岐処理を効率的に行うことができます。また、コードの可読性も向上し、保守性の高いプログラムを書くことができます。
ループ構造
編集ループ構造は、プログラム内で同じコードを繰り返し実行するための基本的な構造です。C++では、主にwhile
文、do-while
文、for
文を使用してループを実装します。
while文
編集while
文は、指定された条件が真の間、コードブロックを繰り返し実行するループ構造です。条件が偽になると、ループは終了します。
基本構文
編集while
文の基本的な構文は次のとおりです。
while (条件式) { // 条件式が真の間、繰り返し実行されるコード }
例えば、1から10までの数値を順に表示するプログラムは次のように書けます。
#include <iostream> auto main() -> int { int i{1}; while (i <= 10) { std::cout << i << std::endl; i++; } return 0; }
このプログラムでは、変数i
が1から10までの間、while
ループが繰り返し実行されます。各反復でi
の値が出力され、最後にi
がインクリメントされます。
無限ループの作成
編集条件式が常に真の場合、while
ループは無限に繰り返されます。無限ループは、特定の条件が発生するまで繰り返し処理を行う場合に使用されます。無限ループを作成するには、条件式としてtrue
を指定します。
#include <iostream> auto main() -> int { while (true) { std::cout << "This loop will run forever." << std::endl; } return 0; }
このプログラムは、"This loop will run forever."というメッセージを無限に表示します。無限ループを使用する際は、ループを適切に終了する手段(例えば、break
文)を用意することが重要です。
サンプルコードと解説
編集次に、while
文を使った実用的なサンプルコードを示します。このコードは、ユーザーからの入力を受け取り、その値が0でない限りその数値を合計していきます。
#include <iostream> auto main() -> int { int sum{0}; std::cout << "Enter numbers to add to the sum. Enter 0 to finish." << std::endl; while (true) { int number{0}; std::cout << "Enter a number: "; std::cin >> number; if (number == 0) { break; } sum += number; } std::cout << "The total sum is: " << sum << std::endl; return 0; }
このプログラムでは、ユーザーが0を入力するまで数値の入力を受け付け、その合計を計算します。ユーザーが0を入力するとbreak
文が実行され、while
ループが終了します。最後に、合計が表示されます。
このように、while
文を使用することで、条件に基づいて繰り返し処理を行うことができます。無限ループを適切に制御し、必要に応じてループを終了する手段を提供することが重要です。
do-while文
編集do-while
文は、少なくとも一度はループの内容を実行したい場合に使用されるループ構造です。このループは、最初の反復後に条件を評価します。
基本構文
編集do-while
文の基本構文は以下の通りです:
do { // 実行されるコードブロック } while (条件式);
この構文では、do
キーワードの後にコードブロックが続き、その後にwhile
キーワードと条件式が続きます。条件式が真である限り、コードブロックが繰り返し実行されます。
while文との違い
編集while
文との主な違いは、do-while
文が最初に条件を評価せずにコードブロックを一度実行する点です。つまり、do-while
文は少なくとも一度は必ず実行されます。一方、while
文は条件が真の場合にのみ実行されます。
以下に、while
文とdo-while
文の違いを示す簡単な例を示します。
while
文の例:
#include <iostream> auto main() -> int { int i{0}; while (i > 0) { std::cout << "This will not be printed." << std::endl; } return 0; }
この例では、i
の値が初めから条件を満たさないため、while
ループ内のコードは一度も実行されません。
do-while
文の例:
#include <iostream> auto main() -> int { int i{0}; do { std::cout << "This will be printed once." << std::endl; } while (i > 0); return 0; }
この例では、条件が偽であっても、do-while
ループ内のコードが少なくとも一度は実行されます。
サンプルコードと解説
編集次に、do-while
文の使用例を示します。このプログラムは、ユーザーから正の数値を入力させ、その数値を合計します。ユーザーが0以下の数値を入力した場合にループが終了します。
#include <iostream> auto main() -> int { int sum{0}; std::cout << "Enter positive numbers to add to the sum. Enter 0 or a negative number to finish." << std::endl; do { int number{0}; std::cout << "Enter a number: "; std::cin >> number; if (number > 0) { sum += number; } } while (number > 0); std::cout << "The total sum is: " << sum << std::endl; return 0; }
このプログラムでは、ユーザーが正の数値を入力し続ける限り、その数値が合計に加算されます。ユーザーが0または負の数値を入力すると、ループが終了し、最終的な合計が表示されます。
このように、do-while
文は、条件を評価する前にコードブロックを少なくとも一度実行したい場合に有効です。これは、ユーザー入力を処理する際や初期化処理を行う際によく使用されます。
for文
編集for
文は、反復回数が事前にわかっている場合に使用されるループ構造です。初期化、条件チェック、反復処理を1行で記述できるため、読みやすくコンパクトにループを表現できます。
基本構文
編集for
文の基本構文は以下の通りです:
for (初期化; 条件式; 反復処理) { // 実行されるコードブロック }
各部分の意味は次の通りです:
- 初期化
- ループ開始前に1回だけ実行されるコード(通常はループカウンタの初期化)。
- 条件式
- 各反復の前に評価される条件式。条件式が真である限り、ループが続行されます。
- 反復処理
- 各反復の最後に実行されるコード(通常はループカウンタの更新)。
無限ループの作成
編集for
文で無限ループを作成する場合、条件式を省略します:
for (;;) { // 無限に実行されるコードブロック }
この構文は条件が常に真と評価されるため、無限にループが続きます。
サンプルコードと解説
編集次に、for
文の基本的な使用例を示します。このプログラムは1から10までの数を順に表示します。
#include <iostream> auto main() -> int { for (int i = 1; i <= 10; i++) { std::cout << i << " "; } std::cout << std::endl; return 0; }
この例では、初期化部分でint i = 1
と宣言し、条件式でi <= 10
を評価し、反復処理でi++
を実行しています。ループ内のコードブロックが10回実行され、1から10までの数が表示されます。
範囲for文 (range-based for loop)
編集範囲for文は、配列やコンテナなどの全要素を簡潔に反復処理するためにC++11で導入された機能です。
基本構文
編集範囲for文の基本構文は以下の通りです:
for (要素 : コレクション) { // 各要素に対して実行されるコードブロック }
要素
はコレクション内の各要素を表す変数で、コレクション
は配列やSTLコンテナなどの反復可能なオブジェクトです。
配列やコンテナでの使用例
編集範囲for文を使用すると、配列やコンテナの全要素に対して簡潔に処理を行うことができます。
#include <iostream> #include <vector> auto main() -> int { std::vector<int> numbers = {1, 2, 3, 4, 5}; for (auto number : numbers) { std::cout << number << " "; } std::cout << std::endl; return 0; }
この例では、std::vector<int>
内の全要素を順に反復処理し、各要素を出力しています。範囲for文を使うことで、よりシンプルで読みやすいコードになります。
サンプルコードと解説
編集さらに、配列を範囲for文で処理する例を示します:
#include <iostream> auto main() -> int { int arr[] = {10, 20, 30, 40, 50}; for (auto x : arr) { std::cout << x << " "; } std::cout << std::endl; return 0; }
この例では、配列arr
の全要素を範囲for文で処理し、各要素を出力しています。範囲for文を使うことで、明確かつ簡潔に全要素を処理できます。
STL
編集STL(Standard Template Library)は、C++の標準ライブラリの一部であり、データ構造とアルゴリズムの集合です。STLは効率的かつ汎用的なプログラムを構築するための強力なツールセットを提供します。ここでは、STLの主要な要素であるコンテナとイテレーションについて説明します。
コンテナとイテレーション
編集STLのコンテナは、データの格納および管理を行うデータ構造です。コンテナには、配列、リスト、スタック、キュー、マップなどがあります。これらのコンテナは、データの効率的な操作とアクセスを可能にします。コンテナには主に次のような種類があります。
- シーケンスコンテナ
std::vector
,std::list
,std::deque
など。これらはデータの線形順序を保持します。- アソシエイティブコンテナ
std::map
,std::set
,std::unordered_map
,std::unordered_set
など。これらはキーと値のペアを保持し、効率的な検索をサポートします。- コンテナアダプタ
std::stack
,std::queue
,std::priority_queue
など。これらは特定の操作を効率化するためのラッパーです。
イテレータ
編集イテレータは、コンテナ内の要素を順にアクセスするためのオブジェクトです。イテレータは、ポインタのような振る舞いをし、特定の操作をサポートします。例えば、イテレータを使用すると、コンテナ内の要素を反復処理することができます。
以下に、std::vector
を使用したイテレータの基本的な使用例を示します。
#include <iostream> #include <vector> auto main() -> int() { std::vector<int> numbers = {10, 20, 30, 40, 50}; for (auto it = numbers.begin(); it != numbers.end(); it++) { std::cout << *it << " "; } std::cout << std::endl; return 0; }
この例では、numbers.begin()
はベクターの最初の要素を指すイテレータを返し、numbers.end()
は最後の要素の次を指すイテレータを返します。*it
はイテレータが指している要素の値を取得します。
その他の反復方法
編集STLには、イテレータ以外にも様々な方法でコンテナの要素を反復処理する手段があります。以下に代表的な方法を紹介します。
範囲for文 (range-based for loop)
編集C++11以降では、範囲for文を使用してコンテナの全要素を簡潔に反復処理することができます。
#include <iostream> #include <vector> auto main() -> int() { std::vector<int> numbers = {10, 20, 30, 40, 50}; for (const auto& number : numbers) { std::cout << number << " "; } std::cout << std::endl; return 0; }
std::for_each
編集標準ライブラリのアルゴリズムの一つであるstd::for_each
を使用すると、範囲指定してコンテナの要素を処理することができます。
#include <iostream> #include <vector> #include <algorithm> auto main() -> int() { std::vector<int> numbers = {10, 20, 30, 40, 50}; std::for_each(numbers.begin(), numbers.end(), [](int number) { std::cout << number << " "; }); std::cout << std::endl; return 0; }
この例では、ラムダ関数を使用して各要素を処理しています。std::for_each
は、指定された範囲内の各要素に対して指定された関数を適用します。
以上が、STLのコンテナとイテレーションに関する基本的な説明です。STLを効果的に利用することで、効率的で読みやすいコードを書くことができます。
制御の移動
編集プログラム内で制御の移動を行うために、C++ではbreak
、continue
、goto
などのキーワードが提供されています。これらを使うことで、ループや条件分岐などの制御構造の中で、特定の条件下で処理を中断したり、次のループに進んだりすることが可能です。
break文
編集break
文は、ループやswitch
文内で使用され、その部分の実行を中断し、ループやswitch
文を抜けるために使われます。
- ループの中断
#include <iostream> auto main() -> int { for (int i = 0; i < 5; ++i) { if (i == 3) { break; // iが3のときループを中断 } std::cout << i << " "; } std::cout << std::endl; return 0; }
この例では、i
が3のときにループが中断されます。
continue文
編集continue
文は、ループ内で使用され、その時点での処理を中断して、次の繰り返し処理に進みます。
- 次のループへのスキップ
#include <iostream> auto main() -> int { for (int i = 0; i < 5; ++i) { if (i == 2) { continue; // iが2のとき、次の繰り返し処理へスキップ } std::cout << i << " "; } std::cout << std::endl; return 0; }
この例では、i
が2のときにcontinue
が実行され、その時点の処理が中断され、次のループに進みます。
goto文
編集goto
文は、特定のラベルにジャンプするために使用されます。ただし、goto
文はコードの可読性や保守性を低下させるため、適切に使用する必要があります。
- 基本構文
#include <iostream> auto main() -> int { int i{0}; loop: // ラベル if (i < 5) { std::cout << i << " "; i++; goto loop; // loopにジャンプ } return 0; }
この例では、goto
文を使用してloop
というラベルにジャンプし、ループを実行しています。