スイッチでブレークを使用する必要があるのはなぜですか?


74

各ステートメントでswitch(多くの言語の)構造を使用する必要breakがあると誰が決定しましたか(また、どの概念に基づいていましたか)?

なぜ次のように書かなければならないのですか:

switch(a)
{
    case 1:
        result = 'one';
        break;
    case 2:
        result = 'two';
        break;
    default:
        result = 'not determined';
        break;
}

(PHPとJSでこれに気づいた;これを使用する他の多くの言語がおそらくあります)

switchがの代替である場合if、なぜと同じ構成を使用できないのifですか?すなわち:

switch(a)
{
    case 1:
    {
        result = 'one';
    }
    case 2:
    {
        result = 'two';
    }
    default:
    {
        result = 'not determined';
    }
}

現在breakのブロックに続くブロックの実行を妨げると言われています。しかし、誰かが本当に現在のブロックと後続のブロックを実行する必要がある状況に遭遇しますか?しなかった。私にとっては、break常にそこにいます。すべてのブロックで。すべてのコードで。


1
ほとんどの回答が指摘しているように、これは複数の条件が同じ「then」ブロックを経由する「フォールスルー」に関連しています。ただし、これは言語に依存します。たとえば、RPGはCASEステートメントを巨大なif / elseifブロックと同等に扱います。
時計じかけミューズ

34
これは、Cデザイナーが下した決定ではなく(多くの決定と同様、アセンブリからCへの移行、およびプログラミングを容易にするためではなく、翻訳を元に戻すために行われました)、残念ながら継承されました他のCベースの言語。単純なルール「常に一般的なケースをデフォルトにする!!」を使用する 、最も一般的なケースであるため、デフォルトの動作は、フォールスルー用の別のキーワードを使用して、ブレークする必要があります。
BlueRaja-ダニーPflughoeft

