ทำไมไม่สามารถประกาศตัวแปรในคำสั่ง switch ได้?


944

ฉันสงสัยอยู่เสมอว่าทำไมคุณไม่สามารถประกาศตัวแปรหลังจากเลเบลเคสในคำสั่ง switch? ใน C ++ คุณสามารถประกาศตัวแปรได้ทุกที่ (และประกาศให้ใกล้เคียงกับการใช้งานครั้งแรกเห็นได้ชัดว่าเป็นสิ่งที่ดี) แต่สิ่งต่อไปนี้ยังคงไม่ทำงาน:

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

ข้างต้นทำให้ฉันข้อผิดพลาดต่อไปนี้ (MSC):

การเริ่มต้นของ 'newVal' ถูกข้ามโดยป้ายกำกับ 'case'

นี่เป็นข้อ จำกัด ในภาษาอื่นด้วย ทำไมปัญหานี้ถึงเป็นเช่นนั้น?


10
สำหรับคำอธิบายตามไวยากรณ์ C BNF ดู stackoverflow.com/questions/1180550/weird-switch-error-in-obj-c/…
johne

นี่คือการอ่านที่ดีมากเกี่ยวกับงบการเปลี่ยนและป้ายกำกับ (ABC :) โดยทั่วไป
Etherealone

4
ฉันจะพูดว่า 'ทำไมตัวแปรไม่สามารถเริ่มต้นได้ในคำสั่งเปลี่ยนแทนที่จะประกาศ' เนื่องจากเพิ่งประกาศตัวแปรให้คำเตือนใน MSVC เท่านั้น
ZoomIn

คำตอบ:


1141

Caseงบเป็นเพียงป้ายชื่อ ซึ่งหมายความว่าคอมไพเลอร์จะตีความสิ่งนี้ว่าเป็นการกระโดดไปที่ป้ายกำกับโดยตรง ใน C ++ ปัญหาที่นี่คือหนึ่งในขอบเขต วงเล็บปีกกาของคุณกำหนดขอบเขตเป็นทุกอย่างภายในswitchคำสั่ง ซึ่งหมายความว่าคุณจะเหลือขอบเขตที่การกระโดดจะดำเนินการเพิ่มเติมในรหัสข้ามการเริ่มต้น

วิธีที่ถูกต้องในการจัดการสิ่งนี้คือการกำหนดขอบเขตเฉพาะของcaseคำสั่งนั้นและกำหนดตัวแปรของคุณภายใน:

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

94
สัมพันธ์กับการเปิดขอบเขตใหม่ - ชอบการอ่านและความสอดคล้องในรหัส ในสมัยก่อนคุณอาจมีเฟรมสแต็ก "พิเศษ" โดยอัตโนมัติ แต่ตอนนี้ไม่ควรเป็นกรณีสำหรับคอมไพเลอร์ที่เหมาะสมที่สุด
เจฟฟ์ Tall

10
ฉันเห็นด้วยกับ Jeff - ทุกอย่างง่ายเกินกว่าที่จะ "ถือว่า" ขอบเขตเมื่ออ่านข้อความสั่ง switch เนื่องจากสไตล์การเยื้องที่คนส่วนใหญ่ใช้ สไตล์ของฉันเองคือการเปิดขอบเขตใหม่สำหรับแต่ละกรณี / ค่าเริ่มต้นเสมอหากมีความยาวมากกว่าหนึ่งบรรทัด
เสนอราคา

39
workmad3 - คุณสามารถหาคอมไพเลอร์ C ++ ได้ไหมที่จะสร้างสแต็กเฟรมใหม่หากคุณไม่ได้ประกาศตัวแปรใหม่ คุณเป็นห่วงฉันสั้น ๆ แต่ไม่มี G ++ 3.1, Visual C ++ 7 หรือ Intel C ++ 8 จะสร้างรหัสใด ๆ สำหรับขอบเขตใหม่ที่คุณไม่ประกาศตัวแปรใด ๆ
Chris Jefferson

10
@ workmad3 โดยการป้อนบล็อกวงเล็บปีกกาใหม่ไม่ได้ทำให้กองซ้อนเฟรมใหม่Stackoverflow.com/questions/2759371/ …
MTVS

3
@TallJef ฉันไม่ทราบว่า 'วันเก่า' ที่คุณอ้างถึงคืออะไร ฉันไม่เคยพบคอมไพเลอร์ที่ไม่ได้จัดสรรพื้นที่สแต็กสำหรับวิธีเมื่อป้อนวิธีใน 40 ปี
user207421

331

