Anahtar deyimi: varsayılan son durumda olmalı?


178

Aşağıdaki switchifadeyi düşünün :

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}

Bu kod derlenir, ancak C90 / C99 için geçerli mi (= tanımlı davranış)? Ben hiç varsayılan durumda son durumda değil kodu görmedim .

DÜZENLEME:
As Jon Cage ve KillianDS yazma: Bu gerçekten çirkin ve kodu kafa karıştırıcı olduğunu ve bunun farkındayım. Sadece genel sözdizimi (tanımlanmış mı?) Ve beklenen çıktı ile ilgileniyorum.


19
+1 Bu davranışı hiç düşünmedim
Jamie Wong

@ Péter Török: Yani değer == 2 ise 6 döndürür mü?
Alexandre

4
@ Péter Török hayır, sipariş önemli değil - eğer değer herhangi bir durumda etikette sabitle eşleşirse, kontrol etiketi takip eden ifadeye atlar, aksi takdirde kontrol mevcutsa varsayılan etiketi takip eden ifadeye atlar.
Pete Kirkham

11
@Jon Cage gotokötü değil. Kargo kült takipçileri! İnsanların kaçınabilecekleri şeyleri hayal edemezsiniz gotoçünkü bu çok kötü bir şeydir ve kodlarının okunamayacağı bir karmaşa yaratır.
Patrick Schlüter

3
Ben gotoesas olarak finallydurduğunda kaynakların (dosyalar, bellek) serbest bırakılması ve bir liste her hata durumunda tekrarlanması gereken freeve closeokunabilirlik için yardımcı olmaz fonksiyonlarında bir cümle gibi bir şey simüle etmek için kullanın. gotoKaçınmak istediğim ama yapamadığım bir kullanım olsa da , bir döngüden çıkmak istediğimde ve switchbu döngü içinde biriyim .
Patrick Schlüter

Yanıtlar:


83

C99 standardı bu konuda açık değildir, ancak tüm gerçekleri birlikte ele alırsak, tamamen geçerlidir.

A caseve defaultetiket bir gotoetikete eşdeğerdir . Bkz. 6.8.1 Etiketli ifadeler. Özellikle ilginç olan, yukarıda belirtilen Duff'ın Cihazını sağlayan 6.8.1.4'tür:

Herhangi bir ifadeden önce, tanımlayıcıyı etiket adı olarak bildiren bir önek olabilir. Etiketler kendi içlerinde engelsiz kontrol akışını değiştirmez.

Düzenleme : Bir anahtar içindeki kod özel bir şey değildir; ifek atlama etiketleriyle, bir durumdaki gibi normal bir kod bloğudur. Bu, düşme davranışını ve neden breakgerekli olduğunu açıklar .

6.8.4.2.7 bir örnek bile veriyor:

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

Yapay program parçasında tanımlayıcısı i olan nesne otomatik depolama süresi (blok içinde) ile var olur, ancak hiçbir zaman başlatılmaz ve bu nedenle kontrol ifadesi sıfırdan farklı bir değere sahipse, printf işlevine yapılan çağrı belirsiz bir değere erişir. Benzer şekilde, f işlevine yapılan çağrıya ulaşılamaz.

Vaka sabitleri bir switch deyimi içinde benzersiz olmalıdır:

6.8.4.2.3 Her bir vaka etiketinin ifadesi bir tamsayı sabit ifadesi olmalı ve aynı anahtar ifadesindeki büyük / küçük harf sabit ifadelerinin hiçbiri dönüşümden sonra aynı değere sahip olmayacaktır. Bir switch deyiminde en fazla bir varsayılan etiket olabilir.

Tüm durumlar değerlendirilir, ardından verilirse varsayılan etikete atlar:

