Sử dụng {} trong một câu lệnh trường hợp. Tại sao?


101

Mục đích của việc sử dụng {}trong một casecâu lệnh là gì? Thông thường, bất kể có bao nhiêu dòng trong một casecâu lệnh, tất cả các dòng đều được thực thi. Đây chỉ là một quy tắc liên quan đến các trình biên dịch cũ hơn / mới hơn hay có điều gì đó đằng sau đó?

int a = 0;
switch (a) {
  case 0:{
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
  }
}

int a = 0;
switch (a) {
  case 0:
    std::cout << "line1\n";
    std::cout << "line2\n";
    break;
}

57
Một cách sử dụng có thể là giới hạn phạm vi của các biến được khai báo trong câu lệnh case.
Abhishek Bansal


1
Quá nhiều thụt lề. Các trường hợp chỉ là các nhãn trong khối của câu lệnh switch: chúng không giới thiệu lồng ghép bổ sung, vì vậy chúng phải phù hợp với switchtừ khóa và trong ví dụ thứ hai, các câu lệnh kèm theo chỉ được thụt lề một lần. Lưu ý cách bạn có dấu thụt lề bốn dấu cách khó xử sau dấu break;.
Kaz

Lưu ý, câu trả lời được chấp nhận chỉ đúng một phần vì nhận xét của Jack đã chỉ ra và bỏ sót một số điều tinh tế mà tôi đề cập trong câu trả lời của mình.
Shafik Yaghmour

Cũng giống như một FYI: trong C (thậm chí C11) chứ không phải C ++, bạn không thể gắn nhãn một khai báo; chúng không thuộc phạm trù cú pháp statement. Trong C ++, bạn có thể (một thành phần của danh mục cú pháp statementdeclaration statement).
Jonathan Leffler

Câu trả lời:


195

Các {}biểu thị một khối mới của phạm vi .

Hãy xem xét ví dụ rất giả sau:

switch (a)
{
    case 42:
        int x = GetSomeValue();
        return a * x;
    case 1337:
        int x = GetSomeOtherValue(); //ERROR
        return a * x;
}

Bạn sẽ gặp lỗi trình biên dịch vì xđã được xác định trong phạm vi.

Việc tách chúng thành phạm vi con của riêng chúng sẽ loại bỏ sự cần thiết phải khai báo xbên ngoài câu lệnh switch.

switch (a)
{
    case 42: {
        int x = GetSomeValue();
        return a * x; 
    }
    case 1337: {
        int x = GetSomeOtherValue(); //OK
        return a * x; 
    }
}

11
Trên thực tế IMO bạn sẽ gặp lỗi trình biên dịch ngay cả khi bạn bỏ qua phần khai báo thứ hai của biến x.
Abhishek Bansal

1
Mặc dù việc sử dụng quá nhiều kiểu này và đặt các khối lớn bên trong câu lệnh switch sẽ khiến bạn không thể đọc được theo các trường hợp. Tôi thích giữ các báo cáo nhỏ.
masoud

2
@MatthieuM. Tôi biết thực tế là MS Visual Studio 2010 sẽ có hành vi mà Abhishek chỉ ra: nó sẽ không biên dịch bất kỳ khai báo biến nào bên trong một trường hợp (trừ khi bạn sử dụng dấu ngoặc nhọn để biểu thị một phạm vi mới trong trường hợp đó). Liệu điều đó có phù hợp với các tiêu chuẩn hay không, tôi không biết.
KRyan

1
@KRyan: thì không, nhưng đó là một giải pháp thay thế an toàn hơn nhiều nên tôi khó có thể trách họ vì đã thực thi điều này.
Matthieu M.

6
Phần 6.7 (3) của tiêu chuẩn (đánh số cho bản nháp năm 2005) quy định rằng bạn không thể chuyển một lần khởi tạo, vì vậy bạn không thể khởi tạo trong một khối trường hợp.
Jack Aidley

23

TL; DR

Cách duy nhất bạn có thể khai báo một biến với intializer hoặc một số đối tượng không tầm thường bên trong một trường hợp là sử dụng phạm vi khối bằng cách sử dụng {}hoặc cấu trúc điều khiển khác có phạm vi riêng của nó như một vòng lặp hoặc câu lệnh if .