แต่เดิมคำถามนี้ถูกแท็กเป็น [C] และ [C ++] ในเวลาเดียวกัน รหัสดั้งเดิมนั้นไม่ถูกต้องทั้งใน C และ C ++ แต่ด้วยเหตุผลที่ไม่เกี่ยวข้องกันอย่างสิ้นเชิง

  • ใน C ++ รหัสนี้ไม่ถูกต้องเนื่องจากcase ANOTHER_VAL:เลเบลข้ามไปในขอบเขตของตัวแปรที่newValข้ามการเริ่มต้น การข้ามที่ข้ามการกำหนดค่าเริ่มต้นของวัตถุอัตโนมัติผิดกฎหมายใน C ++ ปัญหานี้ได้รับการแก้ไขอย่างถูกต้องโดยคำตอบส่วนใหญ่

  • อย่างไรก็ตามในภาษา C การข้ามการกำหนดค่าเริ่มต้นของตัวแปรไม่ใช่ข้อผิดพลาด กระโดดเข้าไปในขอบเขตของตัวแปรมากกว่าการเริ่มต้นเป็นสิ่งที่ถูกต้องตามกฎหมายใน C. มันก็หมายความว่าตัวแปรที่เหลือไม่กำหนดค่าเริ่มต้น รหัสต้นฉบับไม่ได้รวบรวมใน C ด้วยเหตุผลที่แตกต่างอย่างสิ้นเชิง ป้ายในรหัสเดิมอยู่ติดกับประกาศของตัวแปรcase VAL: newValในการประกาศภาษา C ไม่ใช่คำสั่ง ไม่สามารถติดป้ายกำกับได้ และนี่คือสิ่งที่ทำให้เกิดข้อผิดพลาดเมื่อรหัสนี้ถูกตีความว่าเป็นรหัส C

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

การเพิ่ม{}บล็อกเสริมแก้ไขปัญหาทั้ง C ++ และ C แม้ว่าปัญหาเหล่านี้จะแตกต่างกันมาก ในด้าน C ++ จะ จำกัด ขอบเขตnewValให้ตรวจสอบให้แน่ใจว่าcase ANOTHER_VAL:ไม่มีการข้ามไปยังขอบเขตนั้นอีกต่อไปซึ่งช่วยลดปัญหา C ++ ในด้าน C ที่พิเศษ{}แนะนำคำสั่งผสมจึงทำให้case VAL:ฉลากที่จะนำไปใช้กับคำสั่งซึ่งกำจัดปัญหา C

  • ในกรณี C {}ปัญหาจะสามารถแก้ไขได้อย่างง่ายดายโดยไม่ต้อง เพียงเพิ่มคำสั่งว่างหลังจากcase VAL:ป้ายกำกับและรหัสจะใช้ได้

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

    โปรดทราบว่าแม้ว่าตอนนี้จะใช้ได้จากมุมมอง C แต่ก็ยังคงไม่ถูกต้องจากมุมมอง C ++

  • แฟ่ในกรณี c ++ {}ปัญหาที่สามารถแก้ไขได้อย่างง่ายดายโดยไม่ต้อง เพียงลบ initializer ออกจากการประกาศตัวแปรและรหัสจะใช้ได้

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

    โปรดทราบว่าแม้ว่าตอนนี้จะใช้ได้จากมุมมอง C ++ แต่ก็ยังคงไม่ถูกต้องจากมุมมอง C


4
@AnT: ฉันเข้าใจว่าทำไมตัวที่แก้ไข C ++ ไม่สามารถใช้ได้กับ C; อย่างไรก็ตามฉันไม่เข้าใจว่าจะแก้ไขปัญหา C ++ ของการข้ามการเริ่มต้นในครั้งแรกได้อย่างไร มันจะยังไม่ข้ามการประกาศและการมอบหมายnewValเมื่อมันกระโดดไปANOTHER_VAL?
ตำนาน 2k

13
@ legends2k: ใช่มันยังคงข้ามไป แต่เมื่อผมบอกว่า "การแก้ไขปัญหา" ผมหมายถึงว่ามันแก้ที่ C ++ รวบรวมข้อผิดพลาด ใน C ++ มันเป็นสิ่งผิดกฎหมายที่จะข้ามประกาศเกลากับการเริ่มต้นแต่มันก็สมบูรณ์ดีที่จะข้ามประกาศเกลาโดยไม่ต้อง initializer ที่case ANOTHER_VAL:ตัวแปรจุดnewValสามารถมองเห็นได้ แต่มีค่าไม่แน่นอน
AnT

