Tại sao các biến không thể được khai báo trong câu lệnh switch?


945

Tôi đã luôn tự hỏi điều này - tại sao bạn không thể khai báo các biến sau nhãn trường hợp trong câu lệnh chuyển đổi? Trong C ++, bạn có thể khai báo các biến khá nhiều ở bất cứ đâu (và khai báo chúng gần với lần sử dụng đầu tiên rõ ràng là một điều tốt) nhưng sau đây vẫn không hoạt động:

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

Ở trên cho tôi lỗi sau (MSC):

khởi tạo 'newVal' được bỏ qua bởi nhãn 'case'

Đây dường như là một hạn chế trong các ngôn ngữ khác. Tại sao điều này là một vấn đề như vậy?


10
Để biết giải thích dựa trên ngữ pháp C BNF, hãy xem stackoverflow.com/questions/1180550/weird-switch-error-in-obj-c/
phỏng

Dưới đây là một bài đọc thực sự tốt về các câu lệnh và nhãn chuyển đổi (ABC :) nói chung.
Etherealone

4
Tôi sẽ nói 'Tại sao các biến không thể được khởi tạo trong câu lệnh chuyển đổi thay vì khai báo'. Chỉ cần khai báo biến đó chỉ cho tôi một cảnh báo trong MSVC.
Phóng to

Câu trả lời:


1143

Casebáo cáo chỉ là nhãn . Điều này có nghĩa là trình biên dịch sẽ diễn giải điều này như một bước nhảy trực tiếp đến nhãn. Trong C ++, vấn đề ở đây là một trong phạm vi. Dấu ngoặc nhọn của bạn xác định phạm vi là mọi thứ bên trong switchcâu lệnh. Điều này có nghĩa là bạn còn lại một phạm vi trong đó một bước nhảy sẽ được thực hiện thêm vào mã bỏ qua việc khởi tạo.

Cách chính xác để xử lý việc này là xác định phạm vi cụ thể cho casecâu lệnh đó và xác định biến của bạn trong đó:

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

94
Liên quan đến việc mở một phạm vi mới - ưu tiên tính dễ đọc và tính nhất quán trong mã. Ngày xưa, bạn có thể đã tự động có khung ngăn xếp "phụ", nhưng bây giờ điều đó không xảy ra đối với bất kỳ trình biên dịch tối ưu hóa tốt nào.
Cao Jeff

10
Tôi đồng ý với Jeff - quá dễ để "giả sử" phạm vi khi đọc câu lệnh chuyển đổi vì kiểu thụt lề mà hầu hết mọi người sử dụng. Phong cách riêng của tôi là luôn mở một phạm vi mới cho mỗi trường hợp / mặc định nếu nó dài hơn một dòng.
Hồ sơ dự thầu

39
workmad3 - Bạn có thể tìm thấy tôi bất kỳ trình biên dịch C ++ nào sẽ tạo ra một khung ngăn xếp mới nếu bạn không khai báo bất kỳ biến mới nào không? Bạn lo lắng cho tôi một thời gian ngắn, nhưng không có G ++ 3.1, Visual C ++ 7 hoặc Intel C ++ 8 sẽ tạo ra bất kỳ mã nào cho phạm vi mới mà bạn không khai báo bất kỳ biến nào.
Chris Jefferson

10
@ workmad3 bằng cách nhập khối dấu ngoặc nhọn mới không gây ra stackoverflow stackoverflow.com/questions/2759371/
Kẻ

3
@TallJef Tôi không biết "ngày xưa" bạn đang đề cập đến điều gì. Tôi chưa bao giờ gặp trình biên dịch trong đó tất cả không gian ngăn xếp cho một phương thức không được phân bổ khi phương thức được nhập, trong 40 năm.
Hầu tước Lorne

333

