C言語における制御構造は、プログラムの実行フローを制御するために不可欠です。これらは主に、条件に基づいて異なるコードパスを選択する分岐(選択)構造と、特定の条件が満たされるまでコードブロックを繰り返し実行するループ構造に分類されます。
分岐(選択)構造
if文
if文は、指定された条件が真(非ゼロ)の場合にコードブロックを実行するための基本的な制御構造です。
構文
単一条件:
if (condition) {
// condition が真の場合に実行されるコード
}
if-else:
if (condition) {
// condition が真の場合に実行されるコード
} else {
// condition が偽の場合に実行されるコード
}
if-else if-else (多分岐):
if (condition1) {
// condition1 が真の場合に実行されるコード
} else if (condition2) {
// condition2 が真の場合に実行されるコード
} else {
// 上記のいずれの条件も真でない場合に実行されるコード
}
使用例:年齢に基づく判定
#include <stdio.h>
int main() {
int age;
printf("年齢を入力してください: ");
scanf("%d", &age);
if (age < 18) {
printf("未成年です。\n");
} else if (age <= 30) {
printf("青年です。\n");
} else if (age <= 50) {
printf("中年です。\n");
} else if (age <= 70) {
printf("中老年です。\n");
} else if (age <= 99) {
printf("老年です。\n");
} else {
printf("長寿です。\n");
}
return 0;
}
注意点
- 条件の範囲指定: 範囲を指定する際は、
&&(AND) 演算子を適切に使用することが重要です。例:age >= 18 && age <= 30。 - コードブロック:
if、else、else ifが複数の文を制御する必要がある場合は、必ず波括弧{}で囲みます。 - else の所属:
elseは、最も近くにある未完了のifまたはelse ifに関連付けられます。コードの可読性を高めるために、インデントと波括弧を適切に使用してください。 - 代入と等価比較:
=(代入) と==(等価比較) を混同しないように注意してください。可読性を高めるために、定数を変数と比較する際に5 == ageのように記述することも有効です。
switch文
switch文は、単一の整数式に基づいて複数の分岐を効率的に処理するために使用されます。if-else if-else構造の代替として機能します。
構文
switch (expression) {
case constant_expression1:
// expression が constant_expression1 と一致する場合に実行されるコード
break; // switch 文を終了
case constant_expression2:
// expression が constant_expression2 と一致する場合に実行されるコード
break;
// ... 他の case ラベル
default:
// どの case ラベルとも一致しない場合に実行されるコード (オプション)
}
break文の役割
break文は、switch文の実行を終了し、制御をswitch文の直後に移します。breakがない場合、プログラムは次のcaseラベルに「フォールスルー」し、意図しないコードが実行される可能性があります。
default句
default句は、switch式の値がどのcaseラベルとも一致しない場合に実行されるコードを指定します。これはオプションですが、すべての可能な値を処理するために一般的に使用されます。
使用例:曜日表示
#include <stdio.h>
int main() {
int day;
printf("曜日番号 (1-7) を入力してください: ");
scanf("%d", &day);
switch (day) {
case 1:
printf("月曜日\n");
break;
case 2:
printf("火曜日\n");
break;
case 3:
printf("水曜日\n");
break;
case 4:
printf("木曜日\n");
break;
case 5:
printf("金曜日\n");
break;
case 6:
printf("土曜日\n");
break;
case 7:
printf("日曜日\n");
break;
default:
printf("無効な入力です。\n");
break;
}
return 0;
}
フォールスルーの利用
特定のcaseラベルでbreakを省略することにより、複数のcaseラベルで同じコードを実行させることができます。
#include <stdio.h>
int main() {
int day;
printf("曜日番号 (1-7) を入力してください: ");
scanf("%d", &day);
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5:
printf("平日\n");
break;
case 6:
case 7:
printf("週末\n");
break;
default:
printf("無効な入力です。\n");
break;
}
return 0;
}
ループ構造
whileループ
whileループは、指定された条件が真である間、コードブロックを繰り返し実行します。条件はループの各反復の開始前に評価されます。
構文
while (condition) {
// condition が真である間、繰り返し実行されるコード
}
continue文
continue文は、現在のループ反復を終了し、ループの条件評価に直接進みます。ループ本体の残りのコードはスキップされます。
#include <stdio.h>
int main() {
int i = 0;
while (i < 10) {
i++;
if (i == 4) {
continue; // i が 4 の場合、この反復の printf はスキップされる
}
printf("%d ", i);
}
printf("\n");
return 0;
}
break文
break文は、ループを完全に終了させ、制御をループの直後に移します。whileループでbreakに遭遇すると、ループは直ちに終了します。
#include <stdio.h>
int main() {
int i = 0;
while (i < 10) {
i++;
if (i == 4) {
break; // i が 4 になるとループは終了する
}
printf("%d ", i);
}
printf("\n");
return 0;
}
getcharとputchar
getchar()は標準入力から1文字を読み取り、その文字のASCIIコード値を返します。EOF (End Of File) を返すと、入力の終了またはエラーを示します。
putchar()は標準出力に1文字を出力します。
使用例:数値文字のみをフィルタリング
#include <stdio.h>
int main() {
int ch;
printf("文字を入力してください (Ctrl+Z で終了):\n");
while ((ch = getchar()) != EOF) {
if (ch >= '0' && ch <= '9') {
putchar(ch); // 数字文字のみを出力
}
// 数字以外の文字は無視される
}
printf("\n");
return 0;
}
入力バッファのクリア
scanfなどの関数は、入力の一部を読み取った後、改行文字などの残りを入力バッファに残すことがあります。後続の getchar がこれらの残りの文字を予期せず読み取るのを防ぐために、バッファをクリアする必要があります。
#include <stdio.h>
int main() {
char password[20];
char ch_confirm;
printf("パスワードを入力してください: ");
scanf("%19s", password); // 最大 19 文字読み込み、ヌル終端用にスペースを確保
// 入力バッファに残った改行文字などをクリア
while (getchar() != '\n');
printf("パスワードを確認しますか? (Y/N): ");
ch_confirm = getchar();
if (ch_confirm == 'Y' || ch_confirm == 'y') {
printf("確認成功\n");
} else {
printf("確認失敗\n");
}
return 0;
}
forループ
forループは、初期化、条件、および反復処理(通常はカウンタのインクリメント/デクリメント)を1つのステートメントにまとめた、よりコンパクトなループ構造です。これは、反復回数が事前にわかっている場合に特に便利です。
構文
for (initialization; condition; increment) {
// condition が真である間、繰り返し実行されるコード
}
使用例:1から10までの数値表示
#include <stdio.h>
int main() {
for (int i = 1; i <= 10; i++) {
printf("%d ", i);
}
printf("\n");
return 0;
}
forループにおけるbreakとcontinue
breakとcontinueの動作はwhileループと同様です。breakはループを終了させ、continueは現在の反復をスキップして次の反復に進みます。
#include <stdio.h>
int main() {
// continue の例 (5 をスキップ)
printf("continue の例: ");
for (int i = 1; i <= 10; i++) {
if (i == 5) {
continue;
}
printf("%d ", i);
}
printf("\n");
// break の例 (5 で終了)
printf("break の例: ");
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
printf("%d ", i);
}
printf("\n");
return 0;
}
forループのベストプラクティス
- ループ変数のスコープ: C99以降では、
forループ内でループ変数を宣言できます(例:for (int i = 0; ...)。これにより、変数はループブロック内に限定され、スコープ外での意図しない使用を防ぎます。 - 「左閉右開」区間: 配列を処理する場合、
for (int i = 0; i < array_size; i++)のような「左閉右開」の区間([0, array_size))を使用すると、インデックスの範囲指定が容易になります。
do-whileループ
do-whileループは、whileループに似ていますが、条件がループの最後に評価される点が異なります。これにより、ループ本体は少なくとも1回は実行されます。
構文
do {
// 少なくとも1回は実行されるコード
} while (condition); // セミコロンに注意
breakとcontinue
do-whileループでも、breakはループを終了させ、continueは現在の反復をスキップします。
#include <stdio.h>
int main() {
int i = 0;
do {
if (i == 3) {
// continue の例
i++;
continue; // i が 3 の場合、printf はスキップされる
}
printf("%d ", i);
if (i == 5) {
// break の例
break; // i が 5 になるとループは終了する
}
i++;
} while (i < 10);
printf("\n");
return 0;
}
goto文
goto文は、プログラムの実行を指定されたラベルに無条件にジャンプさせます。一般的には、コードの可読性を低下させるため、使用は推奨されません。しかし、非常に深いネストから一度に抜け出す必要があるような、限られた状況で役立つことがあります。
使用例
#include <stdio.h>
int main() {
int should_error = 0; // エラーフラグ
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
// 何らかの条件でエラーが発生した場合
if (i == 2 && j == 3) {
should_error = 1;
goto error_handler; // エラー処理へジャンプ
}
printf("Processing i=%d, j=%d\n", i, j);
}
}
goto end_program; // エラーなしで正常終了
error_handler:
if (should_error) {
printf("エラーが発生しました。処理を中断します。\n");
}
end_program:
printf("プログラム終了。\n");
return 0;
}
まとめ
- 分岐(
if,switch)とループ(while,for,do-while)は、C言語におけるプログラムフロー制御の基本です。 breakはループまたはswitchを終了させ、continueはループの現在の反復をスキップします。getcharとputcharは、文字単位の入出力に便利ですが、入力バッファの管理に注意が必要です。goto文は、その使用が限定的であり、一般的には避けるべきですが、特定のネスト解除シナリオで役立つ場合があります。