3
มโนหร ฉันพบคำถามนี้หลังจากอ่าน§A9.3: Compound Statementจาก K&R C (ฉบับที่สอง) รายการกล่าวถึงคำนิยามทางเทคนิคของสารประกอบคำสั่ง{declaration-list[opt] statement-list[opt]}ซึ่งเป็น สับสนเพราะฉันคิดว่าคำแถลงเป็นคำแถลงฉันค้นหาและพบคำถามนี้ทันทีตัวอย่างที่ความเหลื่อมล้ำดังกล่าวปรากฏชัดเจนและจริงแล้วแบ่งโปรแกรม ฉันเชื่อว่าวิธีแก้ปัญหาอื่น (สำหรับ C) คือการใส่คำสั่งอื่น (อาจเป็นคำสั่งโมฆะ?) ก่อนการประกาศเพื่อให้คำสั่งที่มีข้อความกำกับเป็นที่พอใจ
Braden สุดยอด

โอ๊ะฉันเพิ่งสังเกตเห็นว่าโซลูชันคำสั่งโมฆะที่ฉันแนะนำนั้นมีอยู่ในคำตอบของคุณแล้ว ไม่เป็นไรแล้ว
Braden สุดยอด

3
เป็นที่น่าสังเกตว่าการแก้ไขการเพิ่มคำสั่งที่ว่างเปล่าใช้ได้กับ C99 เป็นต้นไปเท่านั้น ใน C89 ตัวแปรจะต้องประกาศ ณ จุดเริ่มต้นของบล็อกล้อมรอบ
Arthur Tacca

136

ตกลง. เพียงชี้แจงให้ชัดเจนว่าเรื่องนี้ไม่มีส่วนเกี่ยวข้องกับการประกาศ มันเกี่ยวข้องเฉพาะกับ "การกระโดดข้ามการเริ่มต้น" (ISO C ++ '03 6.7 / 3)

โพสต์จำนวนมากที่นี่ได้กล่าวถึงการกระโดดข้ามการประกาศอาจส่งผลให้ตัวแปร "ไม่ถูกประกาศ" นี่ไม่เป็นความจริง. วัตถุ POD สามารถประกาศได้โดยไม่ต้องมี initializer แต่จะมีค่าไม่แน่นอน ตัวอย่างเช่น:

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

ในกรณีที่วัตถุไม่ใช่แบบ POD หรือรวมคอมไพเลอร์โดยปริยายจะเพิ่มตัวเริ่มต้นและดังนั้นจึงเป็นไปไม่ได้ที่จะข้ามการประกาศดังกล่าว:

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

ข้อ จำกัด นี้ไม่ได้ จำกัด อยู่ที่คำสั่งเปลี่ยน นอกจากนี้ยังเป็นข้อผิดพลาดในการใช้ 'goto' เพื่อข้ามการเริ่มต้น:

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

เรื่องไม่สำคัญอยู่ที่นี่คือความแตกต่างระหว่าง C ++ และ C ใน C มันไม่ใช่ข้อผิดพลาดที่จะกระโดดข้ามการเริ่มต้น

ดังที่คนอื่น ๆ ได้กล่าวถึงวิธีการแก้ปัญหาคือการเพิ่มบล็อกซ้อนกันเพื่อให้อายุการใช้งานของตัวแปรถูก จำกัด ไว้ที่ป้ายกรณีแต่ละรายการ


2
"ข้อผิดพลาดการกระโดดข้ามการกำหนดค่าเริ่มต้น" ??? ไม่ได้อยู่กับ GCC ของฉัน มันอาจให้คำเตือน "j อาจใช้ unitialized" เมื่อใช้ j ด้านล่างฉลาก แต่ไม่มีข้อผิดพลาด อย่างไรก็ตามในกรณีที่สวิตช์มีข้อผิดพลาด (ข้อผิดพลาดอย่างหนักไม่เตือนอ่อน)
Mecki

9
@Mecki: มันผิดกฎหมายใน C ++ ISO C ++ '03 - 6.7 / 3: "... โปรแกรมที่กระโดดจากจุดที่ตัวแปรโลคัลที่มีระยะเวลาการจัดเก็บอัตโนมัติไม่อยู่ในขอบเขตจนถึงจุดที่อยู่ในขอบเขตจะเกิดขึ้นไม่ได้เว้นแต่ตัวแปรจะมีประเภท POD (3.9) และถูกประกาศโดยไม่มี initializer (8.5) "
Richard Corden

1
ใช่ แต่ไม่ใช่สิ่งผิดกฎหมายใน C (อย่างน้อย gcc บอกว่าไม่ใช่) j จะไม่ถูกกำหนดค่าเริ่มต้น (มีหมายเลขสุ่มบางส่วน) แต่คอมไพเลอร์รวบรวมมัน อย่างไรก็ตามในกรณีของคำสั่ง switch คอมไพเลอร์จะไม่รวบรวมมันและฉันล้มเหลวที่จะเห็นความแตกต่างระหว่างกรณี goto / label และกรณีสวิตช์
Mecki

8
@Mecki: โดยทั่วไปพฤติกรรมการคอมไพเลอร์เดียวไม่จำเป็นต้องสะท้อนให้เห็นถึงสิ่งที่ภาษาได้รับอนุญาต ฉันได้ตรวจสอบทั้ง C'90 และ C'99 และมาตรฐานทั้งสองรวมถึงตัวอย่างด้วยการกระโดดข้ามการเริ่มต้นในคำสั่งเปลี่ยน
Richard Corden

38

คำสั่ง switch ทั้งหมดอยู่ในขอบเขตเดียวกัน หากต้องการรับรอบทำสิ่งนี้:

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

    case ANOTHER_VAL:
      ...
    break;
}