4
フォールスルーをいくつかの機会に使用しましたが、おそらく当時の私の好みの言語でのswitchステートメント(C)はそれを回避するように設計されていた可能性があります。例えば、私はやっcase 'a': case 'A': case 'b': case 'B'たことがありますが、主に私はできないためですcase in [ 'a', 'A', 'b', 'B' ]。少し良い質問は、私の現在の優先言語(C#)では、ブレークは 必須であり暗黙のフォールスルーはありません。忘却breakは構文エラーです...:\
KutuluMike

1
実際のコードでのフォールスルーは、パーサーの実装でよく見られます。case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
OrangeDog

1
@ BlueRaja-DannyPflughoeft:Cは簡単にコンパイルできるようにも設計されています。「あればジャンプを放ちbreakより実装するために非常にシンプルなルールでどこにでも存在している」「場合は、ジャンプを放出しないでくださいfallthroughに存在していますswitch」。
ジョンパーディ

回答:


95

Cはswitchこの形式のステートメントを持つ最初の言語の1つであり、他のすべての主要言語はそこから継承し、主にデフォルトごとにCセマンティクスを維持することを選択しました-彼らはそれを変更する利点を考えなかったか、判断しました誰もが慣れ親しんだ振る舞いを維持することほど重要ではありません。

Cがそのように設計された理由については、おそらく「ポータブルアセンブリ」としてのCの概念に由来します。このswitchステートメントは基本的にブランチテーブルの抽象化であり、ブランチテーブルにも暗黙的なフォールスルーがあり、それを回避するには追加のジャンプ命令が必要です。

したがって、基本的に、Cの設計者は、デフォルトごとにアセンブラセマンティクスを保持すること選択しました。


3
VB.NETは、それを変更した言語の例です。ケース条項に流れ込む危険性はありません。
突く

1
Tclのもう1つの言語は、次の節に至ることはありません。事実上、それをしたくありませんでした(特定の種類のプログラムを作成するときに役立つCの場合とは異なります)。
ドナルドフェローズ

4
Cの祖先言語であるBと古いBCPLには、両方ともswitchステートメントがありました(持っていましたか?)。BCPLはそれを綴ったswitchon
キーストンプソン

Rubyのcaseステートメントは失敗しません。1つのに2つのテスト値を持つことで、同様の動作を実現できますwhen
ネイサンロング

86

なぜなら、これらの言語switchif ... elseステートメントの代替ではないからです。

を使用switchすることで、一度に複数の条件に一致させることができます。これは場合によっては高く評価されます。

例:

public Season SeasonFromMonth(Month month)
{
    Season season;
    switch (month)
    {
        case Month.December:
        case Month.January:
        case Month.February:
            season = Season.Winter;
            break;

        case Month.March:
        case Month.April:
        case Month.May:
            season = Season.Spring;
            break;

        case Month.June:
        case Month.July:
        case Month.August:
            season = Season.Summer;
            break;

        default:
            season = Season.Autumn;
            break;
    }

    return season;
}

15
more than one block ... unwanted in most cases私はあなたに同意しません。
サティッシュパンディ

2
@DanielBただし、言語によって異なります。C#のswitchステートメントでは、その可能性はありません。
アンディ

5
@アンディフォールスルーについてはまだ話していますか?C#のswitchステートメントは間違いなくそれを許可します(3番目の例を確認してください)break。しかし、はい、私が知る限り、Duff's DeviceはC#では機能しません。
ダニエルB

8
はい、できますが、コンパイラーは条件、コード、およびブレークなしの別の条件を許可しません。間にコードを入れずに複数の条件を積み重ねることができます。または、ブレークが必要です。リンクからC# does not support an implicit fall through from one case label to another。失敗することはできますが、誤って休憩を忘れる可能性はありません。
アンディ

3
@Matsemann ..私は今、すべてのものを使用して行うことができますif..elseが、状況switch..caseによっては望ましいでしょう。if-elseを使用する場合、上記の例は少し複雑になります。
サティッシュパンディ

15

これは、CのコンテキストでStack Overflowで要求されました。なぜswitchステートメントがブレークを必要とするように設計されたのですか?

受け入れられた答えを要約すると、おそらく間違いでした。他のほとんどの言語はおそらくCをたどったばかりです。ただし、C#などの一部の言語は、フォールスルーを許可することでこれを修正したようです。 。


Google Goは同じことをIIRCで行います(明示的に指定されている場合、フォールスルーを許可します)。
レオ

PHPも同様です。そして、結果のコードを見るほど、不安になります。私が好きな/* FALLTHRU */上記@Tapioによってリンクの答えにコメントをするには証明あなたはそれを意味しました。
-DaveP

1
goto caseリンクがそうであるように、C#が持っていると言うことは、なぜそれがうまく機能するのかを説明していません。上記のような複数のケースをスタックすることはできますが、コードを挿入するとすぐgoto case whateverに、次のケースにフォールスルーするために明示的にする必要があります。そうしないと、コンパイラーのエラーが発生します。2つのうち、私に尋ねると、過失による不注意なフォールスルーを心配せずにケースをスタックできることは、明示的なフォールスルーを有効にするよりもはるかに優れた機能ですgoto case
ジェスパー

9

例で答えます。1年の各月の日数をリストしたい場合、31、30、および1 28/29の月があるのは明らかです。このように見えるでしょう、

switch(month) {
    case 4:
    case 6:
    case 9:
    case 11;
        days = 30;
        break;
    case 2:
        //Find out if is leap year( divisible by 4 and all that other stuff)
        days = 28 or 29;
        break;
    default:
        days = 31;
}

これは、複数のケースが同じ効果を持ち、すべて一緒にグループ化される例です。if ... else if構文ではなく、breakキーワードを選択する理由が明らかにありました 。

ここで注意すべき主なことは、多くの同様のケースを持つswitchステートメントは、各ケースのif ... else if ... else if ... elseではなく、if(1、2、3)であるということです。 ... else if(4,5,6)else ...


2
あなたの例はif何の利点もないよりも冗長に見えます。おそらく、あなたの例が名前に割り当てられた場合、またはフォールスルーに加えて月固有の作業を行った場合、フォールスルーの有用性はより明白になるでしょうか?(最初の場所にあるべきかどうかについてはコメントせず)
horatio

1
それは本当です。ただし、フォールスルーを使用できるケースを表示しようとしていました。月ごとに個別に何かを行う必要がある場合は、明らかにはるかに良いでしょう。
-Awemo

8

あるケースから別のケースに「フォールスルー」が発生する可能性がある状況は2つあります。空のケースです。

switch ( ... )
{
  case 1:
  case 2:
    do_something_for_1_and_2();
    break;
  ...
}

そして空でない場合

switch ( ... )
{
  case 1:
    do_something_for_1();
    /* Deliberately fall through */
  case 2:
    do_something_for_1_and_2();
    break;
...
}

Duff's Deviceへの言及にもかかわらず、2番目のケースの正当なインスタンスはほとんどなく、コーディング標準によって禁止されており、静的解析中にフラグが立てられます。そして、それが見つかった場合、それは多くの場合、aの省略によるものbreakです。

前者は完全に賢明で一般的です。

正直に言うbreakと、空のケース本体はフォールスルーであり、空でないケースはスタンドアロンであると言語パーサーが認識する必要がある理由はわかりません。

ISO Cパネルが、非論理的は言うまでもなく、未定義、未指定、または実装定義の機能を修正するのではなく、新しい(望ましくない)機能や不適切に定義された機能を言語に追加することに没頭しているように見えるのは残念です。


2
ISO Cが言語に重大な変更を導入することを好まないことに感謝します!
ジャックエイドリー

4

強制しないことで、break他の方法では困難な多くのことが可能になります。他の人は、いくつかの非自明なケースがあるグループ化ケースに注目しています。

break使用しないことが必須である1つのケースは、Duff's Deviceです。これは、必要な比較の数を制限することで操作を高速化できるループを「展開」するために使用されます。最初の使用では、以前は完全にロールアップされたループで遅すぎた機能が許可されたと思います。場合によっては、コードサイズと速度のトレードオフがあります。

breakケースにコードがある場合は、適切なコメントに置き換えることをお勧めします。そうでなければ、誰かが行方不明を修正しbreak、バグを持ち込みます。


ダフのデバイスの例(+1)に感謝します。私はそれを知りませんでした。
trejder 14

3

起源があると思われるCでは、switchステートメントのコードブロックは特別な構造ではありません。ifステートメントの下のブロックのように、通常のコードブロックです。

switch ()
{
}

if ()
{
}

caseおよびdefaultは、このブロック内のジャンプラベルで、特にに関連していswitchます。それらはの通常のジャンプラベルと同じように処理されgotoます。ここで重要な特定のルールが1つあります。ジャンプラベルは、コードフローを中断することなく、コードのほぼどこにでも配置できます。

通常のコードブロックとして、複合ステートメントである必要はありません。ラベルもオプションです。switchCの有効なステートメントは次のとおりです。

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

C標準自体は、これを例として示しています(6.8.4.2):

switch (expr) 
{ 
    int i = 4; 
    f(i); 
  case 0: 
    i=17; 
    /*falls through into default code */ 
  default: 
    printf("%d\n", i); 
} 

人工プログラムフラグメントでは、識別子がiであるオ​​ブジェクトは自動ストレージ期間(ブロック内)に存在しますが、初期化されることはありません。したがって、制御式の値がゼロ以外の場合、printf関数の呼び出しは不定値にアクセスします。同様に、関数fの呼び出しに到達できません。

さらに、defaultジャンプラベルもあるため、最後のケースである必要はなく、どこにでも配置できます。

これはDuffのデバイスについても説明しています。

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);
}

