コンテンツにスキップ

C++/制御構造

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++

はじめに

[編集]

制御構造の重要性

[編集]

制御構造はプログラミングの基本であり、プログラムの流れを制御するための枠組みです。これにより、条件に応じた処理の分岐や繰り返し処理が可能になります。制御構造を理解することは、効率的でエラーの少ないプログラムを書くための第一歩です。

C++では、豊富な制御構造が提供されています。基本的な制御構造から、C++特有の高度な機能まで幅広くカバーされています。本章では、以下の制御構造について詳しく解説します。

制御構造の役割
条件分岐
特定の条件に基づいて異なる処理を実行する。
if文
条件に基づいた分岐を行います。
switch文
複数の条件分岐を効率的に処理します。
ループ構造
同じ処理を複数回実行する。
while文
条件が真である間、繰り返し処理を行います。
do-while文
最低1回は実行される繰り返し処理を行います。
for文
繰り返し処理を効率的に行います。
制御の移動
プログラムの特定の部分に直接ジャンプする。
break文
イテレーションの中断。
continue文
次のイテレーションへのスキップ。
goto文
ラベルへの移動。
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問題
C++における「懸垂else問題(Dangling else problem)」は、構文解析やコードの意図の明確化に関連する問題で、特にネストされたif-else文において発生します。この問題は、条件分岐が複雑になると、else句がどのif文に対応するのかが曖昧になる場合に生じます。

以下のコードを見てください:

if (condition1)
    if (condition2)
        // Do something
    else
        // Do something else

この場合、else句がcondition2if文に対応するのか、それともcondition1if文に対応するのかが問題になります。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文に対応するかが明確になります。
  • インデントはコードの可読性を向上させるための視覚的ツールですが、コンパイラはインデントを無視して文法通りにコードを解釈します。
これにより、懸垂else問題を避け、より明確でメンテナブルなコードを書くことができます。

サンプルコードと解説

[編集]

以下に、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が特定の値(value1value2など)と一致する場合に実行されるコードを指定します。
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文を適切に使用することで、特定の値に基づく分岐処理を効率的に行うことができます。また、コードの可読性も向上し、保守性の高いプログラムを書くことができます。

if文とswitch文での初期化

[編集]

C++17から、if文やswitch文で初期化が可能になりました。これにより、条件式内でローカル変数を宣言し、その変数を条件に基づいて利用することができます。これは、GoやRustのように、条件のスコープ内で変数を定義するスタイルに似ています。

以下にC++17での新しい機能の例を示します。

if文での初期化
#include <iostream>

int main() {
    int value = 42;

    if (int result = value * 2; result > 50) {
        std::cout << "Result is greater than 50: " << result << std::endl;
    } else {
        std::cout << "Result is less than or equal to 50: " << result << std::endl;
    }
    
    // resultはこのスコープ外では使用できない
    // std::cout << result; // エラーになる

    return 0;
}
上記の例では、if文の条件式でresultというローカル変数を宣言し、value * 2の結果を代入しています。この変数はif文のブロック内でのみ有効です。
switch文での初期化
同様に、switch文でも初期化が可能です。
#include <iostream>

int main() {
    int value = 1;

    switch (int squared = value * value; squared) {
        case 1:
            std::cout << "Value is 1." << std::endl;
            break;
        case 4:
            std::cout << "Value is 2." << std::endl;
            break;
        default:
            std::cout << "Value is not 1 or 2." << std::endl;
            break;
    }

    // squaredはこのスコープ外では使用できない
    // std::cout << squared; // エラーになる

    return 0;
}
まとめ
この機能は、コードの可読性を高め、スコープを明確にするために非常に役立ちます。特に、条件分岐で使用する変数をその場で定義できるため、無駄な変数を減らすことができます。
この新機能を活用することで、C++のプログラミングがさらに柔軟で直感的になります。

ループ構造

[編集]

ループ構造は、プログラム内で同じコードを繰り返し実行するための基本的な構造です。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文を使用することで、条件に基づいて繰り返し処理を行うことができます。無限ループを適切に制御し、必要に応じてループを終了する手段を提供することが重要です。

スコープ内初期化

[編集]

C++17のスコープ内初期化を使って、while文を以下のように書き換えることができます。

#include <iostream>