สังเกตเครื่องหมายวงเล็บ


30

หลังจากอ่านคำตอบทั้งหมดและค้นคว้าเพิ่มเติมฉันได้รับบางอย่าง

Case statements are only 'labels'

ใน C ตามข้อกำหนด

§6.8.1ข้อความที่มีข้อความกำกับ:

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

ใน C ไม่มีประโยคใด ๆ ที่อนุญาตให้มี "การประกาศที่มีข้อความ" มันไม่ได้เป็นส่วนหนึ่งของภาษา

ดังนั้น

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

นี้จะไม่รวบรวมดูhttp://codepad.org/YiyLQTYw GCC กำลังให้ข้อผิดพลาด:

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

แม้

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

นี้จะยังไม่ได้รวบรวมดูhttp://codepad.org/BXnRD3bu ที่นี่ฉันยังได้รับข้อผิดพลาดเดียวกัน


ใน C ++ ตามข้อกำหนด

อนุญาตให้ใช้การประกาศที่มีป้ายกำกับ แต่ไม่อนุญาตให้ติดป้ายกำกับเริ่มต้น

ดูhttp://codepad.org/ZmQ0IyDG


การแก้ปัญหาดังกล่าวมีสองประการ

  1. ใช้ขอบเขตใหม่โดยใช้ {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. หรือใช้คำสั่งดัมมี่ที่มีฉลาก

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. ประกาศตัวแปรก่อน switch () และเริ่มต้นด้วยค่าที่แตกต่างกันในคำสั่ง case หากตอบสนองความต้องการของคุณ

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

บางสิ่งเพิ่มเติมด้วยคำสั่งเปลี่ยน

ห้ามเขียนข้อความใด ๆ ในสวิตช์ที่ไม่ได้เป็นส่วนหนึ่งของป้ายกำกับเพราะจะไม่มีการดำเนินการ:

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

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

    default:
        break;
}

ดูhttp://codepad.org/PA1quYX3


2
คุณอธิบายปัญหา C ได้อย่างถูกต้อง แต่การยืนยันว่าในการเริ่มต้น C ++ ที่มีป้ายกำกับนั้นไม่ได้รับอนุญาตนั้นไม่สมบูรณ์จริง ไม่มีอะไรผิดปกติกับการเริ่มต้นที่มีข้อความใน C ++ อะไร c ++ ไม่อนุญาตให้เป็นกระโดดในช่วงเริ่มต้นของตัวแปรเข้าไปในขอบเขตของตัวแปรa aดังนั้นจากมุมมอง C ปัญหาเกิดขึ้นกับcase VAL:ฉลากและคุณอธิบายอย่างถูกต้อง แต่จากมุมมอง C ++ ปัญหาอยู่ที่case ANOTHER_VAL:ป้ายกำกับ
AnT

ใน C ++ ซึ่งแตกต่างจาก C การประกาศเป็นชุดย่อยของคำสั่ง
Keith Thompson

20

คุณไม่สามารถทำได้เนื่องจากcaseป้ายกำกับเป็นเพียงจุดเข้าสู่บล็อกที่มีอยู่

นี่คือตัวอย่างชัดเจนที่สุดโดยอุปกรณ์ของดัฟฟ์ นี่คือรหัสจาก Wikipedia:

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ป้ายกำกับละเว้นขอบเขตของบล็อกโดยสิ้นเชิงอย่างไร ใช่นี่คือความชั่วร้าย แต่นี่คือสาเหตุที่ตัวอย่างรหัสของคุณใช้งานไม่ได้ การกระโดดไปที่caseป้ายกำกับนั้นเหมือนกับการใช้gotoดังนั้นคุณจะไม่ได้รับอนุญาตให้ข้ามตัวแปรโลคอลด้วยคอนสตรัคเตอร์

ดังที่ผู้โพสต์อื่น ๆ ระบุไว้คุณต้องใส่บล็อกของคุณเอง:

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

