it-swarm-tr.com

Değişkenler neden bir switch ifadesinde bildirilemiyor?

Bunu her zaman merak etmişimdir - neden bir switch ifadesindeki bir vaka etiketinden sonra değişkenleri açıklayamıyorsunuz? C++ 'da değişkenleri hemen hemen her yerde (ve bunları ilk kullanıma yakın olarak ilan etmek kesinlikle iyi bir şeydir) ilan edebilir, ancak aşağıdakiler çalışmayacaktır:

switch (val)  
{  
case VAL:  
  // This won't work
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}  

Yukarıdakiler bana şu hatayı veriyor (MSC):

'newVal' başlatılması 'case' etiketi ile atlandı

Bu diğer dillerde de bir sınırlama gibi görünüyor. Bu neden böyle bir sorun?

874
Rob

Case deyimleri yalnızca etiketler şeklindedir. Bu, derleyicinin bunu doğrudan etikete atlamak olarak yorumlayacağı anlamına gelir. C++ 'da buradaki problem kapsamlardan biridir. Kıvrımlı ayraçlarınız, kapsamı switch ifadesinin içindeki her şey olarak tanımlar. Bu, başlatma işlemini atlayan koda daha fazla atlama yapılacağı bir kapsam dahilinde kaldığınız anlamına gelir. Bunu ele almanın doğru yolu, o case deyimine özgü bir kapsam tanımlamak ve içindeki değişkeninizi tanımlamaktır.

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}
1065
TJ Seabrooks

Bu soru olduğu aynı anda [C] ve [C++] olarak etiketlendi. Orijinal kod hem C hem de C++ 'da geçersiz, ancak tamamen farklı ilgisiz nedenlerden dolayı geçersiz. Bu önemli detayın mevcut cevaplar tarafından kaçırıldığına (veya karıştırıldığına) inanıyorum.

  • C++ 'da bu kod geçersiz çünkü case ANOTHER_VAL: etiketi, ilklendirmeyi atlayarak newVal değişkeni kapsamına atlar. Yerel nesnelerin bypass başlatılmasını engelleyen atlamalar C++ 'da yasaktır. Sorunun bu tarafı çoğu cevap tarafından doğru olarak ele alınmaktadır.

  • Ancak, C dilinde değişken başlatma atlamak bir hata değildir. Bir değişkenin başlangıcına göre bir değişkenin kapsamına atlanması, C'de yasaldır. Değişkenin başlatılmamış olarak bırakıldığı anlamına gelir. Orijinal kod, tamamen farklı bir nedenden dolayı C de derlenmez. Orijinal koddaki case VAL: etiketi, newVal değişkeni bildirgesine eklenir. C dilinde beyanlar ifadeler değildir. Etiketlenemezler. Ve bu, bu kod C kodu olarak yorumlandığında hataya sebep olan şeydir.

    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }
    

Fazladan bir {} bloğu eklemek, bu problemler çok farklı olsa bile hem C++ hem de C sorunlarını düzeltir. C++ tarafında newVal'nin kapsamını kısıtlar, case ANOTHER_VAL: öğesinin artık bu kapsamın içine girmemesini sağlayarak C++ sorununu ortadan kaldırır. Ekstra {} olan C tarafında, bir bileşik deyim ortaya çıkar, böylece case VAL: etiketi, C sorununu ortadan kaldıran bir deyime uygulanacak şekilde yapılır.

  • C durumunda, problem {} olmadan kolayca çözülebilir. case VAL: etiketinden sonra boş bir ifade ekleyin, kod geçerli olur

    switch (val)  
    {  
    case VAL:;            /* Now it works in C! */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:  
      ...
      break;
    }
    

    Şimdi C açısından geçerli olmasına rağmen, C++ açısından geçersiz kaldığını unutmayın.

  • Simetrik olarak, C++ durumunda problem, {} olmadan kolayca çözülebilir. Sadece başlatıcıyı değişken bildirimlerinden kaldırın ve kod geçerli olacak

    switch (val)  
    {  
    case VAL: 
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }
    

    C++ bakış açısından şimdi geçerli olmasına rağmen, C bakış açısından geçersiz kaldığını unutmayın.

297
AnT

Tamam. Sadece bunu açıklığa kavuşturmak için kesinlikle bildirimle ilgisi yok. Sadece "ilklendirmenin üzerinden atlamak" ile ilgilidir (ISO C++ '03 6.7/3)

Buradaki yayınların birçoğu, bildirge üzerine atlamanın "ilan edilmeme" değişkenine neden olabileceğini belirtti. Bu doğru değil. Bir POD nesnesi, başlatıcı olmadan bildirilebilir, ancak belirsiz bir değere sahip olacaktır. Örneğin:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' initialized to 0, but this statement
            // is jumped when 'i == 1'
     break;
   case 1:
     ++j;   // 'j' is in scope here - but it has an indeterminate value
     break;
}