6.8.4.2.5 Tamsayı yükseltmeleri kontrol eden ifade üzerinde gerçekleştirilir. Her bir vaka etiketindeki sabit ifade, kontrol ifadesinin yükseltilmiş tipine dönüştürülür. Dönüştürülen değer, yükseltilen kontrol ifadesinin değeriyle eşleşirse, kontrol, eşleşen vaka etiketini izleyen ifadeye atlar. Aksi takdirde, varsayılan bir etiket varsa, denetim etiketli ifadeye atlar. Dönüştürülen büyük / küçük harf sabit ifadesi eşleşmiyorsa ve varsayılan etiket yoksa, anahtar gövdesinin hiçbir bölümü yürütülmez.


6
@HeathHunnicutt Örneğin amacını açıkça anlamadınız. Kod bu poster tarafından oluşturulmamış, ancak garip anahtar ifadelerinin nasıl olduğunu ve kötü uygulamanın hatalara yol açacağını gösteren bir örnek olarak doğrudan C standardından alınmıştır. Kodun altındaki metni okuma zahmetine girmiş olsaydınız, bunu fark edersiniz.
Lundin

2
Downvayı telafi etmek için +1. Birinin C standardına atıfta bulunmasını reddetmek oldukça sert görünüyor.
Lundin

2
@Lundin C standardını aşağı oylamıyorum ve önerdiğiniz gibi hiçbir şeyi göz ardı etmedim. Kötü ve gereksiz bir örnek kullanmanın kötü pedagojisine oy verdim. Özellikle, bu örnek tamamen istenenden farklı bir durumla ilgilidir. Devam edebilirdim, ama "geri bildiriminiz için teşekkürler."
Heath Hunnicutt

12
Intel, en sık rastlanan kodu önce Yanlış ve Yanlışları Önlemek için Şube ve Döngü Yeniden Düzenleme'deki bir anahtar deyimine yerleştirmenizi söyler . Buradayım çünkü defaultdiğer vakalara yaklaşık 100: 1 hakim olan bir vakam var defaultve ilk vakayı yapmak için geçerli veya tanımsız olup olmadığını bilmiyorum .
jww

@jww Intel ile ne demek istediğinizden emin değilim. Zeka demek istiyorsan buna hipotez derim. Aynı düşünceye sahiptim, ancak daha sonra okuma, ifadelerin aksine, anahtar ifadelerin rastgele erişim olduğunu belirtir. Yani son duruma ulaşmak birinciden daha yavaş değil. Bu, sabit durum değerlerinin hashlenmesi ile gerçekleştirilir. Bu nedenle, switch deyimleri, dallar çok fazla olduğunda ifadelerden daha hızlıdır.

91

Case deyimleri ve varsayılan deyim, switch deyimindeki herhangi bir sırada olabilir. Varsayılan deyim, vaka ifadelerindeki sabitlerden hiçbiri eşleştirilemediğinde eşleşen isteğe bağlı bir deyimdir.

İyi örnek :-

switch(5) {
  case 1:
    echo "1";
    break;
  case 2:
  default:
    echo "2, default";
    break;
  case 3;
    echo "3";
    break;
}


Outputs '2,default'

Vakalarınızın kodda mantıklı bir sırayla sunulmasını istiyorsanız (vaka 1, vaka 3, vaka 2 / varsayılan olarak değil) ve vakalarınız çok uzun olduğundan tüm vakayı tekrarlamak istemiyorsanız çok yararlıdır. varsayılanın altındaki kod


7
Bu, genellikle sondan başka bir yere varsayılan olarak yerleştirdiğim senaryo ... Açık vakalara (1, 2, 3) mantıklı bir sipariş var ve varsayılanın, açık vakalardan biriyle aynı şekilde davranmasını istiyorum. sonuncusu değil.
ArtOfWarfare

51

Bazı durumlarda geçerli ve çok yararlı.

Aşağıdaki kodu göz önünde bulundurun:

switch(poll(fds, 1, 1000000)){
   default:
    // here goes the normal case : some events occured
   break;
   case 0:
    // here goes the timeout case
   break;
   case -1:
     // some error occurred, you have to check errno
}

Mesele, yukarıdaki kodun basamaklıdan daha okunabilir ve verimli olmasıdır if. defaultSonuna koyabilirsiniz , ancak dikkatinizi normal durumlar yerine hata durumlarına odaklayacağı için anlamsızdır (işte burada default).