Câu hỏi này ban đầu được gắn thẻ là [C] và [C ++] cùng một lúc. Mã ban đầu thực sự không hợp lệ trong cả C và C ++, nhưng vì những lý do hoàn toàn không liên quan.

  • Trong C ++, mã này không hợp lệ vì case ANOTHER_VAL:nhãn nhảy vào phạm vi của biến newValbỏ qua việc khởi tạo. Nhảy qua bỏ qua khởi tạo đối tượng tự động là bất hợp pháp trong C ++. Mặt này của vấn đề được giải quyết chính xác bởi hầu hết các câu trả lời.

  • Tuy nhiên, trong ngôn ngữ C bỏ qua khởi tạo biến không phải là một lỗi. Nhảy vào phạm vi của một biến trong quá trình khởi tạo của nó là hợp pháp trong C. Điều đó đơn giản có nghĩa là biến đó không được khởi tạo. Mã ban đầu không biên dịch trong C vì một lý do hoàn toàn khác. Nhãn case VAL:trong mã gốc được đính kèm với khai báo biến newVal. Trong khai báo ngôn ngữ C không phải là báo cáo. Chúng không thể được dán nhãn. Và đây là nguyên nhân gây ra lỗi khi mã này được hiểu là mã C.

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

Thêm một {}khối bổ sung sẽ sửa chữa cả các vấn đề C ++ và C, mặc dù những vấn đề này xảy ra rất khác nhau. Về phía C ++, nó giới hạn phạm vi newVal, đảm bảo rằng case ANOTHER_VAL:không còn nhảy vào phạm vi đó, điều này giúp loại bỏ vấn đề C ++. Về phía C, {}giới thiệu thêm một câu lệnh ghép, do đó làm cho case VAL:nhãn được áp dụng cho một câu lệnh, loại bỏ vấn đề C.

  • Trong trường hợp C, vấn đề có thể được giải quyết dễ dàng mà không cần {}. Chỉ cần thêm một tuyên bố trống sau case VAL:nhãn và mã sẽ trở thành hợp lệ

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

    Lưu ý rằng ngay cả khi nó hợp lệ theo quan điểm C, nó vẫn không hợp lệ theo quan điểm của C ++.

  • Đối xứng, trong trường hợp C ++, vấn đề có thể được giải quyết dễ dàng mà không cần {}. Chỉ cần xóa trình khởi tạo khỏi khai báo biến và mã sẽ trở thành hợp lệ

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

    Lưu ý rằng ngay cả khi nó hợp lệ theo quan điểm C ++, nó vẫn không hợp lệ theo quan điểm C.


4
@AnT: Tôi hiểu tại sao bản sửa lỗi C ++ không áp dụng được cho C; tuy nhiên, tôi không thể hiểu làm thế nào nó khắc phục vấn đề C ++ bỏ qua việc khởi tạo ở nơi đầu tiên? Nó sẽ không bỏ qua việc khai báo và chuyển nhượng newValkhi nó nhảy tới ANOTHER_VALchứ?
huyền thoại2k

13
@ legends2k: Vâng, nó vẫn bỏ qua nó. Tuy nhiên, khi tôi nói "nó khắc phục sự cố", ý tôi là nó đã sửa lỗi trình biên dịch C ++ . Trong C ++, việc bỏ qua khai báo vô hướng với trình khởi tạo là bất hợp pháp , nhưng việc bỏ qua khai báo vô hướng mà không có trình khởi tạo là bất hợp pháp . Tại case ANOTHER_VAL:điểm newValcó thể nhìn thấy, nhưng với giá trị không xác định.
AnT

3
Hấp dẫn. Tôi tìm thấy câu hỏi này sau khi đọc §A9.3: Compound Statementtừ K & R C (ấn bản thứ hai). Mục đề cập đến định nghĩa kỹ thuật của một hợp chất-tuyên bố đó là {declaration-list[opt] statement-list[opt]}. Bối rối, bởi vì tôi đã nghĩ rằng một tuyên bố là một tuyên bố, tôi đã tra cứu nó và ngay lập tức tìm thấy câu hỏi này, một ví dụ cho thấy sự chênh lệch rõ ràng và thực sự phá vỡ một chương trình. Tôi tin rằng một giải pháp khác (cho C) sẽ là đưa một tuyên bố khác (có thể là tuyên bố null?) Trước khi tuyên bố để tuyên bố được dán nhãn được thỏa mãn.
Braden hay nhất

Rất tiếc, tôi chỉ nhận thấy rằng giải pháp null-statement mà tôi đề xuất đã có trong câu trả lời của bạn. Đừng bận tâm.
Braden hay nhất

3
Điều đáng chú ý là việc sửa lỗi thêm một câu lệnh trống chỉ hoạt động cho C99 trở đi. Trong C89, các biến phải được khai báo khi bắt đầu khối kèm theo của chúng.
Arthur Tacca

