C言語/制御文
条件分岐[編集]
「明日、晴れたら、畑を耕そう。雨が降ったら、本を読もう。」のように、何かの条件によって処理が分かれることを条件分岐といいます。
C言語では条件分岐の為に、選択文が用意されています。
選択文[編集]
選択文( Selection statements ) は、制御式の値に応じて、一連の文の中から選択します [1]。 選択文は、そのスコープが周囲のブロックのスコープの厳密なサブセットであるブロックです。 関連するサブステートメントもまた、選択ステートメントのスコープの厳密なサブセットをスコープとするブロックです。
if文[編集]
具体例[編集]
if文( if statement )は、最も単純な選択文です。
たとえば、プログラムでは「もし変数aが4だったら、変数gには15を代入しなさい」は、
if (a == 4) g = 15;
と記述します。
if (a == 4)
と書かれた場合、もしaが 4 なら条件式の値は 1,a が4でなければ0です。 if 文は条件式が 0 でなければ、続く文を実行し、0 であれば実行しません。
- if文の例
#include <stdio.h> int main(void) { int a = 4; printf("aは%dです。\n", a); if (a == 4) { int g = 15; printf("gは%dです。\n", g); } }
- 実行結果
aは4です。 gは15です。
このように、if文の条件式(上記のプログラムの場合は a==4
)が満たされるときには中カッコ{}の内容が実行されます。
なお、条件式を書くときの等号は、==
になります。条件式における等号は、=
ではありません(C言語では、ひとつだけのイコール記号=
は代入演算子です。)。
もし、条件式を変更すると、・・・
#include <stdio.h> int main(void) { int a = 6; printf("aは%dです。\n", a); if (a == 4) { int g = 15; printf("gは%dです。\n", g); } }
- 実行結果
aは6です。
if文の外の文は実行されますが、if文の内側は実行されません。
よくある間違い[編集]
- ==演算子と=演算子の誤用
if文でよくある間違いの1つに、 ==演算子の代わりに=演算子を誤って用いることがあります。 =演算子は左辺に右辺を代入した後その値を返すため、右辺が0の場合は偽となり、右辺が0以外の場合は真となります。 コンパイルエラーとはなりませんが、予期しない結果を引き起こします[2]。
if (i = 0) printf("iは0");
上の例では、式「i=0」の評価結果はiの値によらず偽となってprintf関数の文は常に実行されません。
- 誤った;の付加
if文でよくある間違いの1つに、if文の後に誤った;を付加することがあります。
if (i == 0); printf("iは0");
上の例では、if文の式が真の時に実行される文がなく、printf関数の文は常に実行されます[3]。
if文の書式[編集]
if文には次の2通りの形式があります。
- if形式
if ( 式 ) 文
- if-else形式
if ( 式 ) 文1 else 文2
どちらの形式でも、文1は、式の比較が0に等しくない場合に実行されます。 if-else形式では、式の比較値が0に等しい場合、文2が実行されます。
- 制約事項
- if文の制御式は、スカラー型でなければなりません。
- スカラ型
- 算術演算型とポインタ型の総称
- アグリゲート型
- 配列型と構造体型の総称
- 懸垂if
if文もまた文であるため、上の2つの形式を組み合わせて次のように連続して記述することもできます。
if ( 式 ) 文 else if ( 式 ) 文 else if ( 式 ) 文 ︙ else 文
elseは、構文で許可されている場合、字句的に最も近い先行詞と関連付けられます。
if文に限りませんが、文はブロック(複合文; Compound statement )であってもかまいません。
- if文の使用例
#include <stdio.h> int main(void) { printf("数値を入力してください。:\n"); int i; if (scanf("%d", &i) == EOF) { printf("End of File に達しました。\n"); return 1; } if (i == 0) printf("入力値は0\n"); else if (i == 1) printf("入力値は1\n"); else printf("入力値は0,1以外\n"); }
- まとめ
- if文とは、選択文の内の1つであり、式(制御式)の論理値に従って、指定する文(複文)を実行するかどうか選択します。
- if文には、if形式、if-else形式の2通りの形式があり、またそれらを組み合わせてもよい。
Visual C/C++ では、scanf が「非推奨とマークされている関数」にあたり、設定によってはビルドに失敗します。⇒ コンパイラの警告 (レベル 3) C4996
これを回避するには、ソースコードに #pragma warning(disable:4996)
を補う方法もありますが、
ここでは scanfの境界検査インターフェース版のscanf_sを使った実装を紹介します[4](scanf_sの3つめの引数 1は、記憶するデータの個数で、第1引数がスカラオブジェクトを指している場合、それは1要素の配列であるとみなされます。[5]なので第3引数の値は 1 です。[6])
- 境界検査インターフェース版
#include <stdio.h> int main(void) { printf("数値を入力してください。:"); int i; if (scanf_s("%d", &i, 1) == EOF) { printf("End of File に達しました。\n"); return 1; } if (i == 0) printf("入力値は0\n"); else if (i == 1) printf("入力値は1\n"); else printf("入力値は0,1以外\n"); }
scanf_s の3つ目の引数 1 は、記憶するデータの個数で、第1引数がスカラオブジェクトを指している場合、それは1要素の配列であるとみなされます。
Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合 |
Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合は、
を無効にします。 |
if文のネスト[編集]
if文の中にif文を入れることを、ネストまたは入れ子という。
- if文のネストの使用例
#include <stdio.h> int main(void) { printf("数値を入力してください。:"); int i; if (scanf("%d", &i) == EOF) { printf("End of File に達しました。\n"); return 1; } if (0 <= i) if (i <= 10) printf("入力値は0以上10以下"); }
- if文のネストの使用例(境界チェックインタフェース版)
#include <stdio.h> int main(void) { printf("数値を入力してください。:"); int i; if (scanf_s("%d", &i) == EOF) { printf("End of File に達しました。\n"); return 1; } if (0 <= i) if (i <= 10) printf("入力値は0以上10以下\n"); }
なお、上の入れ子のifの例は &&演算子 を用いて、次のように簡潔に書き換えることができます。
- ネストしたif文を&&演算子で置き換えます。
#include <stdio.h> int main(void) { printf("数値を入力してください。:"); int i; if (scanf("%d", &i) == EOF) { printf("End of File に達しました。\n"); return 1; } if (0 <= i && i <= 10) printf("入力値は0以上10以下"); }
if文での文字列の比較[編集]
C言語のif文で、ある文字列変数が、指定する文字列に等しいかどうかを判定するには、==
ではなく、strcmp
を使う。
strcmpを使うために #include <string.h>
で string.h をインクルードします。
strcmp
関数は文字列どうしを比較して、一致したときに(1ではなく)0を返す標準ライブラリ関数です。
- コード例
#include <stdio.h> #include <string.h> int main(void) { const char a[] = "taro"; if (strcmp(a, "taro") == 0) { puts("taroだった"); } else { puts("taroではない"); } }
- 実行結果
taroだった
C言語では文字列は第一級オブジェクトではない |
C言語では、Fortran のように配列全体のコピー(=配列の代入)や四則演算はできません。 このことを以って、配列はC言語の第一級オブジェクトではないと言われます。 文字列も文字の配列(に番兵として '\0' で終端したもの)なので、配列と同様に文字列も第一級オブジェクトではありません。 文字列の代入や比較、結合には <string.h> で定義された、標準ライブラリ関数[7]を使う必要があります。 |
switch文[編集]
基本[編集]
switch文とbreak 文とを使って、複数個の「場合」のある場合分けができます。
たとえば
- a==1なら、文m1を実行
- a==2なら、文m2を実行
- a==3なら、文m3を実行
- a==4なら、文m4を実行
(以下略)
のような場合に、switch文とbreak 文で、場合分けをできます。
コードはイメージ的に言うと、
(イメージ)
switch(a) { case 1: 文m1 ; break ; case 2: 文m2 ; break ; case 3: 文m3 ; break ; case 4: 文m4 ; break ;
(以下略)
のような記法になります。
まず、実行したい文の前に、それぞれcase文でラベルをつけます。
switch文に遭遇したときに、どのcaseに移動するかを決めます。
C言語の仕組みとして、もしbreakがないとそのまま次の文を実行します。
つまり、
switch {
- case 1: 文m1 ;
- case 2: 文m2 ;
- case 3: 文m3 ;
- case 4: 文m4 ;
(以下略)
というコードなら、もしa=2なら、実行されるのは、文m2だけでなく、さらに文m3や文m4も実行してしまいます。
このような仕組み(つまりbreak 文がないかぎり、次の関数を実行してしまう)のことをフォールスルー( fall through ) と呼びます。
フォールスルー |
ほかのプログラム言語では、switch文においてフォールスルーが廃止されている場合もあります。 たとえばGoogleの開発した「Go言語」というプログラム言語では、原則的にフォールスルーは行わないようになっています。 例外的に現在実行中のcaseの下にある次のcaseを実行したい場合にだけ「fallthrough」というキーワードを付け加えるという仕組みをとっています(2022年10月時点のC言語には「fallthrough」キーワードはありません[8])。 C言語を知りGo言語やSwift言語を学ぶ人は、switch文がディフォルトではフォールスルーしないことに気がつかづ混乱することがあります(これらの新興言語では、ループの中のswitch文のcase節でbreakするとループから抜けてしまう)。 Rubyではこの混乱を避けるためswitch/case文ではなく、case/when(then)文を用意し転換教育がスムースに行われるよう配慮されています。 |
switch文とは、選択文の内の1つであり、整数式(制御式)の値に従って、指定するいくつかの文からいずれかを実行するかどうか選択します。
switch文には、制御式と、ブロック(スイッチ本体)の中に、任意の個数のcaseラベルと、高々1つのdefaultラベルが含まれます。
switch文は、制御式の値と等しい整数定数式の値を持つcaseラベルへ制御を移します。
等しい値を持つcaseラベルがなければ、defaultラベルへ制御を移します。
defaultラベルもなければ、ブロックの次の文に制御を移します。
break 文はswitchブロックの次の文に制御を移します。 同一のswitch文のブロックの中に、同一の整数定数式の値を持つcaseラベルがあってはなりません。
switch文の記述は次のようになっています。
- switch-case-...-default文
- 形式
switch (整数式) { case 整数定数式: 文 break; ︙ default: 文; }
- switch文の使用例
#include <stdio.h> int main(void) { printf("一桁の数値を入力してください。:"); int i; if (scanf("%d", &i) == EOF) { printf("End of File に達しました。\n"); return 1; } switch (i) { case 2: case 3: case 5: case 7: printf("入力値は一桁の素数\n"); break; default: printf("入力値は一桁の素数ではない\n"); break; } }
繰返し文[編集]
繰返し文( iteration statement ; より一般的には反復文)は、制御式( controlling expression )の0との比較が一致するまで、ループ本体( loop body )と呼ばれる文を繰返し実行します[9]。 繰返し文はブロックで、そのスコープは、そのブロックを囲むブロックのスコープの狭義の部分集合( strict subset )です。 ループ本体もまた、繰返し文のスコープの狭義の部分集合をスコープとするブロックです。 繰返し文は、その制御式が定数式ではなく、本体、制御式、(for文の場合)式-3で以下の操作が行われていない場合、実装上は終了するとみなすことができます。
構文
iteration-statement: while ( expression ) statement do statement while ( expression ) ; for ( expressionopt ; expressionopt ; expressionopt ) statement for ( declaration expressionopt ; expressionopt ) statement
※最後のfor文の構文は ; が不足しているように見えますが、declaration(宣言)は文で末尾に ; を含むので不足してはいません。
while文[編集]
while文( while statment )では、制御式の評価は、ループ本体の各実行前に行われます[10]。
- 形式
while ( 式 ) 文
- while文の使用例
#include <stdio.h> int main(void) { int i = 0; while (i < 5) { printf("%d ", i); i++; } }
- 実行結果
0 1 2 3 4
do-while文[編集]
do文( do statement )では、制御式の評価は、ループ本体の各実行後に行われます[11]。
- 形式
do 文 while ( 式 ) ;
- do文の使用例
#include <stdio.h> int main(void) { int i = 0; do { printf("%d ", i); i++; } while (i < 5); }
- 実行結果
0 1 2 3 4
for文[編集]
for文( for statement )
- 文
for (句-1; 式-2; 式-3) 文
は次のように動作します[12]。
- 式-2は、ループ本体の各実行前に評価される制御式です。
- 式-3は、ループ本体の各実行後にvoid式として評価されます。
- 句-1が宣言の場合、宣言で宣言された識別子のスコープは、宣言の残りの部分と、他の2つの式を含むループ全体で、制御式の最初の評価の前に実行順に到達します[13]。
- 句-1が式の場合、制御する式の最初の評価の前にvoid式として評価されます。
- 句-1と式-3の両方が省略可能です。
- 省略された式-2は、非ゼロの定数によって置き換えます。
- for文の使用例
#include <stdio.h> int main(void) { for (int i = 0; i < 5; i++) printf("%d ", i); }
- 実行結果
0 1 2 3 4
for文とスコープ[編集]
先の「for文の使用例」の変数 i のスコープは
for (int i = 0; i < 5; i++) printf("%d ", i);
- の2行です。
- これはfor文の実行文が単文であっても複文であってもかわりません。
for文のネスト[編集]
for文の中にfor文を入れることを、ネストまたは入れ子という。
- for文のネストの使用例。九九を表示します。
#include <stdio.h> int main(void) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { printf("%2d ", i * j); } printf("\n"); } return 0; }
無限ループ[編集]
無限ループとは、文の実行が無限に繰り返されることです。
while文 | do文 | for文 |
---|---|---|
while (1)
文
|
do
文
while (1);
|
for (;;)
文
|
いずれのコードも、継続条件は常に非ゼロの整数(あるいは、非ゼロの整数とみなされます)ので文の実行を無限に繰返します。 無限ループを用いる場合、後述する break br文を用いるなどして適切にループから脱出できるようにする必要があります。
- 無限ループを使った例
#include <stdio.h> int main(void) { for (;;) { printf("ループの中。\n"); char ans; printf("ループを脱出しますか? 「はい」y /「いいえ」N\n "); if (scanf("%c", &ans) == EOF) { printf("End of File に達しました。\n"); return 1; } if (ans == 'y') { printf("ループを脱出します。\n"); break; } } printf("ループの外です。\n"); }
- ※註 過去の版では、scanf の戻り値をチェックしていませんでしたが EOF を返す可能性があり、これを無視すると、それこそ無限ループになってしまうので、戻り値も必ずチェックしましょう。また、この部分は return 文による脱出の例にもなっています。
- 境界検査インターフェース版 無限ループ
#include <stdio.h> int main(void) { for (;;) { printf("ループの中。\n"); char ans; printf("ループを脱出しますか? 「はい」y /「いいえ」N\n "); if (scanf_s("%c", &ans, 1) == EOF) { printf("End of File に達しました。\n"); return 1; } if (ans == 'y') { printf("ループを脱出します。\n"); break; } } printf("ループの外です。\n"); }
- ※註 過去の版では、scanf_s の戻り値をチェックしていませんでしたが EOF を返す可能性があり、これを無視すると、それこそ無限ループになってしまうので、戻り値も必ずチェックしましょう。また、この部分は return 文による脱出の例にもなっています。
このプログラムを終了するには、y(Yの小文字)を入力してエンターキーを押します。入力する「y」は直接入力モード(Windowsの場合は半角英数)にしてください。
なお、文字変数の中身を示す「'y'」のように、文字リテラルは ' (シングルクォーテーションマーク)で囲みます。文字列リテラルを囲む " (ダブルクォーテーションマーク)と間違えないうようにしましょう[14]。
分岐文[編集]
分岐文( jump statements )は、無条件に別の場所にジャンプします[15]。
構文
jump-statement: goto identifier ; continue ; break ; return expressionopt ;
goto文[編集]
goto文( goto statement )は、包含する関数内の、名前の付いたラベルを先頭に持つ文に無条件にジャンプします[16]。 goto文の識別子は、包含する関数のどこかにあるラベルを指し示さなければなりません[16]。 goto文は、可変的に変更された型を持つ識別子のスコープ外から、その識別子のスコープ内にジャンプしてはなりません[16]
- goto文の形式
goto 識別子 ;
- ラベル文の形式
識別子 :
- 例
#include <stdio.h> int main(void) { int x, y; for (y = 1; y <= 9; ++y) { for (x = 1; x <= 9; ++x) { if (y * x >= 50) goto exit_loop; } } exit_loop: printf("九九の表で、最初に50以上になるのは%d×%d。\n", y, x); }
goto文の使用は一般的に推奨されせん。 |
コードの構造の把握が困難なるなどの理由で、goto文の使用は一般的に推奨されません。 goto文を使用する主な理由としては、入れ子になった複数のスイッチやループから抜けるため(大域脱出)があります。 大域脱出は、return文や、setjmpとlongjmpを使うなど、他の方法でも実現できます。 また、フローチャートからプログラムをコーディングしようとするとフロー線はgoto文そのものであるため、while文・switch文やfor文に落ちないことがあります。 その様な時は、フローチャートにプログラムを無理に合わせるのではなく、ロジックを構造構文で置換えてフローチャートを直すことも考慮すべきです[17]。 上の例であれば、
のように置換え可能です。 |
continue文[編集]
continue文( continue statement )は、それを囲む最小の反復文のループ継続部分、すなわち、ループ本体の終わりにジャンプする[18][19]。
- 制約事項
- continue文は、ループ本体の中にのみ現れることが許されます。
- コード例
while (/* ... */) { /* ... */ continue; /* ... */ contin: }
do { /* ... */ continue; /* ... */ contin:; } while (/* ... */);
for (/* ... */) { /* ... */ continue; /* ... */ contin: }
- 上のコード例での continue は、goto continと同等です[20]。
- 形式
continue;
- continue文の使用例
#include <stdio.h> int main(void) { // 1から14までの整数の中で、3の倍数以外を表示します。 for (int i = 1; i <= 14; i++) { if (i % 3 == 0) { continue; } printf("%d \n", i); } }
- 実行結果
1 2 4 5 7 8 10 11 13 14
上の実行結果では、3の倍数の「3」と「6」と「9」と「12」が抜けています。
このように、continue 文は、そのループ内の以降の処理をスキップしつつも、カウントを1回ぶん、進めます。
break文[編集]
break文( break statement)は、最小のswitch文または反復文を囲むものの実行を終了させます[21][22]。 break文は、switch本体またはloop本体の中以外に現れてはいけません。
break文の記述は次のようになっています。
- 形式
break;
- 例
#include <stdio.h> #include <math.h> int main(void) { while (1) { double d; printf("正の数を入力してください。(0以下で終了):"); if (scanf("%lf", &d) == EOF) { printf("End of File に達しました。\n"); return 1; } if (d <= 0) break; printf("%fの平方根は%f。\n", d, sqrt(d)); } }
for文の中のif文のbreak[編集]
for (・・・) {
for (・・・) {
for (・・・) {
if (・・・) {
break;
}
/* ・・・ */
}
break_point:
}
}
kこのように、for文の中にif文があり、if文のブロックの中にbreak;がある場合、一番内側のfor文の終わりに処理が移ります(ここでは、ラベル break_point の位置)。 for文が2つ以上ある場合、break 文で複数のfor文を一度に抜け出すことは不可能です。
return文[編集]
return文( return statement )は、現在の関数の実行を終了し、呼び出し元に制御を返します[23] [24]。 関数はいくつでもreturn文を持つことができます[23]。 式を持つreturn文が実行されると、その式の値が関数呼び出し式の値として呼び出し元に返されます。式の型がその関数の戻り値の型と異なる場合、その値は関数の戻り値の型を持つオブジェクトに代入されたように変換されます[23]。 式を持つreturn文は、戻り値の型がvoidである関数の中に現れてはなりません。式を伴わないreturn文は、戻り値の型がvoidである関数の中でのみ現れます[23]。
- 形式
return 式;
- 例
#include <stdio.h> //整数1234を返す関数function int function() { return 1234; } int main(void) { int i = function(); //関数functionを呼び、返却値をiに代入します。 printf("iの値は%d。", i); }
脚註[編集]
- ^ N1570 Committee Draft — April 12, 2011 ISO/IEC 9899:201x. ISO/IEC. p. 148, §6.8.4 Selection statements .
- ^ clang や gcc であれば、コマンドラインオプション -Wparentheses で警告されるようになります。
- ^ clang では、コマンドラインオプション -Wempty-body で警告されるようになります。
- ^ 逆に、BSDのlibcやglibcには scanf_s のような境界チェックインタフェース(C11 Annex K)は用意されていません。
- ^ N1570 Committee Draft — April 12, 2011 ISO/IEC 9899:201x. ISO/IEC. p. 592, §K.3.5.3.2 The fscanf_s function . "If the first argument points to a scalar object, it is considered to be an array of one element"
- ^ 過去の版で 10 とされていましたが、正しくは 1 です。
- ^ 過去の編集で、標準ライブラリ関数を誤って組み込み関数と表記していましたが、C言語に組み込み関数はありません。
- ^ C++には fallthrough というアトリビュートが有り、ISO/IEC/JTC1/SC22/WG/14 に N2408 The fallthrough attributeとして提案され、C23に追加される予定です。
- ^ N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 125, §6.8.5 Iteration statements .
- ^ N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 125, §6.8.5.1 The while statement .
- ^ N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 125, §6.8.5.2 The do statement .
- ^ N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 125, §6.8.5.3 The for statement .
- ^ 句-1で、宣言が行えるようになったのはC99からです。
- ^ 生徒に教えていると、" であるべきところを と書いているパターンのほうが多く、また発見が遅れます。
- ^ N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 126, §6.8.6 The jump statements .
- ^ 16.0 16.1 16.2 N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 126, §6.8.6.1 The goto statement .
- ^ フローチャートの「構造化されていない」ことによる欠点を補うため、いくつかの構造化チャート(Structure chart)が考案されましたが、
- チャートを書くよりも構造化されたプログラミング言語で書くほうが速い
- データの通用範囲(スコープ)をうまくチャートに表せない
- 抽象度が低い
- ^ N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 126, §6.8.6.2 The continue statement .
- ^ 『JISX3010:2003』p.104「6.8.6.2 continue文」
- ^ 2番目の例のcontin:ラベルに続くのは、NULL文です。1つ目と3つ目の例のnull文は、ラベルによって暗示されてます。
- ^ N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 127, §6.8.6.3 The break statement .
- ^ 『JISX3010:2003』p.105「6.8.6.3 break文」
- ^ 23.0 23.1 23.2 23.3 N2596 working draft — December 11, 2020 ISO/IEC 9899:202x (E). ISO/IEC JTC1/SC22/WG14. p. 127, §6.8.6.4 The return statement .
- ^ 『JISX3010:2003』p.103「6.8.6.4 return文」
参考文献[編集]
- 国際標準化機構/国際電気標準会議 ISO/IEC 9899:2018(en) Information technology — Programming languages — C(2018-07-05)
- 日本工業標準調査会(当時、現:日本産業標準調査会)『JISX3010 プログラム言語C』2003年12月20日改正