簡単なswitchステートメントが与えられた場合
switch (int)
{
case 1 :
{
printf("1\n");
break;
}
case 2 :
{
printf("2\n");
}
case 3 :
{
printf("3\n");
}
}
ケース2にbreakステートメントがないことは、ケース3のコード内で実行が継続されることを意味します。これは偶然ではありません。そのように設計されました。なぜこの決定が下されたのですか?これにより、ブロックの自動ブレークセマンティクスを使用する場合と比べて、どのような利点が得られますか?その根拠は何ですか?
多くの答えは、break
ステートメントを要求するためのreasonとして機能することに焦点を当てているようです。
Cが設計されたとき、これらの構造がどのように使用されるかについての経験はほとんどなかったため、それは単に間違いだったと思います。
Peter Van der Lindenは、彼の著書「Expert C Programming」で主張しています。
Sun Cコンパイラのソースを分析して、デフォルトのフォールスルーが使用される頻度を確認しました。 Sun ANSI Cコンパイラのフロントエンドには244個のswitchステートメントがあり、それぞれに平均7つのケースがあります。フォールスルーは、これらすべてのケースのわずか3%で発生します。
つまり、通常のスイッチの動作は、間違った 97%の時間です。コンパイラだけではありません-反対に、この分析でフォールスルーが使用された場合、他のソフトウェアよりもコンパイラで頻繁に発生する状況、たとえば、1つまたは2つのオペランドを持つことができる演算子をコンパイルする場合がよくありました:
switch (operator->num_of_operands) { case 2: process_operand( operator->operand_2); /* FALLTHRU */ case 1: process_operand( operator->operand_1); break; }
ケースフォールスルーは欠陥として広く認識されているため、上記の特別なコメント規則もあり、「これは実際にフォールスルーが望ましいケースの3%の1つです」とリントに伝えます。
C#の場合、各caseブロックの最後に明示的なジャンプステートメントを要求することは良いアイデアだと思います(ただし、ステートメントのブロックが1つだけである限り、複数のcaseラベルをスタックできます)。 C#では、1つのケースを別のケースにフォールスルーさせることができます-goto
を使用して次のケースにジャンプすることにより、フォールスルーを明示的に行う必要があります。
あまりにも悪いJavaはCのセマンティクスを破る機会を利用しなかった。
多くの点で、cは標準のアセンブリイディオムへのクリーンなインターフェイスです。ジャンプテーブル駆動のフロー制御を記述する場合、プログラマは「制御構造」から抜け出すかジャンプするかを選択でき、ジャンプアウトには明示的な指示が必要です。
だから、cは同じことをします...
Duffのデバイスを実装するには、明らかに:
dsend(to, from, count)
char *to, *from;
int count;
{
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
ケースが暗黙的に中断するように設計されている場合、フォールスルーすることはできません。
case 0:
case 1:
case 2:
// all do the same thing.
break;
case 3:
case 4:
// do something different.
break;
default:
// something else entirely.
スイッチがすべての場合に暗黙のうちにブレークアウトするように設計されている場合は、選択の余地はありません。スイッチケース構造は、より柔軟になるように設計されました。
Switchステートメントのcaseステートメントは、単なるラベルです。
値をオンにすると、switchステートメントは基本的に、一致する値を持つラベルに対してgotoを実行します。
これは、次のラベルの下のコードへのパススルーを避けるためにブレークが必要であることを意味します。
理由としてwhyこの方法で実装されました-switchステートメントのフォールスルーの性質は、いくつかのシナリオで役立ちます。例えば:
case optionA:
// optionA needs to do its own thing, and also B's thing.
// Fall-through to optionB afterwards.
// Its behaviour is a superset of B's.
case optionB:
// optionB needs to do its own thing
// Its behaviour is a subset of A's.
break;
case optionC:
// optionC is quite independent so it does its own thing.
break;
次のようなことを許可するには:
switch(foo) {
case 1:
/* stuff for case 1 only */
if (0) {
case 2:
/* stuff for case 2 only */
}
/* stuff for cases 1 and 2 */
case 3:
/* stuff for cases 1, 2, and 3 */
}
case
キーワードをgoto
ラベルと考えると、もっと自然になります。
複数のケースで同じコード(または同じコードを順番に)実行する必要がある場合に、コードの重複を排除します。
アセンブリ言語レベルでは、それぞれの間で中断するかどうかは気にしないので、とにかくフォールスルーケースのオーバーヘッドはゼロではありません。
ベクトルの値を構造体に割り当てるケースに偶然出くわしました:データベクトルが構造体のデータメンバーの数よりも短い場合、残りのメンバーがそのまま残るように実行する必要がありましたデフォルト値。その場合、break
を省略することは非常に便利です。
switch (nShorts)
{
case 4: frame.leadV1 = shortArray[3];
case 3: frame.leadIII = shortArray[2];
case 2: frame.leadII = shortArray[1];
case 1: frame.leadI = shortArray[0]; break;
default: TS_ASSERT(false);
}
ここで多くの人が指定しているように、コードの単一ブロックが複数のケースで機能することを許可しています。このshouldは、例で指定する「ケースごとのコードのブロック」よりも、switchステートメントのより一般的なオカレンスです。
フォールスルーのないケースごとのコードブロックがある場合は、おそらくif-elseif-elseブロックの使用を検討する必要があります。