136

Đồng ý. Chỉ cần làm rõ điều này hoàn toàn không có gì để làm với tuyên bố. Nó chỉ liên quan đến "nhảy qua khởi tạo" (ISO C ++ '03 6.7 / 3)

Rất nhiều bài viết ở đây đã đề cập rằng việc nhảy qua khai báo có thể dẫn đến biến "không được khai báo". Đây không phải là sự thật. Một đối tượng POD có thể được khai báo mà không cần bộ khởi tạo nhưng nó sẽ có giá trị không xác định. Ví dụ:

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

Trong đó đối tượng không phải là POD hoặc tổng hợp trình biên dịch sẽ thêm trình khởi tạo, và do đó không thể chuyển qua khai báo như vậy:

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

Giới hạn này không giới hạn trong câu lệnh switch. Đó cũng là một lỗi khi sử dụng 'goto' để chuyển qua khởi tạo:

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

Một vài điều nhỏ nhặt là đây là một sự khác biệt giữa C ++ và C. Trong C, việc nhảy qua khởi tạo không phải là một lỗi.

Như những người khác đã đề cập, giải pháp là thêm một khối lồng nhau để thời gian tồn tại của biến được giới hạn trong nhãn trường hợp riêng lẻ.


2
"Lỗi nhảy qua khởi tạo" ??? Không phải với GCC của tôi. Nó có thể đưa ra cảnh báo "j có thể được sử dụng theo đơn vị" khi sử dụng j bên dưới nhãn, nhưng không có lỗi. Tuy nhiên, trong trường hợp chuyển đổi, có một lỗi (một lỗi cứng, không phải là một cảnh báo yếu).
Mecki

9
@Mecki: Nó là bất hợp pháp trong C ++. ISO C ++ '03 - 6.7 / 3: "... Một chương trình nhảy từ điểm mà biến cục bộ có thời gian lưu trữ tự động không nằm trong phạm vi đến điểm có phạm vi không được định dạng trừ khi biến có loại POD (3.9) và được khai báo mà không có trình khởi tạo (8.5). "
Richard Corden

1
Có, nhưng nó không bất hợp pháp trong C (ít nhất gcc nói rằng nó không phải). j sẽ không được khởi tạo (có một số số ngẫu nhiên), nhưng trình biên dịch sẽ biên dịch nó. Tuy nhiên, trong trường hợp câu lệnh chuyển đổi, trình biên dịch thậm chí sẽ không biên dịch nó và tôi không thấy sự khác biệt giữa trường hợp goto / nhãn và trường hợp chuyển đổi.
Mecki

8
@Mecki: Nói chung, một hành vi biên dịch đơn không nhất thiết phản ánh whtat thực sự được ngôn ngữ cho phép. Tôi đã kiểm tra cả C'90 và C'99 và cả hai tiêu chuẩn đều bao gồm một ví dụ với bước nhảy qua khởi tạo trong câu lệnh chuyển đổi.
Richard Corden

38

Toàn bộ câu lệnh chuyển đổi là trong cùng một phạm vi. Để có được xung quanh nó, làm điều này:

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

    case ANOTHER_VAL:
      ...
    break;
}

Lưu ý các dấu ngoặc.


30

Sau khi đọc tất cả các câu trả lời và một số nghiên cứu thêm, tôi nhận được một vài điều.

Case statements are only 'labels'

Trong C, theo đặc điểm kỹ thuật,

§6.8.1 Báo cáo được dán nhãn:

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

Trong C không có bất kỳ mệnh đề nào cho phép "khai báo có nhãn". Nó không phải là một phần của ngôn ngữ.

Vì thế

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

Điều này sẽ không biên dịch , xem http://codepad.org/YiyLQTYw . GCC đang báo lỗi:

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

Cũng

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

đây cũng không phải là biên dịch , xem http://codepad.org/BXnRD3bu . Ở đây tôi cũng nhận được lỗi tương tự.


Trong C ++, theo đặc điểm kỹ thuật,

khai báo có nhãn được cho phép nhưng không được phép dán nhãn -initialization.

Xem http://codepad.org/ZmQ0IyDG .