Nesnenin POD olmayan veya toplu olduğu durumlarda, derleyici dolaylı olarak bir başlatıcı ekler ve bu nedenle böyle bir bildirimin üzerine atlamak mümkün değildir:

class A {
public:
  A ();
};

switch (i)  // Error - jumping over initialization of 'A'
{
   case 0:
     A j;   // Compiler implicitly calls default constructor
     break;
   case 1:
     break;
}

Bu sınırlama switch ifadesiyle sınırlı değildir. Ayrıca, bir başlatmanın üzerine atlamak için 'goto' kullanmak bir hatadır:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

Biraz önemsiz durum, bunun C++ ve C arasındaki fark olduğudur. C'de, başlatmanın üstünden atlamak bir hata değildir.

Diğerlerinin de belirttiği gibi, çözüm, değişkenin kullanım ömrü ayrı ayrı durum etiketi ile sınırlı olacak şekilde iç içe geçmiş bir blok eklemektir.

131
Richard Corden

Bütün anahtar deyimi aynı kapsamdadır. Etrafında dolaşmak için şunu yapın:

switch (val)
{
    case VAL:
    {
        // This **will** work
        int newVal = 42;
    }
    break;

    case ANOTHER_VAL:
      ...
    break;
}

Not = parantezler.

35
Mark Ingram

Tüm cevapları ve biraz daha araştırmayı okuduktan sonra birkaç şey alıyorum.

Case statements are only 'labels'

Şartnameye göre C’de,

§6.8.1 Etiketli İfadeler:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

C'de “etiketli beyan” için izin veren herhangi bir madde yoktur. Bu sadece dilin bir parçası değil.

Yani

case 1: int x=10;
        printf(" x is %d",x);
break;

Bu derlenmeyecektir , bakınız http://codepad.org/YiyLQTYw . GCC bir hata veriyor:

label can only be a part of statement and declaration is not a statement

Üstelik

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

bu da ayrıca derlemiyor , bakınız http://codepad.org/BXnRD3b . Burada da aynı hatayı alıyorum.


C++ 'da, spesifikasyona göre,

etiketli bildirime izin verilir, ancak etiketli başlatmaya izin verilmez.

Bakınız http://codepad.org/ZmQ0IyDG .


Böyle bir durum için çözüm iki

  1. Ya {} kullanarak yeni kapsamı kullanın

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Veya etiketli boş ifadeyi kullanın

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Switch () 'den önce değişkeni bildiriniz ve gereksiniminizi yerine getirmesi durumunda case ifadesinde farklı değerlerle başlatınız.

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    

switch ifadesiyle birlikte daha birçok şey

Anahtarda hiçbir zaman bir etiketin parçası olmayan hiçbir ifade yazmayın, çünkü bunlar asla yürütülmez:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}

Bakınız http://codepad.org/PA1quYX .

28
Jeegar Patel

Bunu yapamazsınız çünkü case etiketleri aslında sadece içeren bloğa giriş noktalarıdır.

Bu en açık şekilde Duff's device ile gösterilmiştir. İşte Wikipedia'dan bir kod:

strcpy(char *to, char *from, size_t 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 etiketlerinin blok sınırlarını nasıl tamamen görmezden geldiğine dikkat edin. Evet, bu kötü. Ancak bu nedenle kod örneğiniz çalışmıyor. Bir case etiketine atlamak, goto kullanmakla aynıdır, bu nedenle bir yapıcıyla yerel bir değişkeni atlamanıza izin verilmez.

Diğer birçok afişin belirttiği gibi, kendi bloğunuzu koymak zorundasınız:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }
20
emk

Şu ana kadarki cevapların çoğu tek bir açıdan yanlıştır: Sen can vaka ifadesinden sonra değişkenleri beyan et, fakat sen yapamazsın bunları başlat:

case 1:
    int x; // Works
    int y = 0; // Error, initialization is skipped by case
    break;
case 2:
    ...

Daha önce belirtildiği gibi, bunun için güzel bir yol, davanız için bir kapsam oluşturmak üzere parantez kullanmaktır.

16
MrZebra

En sevdiğim şeytani anahtar numarası, istenmeyen bir durum etiketini atlamak için if (0) kullanmaktır.

switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}

Ama çok kötülük.

12
Jeremy

Bunu dene:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}
10
Dan Shield

Bir switch ifadesinde değişkenleri bildirebilirsiniz if yeni bir blok başlatırsınız:

switch (thing)
{ 
  case A:
  {
    int i = 0;  // Completely legal
  }
  break;
}

