Tại sao chúng ta phải sử dụng break trong switch?


74

Ai đã quyết định (và dựa trên những khái niệm nào) mà việc switchxây dựng (bằng nhiều ngôn ngữ) phải sử dụng breaktrong mỗi tuyên bố?

Tại sao chúng ta phải viết một cái gì đó như thế này:

switch(a)
{
    case 1:
        result = 'one';
        break;
    case 2:
        result = 'two';
        break;
    default:
        result = 'not determined';
        break;
}

(nhận thấy điều này trong PHP và JS; có thể có nhiều ngôn ngữ khác sử dụng ngôn ngữ này)

Nếu switchlà một thay thế if, tại sao chúng ta không thể sử dụng cùng một cấu trúc như cho if? I E:

switch(a)
{
    case 1:
    {
        result = 'one';
    }
    case 2:
    {
        result = 'two';
    }
    default:
    {
        result = 'not determined';
    }
}

Người ta nói rằng breakngăn chặn việc thực hiện khối theo sau hiện tại. Nhưng, có ai đó thực sự gặp phải tình huống này, nơi có nhu cầu thực hiện khối hiện tại và theo sau không? Tôi đã không. Đối với tôi, breakluôn luôn có. Trong mỗi khối. Trong mỗi mã.


1
Như hầu hết các câu trả lời đã lưu ý, điều này có liên quan đến 'rơi qua', trong đó nhiều điều kiện được chuyển qua cùng một khối 'rồi'. Tuy nhiên, đây là phụ thuộc vào ngôn ngữ - ví dụ như RPG, xử lý một CASEcâu lệnh tương đương với một khối if / otherif khổng lồ.
Đồng hồ-Muse

34
Đó là một quyết định tồi của các nhà thiết kế C (giống như nhiều quyết định, nó được thực hiện để thực hiện quá trình chuyển đổi từ lắp ráp -> C, và dịch trở lại, dễ dàng hơn thay vì dễ lập trình) , sau đó không may được kế thừa trong các ngôn ngữ dựa trên C khác. Sử dụng quy tắc đơn giản "Luôn đặt trường hợp phổ biến thành mặc định !!" , chúng ta có thể thấy rằng hành vi mặc định đáng lẽ phải bị phá vỡ, với một từ khóa riêng để bỏ qua, vì đó là trường hợp phổ biến nhất.
BlueRaja - Daniel Pflughoeft