Giải pháp cho điều kiện như vậy là hai

  1. Sử dụng phạm vi mới bằng cách sử dụng {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
  2. Hoặc sử dụng tuyên bố giả với nhãn

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
  3. Khai báo biến trước khi chuyển đổi () và khởi tạo nó với các giá trị khác nhau trong câu lệnh case nếu nó đáp ứng yêu cầu của bạn

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

Một số điều nữa với câu lệnh switch

Không bao giờ viết bất kỳ câu lệnh nào trong công tắc không phải là một phần của bất kỳ nhãn nào, bởi vì chúng sẽ không bao giờ được thực thi:

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

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

    default:
        break;
}

Xem http://codepad.org/PA1quYX3 .


2
Bạn đã mô tả chính xác vấn đề C. Nhưng khẳng định rằng trong C ++ không được phép khởi tạo nhãn là hoàn toàn không đúng. Không có gì sai với khởi tạo được gắn nhãn trong C ++. Điều mà C ++ không cho phép là nhảy qua khởi tạo biến avào phạm vi của biến a. Vì vậy, từ quan điểm C, các vấn đề là với case VAL:nhãn và bạn đã mô tả chính xác. Nhưng theo quan điểm của C ++, vấn đề là ở case ANOTHER_VAL:nhãn hiệu.
AnT

Trong C ++, không giống như trong C, các khai báo là một tập hợp con của các câu lệnh.
Keith Thompson

20

Bạn không thể làm điều này, vì casecác nhãn thực sự chỉ là các điểm nhập vào khối chứa.

Điều này được minh họa rõ ràng nhất bởi thiết bị của Duff . Đây là một số mã từ 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);
    }
}

Lưu ý cách các casenhãn hoàn toàn bỏ qua các ranh giới khối. Vâng, điều này là xấu xa. Nhưng đây là lý do tại sao ví dụ mã của bạn không hoạt động. Nhảy tới một casenhãn giống như sử dụng goto, vì vậy bạn không được phép nhảy qua một biến cục bộ với hàm tạo.

Như một số áp phích khác đã chỉ ra, bạn cần đặt một khối của riêng bạn:

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

1
Việc triển khai thiết bị của Duff này có một lỗi khiến nó cực kỳ chậm: đếm là kiểu int nên% phải thực hiện thao tác chia / modulo thực. Đặt số lượng không dấu (hoặc tốt hơn nữa, luôn sử dụng size_t cho số đếm / chỉ số) và vấn đề sẽ biến mất.
R .. GitHub DỪNG GIÚP ICE

1
@R ..: Gì?! Trong hệ thống bổ sung của hai, độ ký không ảnh hưởng đến modul bởi lũy thừa 2 (nó chỉ là AND ở các bit dưới cùng) và không ảnh hưởng đến sự phân chia theo quyền hạn 2 miễn là kiến ​​trúc bộ xử lý của bạn có hoạt động dịch chuyển sang phải số học ( SARtrong x86, so với SHRca làm việc không dấu).
Chris Jester-Young

@Chris: Tôi tin rằng anh ta có nghĩa là khi trình biên dịch phải cho phép các giá trị âm trong đó "chỉ một AND trên các bit dưới cùng" không giữ; ví dụ: -1% 8 cho -1 trên hệ thống bổ sung của hai bằng cách sử dụng g ++ (dấu hiệu trong trường hợp này là triển khai được xác định trên 5,6 / 4).

3
@Chris: Tôi đồng ý với bạn rằng R đang phóng đại tác động; Tôi chỉ thấy bình luận của bạn và biết một điều đơn giản VÀ không đủ.

1
Cũng đáng chú ý là mã Wikipedia gốc là để gửi dữ liệu đến đầu ra được ánh xạ bộ nhớ, trông có vẻ kỳ lạ ở đây vì nó không được đề cập và mỗi byte được sao chép vào cùng một vị trí "đến". Có thể khắc phục điều đó bằng cách thêm postfix ++ vào, hoặc đề cập đến trường hợp sử dụng là cho IO được ánh xạ bộ nhớ. Hoàn toàn ngoại vi cho câu hỏi ban đầu :-).
Peter

16

Hầu hết các câu trả lời cho đến nay đều sai ở một khía cạnh: bạn có thể khai báo các biến sau câu lệnh tình huống, nhưng bạn không thể khởi tạo chúng:

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