なぜフォールスルーなのか?通常のコードブロックでの通常のコードフローでは、コードブロックでの期待どおりに、次のステートメントへのフォールスルーが予想されるためですif

if (a == b)
{
    Foo();
    /* "Fall-through" to Bar expected here. */
    Bar();
}

switch (a)
{
    case 1: 
        Foo();
        /* Automatic break would violate expected code execution semantics. */
    case 2:
        Bar();
}

私の推測では、この理由は実装が容易だったためです。switchブロックの解析とコンパイル、特別なルールの管理に特別なコードは必要ありません。他のコードと同じように解析するだけで、ラベルとジャンプ選択に注意するだけです。

これらすべてからの興味深いフォローアップの質問は、次のネストされたステートメントが「Done」を出力する場合です。か否か。

int a = 10;

switch (a)
{
    switch (a)
    {
        case 10: printf("Done.\n");
    }
}

C標準はこれを考慮します(6.8.4.2.4):

caseラベルまたはデフォルトラベルは、最も近い囲むswitchステートメント内でのみアクセス可能です。


1

複数の条件を一致させるという概念についてはすでに何人かが言及していますが、これは時々非常に価値があります。ただし、複数の条件に一致する機能では、一致する各条件でまったく同じことを必ずしも行う必要はありません。以下を考慮してください。