Chi tiết về máu

Chúng ta có thể thấy rằng trường hợp chỉ là báo cáo được dán nhãn như nhãn sử dụng với một goto statement ( này được bao phủ trong C ++ dự thảo tiêu chuẩn phần 6.1 tuyên bố đã được gán nhãn ) và chúng ta có thể nhìn thấy từ phần 6.7đoạn 3 mà nhảy qua một tuyên bố không được phép trong nhiều trường hợp , bao gồm những thứ có khởi tạo:

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

và cung cấp ví dụ này:

void f() {
 // ...
 goto lx; // ill-formed: jump into scope of a

 ly:
  X a = 1;
 // ...
 lx:
  goto ly; // OK, jump implies destructor
          // call for a followed by construction
          // again immediately following label ly
}

Lưu ý, có một số điều tinh tế ở đây, bạn được phép lướt qua một khai báo vô hướng không có phần khởi tạo, ví dụ:

switch( n ) 
{
    int x ;
    //int x  = 10 ; 
    case 0:
      x = 0 ;
      break;
    case 1:
      x = 1 ;
      break;
    default:
      x = 100 ;
      break ;
}

là hoàn toàn hợp lệ ( ví dụ trực tiếp ). Tất nhiên nếu bạn muốn khai báo cùng một biến trong mỗi trường hợp thì chúng sẽ cần phạm vi riêng của chúng nhưng nó hoạt động theo cùng một cách bên ngoài các câu lệnh switch , vì vậy đó không phải là một bất ngờ lớn.

Về lý do không cho phép khởi tạo quá khứ nhảy, báo cáo lỗi 467 mặc dù bao gồm một vấn đề hơi khác nhưng cung cấp một trường hợp hợp lý cho các biến tự động :

[...] các biến tự động, nếu không được khởi tạo rõ ràng, có thể có các giá trị không xác định ("rác"), bao gồm các biểu diễn bẫy, [...]

Có lẽ sẽ thú vị hơn khi xem xét trường hợp bạn mở rộng phạm vi trong một công tắc qua nhiều trường hợp , ví dụ nổi tiếng nhất về điều này có lẽ là thiết bị của Duff trông giống như sau:

void send( int *to, const int *from, int  count)
{
        int n = (count + 7) / 8;
        switch(count % 8) 
        {
            case 0: do {    *to = *from++;   // <- Scope start
            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);    // <- Scope end
        }
}

6

Đây là một thói quen cho phép bạn đưa các khai báo biến với hàm hủy kết quả (hoặc xung đột phạm vi) vào casecác mệnh đề. Một cách khác để xem xét nó là họ đang viết ngôn ngữ mà họ mong muốn, nơi mà tất cả điều khiển luồng bao gồm các khối chứ không phải chuỗi các câu lệnh.


4

Kiểm tra đây là một hạn chế cơ bản của trình biên dịch và bạn sẽ bắt đầu tự hỏi điều gì đang xảy ra:

int c;
c=1;

switch(c)
{
    case 1:
    //{
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    //}

    default : cout<<"def";
}

Điều này sẽ cung cấp cho bạn một lỗi:

error: jump to case label [-fpermissive]
error:   crosses initialization of int* i

Trong khi điều này sẽ không:

int c;
c=1;

switch(c)
{
    case 1:
    {
        int *i = new int;
        *i =1;
        cout<<*i;
        break;
    }

    default : cout<<"def";
}

1

Sử dụng dấu ngoặc trong switch biểu thị một khối phạm vi mới như Rotem đã nói.

Nhưng nó cũng có thể được rõ ràng khi bạn đọc. Để biết trường hợp dừng ở đâu vì bạn có thể bị vỡ điều kiện trong đó.


0

Lý do có thể là:

  1. Khả năng đọc, nó nâng cao trực quan từng trường hợp như một phần có phạm vi.
  2. Khai báo một biến khác có cùng tên cho một số trường hợp chuyển đổi.
  3. Tối ưu hóa vi mô- phạm vi cho một biến được phân bổ tài nguyên thực sự đắt tiền mà bạn muốn hủy ngay khi rời khỏi phạm vi của trường hợp, hoặc thậm chí một kịch bản spaghetti hơn về việc sử dụng lệnh "GOTO".
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.