Như đã đề cập trước đây, một cách hay để giải quyết vấn đề này là sử dụng niềng răng để tạo phạm vi cho trường hợp của bạn.


1
Ông 32 bạn đã hiểu nhầm lỗi của bạn là gì: có, điều đó sẽ không được biên dịch nhưng không phải vì bạn đang khai báo một biến trong một công tắc. Lỗi là do bạn đang cố khai báo một biến sau một câu lệnh, đó là bất hợp pháp trong C.
MrZebra

1
Bây giờ là một ngày hợp pháp trong c90 và phiên bản mới hơn của c
Jeegar Patel

12

Thủ thuật chuyển đổi ác yêu thích của tôi là sử dụng if (0) để bỏ qua nhãn trường hợp không mong muốn.

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

Nhưng rất ác.


Rất đẹp. Ví dụ về lý do: trường hợp 0 ​​và trường hợp 1 có thể khởi tạo một biến khác nhau sau đó được sử dụng trong trường hợp 2.
hlovdal

1
Nếu bạn muốn cả trường hợp 0 ​​và trường hợp 1 rơi vào trường hợp 2. (không có trường hợp 0 ​​rơi vào trường hợp 1). Không biết nó thực sự hữu ích, nhưng chắc chắn hoạt động.
Petruza

1
Bạn chỉ có thể chuyển đến nhãn được yêu cầu gotomà không làm xáo trộn mã
Một số Tên người dùng


7

Bạn có thể khai báo các biến trong câu lệnh switch nếu bạn bắt đầu một khối mới:

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

Lý do là để phân bổ (và lấy lại) không gian trên ngăn xếp để lưu trữ (các) biến cục bộ.


1
Biến có thể được khai báo, nhưng nó không thể được khởi tạo. Ngoài ra, tôi khá chắc chắn rằng vấn đề không liên quan đến dù sao với các biến cục bộ và biến cục bộ.
Richard Corden

6

Xem xét:

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

Trong trường hợp không có câu lệnh break, đôi khi newVal được khai báo hai lần và bạn không biết liệu nó có thực hiện cho đến khi chạy không. Tôi đoán là giới hạn là do loại nhầm lẫn này. Phạm vi của newVal sẽ là gì? Công ước sẽ ra lệnh rằng nó sẽ là toàn bộ khối chuyển đổi (giữa các dấu ngoặc).

Tôi không phải là lập trình viên C ++, nhưng trong C:

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

Hoạt động tốt. Khai báo một biến trong một khối chuyển đổi là tốt. Tuyên bố sau một trường hợp bảo vệ là không.


3
@ Mr.32: thực sự ví dụ của bạn cho thấy printf không được thực thi, nhưng trong trường hợp này, int x không phải là câu lệnh mà là khai báo, x được khai báo, không gian cho nó được dành riêng mỗi khi môi trường hàm được xếp chồng lên nhau, xem: codepad.org/4E9Zuz1e
Petruza

Tôi đã mong đợi để tìm thấy điều này khi đọc tiêu đề của câu hỏi, bởi vì câu hỏi không phải là về việc khai báo các biến trong nhãn "case:", mà là trong các câu lệnh chuyển đổi. Và chỉ có bạn (và VictorH, nhấn mạnh câu trả lời của bạn) thực sự đã nói về các biến trong các câu lệnh chuyển đổi.
chấm dứt

4

Toàn bộ phần của chuyển đổi là một bối cảnh khai báo duy nhất. Bạn không thể khai báo một biến trong một tuyên bố trường hợp như thế. Hãy thử điều này thay thế:

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

Biến có thể được khai báo, nhưng nó không thể được khởi tạo.
Richard Corden

@Richard Corden Tôi tự tin rằng việc khởi tạo sẽ hoạt động. Bạn vẫn khẳng định nó không thể được khởi tạo?
chux - Phục hồi lại

3

Nếu mã của bạn nói "int newVal = 42" thì bạn có thể mong đợi một cách hợp lý rằng newVal không bao giờ bị hủy bỏ. Nhưng nếu bạn xem qua tuyên bố này (đó là những gì bạn đang làm) thì đó chính xác là những gì xảy ra - newVal nằm trong phạm vi nhưng chưa được chỉ định.

