Câu trả lời:
Có một vài lý do để sử dụng câu lệnh "goto" mà tôi biết (một số người đã nói về điều này rồi):
Thoát khỏi chức năng
Thông thường trong một chức năng, bạn có thể phân bổ tài nguyên và cần thoát ra ở nhiều nơi. Các lập trình viên có thể đơn giản hóa mã của họ bằng cách đặt mã dọn dẹp tài nguyên ở cuối hàm và tất cả "điểm thoát" của hàm sẽ lấy nhãn làm sạch. Bằng cách này, bạn không phải viết mã dọn dẹp tại mọi "điểm thoát" của chức năng.
Thoát khỏi các vòng lặp lồng nhau
Nếu bạn đang ở trong một vòng lặp lồng nhau và cần phải thoát ra khỏi tất cả các vòng lặp, một goto có thể làm cho việc này đơn giản và đơn giản hơn nhiều so với các câu lệnh và kiểm tra if.
Cải thiện hiệu suất cấp thấp
Điều này chỉ hợp lệ trong mã quan trọng hoàn hảo, nhưng các câu lệnh goto thực thi rất nhanh và có thể giúp bạn tăng tốc khi di chuyển qua một hàm. Tuy nhiên, đây là con dao hai lưỡi, bởi vì trình biên dịch thường không thể tối ưu hóa mã có chứa gotos.
Lưu ý rằng trong tất cả các ví dụ này, gotos bị giới hạn trong phạm vi của một chức năng.
goto
bằng return
chỉ là ngớ ngẩn. Nó không "tái cấu trúc" bất cứ thứ gì, nó chỉ là "đổi tên" để những người lớn lên trong một goto
môi trường bị áp bức (tức là tất cả chúng ta) cảm thấy tốt hơn khi sử dụng số tiền về mặt đạo đức cho a goto
. Tôi rất thích nhìn thấy vòng lặp nơi tôi sử dụng nó và nhìn thấy một chút goto
, mà bản thân nó chỉ là một công cụ , hơn là thấy ai đó đã di chuyển vòng lặp ở đâu đó không liên quan chỉ để tránh a goto
.
break
, continue
, return
về cơ bản goto
, chỉ trong bao bì đẹp.
do{....}while(0)
được coi là một ý tưởng tốt hơn goto, ngoại trừ thực tế nó hoạt động trong Java.
Mọi người chống lại goto
, trực tiếp hoặc gián tiếp, bài viết hại được coi là có hại của Edsger Dijkstra để chứng minh vị trí của họ. Quá tệ bài viết của Dijkstra hầu như không liên quan gì đến cách goto
sử dụng các câu lệnh ngày nay và do đó những gì bài báo nói có rất ít khả năng áp dụng cho bối cảnh lập trình hiện đại. Cácgoto
meme không tập trung vào một tôn giáo, ngay đến kinh sách của nó được chỉ định từ trên cao, các linh mục cao cấp của nó và sự xấu hổ (hoặc tệ hơn) của những kẻ dị giáo nhận thức.
Hãy đặt bài viết của Dijkstra vào bối cảnh để làm sáng tỏ một chút về chủ đề này.
Khi Dijkstra viết bài báo của mình, các ngôn ngữ phổ biến thời bấy giờ là những ngôn ngữ thủ tục không có cấu trúc như BASIC, FORTRAN (các phương ngữ trước đó) và các ngôn ngữ lắp ráp khác nhau. Điều này khá phổ biến đối với những người sử dụng các ngôn ngữ cấp cao hơn để nhảy khắp cơ sở mã của họ trong các luồng thực thi bị xoắn, méo mó dẫn đến thuật ngữ "mã spaghetti". Bạn có thể thấy điều này bằng cách nhảy vào trò chơi Trek kinh điển được viết bởi Mike Mayfield và cố gắng tìm ra cách mọi thứ hoạt động. Hãy dành một vài phút để xem qua.
ĐÂY là "việc sử dụng không bị hạn chế trong tuyên bố" mà Dijkstra đã chống lại trong bài báo của mình vào năm 1968. ĐÂY là môi trường mà anh ta sống trong đó đã khiến anh ta viết bài báo đó. Khả năng nhảy bất cứ nơi nào bạn thích trong mã của bạn tại bất kỳ điểm nào bạn thích là những gì anh ta đang chỉ trích và yêu cầu dừng lại. So sánh điều đó với các năng lực thiếu máu của goto
C hoặc các ngôn ngữ hiện đại hơn như vậy chỉ đơn giản là có khả năng.
Tôi đã có thể nghe thấy tiếng hô vang của những người sùng bái khi họ đối mặt với những kẻ dị giáo. "Nhưng," họ sẽ hô vang, "bạn có thể tạo mã rất khó đọc bằng goto
C." Ồ vâng? Bạn có thể làm cho mã rất khó đọc mà không có goto
. Giống như cái này:
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
Không phải goto
trong tầm nhìn, vì vậy nó phải dễ đọc, phải không? Thế còn cái này thì như thế nào:
a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k,
l)char* *l;{g= atoi(* ++l); for(k=
0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<=
g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1;
while(d <=g){ ++O;for (f=0;f< O&&d<=g
;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f<O
&&d<=g; ++f)a[b <<5|c]= d++,c+= e;e= -e
;}for(c =0;c<h; ++c){ for(b=0 ;b<k;++
b){if(b <k/2)a[ b<<5|c] ^=a[(k -(b+1))
<<5|c]^= a[b<<5 |c]^=a[ (k-(b+1 ))<<5|c]
;printf( a[b<<5|c ]?"%-4d" :" " ,a[b<<5
|c]);} putchar( '\n');}} /*Mike Laman*/
Không goto
có ở đó. Do đó nó phải được đọc.
Quan điểm của tôi với những ví dụ này là gì? Đó không phải là các tính năng ngôn ngữ làm cho mã không thể đọc được, không thể nhầm lẫn. Đó không phải là cú pháp làm điều đó. Đó là những lập trình viên tồi gây ra điều này. Và các lập trình viên xấu, như bạn có thể thấy trong mục trên, có thể làm cho bất kỳ tính năng ngôn ngữ nào không thể đọc được và không thể sử dụng được. Giống như các for
vòng lặp ở đó. (Bạn có thể thấy chúng, phải không?)
Bây giờ để công bằng, một số cấu trúc ngôn ngữ dễ lạm dụng hơn những ngôn ngữ khác. Tuy nhiên, nếu bạn là một lập trình viên C, tôi sẽ xem xét kỹ hơn khoảng 50% việc sử dụng từ #define
lâu trước khi tôi tham gia một cuộc thập tự chinh chống lại goto
!
Vì vậy, đối với những người đã bận tâm đọc đến nay, có một số điểm chính cần lưu ý.
goto
báo cáo được viết cho một môi trường lập trình ở đâu goto
được một rất nhiều
tiềm năng hơn gây tổn hại hơn là trong hầu hết các ngôn ngữ hiện đại mà không phải là một nhà lắp ráp.goto
vì điều này là hợp lý như nói rằng "Tôi đã cố gắng vui vẻ một lần nhưng không thích nó vì vậy bây giờ tôi chống lại nó".goto
trong mã không thể được thay thế đầy đủ bằng các cấu trúc khác.godo
"gớm ghiếc" trong đó một do
vòng lặp luôn luôn sai được sử dụng break
thay cho a goto
. Đây thường là tồi tệ hơn sử dụng hợp lý goto
.goto
thực sự là gì (đó là câu hỏi được đăng)
Tuân thủ các thực hành tốt nhất một cách mù quáng không phải là một thực hành tốt nhất. Ý tưởng tránh các goto
câu lệnh là hình thức kiểm soát dòng chính của một người là để tránh tạo ra mã spaghetti không thể đọc được. Nếu được sử dụng một cách tiết kiệm ở đúng nơi, đôi khi chúng có thể là cách đơn giản nhất, rõ ràng nhất để thể hiện một ý tưởng. Walter Bright, người tạo ra trình biên dịch Zortech C ++ và ngôn ngữ lập trình D, sử dụng chúng thường xuyên, nhưng thận trọng. Ngay cả vớigoto
tuyên bố, mã của anh ấy vẫn hoàn toàn có thể đọc được.
Điểm mấu chốt: Tránh goto
để tránh goto
là vô nghĩa. Những gì bạn thực sự muốn tránh là sản xuất mã không thể đọc được. Nếu goto
mã -laden của bạn có thể đọc được, thì không có gì sai với nó.
Vì goto
làm cho lý do về chương trình chảy khó 1 (hay còn gọi là mã spaghetti của Spon),goto
thường chỉ được sử dụng để bù cho các tính năng bị thiếu: Việc sử dụng goto
có thể thực sự được chấp nhận, nhưng chỉ khi ngôn ngữ không cung cấp biến thể có cấu trúc chặt chẽ hơn để có được cùng một mục tiêu. Lấy ví dụ về nghi ngờ:
Quy tắc với goto mà chúng tôi sử dụng là goto vẫn ổn khi nhảy về phía trước một điểm dọn dẹp duy nhất trong một chức năng.
Điều này đúng - nhưng chỉ khi ngôn ngữ không cho phép xử lý ngoại lệ có cấu trúc với mã dọn dẹp (chẳng hạn như RAII hoặc finally
), hoạt động tương tự tốt hơn (vì nó được xây dựng đặc biệt để thực hiện) hoặc khi có lý do chính đáng sử dụng xử lý ngoại lệ có cấu trúc (nhưng bạn sẽ không bao giờ gặp trường hợp này ngoại trừ ở mức rất thấp).
Trong hầu hết các ngôn ngữ khác, việc sử dụng duy nhất được chấp nhận goto
là thoát khỏi các vòng lặp lồng nhau. Và thậm chí ở đó, hầu như luôn luôn tốt hơn để nâng vòng lặp bên ngoài thành một phương thức riêng và sử dụngreturn
thay thế.
Ngoài ra, đó goto
là một dấu hiệu cho thấy không đủ suy nghĩ đã đi vào đoạn mã cụ thể.
1 ngôn ngữ hiện đại hỗ trợ goto
thực hiện một số hạn chế (ví dụ:goto
có thể không nhảy vào hoặc ra khỏi chức năng) nhưng về cơ bản vấn đề vẫn như cũ.
Ngẫu nhiên, điều tương tự tất nhiên cũng đúng với các tính năng ngôn ngữ khác, đáng chú ý nhất là ngoại lệ. Và thường có các quy tắc nghiêm ngặt tại chỗ chỉ sử dụng các tính năng này khi được chỉ định, chẳng hạn như quy tắc không sử dụng ngoại lệ để kiểm soát luồng chương trình không đặc biệt.
finally
? Vì vậy, sử dụng ngoại lệ cho những thứ khác ngoài xử lý lỗi là tốt nhưng sử dụng goto
là xấu? Tôi nghĩ rằng các ngoại lệ được đặt tên khá thông minh.
Chà, có một thứ luôn tệ hơn goto's
; sử dụng lạ các toán tử dòng chương trình khác để tránh một goto:
Ví dụ:
// 1
try{
...
throw NoErrorException;
...
} catch (const NoErrorException& noe){
// This is the worst
}
// 2
do {
...break;
...break;
} while (false);
// 3
for(int i = 0;...) {
bool restartOuter = false;
for (int j = 0;...) {
if (...)
restartOuter = true;
if (restartOuter) {
i = -1;
}
}
etc
etc
do{}while(false)
Tôi nghĩ có thể được coi là thành ngữ. Bạn không được phép không đồng ý: D
goto after_do_block;
mà không thực sự nói điều đó. Nếu không ... một "vòng lặp" chạy chính xác một lần? Tôi gọi đó là lạm dụng các cấu trúc kiểm soát.
#define
rất nhiều, tệ hơn nhiều lần so với việc sử dụng goto
một lần trong một thời gian: D
Trong câu lệnh chuyển đổi C # không cho phép rơi qua . Vì vậy, goto được sử dụng để chuyển điều khiển sang nhãn trường hợp chuyển đổi cụ thể hoặc mặc định nhãn .
Ví dụ:
switch(value)
{
case 0:
Console.Writeln("In case 0");
goto case 1;
case 1:
Console.Writeln("In case 1");
goto case 2;
case 2:
Console.Writeln("In case 2");
goto default;
default:
Console.Writeln("In default");
break;
}
Chỉnh sửa: Có một ngoại lệ đối với quy tắc "không rơi". Fall-through được cho phép nếu một tuyên bố trường hợp không có mã.
goto case 5:
khi bạn ở trong trường hợp 1). Có vẻ như câu trả lời của Konrad Rudolph là chính xác ở đây: goto
đang bù cho một tính năng bị thiếu (và không rõ ràng hơn tính năng thực sự). Nếu những gì chúng ta thực sự muốn là thông qua, có lẽ mặc định tốt nhất sẽ không phải là thông qua, nhưng một cái gì đó muốn continue
yêu cầu nó một cách rõ ràng.
#ifdef TONGUE_IN_CHEEK
Perl có một goto
cho phép bạn thực hiện các cuộc gọi đuôi của người nghèo. :-P
sub factorial {
my ($n, $acc) = (@_, 1);
return $acc if $n < 1;
@_ = ($n - 1, $acc * $n);
goto &factorial;
}
#endif
Được rồi, vì vậy mà không có gì để làm với C goto
. Nghiêm trọng hơn, tôi đồng ý với các ý kiến khác về việc sử dụng goto
để dọn dẹp hoặc triển khai thiết bị của Duff hoặc tương tự. Đó là tất cả về việc sử dụng, không lạm dụng.
(Nhận xét tương tự có thể áp dụng cho longjmp
, ngoại lệ call/cc
và tương tự --- chúng có những cách sử dụng hợp pháp, nhưng có thể dễ dàng bị lạm dụng. Ví dụ, ném một ngoại lệ hoàn toàn để thoát khỏi cấu trúc kiểm soát được lồng sâu, trong những trường hợp hoàn toàn không đặc biệt .)
Tôi đã viết hơn một vài dòng ngôn ngữ lắp ráp trong những năm qua. Cuối cùng, mọi ngôn ngữ cấp cao biên dịch thành gotos. Được rồi, gọi chúng là "cành" hoặc "nhảy" hoặc bất cứ thứ gì khác, nhưng chúng là hình ảnh. Bất cứ ai cũng có thể viết trình biên dịch goto-less?
Bây giờ chắc chắn, bạn có thể chỉ ra cho một lập trình viên Fortran, C hoặc BASIC rằng chạy bạo loạn với gotos là một công thức cho món bolognaise spaghetti. Tuy nhiên, câu trả lời không phải là tránh chúng, mà là sử dụng chúng cẩn thận.
Một con dao có thể được sử dụng để chuẩn bị thức ăn, giải thoát ai đó hoặc giết chết ai đó. Chúng ta có làm mà không có dao thông qua sợ sau này? Tương tự như goto: sử dụng bất cẩn nó cản trở, sử dụng cẩn thận nó giúp.
Hãy xem khi nào nên sử dụng Goto khi lập trình trong C :
Mặc dù việc sử dụng goto hầu như luôn luôn là thực hành lập trình tồi (chắc chắn bạn có thể tìm thấy cách làm XYZ tốt hơn), nhưng có những lúc nó thực sự không phải là một lựa chọn tồi. Một số thậm chí có thể lập luận rằng, khi nó hữu ích, đó là sự lựa chọn tốt nhất.
Hầu hết những gì tôi phải nói về goto thực sự chỉ áp dụng cho C. Nếu bạn đang sử dụng C ++, không có lý do chính đáng nào để sử dụng goto thay cho ngoại lệ. Tuy nhiên, trong C, bạn không có sức mạnh của cơ chế xử lý ngoại lệ, vì vậy nếu bạn muốn tách riêng việc xử lý lỗi khỏi phần còn lại của logic chương trình và bạn muốn tránh viết lại mã nhiều lần trong toàn bộ mã của mình, sau đó goto có thể là một lựa chọn tốt.
Ý tôi là sao Bạn có thể có một số mã trông như thế này:
int big_function()
{
/* do some work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* clean up*/
return [success];
}
Điều này là tốt cho đến khi bạn nhận ra rằng bạn cần thay đổi mã dọn dẹp của bạn. Sau đó, bạn phải trải qua và thực hiện 4 thay đổi. Bây giờ, bạn có thể quyết định rằng bạn chỉ có thể gói gọn tất cả việc dọn dẹp vào một chức năng duy nhất; Đó không phải là một ý tưởng tồi. Nhưng điều đó có nghĩa là bạn sẽ cần cẩn thận với các con trỏ - nếu bạn có kế hoạch giải phóng một con trỏ trong chức năng dọn dẹp của mình, không có cách nào để đặt nó sau đó trỏ đến NULL trừ khi bạn chuyển con trỏ đến một con trỏ. Trong nhiều trường hợp, dù sao bạn cũng sẽ không sử dụng con trỏ đó nữa, vì vậy đó có thể không phải là mối quan tâm chính. Mặt khác, nếu bạn thêm vào một con trỏ mới, xử lý tệp hoặc những thứ khác cần dọn dẹp, thì bạn sẽ cần phải thay đổi chức năng dọn dẹp của mình một lần nữa; và sau đó bạn sẽ cần thay đổi các đối số cho chức năng đó.
Bằng cách sử dụng goto
, nó sẽ được
int big_function()
{
int ret_val = [success];
/* do some work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
end:
/* clean up*/
return ret_val;
}
Lợi ích ở đây là mã theo sau của bạn có quyền truy cập vào mọi thứ cần thiết để thực hiện dọn dẹp và bạn đã quản lý để giảm số lượng điểm thay đổi đáng kể. Một lợi ích khác là bạn đã đi từ việc có nhiều điểm thoát cho chức năng của mình thành một điểm; không có cơ hội bạn sẽ vô tình quay trở lại từ chức năng mà không dọn dẹp.
Hơn nữa, vì goto chỉ được sử dụng để nhảy đến một điểm duy nhất, nên không phải là bạn đang tạo ra một khối mã spaghetti nhảy qua lại trong một nỗ lực để mô phỏng các cuộc gọi chức năng. Thay vào đó, goto thực sự giúp viết mã có cấu trúc hơn.
Trong một từ, goto
nên luôn luôn được sử dụng một cách tiết kiệm, và như là phương sách cuối cùng - nhưng có một thời gian và một nơi dành cho nó. Câu hỏi không phải là "bạn có phải sử dụng nó không" mà là "nó có phải là lựa chọn tốt nhất" để sử dụng nó không.
Một trong những lý do goto là xấu, bên cạnh phong cách mã hóa là bạn có thể sử dụng nó để tạo các vòng lặp chồng chéo , nhưng không lồng nhau :
loop1:
a
loop2:
b
if(cond1) goto loop1
c
if(cond2) goto loop2
Điều này sẽ tạo ra cấu trúc kiểm soát luồng kỳ lạ, nhưng có thể hợp pháp trong đó một chuỗi như (a, b, c, b, a, b, a, b, ...) là có thể, khiến tin tặc biên dịch không hài lòng. Rõ ràng có một số thủ thuật tối ưu hóa thông minh dựa trên loại cấu trúc này không xảy ra. (Tôi nên kiểm tra bản sao của cuốn sách rồng ...) Kết quả của việc này có thể (sử dụng một số trình biên dịch) là các tối ưu hóa khác không được thực hiện cho mã có chứa goto
s.
Nó có thể hữu ích nếu bạn biết điều đó, "ồ, nhân tiện", tình cờ thuyết phục trình biên dịch phát ra mã nhanh hơn. Cá nhân, tôi muốn thử giải thích với trình biên dịch về những gì có thể xảy ra và những gì không phải trước khi sử dụng một thủ thuật như goto, nhưng có thể nói, tôi cũng có thể thử goto
trước khi hack trình biên dịch chương trình .
goto
là hữu ích là nó cho phép bạn xây dựng các vòng như thế này, mà sẽ đòi hỏi một loạt các vặn vẹo logic khác. Tôi muốn tranh luận thêm rằng nếu trình tối ưu hóa không biết cách viết lại cái này thì tốt . Một vòng lặp như thế này không nên được thực hiện cho hiệu suất hoặc khả năng đọc, nhưng bởi vì đó chính xác là thứ tự mà mọi thứ cần phải xảy ra. Trong trường hợp đó, tôi sẽ không đặc biệt muốn trình tối ưu hóa xoay quanh nó.
Tôi thấy buồn cười khi một số người sẽ đưa ra một danh sách các trường hợp mà goto được chấp nhận, nói rằng tất cả các cách sử dụng khác là không thể chấp nhận được. Bạn có thực sự nghĩ rằng bạn biết mọi trường hợp trong đó goto là lựa chọn tốt nhất để thể hiện một thuật toán?
Để minh họa, tôi sẽ cho bạn một ví dụ mà chưa có ai ở đây thể hiện:
Hôm nay tôi đã viết mã để chèn một phần tử trong bảng băm. Bảng băm là một bộ đệm của các tính toán trước đó có thể được ghi đè theo ý muốn (ảnh hưởng đến hiệu suất nhưng không chính xác).
Mỗi nhóm của bảng băm có 4 vị trí và tôi có một loạt các tiêu chí để quyết định phần tử nào sẽ ghi đè khi một nhóm đầy. Ngay bây giờ điều này có nghĩa là thực hiện tối đa ba lần đi qua một cái xô, như thế này:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
goto add;
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
goto add;
// Additional passes go here...
add:
// element is written to the hash table here
Bây giờ nếu tôi không sử dụng goto, mã này sẽ trông như thế nào?
Một cái gì đó như thế này:
// Overwrite an element with same hash key if it exists
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if (slot_p[add_index].hash_key == hash_key)
break;
if (add_index >= ELEMENTS_PER_BUCKET) {
// Otherwise, find first empty element
for (add_index=0; add_index < ELEMENTS_PER_BUCKET; add_index++)
if ((slot_p[add_index].type == TT_ELEMENT_EMPTY)
break;
if (add_index >= ELEMENTS_PER_BUCKET)
// Additional passes go here (nested further)...
}
// element is written to the hash table here
Nó sẽ trông tệ hơn và tệ hơn nếu thêm nhiều lượt đi, trong khi phiên bản với goto luôn giữ cùng mức thụt đầu dòng và tránh sử dụng giả nếu các câu lệnh có kết quả được ngụ ý bởi việc thực hiện vòng lặp trước.
Vì vậy, có một trường hợp khác mà goto làm cho mã sạch hơn và dễ viết và dễ hiểu hơn ... Tôi chắc chắn còn nhiều điều nữa, vì vậy đừng giả vờ biết tất cả các trường hợp mà goto hữu ích, loại bỏ bất kỳ trường hợp tốt nào bạn có thể Tôi không nghĩ đến.
goto
là có mỗi chức năng ở cùng một mức độ trừu tượng. Điều đó tránh được goto
là một phần thưởng.
container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();
Tôi thấy rằng loại lập trình đó dễ đọc hơn nhiều. Mỗi hàm trong trường hợp này có thể được viết dưới dạng một hàm thuần túy, dễ hiểu hơn nhiều. Hàm chính bây giờ giải thích những gì mã làm chỉ bằng tên của các hàm, và sau đó nếu bạn muốn, bạn có thể xem định nghĩa của chúng để tìm hiểu làm thế nào nó hoạt động.
Quy tắc với goto mà chúng tôi sử dụng là goto vẫn ổn khi nhảy về phía trước một điểm dọn dẹp duy nhất trong một chức năng. Trong các chức năng thực sự phức tạp, chúng tôi thư giãn quy tắc đó để cho phép các bước nhảy khác về phía trước. Trong cả hai trường hợp, chúng tôi đều tránh lồng nhau sâu nếu các câu lệnh thường xảy ra khi kiểm tra mã lỗi, giúp dễ đọc và duy trì.
Cuộc thảo luận chu đáo và kỹ lưỡng nhất về các tuyên bố goto, sử dụng hợp pháp của chúng và các cấu trúc thay thế có thể được sử dụng thay cho "các tuyên bố goto có đạo đức" nhưng có thể bị lạm dụng dễ dàng như các tuyên bố goto, là bài viết của Donald Knuth " Lập trình có cấu trúc với các tuyên bố goto " , trong Khảo sát tính toán tháng 12 năm 1974 (tập 6, số 4. trang 261 - 301).
Không có gì đáng ngạc nhiên, một số khía cạnh của bài báo 39 tuổi này đã được đề xuất: Sức mạnh xử lý của đơn đặt hàng làm cho một số cải tiến hiệu suất của Knuth không được chú ý đối với các vấn đề có kích thước vừa phải và các cấu trúc ngôn ngữ lập trình mới đã được phát minh kể từ đó. .
Trong mô-đun Perl, đôi khi bạn muốn tạo chương trình con hoặc đóng khi đang di chuyển. Vấn đề là, một khi bạn đã tạo ra chương trình con, làm thế nào để bạn có được nó. Bạn chỉ có thể gọi nó, nhưng sau đó nếu chương trình con sử dụng caller()
nó sẽ không hữu ích như nó có thể. Đó là nơi goto &subroutine
biến thể có thể hữu ích.
sub AUTOLOAD{
my($self) = @_;
my $name = $AUTOLOAD;
$name =~ s/.*:://;
*{$name} = my($sub) = sub{
# the body of the closure
}
goto $sub;
# nothing after the goto will ever be executed.
}
Bạn cũng có thể sử dụng hình thức này goto
để cung cấp một hình thức tối ưu hóa cuộc gọi đuôi thô sơ.
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
$tally *= $n--;
@_ = ($n,$tally);
goto &factorial;
}
(Trong Perl 5 phiên bản 16 sẽ được viết tốt hơn goto __SUB__;
)
Có một mô-đun sẽ nhập một công cụ tail
sửa đổi và một mô-đun sẽ nhập recur
nếu bạn không thích sử dụng hình thức này goto
.
use Sub::Call::Tail;
sub AUTOLOAD {
...
tail &$sub( @_ );
}
use Sub::Call::Recur;
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
recur( $n-1, $tally * $n );
}
goto
được thực hiện tốt hơn với các từ khóa khác.Giống như redo
ing một chút mã:
LABEL: ;
...
goto LABEL if $x;
{
...
redo if $x;
}
Hoặc đi đến last
một chút mã từ nhiều nơi:
goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
last if $x;
...
last if $y
...
}
Nếu vậy, tại sao?
C không có ngắt đa cấp / có nhãn và không phải tất cả các luồng điều khiển đều có thể được mô hình hóa dễ dàng với các nguyên hàm lặp và quyết định của C. gotos đi một chặng đường dài để khắc phục những sai sót.
Đôi khi, rõ ràng hơn là sử dụng một biến cờ nào đó để tạo ra một loại phá vỡ đa cấp giả, nhưng nó không phải lúc nào cũng vượt trội hơn goto (ít nhất là một goto cho phép người ta dễ dàng xác định nơi điều khiển đi đến, không giống như biến cờ ) và đôi khi bạn chỉ đơn giản là không muốn trả giá hiệu suất của cờ / các hình thức khác để tránh goto.
libavcodec là một đoạn mã nhạy cảm về hiệu năng. Biểu hiện trực tiếp của luồng điều khiển có lẽ là ưu tiên, bởi vì nó sẽ có xu hướng chạy tốt hơn.
Tôi thấy việc sử dụng do {} while (false) hoàn toàn nổi loạn. Có thể hiểu được có thể thuyết phục tôi rằng nó là cần thiết trong một số trường hợp kỳ lạ, nhưng không bao giờ rằng đó là mã hợp lý sạch.
Nếu bạn phải thực hiện một số vòng lặp như vậy, tại sao không làm cho sự phụ thuộc vào biến cờ rõ ràng?
for (stepfailed=0 ; ! stepfailed ; /*empty*/)
/*empty*/
được stepfailed = 1
? Trong mọi trường hợp, làm thế nào là tốt hơn a do{}while(0)
? Trong cả hai, bạn cần phải break
ra khỏi nó (hoặc trong của bạn stepfailed = 1; continue;
). Có vẻ không cần thiết với tôi.
1) Việc sử dụng goto phổ biến nhất mà tôi biết là mô phỏng xử lý ngoại lệ trong các ngôn ngữ không cung cấp nó, cụ thể là bằng C. (Mã được cung cấp bởi Nucle ở trên chỉ là vậy.) Hãy nhìn vào mã nguồn Linux và bạn ' sẽ thấy một bazillion gotos được sử dụng theo cách đó; có khoảng 100.000 gotos trong mã Linux theo một cuộc khảo sát nhanh được thực hiện vào năm 2013: http://blog.regehr.org/archives/894 . Việc sử dụng Goto thậm chí còn được đề cập trong hướng dẫn về phong cách mã hóa Linux: https://www.kernel.org/doc/Documentation/CodingStyle . Giống như lập trình hướng đối tượng được mô phỏng bằng cách sử dụng các cấu trúc được điền với các con trỏ hàm, goto có vị trí của nó trong lập trình C. Vậy ai đúng: Dijkstra hoặc Linus (và tất cả các lập trình viên nhân Linux)? Đó là lý thuyết so với thực hành về cơ bản.
Tuy nhiên, có một Gotcha thông thường vì không có hỗ trợ và kiểm tra mức trình biên dịch cho các cấu trúc / mẫu chung: dễ sử dụng sai và giới thiệu các lỗi mà không cần kiểm tra thời gian biên dịch. Windows và Visual C ++ nhưng ở chế độ C cung cấp xử lý ngoại lệ thông qua SEH / VEH vì lý do này: các ngoại lệ rất hữu ích ngay cả bên ngoài các ngôn ngữ OOP, tức là trong ngôn ngữ thủ tục. Nhưng trình biên dịch không thể luôn lưu thịt xông khói của bạn, ngay cả khi nó cung cấp hỗ trợ cú pháp cho các ngoại lệ trong ngôn ngữ. Hãy xem xét ví dụ về trường hợp sau, lỗi "goto fail" nổi tiếng của Apple SSL, lỗi này chỉ nhân đôi một goto với hậu quả tai hại ( https://www.imperialviolet.org/2014/02/22/applebug.html ):
if (something())
goto fail;
goto fail; // copypasta bug
printf("Never reached\n");
fail:
// control jumps here
Bạn có thể có chính xác cùng một lỗi bằng cách sử dụng các ngoại lệ được hỗ trợ bởi trình biên dịch, ví dụ như trong C ++:
struct Fail {};
try {
if (something())
throw Fail();
throw Fail(); // copypasta bug
printf("Never reached\n");
}
catch (Fail&) {
// control jumps here
}
Nhưng cả hai biến thể của lỗi đều có thể tránh được nếu trình biên dịch phân tích và cảnh báo bạn về mã không thể truy cập được. Ví dụ, biên dịch với Visual C ++ ở mức cảnh báo / W4 tìm thấy lỗi trong cả hai trường hợp. Ví dụ, Java cấm mã không thể truy cập (nơi có thể tìm thấy mã!) Vì một lý do khá chính đáng: đó có thể là một lỗi trong mã trung bình của Joe. Miễn là cấu trúc goto không cho phép các mục tiêu mà trình biên dịch không thể dễ dàng tìm ra, như gotos cho các địa chỉ được tính toán (**), trình biên dịch không tìm thấy mã không thể truy cập trong hàm với gotos so với sử dụng Dijkstra mã được phê duyệt.
(**) Chú thích: Có thể sử dụng số dòng Gotos để tính toán trong một số phiên bản Cơ bản, ví dụ GOTO 10 * x trong đó x là một biến. Khá khó hiểu, trong Fortran "goto tính toán" đề cập đến một cấu trúc tương đương với câu lệnh chuyển đổi trong C. Tiêu chuẩn C không cho phép gotos tính toán trong ngôn ngữ, mà chỉ gotos cho nhãn được khai báo tĩnh / tổng hợp. Tuy nhiên, GNU C có một phần mở rộng để lấy địa chỉ của nhãn (toán tử đơn nguyên, tiền tố &&) và cũng cho phép một goto đến một biến có kiểu void *. Xem https://gcc.gnu.org/onlinesocs/gcc/Labels-as-Values.html để biết thêm về chủ đề phụ tối nghĩa này. Phần còn lại của bài đăng này không liên quan đến tính năng GNU C tối nghĩa đó.
Gotos tiêu chuẩn C (tức là không được tính toán) thường không phải là lý do tại sao mã không thể truy cập không thể được tìm thấy tại thời điểm biên dịch. Lý do thông thường là mã logic như sau. Được
int computation1() {
return 1;
}
int computation2() {
return computation1();
}
Trình biên dịch cũng khó tìm được mã không thể truy cập trong bất kỳ cấu trúc nào trong 3 cấu trúc sau:
void tough1() {
if (computation1() != computation2())
printf("Unreachable\n");
}
void tough2() {
if (computation1() == computation2())
goto out;
printf("Unreachable\n");
out:;
}
struct Out{};
void tough3() {
try {
if (computation1() == computation2())
throw Out();
printf("Unreachable\n");
}
catch (Out&) {
}
}
(Xin lỗi vì phong cách mã hóa liên quan đến cú đúp của tôi, nhưng tôi đã cố gắng giữ các ví dụ nhỏ gọn nhất có thể.)
Visual C ++ / W4 (ngay cả với / Ox) không tìm thấy mã không thể truy cập trong bất kỳ mã nào trong số này và vì có thể bạn biết rằng vấn đề tìm mã không thể truy cập nói chung là không thể giải quyết được. (Nếu bạn không tin tôi về điều đó: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )
Là một vấn đề liên quan, goto C có thể được sử dụng để mô phỏng các ngoại lệ chỉ bên trong cơ thể của một chức năng. Thư viện C tiêu chuẩn cung cấp một cặp hàm setjmp () và longjmp () để mô phỏng các ngoại lệ / ngoại lệ không cục bộ, nhưng chúng có một số nhược điểm nghiêm trọng so với những ngôn ngữ khác cung cấp. Bài viết Wikipedia http://en.wikipedia.org/wiki/setjmp.h giải thích khá rõ vấn đề sau này. Cặp chức năng này cũng hoạt động trên Windows ( http://msdn.microsoft.com/en-us/l Library / yz2ez4as.aspx), nhưng hầu như không ai sử dụng chúng ở đó vì SEH / VEH vượt trội. Ngay cả trên Unix, tôi nghĩ setjmp và longjmp rất hiếm khi được sử dụng.
2) Tôi nghĩ rằng việc sử dụng goto phổ biến thứ hai trong C là triển khai phá vỡ đa cấp hoặc tiếp tục đa cấp, đây cũng là một trường hợp sử dụng khá không gây tranh cãi. Hãy nhớ lại rằng Java không cho phép nhãn goto, nhưng cho phép phá vỡ nhãn hoặc tiếp tục nhãn. Theo http://www.oracle.com/technetwork/java/simple-142616.html , đây thực sự là trường hợp sử dụng phổ biến nhất của gotos trong C (90% họ nói), nhưng theo kinh nghiệm chủ quan của tôi, mã hệ thống có xu hướng để sử dụng gotos để xử lý lỗi thường xuyên hơn. Có lẽ trong mã khoa học hoặc nơi HĐH cung cấp xử lý ngoại lệ (Windows) thì lối thoát đa cấp là trường hợp sử dụng chi phối. Họ không thực sự đưa ra bất kỳ chi tiết nào về bối cảnh khảo sát của họ.
Chỉnh sửa để thêm: hóa ra hai mẫu sử dụng này được tìm thấy trong sách C của Kernighan và Ritchie, khoảng trang 60 (tùy thuộc vào phiên bản). Một điều lưu ý là cả hai trường hợp sử dụng chỉ liên quan đến gotos phía trước. Và hóa ra phiên bản MISRA C 2012 (không giống như phiên bản 2004) hiện cho phép gotos, miễn là chúng chỉ là phiên bản chuyển tiếp.
Một số người nói không có lý do cho goto trong C ++. Một số người nói rằng trong 99% trường hợp có những lựa chọn thay thế tốt hơn. Đây không phải là lý luận, chỉ là ấn tượng phi lý. Đây là một ví dụ chắc chắn trong đó goto dẫn đến một mã đẹp, một cái gì đó như vòng lặp do-while nâng cao:
int i;
PROMPT_INSERT_NUMBER:
std::cout << "insert number: ";
std::cin >> i;
if(std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000,'\n');
goto PROMPT_INSERT_NUMBER;
}
std::cout << "your number is " << i;
So sánh nó với mã goto-free:
int i;
bool loop;
do {
loop = false;
std::cout << "insert number: ";
std::cin >> i;
if(std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000,'\n');
loop = true;
}
} while(loop);
std::cout << "your number is " << i;
Tôi thấy những khác biệt này:
{}
khối lồng nhau là cần thiết (mặc dù do {...} while
trông quen thuộc hơn)loop
biến phụ là cần thiết, được sử dụng ở bốn nơiloop
loop
không giữ bất kỳ dữ liệu, nó chỉ kiểm soát dòng chảy của việc thực hiện, đó là ít hiểu hơn nhãn đơn giảnCó một ví dụ khác
void sort(int* array, int length) {
SORT:
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
goto SORT; // it is very easy to understand this code, right?
}
}
Bây giờ chúng ta hãy thoát khỏi goto "ác":
void sort(int* array, int length) {
bool seemslegit;
do {
seemslegit = true;
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
seemslegit = false;
}
} while(!seemslegit);
}
Bạn thấy đó là cùng một kiểu sử dụng goto, nó có cấu trúc tốt và nó không chuyển tiếp goto như nhiều cách quảng bá như cách duy nhất được đề xuất. Chắc chắn bạn muốn tránh mã "thông minh" như thế này:
void sort(int* array, int length) {
for(int i=0; i<length-1; ++i) if(array[i]>array[i+1]) {
swap(data[i], data[i+1]);
i = -1; // it works, but WTF on the first glance
}
}
Vấn đề là goto có thể dễ dàng bị sử dụng sai, nhưng bản thân goto không đáng trách. Lưu ý rằng nhãn có phạm vi chức năng trong C ++, vì vậy nó không gây ô nhiễm phạm vi toàn cầu như trong lắp ráp thuần túy, trong đó các vòng lặp chồng chéo có vị trí của nó và rất phổ biến - như trong mã sau đây cho 8051, trong đó hiển thị 7sejection được kết nối với P1. Chương trình vòng lặp sét xung quanh:
; P1 states loops
; 11111110 <-
; 11111101 |
; 11111011 |
; 11110111 |
; 11101111 |
; 11011111 |
; |_________|
init_roll_state:
MOV P1,#11111110b
ACALL delay
next_roll_state:
MOV A,P1
RL A
MOV P1,A
ACALL delay
JNB P1.5, init_roll_state
SJMP next_roll_state
Có một lợi thế khác: goto có thể phục vụ như các vòng lặp, điều kiện và các luồng khác:
if(valid) {
do { // while(loop)
// more than one page of code here
// so it is better to comment the meaning
// of the corresponding curly bracket
} while(loop);
} // if(valid)
Hoặc bạn có thể sử dụng goto tương đương với thụt lề, vì vậy bạn không cần bình luận nếu bạn chọn tên nhãn một cách khôn ngoan:
if(!valid) goto NOTVALID;
LOOPBACK:
// more than one page of code here
if(loop) goto LOOPBACK;
NOTVALID:;
Trong Perl, sử dụng nhãn để "goto" từ một vòng lặp - sử dụng câu lệnh "cuối cùng", tương tự như ngắt.
Điều này cho phép kiểm soát tốt hơn các vòng lặp lồng nhau.
Nhãn goto truyền thống cũng được hỗ trợ, nhưng tôi không chắc có quá nhiều trường hợp trong đó đây là cách duy nhất để đạt được những gì bạn muốn - chương trình con và vòng lặp nên đủ cho hầu hết các trường hợp.
goto &subroutine
. Mà khởi động chương trình con với @_ hiện tại, trong khi thay thế chương trình con hiện tại trong ngăn xếp.
Vấn đề với 'goto' và đối số quan trọng nhất của phong trào 'lập trình không có goto' là, nếu bạn sử dụng nó quá thường xuyên, mã của bạn, mặc dù nó có thể hoạt động chính xác, trở nên không thể đọc được, không thể hiểu được, không thể xem được, v.v. các trường hợp 'goto' dẫn đến mã spaghetti. Cá nhân, tôi không thể nghĩ ra bất kỳ lý do chính đáng nào về lý do tại sao tôi sẽ sử dụng 'goto'.
goto
). Cách sử dụng của @ cschol tương tự nhau: Mặc dù có thể không thiết kế ngôn ngữ ngay bây giờ, nhưng về cơ bản, anh ấy đang đánh giá nỗ lực của người thiết kế.
goto
ngoại trừ trong bối cảnh nơi nó sẽ đưa các biến vào tồn tại là rẻ hơn so với việc cố gắng hỗ trợ mọi loại cấu trúc điều khiển mà ai đó có thể cần. Viết mã với goto
có thể không tốt bằng sử dụng một số cấu trúc khác, nhưng có thể viết mã như vậy goto
sẽ giúp tránh "lỗ hổng trong tính biểu cảm" - các cấu trúc mà ngôn ngữ không có khả năng viết mã hiệu quả.
goto
trên trang web xem xét mã, loại bỏ goto
rất nhiều đơn giản hóa logic của mã.
Tất nhiên, GOTO có thể được sử dụng, nhưng có một điều quan trọng hơn kiểu mã hoặc nếu mã có hoặc không đọc được mà bạn phải có khi sử dụng: mã bên trong có thể không mạnh như bạn nghĩ .
Chẳng hạn, hãy xem hai đoạn mã sau:
If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)
Mã tương đương với GOTO
If A == 0 Then GOTO FINAL EndIf
A = 0
FINAL:
Write("Value of A:" + A)
Điều đầu tiên chúng tôi nghĩ là kết quả của cả hai bit mã sẽ là "Giá trị của A: 0" (tất nhiên chúng tôi cho rằng một thực thi không có sự song song)
Điều đó không đúng: trong mẫu đầu tiên, A sẽ luôn là 0, nhưng trong mẫu thứ hai (với câu lệnh GOTO) A có thể không bằng 0. Tại sao?
Lý do là vì từ một điểm khác của chương trình tôi có thể chèn a GOTO FINAL
mà không kiểm soát giá trị của A.
Ví dụ này rất rõ ràng, nhưng khi các chương trình trở nên phức tạp hơn, khó khăn trong việc nhìn thấy những thứ đó tăng lên.
Tài liệu liên quan có thể được tìm thấy trong bài báo nổi tiếng từ ông Dijkstra "Một trường hợp chống lại tuyên bố GO TO"
Tôi sử dụng goto trong trường hợp sau: khi cần quay trở lại từ chức năng ở những nơi khác nhau và trước khi trả lại một số việc chưa được khởi tạo cần phải được thực hiện:
phiên bản không phải là goto:
int doSomething (struct my_complicated_stuff *ctx)
{
db_conn *conn;
RSA *key;
char *temp_data;
conn = db_connect();
if (ctx->smth->needs_alloc) {
temp_data=malloc(ctx->some_size);
if (!temp_data) {
db_disconnect(conn);
return -1;
}
}
...
if (!ctx->smth->needs_to_be_processed) {
free(temp_data);
db_disconnect(conn);
return -2;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->some_other_thing->error) {
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -3;
}
...
key=rsa_load_key(....);
...
if (ctx->something_else->error) {
rsa_free(key);
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -4;
}
if (ctx->something_else->additional_check) {
rsa_free(key);
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return -5;
}
pthread_mutex_unlock(ctx->mutex);
free(temp_data);
db_disconnect(conn);
return 0;
}
phiên bản goto:
int doSomething_goto (struct my_complicated_stuff *ctx)
{
int ret=0;
db_conn *conn;
RSA *key;
char *temp_data;
conn = db_connect();
if (ctx->smth->needs_alloc) {
temp_data=malloc(ctx->some_size);
if (!temp_data) {
ret=-1;
goto exit_db;
}
}
...
if (!ctx->smth->needs_to_be_processed) {
ret=-2;
goto exit_freetmp;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->some_other_thing->error) {
ret=-3;
goto exit;
}
...
key=rsa_load_key(....);
...
if (ctx->something_else->error) {
ret=-4;
goto exit_freekey;
}
if (ctx->something_else->additional_check) {
ret=-5;
goto exit_freekey;
}
exit_freekey:
rsa_free(key);
exit:
pthread_mutex_unlock(ctx->mutex);
exit_freetmp:
free(temp_data);
exit_db:
db_disconnect(conn);
return ret;
}
Phiên bản thứ hai giúp dễ dàng hơn, khi bạn cần thay đổi một cái gì đó trong các câu lệnh thỏa thuận (mỗi câu được sử dụng một lần trong mã) và giảm cơ hội bỏ qua bất kỳ trong số chúng, khi thêm một nhánh mới. Di chuyển chúng trong một chức năng sẽ không giúp ích gì ở đây, bởi vì việc phân bổ có thể được thực hiện ở các "cấp độ" khác nhau.
finally
các khối trong C #
finally
). Để thay thế, sử dụng goto
s, nhưng đến một điểm thoát chung , luôn luôn làm sạch tất cả . Nhưng mỗi phương pháp dọn dẹp có thể xử lý một giá trị là null hoặc đã sạch hoặc được bảo vệ bằng một thử nghiệm có điều kiện, do đó bỏ qua khi không phù hợp.
goto
tất cả đi đến cùng một điểm thoát, có cùng logic (yêu cầu thêm nếu mỗi tài nguyên, như bạn nói). Nhưng đừng bận tâm, khi sử dụng C
bạn là đúng - bất kể lý do gì là mã trong C, gần như chắc chắn đó là một sự đánh đổi ủng hộ mã "trực tiếp" nhất. (Đề xuất của tôi xử lý các tình huống phức tạp trong đó bất kỳ tài nguyên cụ thể nào có thể được phân bổ hoặc không được phân bổ. Nhưng vâng, quá mức trong trường hợp này.)
Edsger Dijkstra, một nhà khoa học máy tính có những đóng góp lớn trong lĩnh vực này, cũng nổi tiếng vì chỉ trích việc sử dụng GoTo. Có một bài viết ngắn về lập luận của ông trên Wikipedia .
Nó có ích để xử lý chuỗi ký tự thông minh theo thời gian.
Hãy tưởng tượng một cái gì đó giống như ví dụ printf-esque này:
for cur_char, next_char in sliding_window(input_string) {
if cur_char == '%' {
if next_char == '%' {
cur_char_index += 1
goto handle_literal
}
# Some additional logic
if chars_should_be_handled_literally() {
goto handle_literal
}
# Handle the format
}
# some other control characters
else {
handle_literal:
# Complicated logic here
# Maybe it's writing to an array for some OpenGL calls later or something,
# all while modifying a bunch of local variables declared outside the loop
}
}
Bạn có thể cấu trúc lại goto handle_literal
một lệnh gọi hàm, nhưng nếu nó sửa đổi một số biến cục bộ khác nhau, bạn sẽ phải chuyển các tham chiếu đến từng biến trừ khi ngôn ngữ của bạn hỗ trợ các bao đóng có thể thay đổi. Bạn vẫn phải sử dụng một continue
câu lệnh (được cho là một dạng goto) sau cuộc gọi để có cùng ngữ nghĩa nếu logic của bạn làm cho một trường hợp khác không hoạt động.
Tôi cũng đã sử dụng gotos một cách thận trọng trong các từ vựng, điển hình cho các trường hợp tương tự. Bạn không cần chúng hầu hết thời gian, nhưng họ rất vui khi có những trường hợp kỳ lạ đó.