switch (someCase)
{
    case 1:
    case 2:
        doSomething1And2();
        break;

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

    default:
        throw new Error("Invalid Case");
}

ここでは、複数の条件のセットが一致する2つの異なる方法があります。条件1と2を使用すると、まったく同じコードのプロットに単純に移行し、まったく同じことを行います。ただし、条件3および4では、どちらもを呼び出すことdoSomething3And4()で終了しますが、3つの呼び出しのみdoSomething3()です。


パーティーに遅れましたが、関数名が示すようにこれは絶対に「1と2」ではありません。トリガーのコードにつながる可能性のある3つの異なる状態がcase 2あります。関数名はdoSomethingBecause_1_OR_2_OR_1AND2()、何かである必要があります(ただし、イミュータブルが2つの異なるケースに一致する一方で、適切に記述されたコードは事実上存在しないため、少なくともこれはである必要がありますdoSomethingBecause1or2()
Mike 'Pomax' Kamermans

言い換えれば、doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2()。論理的および/またはを指していません。
パンツァークライシス

そうではないことを知っていますが、なぜこの種のコードで混乱するのかを考えると、「そして」とは何かを絶対的に説明する必要があります。正確に説明されている場合は、回答自体でより多くの説明が必要か、その曖昧さを削除するために関数名を書き換える必要があります=)
Mike 'Pomax' Kamermans

1

2つの質問に答えるため。

Cにブレークが必要なのはなぜですか?

「ポータブルアセンブラー」としてCsのルートになります。このようなpsudoコードは一般的でした:-

    targets=(addr1,addr2,addr3);
    opt = 1  ## or 0 or 2
switch:
    br targets[opt]  ## go to addr2 
addr1:
    do 1stuff
    br switchend
addr2:
    do 2stuff
    br switchend
addr3
    do 3stuff
switchend:
    ......

switchステートメントは、同様の機能をより高いレベルで提供するように設計されています。

ブレークのないスイッチはありますか?

はい、これは非常に一般的であり、いくつかのユースケースがあります。

まず、いくつかのケースで同じアクションを実行することをお勧めします。ケースを互いの上に積み重ねることでこれを行います。

case 6:
case 9:
    // six or nine code

ステートマシンで一般的な別のユースケースは、1つの状態を処理した後、すぐに別の状態に入り、処理することです。

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;

-2

Breakステートメントは、ユーザーが最も近い囲みスイッチ(あなたの場合)、while、do、forまたはforeachを終了できるようにするジャンプステートメントです。それと同じくらい簡単です。


-3

上記のすべてのことで-このスレッドの主な結果は、新しい言語を設計する場合、デフォルトではbreakステートメントを追加する必要はなく、コンパイラはそれをユーザーが行ったように処理することです。

次のケースに進みたいというまれなケースが必要な場合は、単純にcontinueステートメントで述べてください。

これを改善して、ケース内で中括弧を使用する場合にのみ続行し、同じコードを実行する複数のケースの上記の月の例が、continue


2
continueステートメントに関するあなたの提案を理解しているかどうかはわかりません。ContinueはCとC ++で非常に異なる意味を持ち、新しい言語がCに似ていたが、Cのようにキーワードを使用したり、この代替方法でキーワードを使用したりすると、混乱が治まると思います。あるラベル付きブロックから別のラベル付きブロックに落ちることにいくつかの問題があるかもしれませんが、コードの共有ブロックで同じ処理を行う複数のケースの使用が好きです。
DeveloperDon

Cには、同じキーワードを複数の目的に使用する長い伝統があります。考え*&staticなど
idrougge
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.