Nếu đó là những gì bạn thực sự có ý định xảy ra thì ngôn ngữ yêu cầu phải làm cho nó rõ ràng bằng cách nói "int newVal; newVal = 42;". Nếu không, bạn có thể giới hạn phạm vi của newVal trong trường hợp duy nhất, nhiều khả năng là những gì bạn muốn.

Nó có thể làm rõ mọi thứ nếu bạn xem xét cùng một ví dụ nhưng với "const int newVal = 42;"


3

Tôi chỉ muốn nhấn mạnh mỏng 's điểm . Một cấu trúc chuyển đổi tạo ra một phạm vi toàn bộ, hạng nhất-công dân. Vì vậy, có thể khai báo (và khởi tạo) một biến trong câu lệnh chuyển đổi trước nhãn trường hợp đầu tiên, không có cặp ngoặc bổ sung:

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

-1 ở đây int newVal = 42; sẽ không bao giờ được thực hiện. xem codepad.org/PA1quYX3
Jeegar Patel

4
khai báo int newVal sẽ được thực thi, nhưng không được = 42gán.
Petruza

3

Cho đến nay các câu trả lời đã được dành cho C ++.

Đối với C ++, bạn không thể chuyển qua khởi tạo. Bạn có thể trong C. Tuy nhiên, trong C, một khai báo không phải là một câu lệnh và các nhãn trường hợp phải được theo sau bởi các câu lệnh.

Vì vậy, C hợp lệ (nhưng xấu), C ++ không hợp lệ

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

Ngược lại, trong C ++, khai báo là một câu lệnh, do đó, sau đây là C ++ hợp lệ, C không hợp lệ

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

1
Ví dụ thứ hai KHÔNG hợp lệ C ++ (thử nghiệm với vc2010 và gcc 4.6.1 C ++ không cho phép bỏ qua phần khởi tạo. Thông báo lỗi gcc là: khởi tạo chéo của 'int i'
zhaorufei

3

Điều thú vị là điều này là tốt:

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

case 1:  
    break;
}

... nhưng đây không phải là:

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

case 1:  
    break;
}

Tôi hiểu rằng một bản sửa lỗi là đủ đơn giản, nhưng tôi vẫn chưa hiểu tại sao ví dụ đầu tiên không làm phiền trình biên dịch. Như đã đề cập trước đó (2 năm trước hehe), khai báo không phải là nguyên nhân gây ra lỗi, ngay cả khi có logic. Khởi tạo là vấn đề. Nếu biến được khởi tạo và khai báo trên các dòng khác nhau, nó sẽ biên dịch.


1
Đầu tiên là không ổn trên gcc 4.2: "lỗi: biểu thức dự kiến ​​trước 'int'". Như Peter và Mr.32 nói, "trường hợp 0 ​​:; int j; ..." và "trường hợp 0 ​​:; int j = 7; ..." làm cả hai công việc. Vấn đề trong C chỉ là "trường hợp <nhãn>: khai báo" không phải là cú pháp C hợp lệ.
dubiousjim

3

Tôi đã viết câu trả lời này cho câu hỏi này . Tuy nhiên khi tôi hoàn thành nó tôi thấy rằng câu trả lời đã bị đóng. Vì vậy, tôi đã đăng nó ở đây, có lẽ ai đó thích tham khảo tiêu chuẩn sẽ thấy nó hữu ích.

Mã gốc trong câu hỏi:

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

Thực tế có 2 câu hỏi:

1. Tại sao tôi có thể khai báo một biến sau casenhãn?

Đó là bởi vì trong nhãn C ++ phải ở dạng:

N3337 6.1 / 1

dán nhãn-tuyên bố:

...

  • thuộc tính-specifier-seqopt case constant-expression :statement

...

Và trong C++ tuyên bố tuyên bố cũng được coi là tuyên bố (trái ngược với C):

N3337 6/1:

tuyên bố :

...

bản tuyên bố

...

2. Tại sao tôi có thể nhảy qua khai báo biến và sau đó sử dụng nó?

Vì: N3337 6,7 / 3

Có thể chuyển thành một khối, nhưng không phải theo cách bỏ qua các khai báo với khởi tạo . Một chương trình nhảy (Việc chuyển từ điều kiện của câu lệnh chuyển sang nhãn trường hợp được coi là một bước nhảy trong khía cạnh này.)