1
การใช้อุปกรณ์ของดัฟฟ์นี้มีข้อผิดพลาดที่ทำให้มันช้ามาก: count เป็นประเภท int ดังนั้น% จะต้องดำเนินการหาร / โมดูโลจริง ทำให้นับไม่ได้ลงนาม (หรือดีกว่ายังใช้ size_t เสมอสำหรับการนับ / ดัชนี) และปัญหาจะหายไป
. GitHub หยุดช่วยน้ำแข็ง

1
@R .. : อะไรนะ! ในระบบประกอบสองระบบการเซ็นชื่อจะไม่ส่งผลต่อโมดูโลโดยอำนาจของ 2 (เป็นเพียงแค่ AND ที่บิตด้านล่าง) และจะไม่ส่งผลกระทบต่อการหารด้วยพลังของ 2 ตราบใดที่สถาปัตยกรรมโปรเซสเซอร์ของคุณมีการดำเนินการคำนวณแบบกะขวา ( SARใน x86 กับSHRที่สำหรับการเปลี่ยนแปลงที่ไม่ได้ลงชื่อ)
Chris Jester-Young

@Chris: ฉันเชื่อว่าเขาหมายถึงเมื่อคอมไพเลอร์ต้องอนุญาตสำหรับค่าลบที่ "เพียงแค่และบนบิตด้านล่าง" ไม่ถือ ตัวอย่างเช่น -1% 8 ให้ -1 ในระบบคอมพลีเมนต์ของทั้งสองนี้โดยใช้ g ++ (เครื่องหมายในกรณีนี้คือการใช้งานที่กำหนดไว้ตาม 5.6 / 4)

3
@ Chris: ฉันเห็นด้วยกับคุณว่า R กำลังพูดเกินจริงถึงผลกระทบ ฉันเห็นความคิดเห็นของคุณเท่านั้นและรู้ว่าง่ายและไม่เพียงพอ

1
สิ่งที่ควรสังเกตคือรหัสต้นฉบับของ Wikipedia นั้นใช้สำหรับส่งข้อมูลไปยังหน่วยความจำที่แมปเอาท์พุทซึ่งดูแปลกเพราะที่นี่ไม่ได้กล่าวถึงและทุก ๆ ไบต์จะถูกคัดลอกไปยังตำแหน่ง "สู่" เดียวกัน สามารถแก้ไขได้โดยเพิ่ม postfix ++ ลงใน to หรือกล่าวถึงกรณีการใช้สำหรับการแมปหน่วยความจำ IO อุปกรณ์ต่อพ่วงทั้งหมดกับคำถามเดิม :-)
Peter

16

การตอบกลับส่วนใหญ่นั้นผิดในแง่เดียว: คุณสามารถประกาศตัวแปรหลังคำสั่ง case แต่คุณไม่สามารถกำหนดค่าเริ่มต้นได้:

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

วิธีที่ดีในการแก้ไขปัญหานี้คือการใช้เครื่องมือจัดฟันเพื่อสร้างขอบเขตสำหรับเคสของคุณ


1
นาย 32 คุณเข้าใจผิดว่าอะไรคือข้อผิดพลาดของคุณ: ใช่นั่นจะไม่รวบรวม แต่ไม่ใช่เพราะคุณประกาศตัวแปรภายในสวิตช์ ข้อผิดพลาดเป็นเพราะคุณกำลังพยายามประกาศตัวแปรหลังจากคำสั่งซึ่งผิดกฎหมายใน C.
MrZebra

1
ตอนนี้เป็นวันที่ถูกกฎหมายใน c90 และรุ่นใหม่กว่าของ c
Jeegar Patel

12

เคล็ดลับสวิตช์ชั่วร้ายที่ฉันโปรดปรานคือใช้ if (0) เพื่อข้ามฉลากเคสที่ไม่ต้องการ

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

แต่ชั่วร้ายมาก


ดีมาก. ตัวอย่างของสาเหตุ: case 0 และ case 1 อาจยกตัวอย่างเช่นการเริ่มต้นตัวแปรที่แตกต่างกันซึ่งจะใช้ในกรณีที่ 2
hlovdal

1
หากคุณต้องการให้ทั้งกรณีที่ 0 และกรณีที่ 1 ผ่านไปในกรณีที่ 2 (โดยไม่ต้องมีกรณีที่ 0 ล้มลงในกรณีที่ 1) ไม่รู้ว่ามันมีประโยชน์จริงๆ แต่ใช้ได้จริง
Petruza

1
คุณสามารถข้ามไปยังป้ายกำกับที่ต้องการgotoโดยไม่ทำให้งงงวยรหัส
SomeWittyUsername


7

คุณสามารถประกาศตัวแปรภายในคำสั่ง switch หากคุณเริ่มบล็อกใหม่:

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

เหตุผลคือทำกับการจัดสรรพื้นที่ (และเรียกคืน) บนสแต็กสำหรับการจัดเก็บของตัวแปรท้องถิ่น