Bunun nedeni, yerel değişken (ler) in depolanması için yığına alan tahsis edilmesi (ve geri kazanılması) ile ilgilidir.

7
Seb Rose

Düşünmek:

switch(val)
{
case VAL:
   int newVal = 42;
default:
   int newVal = 23;
}

Break ifadelerinin yokluğunda, bazen newVal iki kez bildirilir ve çalışma zamanına kadar yapıp yapmadığını bilmiyorsunuz. Tahminime göre sınırlama bu tür bir karışıklıktan kaynaklanıyor. NewVal'in kapsamı ne olurdu? Sözleşme, anahtar bloğunun tamamı (diş telleri arasında) olacağını dikte edecektir.

C++ programcısı değilim ama C’de:

switch(val) {
    int x;
    case VAL:
        x=1;
}

İyi çalışıyor. Anahtar bloğunun içindeki bir değişkeni bildirmek gayet iyi. Bir dava görevlisi sonra ilan değil.

6
slim

Anahtarın tüm bölümü tek bir bildirim bağlamıdır. Bunun gibi bir ifadede bir değişken bildiremezsiniz. Bunun yerine şunu deneyin:

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}
4
Andrew Eidsness

Kodunuz "int newVal = 42" diyorsa, newVal öğesinin asla başlatılmamasını makul bir şekilde beklersiniz. Ancak bu ifadeyi ele geçirirseniz (yaptığınız şey budur) o zaman aynen böyle olur - newVal kapsamdadır ancak atanmamıştır.

Eğer gerçekten demek istediğin buysa, dil "int newVal; newVal = 42;" diyerek açık olmasını gerektirir. Aksi takdirde, newVal'in kapsamını tek bir durumla sınırlayabilirsiniz;.

Aynı örneği düşünüyorsanız, ancak "const int newVal = 42;" ifadesiyle durumları netleştirebilir.

3
Mike F

Sadece vurgulamak istedim ince 'nin nokta . Bir değişim yapısı, birinci sınıf vatandaşlık kapsamı oluşturur. Bu nedenle, bir anahtar ifadesinde bir değişkeni ilk vaka etiketinden önce ek bir destek çifti olmadan bildirmek (ve başlatmak) mümkündür:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}
3
VictorH

Bunun iyi olması ilginç:

switch (i)  
{  
case 0:  
    int j;  
    j = 7;  
    break;  

case 1:  
    break;
}

... ama bu değil:

switch (i)  
{  
case 0:  
    int j = 7;  
    break;  

case 1:  
    break;
}

Bir düzeltmenin yeterince basit olduğunu anladım, ancak ilk örneğin derleyiciyi neden rahatsız etmediğini henüz anlamadım. Daha önce de belirtildiği gibi (2 yıl önce hehe), bildirisi mantığa rağmen hataya neden olan şey değildir. Başlatma problemdir. Değişken başlatılır ve farklı satırlarda bildirilirse, derlenir.

3
Dan

Bu cevabı orjinal olarak bu sor için yazdım. Ancak bittiğinde cevabın kapandığını buldum. Ben de buraya gönderdim, belki de standarda atıfları seven biri yararlı bulabilir.

Söz konusu orijinal kod:

int i;
i = 2;
switch(i)
{
    case 1: 
        int k;
        break;
    case 2:
        k = 1;
        cout<<k<<endl;
        break;
}

Aslında 2 soru var:

1. Neden bir değişkenini case etiketinden sonra bildirebilirim?

Çünkü C++ etiketi biçiminde olmalı:

N3337 6.1/1

etiketli-deyim:

...

  • özniteliği-şartname-seqopt caseconstant-expression: statement

...

Ve C++ ifadesinde bildirim ifadesi , ifadesi olarak da kabul edilir (C yerine):

N3337 6/1:

ifadesi :

...

bildirimi-beyanı

...

2. Neden değişken bildiriminin üstünden atlayabilir ve sonra kullanabilirim?

Çünkü: N3337 6.7/3

Bir bloğa transfer etmek mümkündür, ancak başlatma ile bildirimleri atlayacak şekilde değil . Atlayan bir program ( anahtar ifadesinden anahtar ifadesinin durum etiketine aktarılması bir atlama olarak kabul edilir bu bakımdan.)

otomatik saklama süresi olan bir değişkenin kapsamda olmadığı bir noktaya yönelik olmadığı bir noktadan değişken skaler tipe , önemsiz bir varsayılan kurucu ve önemsiz bir yıkıcıya sahip sınıf türü, bu türlerden birinin cv onaylı bir sürümü veya öncekilerden birinin dizisi türler ve bir başlatıcı olmadan bildirilir (8.5).