Aslında, bu iyi bir örnek değil, pollen fazla kaç olayın meydana gelebileceğini biliyorsunuz. Benim gerçek nokta olmasıdır olan 'istisnalar' ve normal durumlar vardır giriş değerleri tanımlanmış bir set ile olgular. İstisnaları veya normal vakaları öne koymak daha iyi bir seçimse.

Yazılım alanında başka bir olağan durum daha düşünüyorum: bazı terminal değerleriyle özyineleme. Bir anahtar kullanarak ifade edebiliyorsanız default, yinelenen çağrı ve ayırt edici öğeler (bireysel durumlar) içeren terminal değerleri normal değer olacaktır. Genellikle terminal değerlerine odaklanmaya gerek yoktur.

Diğer bir neden, vakaların sıralamasının derlenmiş kod davranışını değiştirebilmesidir ve performanslar için önemlidir. Çoğu derleyici, anahtarda görünen kodla aynı sırada derlenmiş montaj kodu oluşturur. Bu, ilk vakayı diğerlerinden çok farklı kılar: Birincisi dışındaki tüm durumlar bir sıçrama içerecek ve bu da işlemci boru hatlarını boşaltacaktır. Bunu, anahtardaki ilk görünen durumu çalıştırmayı varsayılan olarak belirten dal tahmincisi gibi anlayabilirsiniz. Eğer bir vaka diğerlerinden daha yaygın ise, o zaman ilk dava olarak koymak için çok iyi nedenleriniz var.

Yorumları okumak, orijinal posterin Intel derleyici Branch Loop'u kod optimizasyonu ile ilgili yeniden düzenlemeyi okuduktan sonra bu soruyu sormasının özel nedenidir .

Ardından, kod okunabilirliği ve kod performansı arasında bir tahkim haline gelecektir. Gelecekteki okuyucuya bir vakanın neden ilk ortaya çıktığını açıklamak için yorum yapmak muhtemelen daha iyidir.


6
Açılış davranışı olmadan (iyi) bir örnek vermek için +1.
KillianDS

1
... bununla ilgili düşünmekle birlikte, en üstte varsayılana sahip olmanın iyi olduğuna ikna olmadım çünkü çok az insan orada arıyor olacaktı. Bir değişkene dönüş atamak ve bir if ifadesinin bir tarafında başarıyı ve diğer tarafında bir case deyimi ile hataları ele almak daha iyi olabilir.
Jon Cage

@Jon: Sadece yaz. Okunabilirlik avantajı olmadan sözdizimi gürültüsü eklersiniz. Ve eğer varsayılan en üstte ise, gerçekten bakmaya gerek yoktur, gerçekten açıktır (ortaya koyarsanız daha zor olabilir).
kriss

Bu arada C anahtarı / vaka sözdizimini gerçekten sevmiyorum. Çok sayıda ardışık koymak zorunda kalmak yerine, bir davadan sonra birkaç etiket koymayı tercih ederim case. İç karartıcı olan şey, sözdizimsel şeker gibi görünmesi ve desteklenmesi durumunda mevcut herhangi bir kodu kırmamasıdır.
kriss

1
@kriss: "Ben de bir python programcısı değilim!" :)
Andrew Grimm

16

evet, bu geçerlidir ve bazı durumlarda bile yararlıdır. Genel olarak, ihtiyacınız yoksa, yapma.


-1: Bu bana kötülük kokuyor. Kodu bir çift switch deyimine bölmek daha iyi olur.
Jon Cage

25
@John Cage: Bana bir -1 koymak kötü. Bunun geçerli bir kod olması benim hatam değil.
Jens Gustedt

sadece merak ediyorum, hangi koşullar altında yararlı olduğunu bilmek isterim?
Salil

1
-1, yararlı olduğunu iddia etmenizi amaçladı. Talebinizi yedeklemek için geçerli bir örnek sağlayabilirseniz + 1 olarak değiştiririm.
Jon Cage