1
สามารถประกาศตัวแปรได้ แต่ไม่สามารถเริ่มต้นได้ นอกจากนี้ฉันค่อนข้างแน่ใจว่าปัญหาไม่เกี่ยวข้องกับสแต็คและตัวแปรท้องถิ่น
Richard Corden

6

พิจารณา:

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

ในกรณีที่ไม่มีคำสั่ง break บางครั้ง newVal จะได้รับการประกาศสองครั้งและคุณไม่ทราบว่าจะใช้งานจนกว่ารันไทม์ ฉันเดาว่าข้อ จำกัด นั้นเป็นเพราะความสับสนเช่นนี้ ขอบเขตของ newVal จะเป็นอย่างไร อนุสัญญาจะกำหนดว่ามันจะเป็นทั้งบล็อกสวิตช์ (ระหว่างวงเล็บปีกกา)

ฉันไม่ใช่โปรแกรมเมอร์ C ++ แต่เป็น C:

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

ทำงานได้ดี การประกาศตัวแปรภายในบล็อกสวิตช์นั้นดี ประกาศหลังจากยามกรณีไม่ได้


3
@ Mr.32: ตัวอย่างจริงของคุณแสดงให้เห็นว่า printf ไม่ได้ถูกดำเนินการ แต่ในกรณีนี้ int x ไม่ใช่คำสั่ง แต่เป็นการประกาศ x จะถูกประกาศช่องว่างถูกสงวนไว้ทุกครั้งที่สภาพแวดล้อมของฟังก์ชั่นถูกซ้อนกัน โปรดดูที่: codepad.org/4E9Zuz1e
Petruza

ฉันคาดหวังว่าจะพบสิ่งนี้เมื่ออ่านชื่อของคำถามเนื่องจากคำถามไม่ได้เกี่ยวกับการประกาศตัวแปรภายในป้ายกำกับ "case:" แต่ในคำสั่งสลับ และมีเพียงคุณ (และ VictorH ที่เน้นคำตอบของคุณ) พูดคุยเกี่ยวกับตัวแปรในงบการเปลี่ยน
เลิก

4

ส่วนทั้งหมดของสวิตช์เป็นบริบทการประกาศเดียว คุณไม่สามารถประกาศตัวแปรในคำสั่ง case เช่นนั้นได้ ลองใช้สิ่งนี้แทน:

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

สามารถประกาศตัวแปรได้ แต่ไม่สามารถเริ่มต้นได้
Richard Corden

@ Richard Corden ฉันมั่นใจว่าการเริ่มต้นจะทำงานได้ คุณยังยืนยันว่าไม่สามารถเริ่มต้นได้หรือไม่?
chux - Reinstate Monica

3

หากรหัสของคุณระบุว่า "int newVal = 42" คุณจะคาดหวังได้เลยว่า newVal จะไม่ถูกกำหนดค่าเริ่มต้น แต่ถ้าคุณข้ามคำแถลงนี้ (ซึ่งเป็นสิ่งที่คุณกำลังทำ) นั่นคือสิ่งที่เกิดขึ้น - newVal อยู่ในขอบเขต แต่ยังไม่ได้รับมอบหมาย

หากนั่นคือสิ่งที่คุณตั้งใจจะให้เกิดขึ้นจริง ๆ แล้วภาษานั้นต้องทำให้ชัดเจนโดยการพูดว่า "int newVal; newVal = 42;" มิฉะนั้นคุณสามารถ จำกัด ขอบเขตของ newVal ให้เป็นกรณีเดียวซึ่งมีโอกาสมากขึ้นที่คุณต้องการ

มันอาจทำให้สิ่งต่าง ๆ ชัดเจนถ้าคุณพิจารณาตัวอย่างเดียวกัน แต่ด้วย "const int newVal = 42;"


3

ฉันแค่อยากจะเน้นความบางเฉียบของจุด โครงสร้างสวิตช์สร้างขอบเขตทั้งหมดของพลเมืองชั้นหนึ่ง ดังนั้นจึงเป็นไปได้ที่จะประกาศ (และเริ่มต้น) ตัวแปรในคำสั่ง switch ก่อนเลเบลเคสแรกโดยไม่มีคู่วงเล็บเหลี่ยมเพิ่มเติม:

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

-1 ที่นี่ int newVal = 42; จะไม่ถูกประหารชีวิต เห็นนี้codepad.org/PA1quYX3
Jeegar Patel

4
การประกาศint newVal จะดำเนินการ แต่ไม่ใช่การ= 42มอบหมาย
Petruza

3

จนถึงตอนนี้คำตอบก็คือ C ++