k skaler tipinde olduğundan ve üzerine atlayarak bildirim noktasından başlatılmadığından bildirimi mümkündür. Bu anlamsal olarak eşdeğerdir:

goto label;

int x;

label:
cout << x << endl;

Ancak, x, bildirim noktasında başlatılmış olsaydı, bu mümkün olmazdı:

 goto label;

    int x = 58; //error, jumping over declaration with initialization

    label:
    cout << x << endl;
3
PcAF

Şimdiye kadar cevaplar C++ için olmuştur.

C++ için, bir başlatmanın üzerine atlayamazsınız. C'de yapabilirsiniz. Ancak, C'de bir bildirim bir ifade değildir ve vaka etiketlerinin ifadeleri izlemesi gerekir.

Yani geçerli (ama çirkin) C, geçersiz C++

switch (something)
{
  case 1:; // Ugly hack empty statement
    int i = 6;
    do_stuff_with_i(i);
    break;
  case 2:
    do_something();
    break;
  default:
    get_a_life();
}

Tersine, C++ 'da bildirim bir ifadedir, bu nedenle aşağıdaki geçerli C++, geçersiz C

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}
3
Peter

Yeni değişkenler sadece blok kapsamında bildirilebilir. Böyle bir şey yazmanız gerekir:

case VAL:  
  // This will work
  {
  int newVal = 42;  
  }
  break;

Tabii ki, newVal sadece parantez içinde kapsamı vardır ...

Şerefe, Ralph.

1
Ralph Hempel

Bir switchbloğu if/else if bloklarının art arda aynısı değildir. Başka hiçbir cevabın açık bir şekilde açıklamasına şaşırmam.

Bu switchdeyimini göz önünde bulundurun:

switch (value) {
    case 1:
        int a = 10;
        break;
    case 2:
        int a = 20;
        break;
}

Şaşırtıcı olabilir, ancak derleyici basit bir if/else if olarak görmez. Aşağıdaki kodu üretecektir:

if (value == 1)
    goto label_1;
else if (value == 2)
    goto label_2;
else
    goto label_end;

{
label_1:
    int a = 10;
    goto label_end;
label_2:
    int a = 20; // Already declared !
    goto label_end;
}

label_end:
    // The code after the switch block

casedeyimleri etiketlere dönüştürülür ve ardından gotoile çağrılır. Köşeli ayraçlar yeni bir kapsam oluşturur ve şimdi neden aynı adı taşıyan iki değişkeni bir switchbloğu içinde bildiremediğinizi görmek kolaydır.

Tuhaf görünebilir, ancak sonbaharın geçmesini desteklemeniz gerekir (yani, uygulamanın bir sonraki breakname__'sine devam etmesi için casekullanmayın).

1
Dalmas

newVal, anahtarın tümünde bulunur, ancak yalnızca VAL uzuvuna çarpıldığında başlatılır. VAL'deki kodun etrafında bir blok oluşturursanız, OK olmalıdır.

0
marijne

C++ Standardı vardır: Bir bloğa transfer etmek mümkündür, ancak başlatma ile bildirimleri atlayacak şekilde değildir. Otomatik saklama süresi olan bir yerel değişkenin, POD tipine (3.9) sahip olmayan ve bir başlatıcı (8.5) olmadan bildirilmediği sürece, kapsamda olmadığı bir noktaya kapsam dışı olmadığı bir noktadan atlayan bir program.

Bu kuralı gösterecek kod:

#include <iostream>

using namespace std;

class X {
  public:
    X() 
    {
     cout << "constructor" << endl;
    }
    ~X() 
    {
     cout << "destructor" << endl;
    }
};

template <class type>
void ill_formed()
{
  goto lx;
ly:
  type a;
lx:
  goto ly;
}

template <class type>
void ok()
{
ly:
  type a;
lx:
  goto ly;
}

void test_class()
{
  ok<X>();
  // compile error
  ill_formed<X>();
}

void test_scalar() 
{
  ok<int>();
  ill_formed<int>();
}

int main(int argc, const char *argv[]) 
{
  return 0;
}

İlkleyici etkisini gösterecek kod:

#include <iostream>

using namespace std;

int test1()
{
  int i = 0;
  // There jumps fo "case 1" and "case 2"
  switch(i) {
    case 1:
      // Compile error because of the initializer
      int r = 1; 
      break;
    case 2:
      break;
  };
}

void test2()
{
  int i = 2;
  switch(i) {
    case 1:
      int r;
      r= 1; 
      break;
    case 2:
      cout << "r: " << r << endl;
      break;
  };
}

int main(int argc, const char *argv[]) 
{
  test1();
  test2();
  return 0;
}
0
Jingguo Yao