4
Bazen bir errno için geçiş yaparken bazı sistem işlevlerinden karşılığında aldık. Diyelim ki temiz bir çıkış yapmamız gerektiğini iyi bildiğimiz bir vakamız var, ancak bu temiz çıkış için tekrarlamak istemediğimiz bazı kodlama satırları gerekebilir. Ancak, bireysel olarak ele almak istemediğimiz birçok egzotik hata kodumuz olduğunu da varsayalım. Ben sadece varsayılan durumda bir perror koyarak düşünmek ve diğer dava geçmesine izin ve temiz çıkmak. Bunu böyle yapmanız gerektiğini söylemiyorum. Bu sadece bir zevk meselesi.
Jens Gustedt

8

Bir switch deyiminde tanımlanmış bir sıra yok. Olgulara etiket gibi adlandırılmış bir etiket gibi bakabilirsiniz goto. İnsanların burada düşündüklerinin aksine, değer 2 durumunda varsayılan etiket atlanmaz. Klasik bir örnekle göstermek için , C'nin uçlarının poster çocuğu olan Duff'ın cihazıswitch/case .

send(to, from, count)
register short *to, *from;
register count;
{
  register 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);
  }
}

4
Ve Duff'ın cihazına aşina olmayan herkes için bu kod tamamen okunamaz ...
KillianDS

7

Bir vaka deyiminin sonu dışında bir yerde bulunan 'varsayılan' olmanın uygun olacağını düşündüğüm bir senaryo, geçersiz bir durumun makineyi sıfırlaması ve başlangıç ​​durumu gibi ilerlemesi gereken bir durum makinesinde. Örneğin:

anahtar (widget_state)
{
  varsayılan: / * Raylardan düştü - sıfırla ve devam et * /
    widget_state = WIDGET_START;
    /* Suya düşmek */
  vaka WIDGET_START:
    ...
    break;
  vaka WIDGET_WHATEVER:
    ...
    break;
}

geçersiz bir durum makineyi sıfırlamamalı ancak geçersiz bir durum olarak kolayca tanımlanabiliyorsa alternatif bir düzenleme:

anahtar (widget_state) { vaka WIDGET_IDLE: widget_ready = 0; widget_hardware_off (); break; vaka WIDGET_START: ... break; vaka WIDGET_WHATEVER: ... break; varsayılan: widget_state = WIDGET_INVALID_STATE; /* Suya düşmek */ vaka WIDGET_INVALID_STATE: widget_ready = 0; widget_hardware_off (); ... "güvenli" bir koşul oluşturmak için ne gerekiyorsa yap }

Daha sonra başka bir yerde kod (widget_state == WIDGET_INVALID_STATE) olup olmadığını kontrol edebilir ve uygun hata bildirimi veya durum sıfırlama davranışı sağlayabilir. Örneğin, durum çubuğu kodu bir hata simgesi gösterebilir ve boşta olmayan durumların çoğunda devre dışı bırakılan "widget'ı başlat" menü seçeneği WIDGET_INVALID_STATE ve WIDGET_IDLE için etkinleştirilebilir.


6

Başka bir örnekle giriş yapma: "Varsayılan" beklenmeyen bir durumsa ve hatayı günlüğe kaydetmek, ancak mantıklı bir şey yapmak istiyorsanız bu yararlı olabilir. Kendi kodumdan bazıları:

  switch (style)
  {
  default:
    MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
  case SOLID:
    return Dash(0, RECT_DOT);
  case DASH_SYS:
  {
    Dash ret(shapeLineWidth, dotStyle);
    ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
    return ret;
  }
  // more cases follow
  }

5

Bir dosyaya yazdığınız / dosyadan okuduğunuzda, ENUM öğesini bir dizeye veya dizeyi enum'a dönüştürdüğünüz durumlar vardır.

Dosyaları manuel olarak düzenleyerek yapılan hataları kapsamak için bazen değerlerden birini varsayılan yapmanız gerekir.

switch(textureMode)
{
case ModeTiled:
default:
    // write to a file "tiled"
    break;

case ModeStretched:
    // write to a file "stretched"
    break;
}

2