4
Tôi đã sử dụng thông qua nhiều lần, mặc dù có thể cho rằng câu lệnh chuyển đổi trong ngôn ngữ ưa thích của tôi tại thời điểm đó (C) có thể được thiết kế để tránh điều đó; ví dụ tôi đã làm case 'a': case 'A': case 'b': case 'B'nhưng chủ yếu là vì tôi không thể làm được case in [ 'a', 'A', 'b', 'B' ]. Một câu hỏi tốt hơn một chút là, trong ngôn ngữ ưa thích hiện tại của tôi (C #), việc nghỉ là bắt buộc và không có sự sụp đổ ngầm; quên breaklà lỗi cú pháp ...: \
KutuluMike

1
Dự phòng với mã thực tế thường được tìm thấy trong các triển khai trình phân tích cú pháp. case TOKEN_A: /*set flag*/; case TOKEN_B: /*consume token*/; break; case TOKEN_C: /*...*/
OrangeDog

1
@ BlueRaja-DannyPflughoeft: C cũng được thiết kế để dễ biên dịch. “Phát ra một bước nhảy nếu breakcó mặt ở bất cứ đâu” là một quy tắc đơn giản hơn đáng kể để thực hiện hơn “Đừng phát ra một bước nhảy nếu fallthroughcó mặt trong một switch”.
Jon Purdy

Câu trả lời:


95

C là một trong những ngôn ngữ đầu tiên có switchtuyên bố ở dạng này và tất cả các ngôn ngữ chính khác được thừa hưởng từ đó, chủ yếu chọn giữ ngữ nghĩa C theo mặc định - họ không nghĩ đến những lợi thế của việc thay đổi hoặc đánh giá chúng ít quan trọng hơn việc giữ hành vi mà mọi người đã quen.

Về lý do tại sao C được thiết kế theo cách đó, có lẽ nó bắt nguồn từ khái niệm C là "lắp ráp di động". Các switchtuyên bố về cơ bản là một sự trừu tượng của một bảng chi nhánh , và một bảng chi nhánh cũng có một tiềm ẩn mùa thu qua và yêu cầu một lệnh nhảy thêm để tránh nó.

Vì vậy, về cơ bản, các nhà thiết kế của C cũng chọn giữ ngữ nghĩa của trình biên dịch chương trình mặc định.


3
VB.NET là ví dụ về ngôn ngữ đã thay đổi nó. Không có nguy cơ của việc nó chảy vào mệnh đề trường hợp.
chọc

1
Một ngôn ngữ khác không bao giờ thuộc về mệnh đề tiếp theo. Hầu như không bao giờ muốn làm điều đó (không giống như trong C, nơi nó hữu ích khi viết một số loại chương trình nhất định).
Donal Fellows

4
Các ngôn ngữ tổ tiên của C, BBCPL cũ hơn , cả hai đều có câu lệnh chuyển đổi (có?); BCPL đánh vần nó switchon.
Keith Thompson

Báo cáo trường hợp của Ruby không rơi vào; bạn có thể có hành vi tương tự bằng cách có hai giá trị thử nghiệm trong một when.
Nathan Long

86

Bởi vì switchkhông phải là một thay thế của các if ... elsetuyên bố trong các ngôn ngữ.

Bằng cách sử dụng switch, chúng ta có thể phù hợp với nhiều điều kiện cùng một lúc, được đánh giá cao trong một số trường hợp.

Thí dụ:

public Season SeasonFromMonth(Month month)
{
    Season season;
    switch (month)
    {
        case Month.December:
        case Month.January:
        case Month.February:
            season = Season.Winter;
            break;

        case Month.March:
        case Month.April:
        case Month.May:
            season = Season.Spring;
            break;

        case Month.June:
        case Month.July:
        case Month.August:
            season = Season.Summer;
            break;

        default:
            season = Season.Autumn;
            break;
    }

    return season;
}

15
more than one block ... unwanted in most casesTôi không đồng ý với bạn.
Satish Pandey

2
@DanielB Nó phụ thuộc vào ngôn ngữ mặc dù; Các câu lệnh chuyển đổi C # không cho phép khả năng đó.
Andy

5
@Andy Có phải chúng ta đang nói về sự sụp đổ? Các câu lệnh chuyển đổi C # chắc chắn cho phép nó (kiểm tra ví dụ thứ 3), do đó cần phải có break. Nhưng vâng, theo như tôi biết, Thiết bị của Duff không hoạt động trong C #.
Daniel B

8
@DanielB Có, nhưng trình biên dịch sẽ không cho phép một điều kiện, mã và sau đó là một điều kiện khác mà không nghỉ. Bạn có thể có các điều kiện khác nhau được xếp chồng lên nhau mà không có mã giữa hoặc bạn cần nghỉ ngơi. Từ liên kết của bạn C# does not support an implicit fall through from one case label to another. Bạn có thể rơi qua, nhưng không có cơ hội vô tình quên nghỉ.
Andy

3
@Matsemann .. Bây giờ tôi có thể làm tất cả mọi thứ bằng cách sử dụng if..elsenhưng trong một số trường hợp switch..casesẽ tốt hơn. Ví dụ trên sẽ hơi phức tạp nếu chúng ta sử dụng if-other.
Satish Pandey

15

Điều này đã được hỏi về Stack Overflow trong ngữ cảnh của C: Tại sao câu lệnh switch được thiết kế để cần nghỉ?

Để tóm tắt câu trả lời được chấp nhận, nó có thể là một sai lầm. Hầu hết các ngôn ngữ khác có thể chỉ theo C. Tuy nhiên, một số ngôn ngữ như C # dường như đã khắc phục điều này bằng cách cho phép thông qua - nhưng chỉ khi lập trình viên nói rõ ràng như vậy (nguồn: liên kết ở trên, tôi không tự nói C #) .


Google Go thực hiện điều tương tự IIRC (cho phép dự phòng nếu được chỉ định rõ ràng).
Leo

PHP cũng vậy. Và bạn càng nhìn vào mã kết quả, nó càng khiến bạn cảm thấy khó chịu. Tôi thích /* FALLTHRU */nhận xét trong câu trả lời được liên kết bởi @Tapio ở trên, để chứng minh bạn có ý đó.
DaveP

1
Nói C # goto case, như liên kết, không minh họa tại sao nó hoạt động tốt như vậy. Bạn vẫn có thể xếp chồng nhiều trường hợp như trên, nhưng ngay khi bạn chèn mã, bạn phải có một tường minh goto case whateverđể chuyển sang trường hợp tiếp theo hoặc bạn có được trình biên dịch. Nếu bạn hỏi tôi, trong số hai, khả năng xếp chồng các trường hợp mà không lo lắng về sơ suất do sơ suất do sơ suất là một tính năng tốt hơn so với việc cho phép thông qua rõ ràng goto case.
Jesper

9

Tôi sẽ trả lời với một ví dụ. Nếu bạn muốn liệt kê số ngày cho mỗi tháng trong năm, thì rõ ràng là một số tháng có 31, một số 30 và 1 28/29. Nó sẽ trông như thế này,

switch(month) {
    case 4:
    case 6:
    case 9:
    case 11;
        days = 30;
        break;
    case 2:
        //Find out if is leap year( divisible by 4 and all that other stuff)
        days = 28 or 29;
        break;
    default:
        days = 31;
}

Đây là một ví dụ trong đó nhiều trường hợp có cùng tác dụng và tất cả được nhóm lại với nhau. Rõ ràng có một lý do cho việc lựa chọn từ khóa break và không phải là ... khác nếu xây dựng.

Điều quan trọng để lưu ý ở đây là một chuyển đổi tuyên bố với nhiều trường hợp tương tự không phải là một if ... else if ... else if ... else cho mỗi người trong số các trường hợp, mà đúng hơn là if (1, 2, 3) ... khác nếu (4,5,6) khác ...


2
Ví dụ của bạn trông dài dòng hơn là ifkhông có lợi ích gì. Có lẽ nếu ví dụ của bạn được gán tên hoặc thực hiện công việc cụ thể theo tháng ngoài việc thông qua, thì tiện ích của thông qua sẽ rõ ràng hơn? (không bình luận về việc liệu một người NÊN ở vị trí đầu tiên)
horatio

1
Điều đó đúng. Tuy nhiên, tôi chỉ cố gắng chỉ ra những trường hợp có thể sử dụng được. Rõ ràng sẽ tốt hơn nhiều nếu mỗi tháng cần một cái gì đó được thực hiện riêng lẻ.
Awemo

8

Có hai tình huống có thể xảy ra khi rơi vào trường hợp từ trường hợp này sang trường hợp khác - trường hợp trống:

switch ( ... )
{
  case 1:
  case 2:
    do_something_for_1_and_2();
    break;
  ...
}

và trường hợp không trống

switch ( ... )
{
  case 1:
    do_something_for_1();
    /* Deliberately fall through */
  case 2:
    do_something_for_1_and_2();
    break;
...
}

Mặc dù tham chiếu đến Thiết bị của Duff , các trường hợp hợp pháp cho trường hợp thứ hai rất ít và xa, và thường bị cấm bởi các tiêu chuẩn mã hóa và được gắn cờ trong quá trình phân tích tĩnh. Và nơi nó được tìm thấy, nó thường xuyên hơn không phải do thiếu sót của a break.

Các cựu là hoàn toàn hợp lý và phổ biến.

Thành thật mà nói, tôi có thể thấy không có lý do gì để cần breakvà trình phân tích cú pháp ngôn ngữ biết rằng một trường hợp trống rỗng là một trường hợp rơi, trong khi một trường hợp không trống là độc lập.

Thật đáng tiếc khi bảng điều khiển ISO C dường như bận tâm hơn với việc thêm các tính năng mới (không mong muốn) và được xác định xấu vào ngôn ngữ, thay vì sửa các tính năng không xác định, không xác định hoặc xác định thực hiện, không đề cập đến tính phi logic.


2
Cảm ơn chúa, ISO C không giới thiệu những thay đổi đột phá trong ngôn ngữ!
Jack Aidley

4

Không ép buộc breakcho phép một số điều có thể khó thực hiện. Những người khác đã lưu ý các trường hợp nhóm, trong đó có một số trường hợp không tầm thường.

Một trường hợp bắt buộc breakkhông được sử dụng là Thiết bị của Duff . Này được sử dụng để " cuộn " vòng nơi nó có thể tăng tốc độ hoạt động bằng cách hạn chế số lượng so sánh yêu cầu. Tôi tin rằng chức năng cho phép sử dụng ban đầu mà trước đây quá chậm với một vòng lặp hoàn chỉnh. Nó giao dịch kích thước mã cho tốc độ trong một số trường hợp.

Đó là một thực hành tốt để thay thế breakbằng một nhận xét thích hợp, nếu trường hợp có bất kỳ mã. Nếu không, ai đó sẽ sửa lỗi thiếu break, và giới thiệu một lỗi.


Cảm ơn ví dụ về Thiết bị của Duff (+1). Tôi đã không nhận thức được điều đó.
trejder

3

Trong C, nơi gốc switchtọa độ dường như, khối mã của câu lệnh không phải là một cấu trúc đặc biệt. Nó là một khối mã bình thường, giống như một khối theo một ifcâu lệnh.

switch ()
{
}

if ()
{
}

casedefaultlà các nhãn nhảy trong khối này, liên quan cụ thể đến switch. Chúng được xử lý giống như nhãn nhảy bình thường cho goto. Có một quy tắc cụ thể rất quan trọng ở đây: Nhãn nhảy có thể ở gần như mọi nơi trong mã, mà không làm gián đoạn dòng mã.

Là một khối mã bình thường, nó không cần phải là một câu lệnh ghép. Các nhãn là tùy chọn, quá. Đây là những switchtuyên bố hợp lệ trong C:

switch (a)
    case 1: Foo();

switch (a)
    Foo();

switch (a)
{
    Foo();
}

Bản thân tiêu chuẩn C đưa ra điều này như một ví dụ (6.8.4.2):

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

Trong đoạn chương trình nhân tạo, đối tượng có mã định danh i tồn tại với thời lượng lưu trữ tự động (trong khối) nhưng không bao giờ được khởi tạo và do đó, nếu biểu thức điều khiển có giá trị khác 0, lệnh gọi hàm printf sẽ truy cập giá trị không xác định. Tương tự, không thể gọi được hàm f.

Hơn nữa, defaultlà một nhãn nhảy, quá, và do đó có thể là bất cứ nơi nào, mà không cần phải là trường hợp cuối cùng.

Điều này cũng giải thích Thiết bị của Duff:

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

Tại sao lại rơi? Bởi vì trong dòng mã bình thường trong một khối mã thông thường , dự kiến ​​sẽ có thông báo tiếp theo , giống như bạn mong đợi nó trong một ifkhối mã.

if (a == b)
{
    Foo();
    /* "Fall-through" to Bar expected here. */
    Bar();
}

switch (a)
{
    case 1: 
        Foo();
        /* Automatic break would violate expected code execution semantics. */
    case 2:
        Bar();
}

Tôi đoán là lý do cho việc này là dễ thực hiện. Bạn không cần mã đặc biệt để phân tích cú pháp và biên dịch một switchkhối, chăm sóc các quy tắc đặc biệt. Bạn chỉ cần phân tích nó như bất kỳ mã nào khác và chỉ phải quan tâm đến các nhãn và lựa chọn nhảy.

Một câu hỏi tiếp theo thú vị từ tất cả những điều này là nếu các câu lệnh lồng nhau sau đây in "Xong". hay không.

int a = 10;

switch (a)
{
    switch (a)
    {
        case 10: printf("Done.\n");
    }
}

Tiêu chuẩn C quan tâm đến điều này (6.8.4.2.4):

Một trường hợp hoặc nhãn mặc định chỉ có thể truy cập được trong câu lệnh chuyển đổi kèm theo gần nhất.


1

Một số người đã đề cập đến khái niệm phù hợp với nhiều điều kiện, rất có giá trị theo thời gian. Tuy nhiên, khả năng khớp nhiều điều kiện không nhất thiết đòi hỏi bạn phải làm chính xác điều tương tự với từng điều kiện phù hợp. Hãy xem xét những điều sau đây:

switch (someCase)
{
    case 1:
    case 2:
        doSomething1And2();
        break;

    case 3:
        doSomething3();

    case 4:
        doSomething3And4();
        break;

    default:
        throw new Error("Invalid Case");
}

Có hai cách khác nhau của nhiều điều kiện đang được khớp ở đây. Với điều kiện 1 và 2, họ chỉ cần rơi vào cùng một mã chính xác và thực hiện cùng một điều chính xác. Tuy nhiên, với điều kiện 3 và 4, mặc dù cả hai đều kết thúc bằng cách gọi doSomething3And4(), chỉ có 3 cuộc gọi doSomething3().


Đến bữa tiệc muộn, nhưng đây hoàn toàn không phải là "1 và 2" như tên hàm gợi ý: có ba trạng thái khác nhau có thể dẫn đến mã khi case 2kích hoạt, vì vậy nếu chúng ta muốn chính xác (và chúng ta làm) tên hàm sẽ phải doSomethingBecause_1_OR_2_OR_1AND2()hoặc một cái gì đó (mặc dù các mã hợp lệ, trong đó một bất biến phù hợp với hai trường hợp khác nhau trong khi vẫn đang được viết code đang hầu như không tồn tại, vì vậy ít nhất này nên doSomethingBecause1or2())
Mike 'Pomax' Kamermans

Nói cách khác , doSomething[ThatShouldBeDoneInTheCaseOf]1And[InTheCaseOf]2(). Nó không đề cập đến logic và / hoặc.
Panzercrisis

Tôi biết là không, nhưng đưa ra lý do tại sao mọi người nhầm lẫn về loại mã này, nó nên giải thích tuyệt đối "và" là gì, bởi vì dựa vào ai đó để đoán "tự nhiên và" so với "kết thúc hợp lý" trong một câu trả lời chỉ hoạt động khi được giải thích chính xác, cần giải thích thêm trong chính câu trả lời hoặc viết lại tên hàm để loại bỏ sự mơ hồ đó =)
Mike 'Pomax' Kamermans 12/12/17