từ một điểm trong đó một biến có thời gian lưu trữ tự động không nằm trong phạm vi đến một điểm có phạm vi không được định dạng trừ khi biến đó có kiểu vô hướng , loại lớp với hàm tạo mặc định tầm thường và hàm hủy tầm thường, phiên bản đủ điều kiện cv của một trong các loại này, hoặc một mảng của một trong các loại trước đó và được khai báo mà không có bộ khởi tạo (8.5).

kloại vô hướng , và không được khởi tạo tại điểm khai báo nhảy qua khai báo nên có thể. Điều này tương đương về mặt ngữ nghĩa:

goto label;

int x;

label:
cout << x << endl;

Tuy nhiên, điều đó là không thể, nếu xđược khởi tạo tại điểm khai báo:

 goto label;

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

    label:
    cout << x << endl;

1

Các biến mới chỉ có thể được dán nhãn ở phạm vi khối. Bạn cần phải viết một cái gì đó như thế này:

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

Tất nhiên, newVal chỉ có phạm vi trong phạm vi ...

Chúc mừng, Ralph


1

Một switchkhối không giống như một chuỗi if/else ifcác khối. Tôi ngạc nhiên không có câu trả lời khác giải thích rõ ràng.

Hãy xem xét switchtuyên bố này :

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

Nó có thể gây ngạc nhiên, nhưng trình biên dịch sẽ không xem nó là đơn giản if/else if. Nó sẽ tạo ra mã sau đây:

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

Các casebáo cáo được chuyển đổi thành nhãn và sau đó được gọi với goto. Dấu ngoặc tạo một phạm vi mới và bây giờ dễ dàng thấy tại sao bạn không thể khai báo hai biến có cùng tên trong một switchkhối.

Nó có thể trông lạ, nhưng nó là cần thiết để hỗ trợ fallthrough (có nghĩa là, không sử dụng breakđể thực hiện tiếp tục tiếp theo case).


0

Tôi tin rằng vấn đề hiện tại là tuyên bố đã bị bỏ qua và bạn đã thử sử dụng var ở nơi khác, nó sẽ không được khai báo.


0

newVal tồn tại trong toàn bộ phạm vi của công tắc nhưng chỉ được khởi tạo nếu nhấn vào chi VAL. Nếu bạn tạo một khối xung quanh mã trong VAL thì sẽ ổn.


0

C ++ Standard có: Có thể chuyển thành một khối, nhưng không phải theo cách bỏ qua các khai báo với khởi tạo. Một chương trình nhảy từ một điểm trong đó một biến cục bộ có thời gian lưu trữ tự động không nằm trong phạm vi đến một điểm mà nó nằm trong phạm vi không được định dạng trừ khi biến đó có loại POD (3.9) và được khai báo mà không có bộ khởi tạo (8.5).

Mã để minh họa quy tắc này:

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

Mã để hiển thị hiệu ứng khởi tạo:

#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

Dường như các đối tượng ẩn danh có thể được khai báo hoặc tạo trong một tuyên bố trường hợp chuyển đổi với lý do chúng không thể được tham chiếu và vì vậy không thể chuyển sang trường hợp tiếp theo. Xem xét ví dụ này biên dịch trên GCC 4.5.3 và Visual Studio 2008 (có thể là vấn đề tuân thủ tho 'vì vậy các chuyên gia vui lòng cân nhắc)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

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

    default:
        break;
    }
    return EXIT_SUCCESS;
}

Nếu bạn sẽ bỏ phiếu xuống, vui lòng giải thích lý do. Tôi tò mò muốn biết tại sao việc tạo một đối tượng ẩn danh dường như là một sự miễn trừ.
Olumide

1
không phải là DV, nhưng: Toàn bộ câu hỏi là về khai báo / phạm vi của các biến được đặt tên. Một tạm thời ("đối tượng ẩn danh" không phải là một thuật ngữ) không phải là một biến được đặt tên, nó cũng không phải là một tuyên bố, cũng không phải là phạm vi (trừ khi bị ràng buộc với một consttham chiếu có phạm vi của chính nó). Đó là một biểu hiện sống và chết trong tuyên bố của nó (bất cứ nơi nào có thể). Do đó, nó hoàn toàn không liên quan.
gạch dưới

Foo();không phải là một tuyên bố; Câu hỏi là về khai báo.
MM
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.