defaultKoşul bir vaka hükmü var olabilir anahtar içinde her yerde olabilir. Son fıkra olması şart değildir. İlk fıkra olarak varsayılan koymak kodu gördüm. case 2:Varsayılan fıkra üstünde olsa bile, normalde çalıştırılmaktadır.

Bir test olarak, örnek kod denilen test(int value){}ve çalıştırılan bir fonksiyona koydu :

  printf("0=%d\n", test(0));
  printf("1=%d\n", test(1));
  printf("2=%d\n", test(2));
  printf("3=%d\n", test(3));
  printf("4=%d\n", test(4));

Çıktı:

0=2
1=1
2=4
3=8
4=10

1

Geçerli, ama oldukça kötü. Bazı çok dağınık spagetti koduna yol açabileceği için düşmelere izin vermek genellikle kötü olur.

Bu durumları birkaç anahtar deyimine veya daha küçük işlevlere bölmek neredeyse kesinlikle daha iyidir.

[değiştir] @Tristopia: Örneğiniz:

Example from UCS-2 to UTF-8 conversion 

r is the destination array, 
wc is the input wchar_t  

switch(utf8_length) 
{ 
    /* Note: code falls through cases! */ 
    case 3: r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
    case 2: r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 
    case 1: r[0] = wc;
}

bu şekilde yazılmış olsaydı (bence) niyetiyle ilgili daha net olurdu:

if( utf8_length >= 1 )
{
    r[0] = wc;

    if( utf8_length >= 2 )
    {
        r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 

        if( utf8_length == 3 )
        {
            r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
        }
    }
}   

[edit2] @Tristopia: İkinci örneğiniz muhtemelen takip için iyi bir kullanımın en temiz örneğidir:

for(i=0; s[i]; i++)
{
    switch(s[i])
    {
    case '"': 
    case '\'': 
    case '\\': 
        d[dlen++] = '\\'; 
        /* fall through */ 
    default: 
        d[dlen++] = s[i]; 
    } 
}

.. ama kişisel olarak yorum tanımayı kendi işlevine böldüm:

bool isComment(char charInQuestion)
{   
    bool charIsComment = false;
    switch(charInQuestion)
    {
    case '"': 
    case '\'': 
    case '\\': 
        charIsComment = true; 
    default: 
        charIsComment = false; 
    } 
    return charIsComment;
}

for(i=0; s[i]; i++)
{
    if( isComment(s[i]) )
    {
        d[dlen++] = '\\'; 
    }
    d[dlen++] = s[i]; 
}

2
Düşüşün gerçekten, gerçekten iyi bir fikir olduğu durumlar vardır.
Patrick Schlüter

UCS-2'den UTF-8'e dönüşüm rörneği hedef dizidir, wcgiriş wchar_t anahtarıdır (utf8_length) {/ * Not: kod vakalara düşer! * / vaka 3: r [2] = 0x80 | (wc & 0x3f); wc >> = 6; wc | = 0x800; durum 2: r [1] = 0x80 | (wc & 0x3f); wc >> = 6; wc | = 0xc0; vaka 1: r [0] = wc; }
Patrick Schlüter

Başka bir karakter kaçan bir dize kopya rutin: for(i=0; s[i]; i++) { switch(s[i]) { case '"': case '\'': case '\\': d[dlen++] = '\\'; /* fall through */ default: d[dlen++] = s[i]; } }
Patrick Schlüter

Evet, ama bu rutin sıcak noktalarımızdan biri, bu onu uygulamanın en hızlı, taşınabilir (montaj yapmayacağız) yoluydu. Herhangi bir UTF uzunluğu için sadece 1 testi var, seninki 2 veya hatta 3 var. Ayrıca, ben gelmedim, BSD'den aldım.
Patrick Schlüter

1
Evet, özellikle Bulgarca ve Yunanca (Solaris SPARC'da) dönüşümlerinde ve dahili işaretlememizle (3 bayt UTF8 olan) metin vardı. Kabul ettiğimizde, çok fazla değil ve son donanım güncellememizden bu yana önemsiz hale geldi, ancak yazıldığı zaman biraz fark yarattı.
Patrick Schlüter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.