Để thêm vào câu trả lời ở đây, tôi nghĩ rằng đáng để xem xét câu hỏi ngược lại kết hợp với điều này, viz. Tại sao C cho phép rơi xuống ở nơi đầu tiên?
Bất kỳ ngôn ngữ lập trình tất nhiên đều phục vụ hai mục tiêu:
- Cung cấp hướng dẫn cho máy tính.
- Để lại một hồ sơ về ý định của lập trình viên.
Do đó, việc tạo ra bất kỳ ngôn ngữ lập trình nào là sự cân bằng giữa cách phục vụ tốt nhất hai mục tiêu này. Một mặt, việc biến thành các hướng dẫn máy tính càng dễ dàng hơn (cho dù đó là mã máy, mã byte như IL hay hướng dẫn được diễn giải khi thực hiện) thì quá trình biên dịch hoặc giải thích sẽ có hiệu quả, đáng tin cậy và đầu ra nhỏ gọn. Thực hiện đến cùng cực, mục tiêu này dẫn đến việc chúng ta chỉ viết bằng cách lắp ráp, IL hoặc thậm chí là mã op thô, bởi vì phần biên dịch đơn giản nhất là ở đó không có phần biên dịch nào cả.
Ngược lại, ngôn ngữ càng thể hiện ý định của lập trình viên, thay vì các phương tiện được đưa đến mục đích đó, chương trình càng dễ hiểu hơn cả khi viết và trong khi bảo trì.
Bây giờ, switch
luôn có thể được biên dịch bằng cách chuyển đổi nó thành chuỗi if-else
khối tương đương hoặc tương tự, nhưng nó được thiết kế để cho phép biên dịch thành một mô hình lắp ráp chung cụ thể trong đó người ta lấy một giá trị, tính toán một phần bù từ nó (cho dù bằng cách tra cứu bảng được lập chỉ mục bởi một hàm băm hoàn hảo của giá trị hoặc theo số học thực tế trên giá trị *). Điều đáng chú ý ở thời điểm này là ngày nay, quá trình biên dịch C # đôi khi sẽ chuyển switch
thành tương đương if-else
và đôi khi sử dụng cách tiếp cận nhảy dựa trên hàm băm (và tương tự với C, C ++ và các ngôn ngữ khác có cú pháp tương đương).
Trong trường hợp này, có hai lý do chính đáng để cho phép thông qua:
Dù sao nó cũng chỉ xảy ra một cách tự nhiên: nếu bạn xây dựng một bảng nhảy thành một tập hợp các hướng dẫn và một trong những hướng dẫn trước đó không chứa một số bước nhảy hoặc trả về, thì việc thực thi sẽ tự nhiên tiến triển thành đợt tiếp theo. Cho phép rơi xuống là điều sẽ "chỉ xảy ra" nếu bạn biến switch
C sử dụng thành bảng nhảy bằng cách sử dụng mã máy.
Các lập trình viên đã viết trong phần lắp ráp đã được sử dụng tương đương: khi viết một bảng nhảy bằng tay trong phần lắp ráp, họ sẽ phải xem xét liệu một khối mã đã cho có kết thúc bằng một sự trở lại, một bước nhảy ra khỏi bảng hay chỉ tiếp tục đến khối tiếp theo. Như vậy, việc lập trình viên thêm một phần rõ ràng break
khi cần thiết là "tự nhiên" đối với người viết mã.
Do đó, tại thời điểm đó, đây là một nỗ lực hợp lý để cân bằng hai mục tiêu của ngôn ngữ máy tính vì nó liên quan đến cả mã máy được sản xuất và tính biểu cảm của mã nguồn.
Bốn thập kỷ sau, mọi thứ không hoàn toàn giống nhau, vì một vài lý do:
- Coders trong C ngày nay có thể có ít hoặc không có kinh nghiệm lắp ráp. Các lập trình viên trong nhiều ngôn ngữ kiểu C khác thậm chí ít có khả năng hơn (đặc biệt là Javascript!). Mọi khái niệm về "những gì mọi người đã sử dụng để lắp ráp" không còn phù hợp.
- Những cải tiến trong tối ưu hóa có nghĩa là khả năng
switch
bị biến thành if-else
bởi vì nó được coi là phương pháp có khả năng hiệu quả nhất, hoặc nếu không thì biến thành một biến thể bí truyền đặc biệt của phương pháp nhảy bàn là cao hơn. Ánh xạ giữa các cách tiếp cận cấp cao hơn và cấp thấp hơn không mạnh như trước đây.
- Kinh nghiệm đã chỉ ra rằng rơi xuống có xu hướng là trường hợp thiểu số chứ không phải là tiêu chuẩn (một nghiên cứu về trình biên dịch của Sun đã tìm thấy 3%
switch
khối được sử dụng khác với nhiều nhãn trên cùng một khối và người ta cho rằng việc sử dụng- trường hợp ở đây có nghĩa là 3% này thực tế cao hơn nhiều so với bình thường). Vì vậy, ngôn ngữ được nghiên cứu làm cho sự khác thường dễ phục vụ hơn so với thông thường.
- Kinh nghiệm đã chỉ ra rằng việc rơi qua có xu hướng là nguồn gốc của các vấn đề cả trong trường hợp nó vô tình được thực hiện và cả trong trường hợp rơi đúng cách bị ai đó duy trì mã. Cái sau này là một bổ sung tinh tế cho các lỗi liên quan đến sự cố, bởi vì ngay cả khi mã của bạn hoàn toàn không có lỗi, sự cố của bạn vẫn có thể gây ra sự cố.
Liên quan đến hai điểm cuối cùng, hãy xem xét trích dẫn sau từ phiên bản hiện tại của K & R:
Rơi xuống từ trường hợp này sang trường hợp khác là không mạnh mẽ, dễ bị tan rã khi chương trình được sửa đổi. Ngoại trừ nhiều nhãn cho một tính toán duy nhất, thông qua nên được sử dụng một cách tiết kiệm và nhận xét.
Là một vấn đề về hình thức tốt, hãy tạm dừng sau trường hợp cuối cùng (mặc định ở đây) mặc dù điều đó không cần thiết về mặt logic. Một ngày nào đó khi một trường hợp khác được thêm vào cuối, một chút lập trình phòng thủ này sẽ cứu bạn.
Vì vậy, từ miệng ngựa, rơi vào C là có vấn đề. Nó được coi là một cách thực hành tốt để luôn ghi lại các thông tin bằng các bình luận, đó là một ứng dụng của nguyên tắc chung mà người ta nên ghi lại khi một người làm điều gì đó bất thường, bởi vì đó là điều sẽ xảy ra sau khi kiểm tra mã và / hoặc làm cho mã của bạn trông giống như vậy có một lỗi của người mới trong khi nó thực sự đúng.
Và khi bạn nghĩ về nó, mã như thế này:
switch(x)
{
case 1:
foo();
/* FALLTHRU */
case 2:
bar();
break;
}
Việc thêm một cái gì đó để làm cho thông tin rõ ràng trong mã, nó không phải là thứ gì đó có thể được phát hiện (hoặc sự vắng mặt của nó có thể được phát hiện) bởi trình biên dịch.
Như vậy, thực tế là phải rõ ràng với thông báo rơi vào C # không thêm bất kỳ hình phạt nào cho những người viết tốt bằng các ngôn ngữ kiểu C khác, vì họ sẽ rõ ràng trong các lần rơi của họ.
Cuối cùng, việc sử dụng goto
ở đây đã là một chuẩn mực từ C và các ngôn ngữ khác:
switch(x)
{
case 0:
case 1:
case 2:
foo();
goto below_six;
case 3:
bar();
goto below_six;
case 4:
baz();
/* FALLTHRU */
case 5:
below_six:
qux();
break;
default:
quux();
}
Trong trường hợp này, trong đó chúng tôi muốn một khối được bao gồm trong mã được thực thi cho một giá trị khác với khối mang đến một khối trước đó, thì chúng tôi đã phải sử dụng goto
. (Tất nhiên, có những cách và cách để tránh điều này với các điều kiện khác nhau nhưng điều đó đúng với mọi thứ liên quan đến câu hỏi này). Vì vậy, C # được xây dựng theo cách đã bình thường để đối phó với một tình huống trong đó chúng tôi muốn đạt được nhiều hơn một khối mã trong một switch
, và chỉ khái quát hóa nó để bao quát cả sự sụp đổ. Nó cũng làm cho cả hai trường hợp thuận tiện hơn và tự ghi lại tài liệu, vì chúng ta phải thêm nhãn mới trong C nhưng có thể sử dụng case
làm nhãn trong C #. Trong C #, chúng ta có thể thoát khỏi below_six
nhãn và sử dụng goto case 5
rõ ràng hơn những gì chúng ta đang làm. (Chúng tôi cũng phải thêmbreak
đối với default
, cái mà tôi đã bỏ qua chỉ để làm cho mã C ở trên rõ ràng không phải là mã C #).
Tóm lại:
- C # không còn liên quan đến đầu ra của trình biên dịch không được tối ưu hóa như mã C đã làm cách đây 40 năm (cũng như C ngày nay), điều này làm cho một trong những nguồn cảm hứng của sự sụp đổ không liên quan.
- C # vẫn tương thích với C không chỉ ẩn
break
, để học ngôn ngữ dễ dàng hơn bởi những người quen thuộc với các ngôn ngữ tương tự và chuyển dễ dàng hơn.
- C # loại bỏ một nguồn có thể có lỗi hoặc mã bị hiểu lầm đã được ghi nhận tốt là gây ra vấn đề trong bốn thập kỷ qua.
- C # làm cho thực tiễn tốt nhất hiện có với C (tài liệu rơi qua) được thực thi bởi trình biên dịch.
- C # làm cho trường hợp bất thường trở thành trường hợp có mã rõ ràng hơn, trường hợp thông thường là trường hợp có mã chỉ ghi tự động.
- C # sử dụng
goto
cách tiếp cận dựa trên cùng một cách để đánh cùng một khối từ các case
nhãn khác nhau như được sử dụng trong C. Nó chỉ khái quát nó cho một số trường hợp khác.
- C # làm cho
goto
cách tiếp cận dựa trên đó thuận tiện hơn và rõ ràng hơn so với trong C, bằng cách cho phép các case
câu lệnh đóng vai trò là nhãn.
Tất cả trong tất cả, một quyết định thiết kế khá hợp lý
* Một số hình thức BASIC sẽ cho phép một người thực hiện những GOTO (x AND 7) * 50 + 240
điều tương tự trong khi dễ vỡ và do đó, một trường hợp đặc biệt thuyết phục để cấm goto
, sẽ phục vụ để hiển thị một ngôn ngữ cao hơn tương tự như cách mà mã cấp thấp hơn có thể thực hiện bước nhảy dựa trên số học dựa trên một giá trị, hợp lý hơn nhiều khi đó là kết quả của quá trình biên dịch thay vì một cái gì đó phải được duy trì bằng tay. Việc triển khai Thiết bị của Duff nói riêng cho vay chính xác mã máy tương đương hoặc IL vì mỗi khối hướng dẫn thường sẽ có cùng độ dài mà không cần thêm nop
chất độn.
Device Thiết bị của Duff trở lại đây một lần nữa, như một ngoại lệ hợp lý. Thực tế là với mô hình tương tự và tương tự đó, có một sự lặp lại của các hoạt động phục vụ cho việc sử dụng thông qua tương đối rõ ràng ngay cả khi không có nhận xét rõ ràng về hiệu ứng đó.