สำหรับ C ++ คุณไม่สามารถข้ามการกำหนดค่าเริ่มต้นได้ คุณสามารถเป็น C ได้อย่างไรก็ตามใน C การประกาศไม่ใช่คำสั่งและฉลากเคสจะต้องตามด้วยข้อความสั่ง

ดังนั้นถูกต้อง (แต่น่าเกลียด) C, 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();
}

ตรงกันข้ามใน C ++ การประกาศเป็นคำสั่งดังนั้นต่อไปนี้เป็น C ++ ที่ถูกต้อง C ที่ไม่ถูกต้อง

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

1
ตัวอย่างที่สองไม่ถูกต้อง C ++ (ทดสอบกับ vc2010 และ gcc 4.6.1 C ++ ไม่อนุญาตให้ข้ามส่วนการเริ่มต้นข้อความแสดงข้อผิดพลาด gcc คือ: การเริ่มต้นข้ามของ 'int i'
zhaorufei

3

น่าสนใจว่านี่เป็นเรื่องปกติ:

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

case 1:  
    break;
}

... แต่นี่ไม่ใช่:

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

case 1:  
    break;
}

ฉันได้รับการแก้ไขนั้นง่ายพอ แต่ฉันยังไม่เข้าใจเลยว่าทำไมตัวอย่างแรกไม่รบกวนคอมไพเลอร์ ดังที่ได้กล่าวไว้ก่อนหน้านี้ (2 ปีก่อนฮิฮิ) การประกาศไม่ใช่สิ่งที่ทำให้เกิดข้อผิดพลาดแม้จะมีเหตุผลก็ตาม การเริ่มต้นเป็นปัญหา หากตัวแปรนั้นถูกกำหนดค่าเริ่มต้นและประกาศในบรรทัดที่แตกต่างกันก็จะรวบรวม


1
อย่างแรกไม่ถูกต้องสำหรับ gcc 4.2: "ข้อผิดพลาด: นิพจน์ที่คาดไว้ก่อนหน้า 'int'" ดังที่ Peter และ Mr.32 พูดว่า "case 0:; int j; ... " และ "case 0:; int j = 7; ... " ทำงานได้ทั้งคู่ ปัญหาใน C เป็นเพียง "กรณี <label>: การประกาศ" ไม่ใช่ไวยากรณ์ C ที่ถูกต้อง
dubiousjim

3

ฉันเขียนคำตอบนี้หรือเสริมสำหรับคำถามนี้ อย่างไรก็ตามเมื่อฉันเสร็จฉันพบว่าคำตอบถูกปิด ดังนั้นฉันโพสต์ไว้ที่นี่บางทีคนที่ชอบการอ้างอิงถึงมาตรฐานจะพบว่ามีประโยชน์

รหัสดั้งเดิมในคำถาม:

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

มีคำถาม 2 ข้อจริง:

1. เหตุใดฉันจึงประกาศตัวแปรหลังcaseป้ายกำกับ

เป็นเพราะในฉลาก C ++ ต้องอยู่ในรูปแบบ:

N3337 6.1 / 1

ที่มีข้อความคำสั่ง:

...

  • attribute-specifier-seqopt case constant-expression :statement

...

และในC++ คำแถลงการณ์ก็ถือว่าเป็นคำสั่ง (ตรงข้ามกับC):

N3337 6/1:

คำสั่ง :

...

ประกาศคำสั่ง

...

2. ทำไมฉันสามารถข้ามการประกาศตัวแปรแล้วใช้มันได้หรือไม่

เพราะ: N3337 6.7 / 3

มันเป็นไปได้ที่จะโอนเข้าสู่บล็อกแต่ไม่ใช่ในลักษณะที่ทะลุประกาศที่มีการเริ่มต้น โปรแกรมที่ข้ามไป (การถ่ายโอนจากเงื่อนไขของคำสั่ง switch ไปยังเลเบลเคสถือเป็นการข้ามในส่วนนี้)

จากจุดที่ตัวแปรที่มีระยะเวลาการจัดเก็บอัตโนมัติไม่อยู่ในขอบเขตไปยังจุดที่อยู่ในขอบเขตนั้นจะเกิดขึ้นไม่ดี เว้นแต่ตัวแปรนั้นมีประเภทสเกลาร์ , ประเภทคลาสที่มีตัวสร้างปริยายเล็กน้อยและ destructor เล็กน้อยรุ่น cv ของหนึ่งในประเภทเหล่านี้หรืออาเรย์ของหนึ่งในประเภทก่อนหน้านี้และมีการประกาศโดยไม่ต้องมี initializer (8.5)

เนื่องจากkเป็นชนิดสเกลาร์และไม่สามารถกำหนดค่าเริ่มต้นที่จุดของการประกาศกระโดดข้ามการประกาศเป็นไปได้ นี่คือความหมายเทียบเท่า:

goto label;

int x;

label:
cout << x << endl;

อย่างไรก็ตามสิ่งนั้นอาจเป็นไปไม่ได้หากxถูกกำหนดค่าเริ่มต้น ณ จุดประกาศ:

 goto label;

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

    label:
    cout << x << endl;

1

ตัวแปรใหม่สามารถถูกแจกจ่ายได้ที่ขอบเขตบล็อกเท่านั้น คุณต้องเขียนสิ่งนี้:

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

แน่นอน newVal มีขอบเขตภายในวงเล็บปีกกาเท่านั้น ...

ไชโยราล์ฟ


1

switchบล็อกไม่ได้เช่นเดียวกับการสืบทอดของif/else ifบล็อก ฉันประหลาดใจที่ไม่มีคำตอบอื่นอธิบายอย่างชัดเจน

พิจารณาswitchข้อความนี้:

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

มันอาจจะเป็นที่น่าแปลกใจ if/else ifแต่คอมไพเลอร์จะไม่เห็นว่ามันเป็นที่เรียบง่าย มันจะสร้างรหัสต่อไปนี้:

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

งบจะถูกแปลงเป็นป้ายชื่อและเรียกว่ามีแล้วcase gotoวงเล็บสร้างขอบเขตใหม่และเป็นเรื่องง่ายที่จะเห็นตอนนี้ทำไมคุณไม่สามารถประกาศตัวแปรสองตัวที่มีชื่อเดียวกันภายในswitchบล็อกได้

มันอาจจะดูแปลก ๆ แต่มันก็เป็นสิ่งที่จำเป็นเพื่อสนับสนุนfallthrough (นั่นคือไม่ได้ใช้breakเพื่อให้การดำเนินการอย่างต่อเนื่องต่อไปcase)



0

newVal มีอยู่ในขอบเขตทั้งหมดของสวิตช์ แต่จะถูกกำหนดค่าเริ่มต้นก็ต่อเมื่อมีการชน VAL limb หากคุณสร้างบล็อกรอบรหัสใน VAL ควรเป็น OK


0

C ++ Standard มี: เป็นไปได้ที่จะถ่ายโอนไปยังบล็อก แต่ไม่สามารถข้ามการประกาศด้วยการเริ่มต้นได้ โปรแกรมที่กระโดดจากจุดที่ตัวแปรโลคัลที่มีระยะเวลาการจัดเก็บอัตโนมัติไม่อยู่ในขอบเขตไปยังจุดที่อยู่ในขอบเขตจะเกิดขึ้นไม่ดีเว้นแต่ตัวแปรนั้นมีประเภท POD (3.9) และถูกประกาศโดยไม่มี initializer (8.5)

รหัสเพื่อแสดงกฎนี้:

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

รหัสเพื่อแสดงเอฟเฟ็กต์เริ่มต้น:

#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

ดูเหมือนว่าวัตถุที่ไม่ระบุตัวตนสามารถประกาศหรือสร้างในงบกรณีสวิตช์ด้วยเหตุผลที่พวกเขาไม่สามารถอ้างอิงได้และเป็นเช่นนั้นไม่สามารถผ่านไปยังกรณีถัดไป พิจารณาตัวอย่างนี้รวบรวมใน GCC 4.5.3 และ Visual Studio 2008 (อาจเป็นปัญหาการปฏิบัติตามกฎหมายดังนั้นผู้เชี่ยวชาญโปรดชั่งน้ำหนักใน)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

    switch( i )
    {
    case 42:
        Foo();  // Apparently valid
        break;

    default:
        break;
    }
    return EXIT_SUCCESS;
}

หากคุณกำลังจะลงคะแนนโปรดอธิบายว่าทำไม ฉันอยากรู้ว่าทำไมการสร้างวัตถุที่ไม่ระบุชื่อดูเหมือนจะได้รับการยกเว้น
Olumide

1
ไม่ใช่ DV แต่: คำถามทั้งหมดเกี่ยวกับการประกาศ / ขอบเขตของตัวแปรที่มีชื่อ ชั่วคราว ("วัตถุที่ไม่ระบุชื่อ" ไม่ใช่คำศัพท์) ไม่ใช่ตัวแปรที่มีชื่อและไม่เป็นการประกาศและไม่อยู่ภายใต้ขอบเขต (เว้นแต่จะผูกไว้กับการconstอ้างอิงที่มีขอบเขตของตนเอง) มันคือการแสดงออกที่มีชีวิตและตายไปตามคำแถลงของมัน ดังนั้นจึงไม่เกี่ยวข้องทั้งหมด
underscore_d

Foo();ไม่ใช่การประกาศ คำถามเกี่ยวกับการประกาศ
MM
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.