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
は常にそこにあります。すべてのブロックで。すべてのコードで。
Cは、この形式でswitch
ステートメントを記述した最初の言語の1つであり、他のすべての主要言語はそこから継承され、ほとんどの場合、デフォルトのCセマンティクスを維持することを選択しました。どちらも利点を考えていませんでした。それを変更するか、誰もが慣れている行動を維持することよりも重要性が低いと判断した。
Cがそのように設計された理由については、おそらく「ポータブルアセンブリ」としてのCの概念に由来します。 switch
ステートメントは基本的に 分岐テーブル を抽象化したものであり、分岐テーブルにも暗黙のフォールスルーがあり、それを回避するには追加のジャンプ命令が必要です。
したがって、基本的に、Cの設計者もは、アセンブラのセマンティクスをデフォルトのままにすることを選択しました。
これらの言語では、switch
はif ... 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;
}
これは、Cのコンテキストでスタックオーバーフローで尋ねられました: switchステートメントが中断を必要とするように設計されたのはなぜですか?
受け入れられた答えを要約すると、それはおそらく間違いでした。他のほとんどの言語はおそらくCに続いているでしょう。しかし、C#などの一部の言語は、フォールスルーを許可することでこれを修正しているようです。 。
例を挙げて答えます。 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つあります-空のケース:
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番目のケースの正当なインスタンスはほとんどなく、コーディング標準によって禁止されており、静的分析中にフラグが付けられます。そして、それが見つかった場所では、break
が省略されていることが原因であることがよくあります。
前者は完全に賢明で一般的です。
正直に言うと、空ではないケースがスタンドアロンであるのに対して、空のケースボディはフォールスルーであることを知っているbreak
と言語パーサーが必要な理由はわかりません。
ISO Cパネルが、未定義、未指定、または実装定義の機能を修正するのではなく、非論理的なことは言うまでもなく、新しい(不要な)定義や不適切に定義された機能を言語に追加することに夢中になっているのは残念です。
break
を強制しないことで、他の方法では実行が困難な多くのことが可能になります。他の人は、いくつかの重要なケースがあるグループ化のケースに注意しました。
break
を使用しないことが必須である1つのケースは Duff's Device です。これは、「unroll」ループに使用され、必要な比較の数を制限することで操作を高速化できます。最初の使用では、以前は完全にロールアップされたループでは遅すぎた機能が許可されたと思います。場合によっては、コードサイズと速度をトレードオフします。
ケースにコードがある場合は、break
を適切なコメントに置き換えることをお勧めします。そうでなければ、誰かが欠落しているbreak
を修正し、バグを紹介します。
Originのように見えるCでは、switch
ステートメントのコードブロックは特別な構成ではありません。これは、if
ステートメントの下のブロックと同じように、通常のコードブロックです。
switch ()
{
}
if ()
{
}
case
およびdefault
は、このブロック内のジャンプラベルで、特にswitch
に関連しています。それらは、goto
の通常のジャンプラベルと同じように処理されます。ここで重要なルールが1つあります。ジャンプラベルは、コードフローを中断することなく、コードのほぼすべての場所に配置できます。
通常のコードブロックと同様に、複合ステートメントである必要はありません。ラベルもオプションです。これらは、Cで有効なswitch
ステートメントです。
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
もジャンプラベルであるため、最後のケースである必要はなく、どこにでも置くことができます。
これは、ダフのデバイスについても説明しています。
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
コードブロックで期待されるように、次のステートメントへのフォールスルーはexpectedです。
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
ブロックを解析およびコンパイルするための特別なコードは必要なく、特別なルールを考慮します。他のコードと同じように解析するだけで、ラベルとジャンプ選択のみを処理する必要があります。
これらすべてから興味深いフォローアップの質問は、次のネストされたステートメントが「完了」と出力するかどうかです。か否か。
int a = 10;
switch (a)
{
switch (a)
{
case 10: printf("Done.\n");
}
}
C標準はこれを扱います(6.8.4.2.4):
ケースまたはデフォルトのラベルには、最も近い囲みのswitchステートメント内でのみアクセスできます。
あなたの二つの質問に答える。
なぜ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;
複数の条件に一致するという概念についてはすでに何度か言及していますが、これは時々非常に貴重です。ただし、複数の条件に一致する機能では、一致する各条件でまったく同じことを実行する必要はありません。以下を検討してください。
_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()
を呼び出すことで終了しますが、only 3はdoSomething3()
を呼び出します。