1

Để trả lời hai câu hỏi của bạn.

Tại sao C cần nghỉ?

Nó đi xuống gốc Cs như là một "trình biên dịch di động". Trường hợp mã psudo như thế này là phổ biến: -

    targets=(addr1,addr2,addr3);
    opt = 1  ## or 0 or 2
switch:
    br targets[opt]  ## go to addr2 
addr1:
    do 1stuff
    br switchend
addr2:
    do 2stuff
    br switchend
addr3
    do 3stuff
switchend:
    ......

câu lệnh switch được thiết kế để cung cấp chức năng tương tự ở mức cao hơn.

Chúng ta có bao giờ có công tắc mà không nghỉ?

Có, điều này khá phổ biến và có một vài trường hợp sử dụng;

Đầu tiên bạn có thể muốn thực hiện hành động tương tự cho một số trường hợp. Chúng tôi làm điều này bằng cách xếp chồng các trường hợp lên nhau:

case 6:
case 9:
    // six or nine code

Một trường hợp sử dụng phổ biến khác trong các máy trạng thái là sau khi xử lý một trạng thái, chúng tôi ngay lập tức muốn nhập và xử lý trạng thái khác:

case 9:
    crashed()
    newstate=10;
case 10:
    claim_damage();
    break;

-2

Lệnh break là một câu lệnh nhảy cho phép người dùng thoát khỏi công tắc kèm theo gần nhất (đối với trường hợp của bạn), trong khi, làm, cho hoặc foreach. nó chỉ dễ dàng như vậy