auto main() -> int {
    int sum{0};

    std::cout << "Enter numbers to add to the sum. Enter 0 to finish."
              << std::endl;

    while (int number{0}; std::cout << "Enter a number: ", std::cin >> number && number != 0) {
        sum += number;
    }

    std::cout << "The total sum is: " << sum << std::endl;

    return 0;
}
改善点と説明
  • while文の初期化部分でint number{0}を宣言しており、この変数はwhile文のスコープ内で有効です。
  • std::cout << "Enter a number: "がループ条件部分で評価されることで、毎回数値入力のプロンプトが表示されます。
  • 条件部分はstd::cin >> number && number != 0として、0が入力されるまでループを続けるようになっています。

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++ではbreakcontinuegotoなどのキーワードが提供されています。これらを使うことで、ループや条件分岐などの制御構造の中で、特定の条件下で処理を中断したり、次のループに進んだりすることが可能です。

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というラベルにジャンプし、ループを実行しています。

まとめ

[編集]

範囲for文を含め、C++の各制御構造について理解することは、効率的で読みやすいコードを書くために非常に重要です。以下は、制御構造の使い分けと効率的なプログラミングのためのガイドです。

各制御構造の比較と使い分け

[編集]

C++では、複数の制御構造が提供されており、それぞれの用途やメリットが異なります。

  • while文: 条件が真である間繰り返す処理に適しています。ループの反復回数が事前に決まっていない場合に使うと効果的です。
  • do-while文: 少なくとも1回はループを実行したい場合に適しています。条件判定が後置されているため、最初の実行を保証します。
  • for文: 明確な初期化、条件、更新を一つの構文で記述でき、カウンタを使用した反復に適しています。ループの回数が決まっている場合に使うと良いでしょう。
  • 範囲for文: 配列やコンテナの全要素を簡潔に反復処理する場合に便利です。読みやすく、ミスを減らすことができます。C++20以降では、初期化付きの範囲for文も利用でき、スコープの限定に役立ちます。

制御構造を使った効率的なプログラミング

[編集]

効率的なプログラミングのためには、適切な制御構造を選ぶことが重要です。以下のポイントを考慮してください:

  • 範囲for文を使って可読性を向上: 明示的なインデックスを使用する必要がなく、コードがシンプルになります。配列やSTLコンテナで全要素を反復処理する場合は、範囲for文を検討しましょう。
  • 初期化付きの範囲for文でスコープ管理: C++20以降では、初期化をループ内に組み込むことで、変数のスコープを制限し、バグを防ぐことができます。
  • ループ条件を考慮した最適化: while文やfor文では、条件を最適化することでパフォーマンスを向上させることができます。無駄な条件評価を避けるために、条件式を簡潔に保ちましょう。

これらのポイントを活用して、より効率的で可読性の高いプログラムを作成しましょう。

演習問題

[編集]

C++の制御構造を深く理解するために、以下の演習問題に挑戦してみましょう。これらの問題は、基本から応用までをカバーしており、実践的なプログラム作成スキルを高めることができます。

基本的な制御構造を使ったプログラム作成

[編集]
  1. 数値の合計を求めるプログラム: while文を使用して、ユーザーが入力した正の数値を合計し、0が入力されたら終了するプログラムを作成してください。
  2. 九九表の出力: for文を使って九九表を出力するプログラムを作成してください。

条件分岐やループの応用問題

[編集]
  1. 素数判定プログラム: for文とif文を組み合わせて、入力された数が素数かどうかを判定するプログラムを作成してください。
  2. フィボナッチ数列の生成: do-while文を使って、指定された項数分のフィボナッチ数列を出力するプログラムを作成してください。

if constexprや範囲for文を使った問題

[編集]
  1. コンパイル時条件分岐: if constexprを使って、テンプレート関数で型に応じた異なる処理を行うプログラムを作成してください。
  2. 配列要素の合計: 範囲for文を使って、配列内の全要素を合計し、その結果を出力するプログラムを作成してください。
  3. 初期化付き範囲for文: C++20以降を使用し、初期化付きの範囲for文で整数のリストを処理し、偶数のみを出力するプログラムを作成してください。

これらの演習を通じて、制御構造の基礎から応用までを身につけ、効率的なプログラミングスキルを習得しましょう。