-3

Tôi nghĩ rằng với tất cả những gì được viết ở trên - kết quả chính của luồng này là nếu bạn định thiết kế một ngôn ngữ mới - mặc định là bạn không cần thêm một breakcâu lệnh và trình biên dịch sẽ coi nó như thể bạn đã làm.

Nếu bạn muốn trường hợp hiếm hoi đó mà bạn muốn tiếp tục trường hợp tiếp theo - chỉ cần nêu nó bằng một continuetuyên bố.

Điều này có thể được cải thiện để chỉ khi bạn sử dụng dấu ngoặc nhọn bên trong vỏ thì nó sẽ không tiếp tục để ví dụ với các tháng trên của một số trường hợp thực hiện cùng một mã chính xác - sẽ luôn hoạt động như mong đợi mà không cần continue.


2
Tôi không chắc chắn tôi hiểu đề xuất của bạn liên quan đến tuyên bố tiếp tục. Tiếp tục có một ý nghĩa rất khác trong C và C ++ và nếu một ngôn ngữ mới giống như C, nhưng đôi khi sử dụng từ khóa như với C và đôi khi theo cách khác, tôi nghĩ sự nhầm lẫn sẽ ngự trị. Mặc dù có thể có một số vấn đề với việc rơi từ một khối được gắn nhãn này sang một khối khác, tôi thích việc sử dụng nhiều trường hợp có cùng cách xử lý với một khối mã được chia sẻ.
Nhà phát triển vào

C có truyền thống sử dụng cùng một từ khóa cho nhiều mục đích. Hãy suy nghĩ về *, &, static, vv
idrougge
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.