ฉันอยู่ภายใต้การแสดงผลที่goto
ไม่ควรใช้ถ้าเป็นไปได้ ในขณะที่อ่าน libavcodec (ซึ่งเขียนเป็นภาษา C) เมื่อวันก่อนฉันสังเกตเห็นการใช้งานหลายอย่าง มันเคยเป็นประโยชน์ที่จะใช้goto
ในภาษาที่รองรับลูปและฟังก์ชั่นหรือไม่? ถ้าเป็นเช่นนั้นทำไม
ฉันอยู่ภายใต้การแสดงผลที่goto
ไม่ควรใช้ถ้าเป็นไปได้ ในขณะที่อ่าน libavcodec (ซึ่งเขียนเป็นภาษา C) เมื่อวันก่อนฉันสังเกตเห็นการใช้งานหลายอย่าง มันเคยเป็นประโยชน์ที่จะใช้goto
ในภาษาที่รองรับลูปและฟังก์ชั่นหรือไม่? ถ้าเป็นเช่นนั้นทำไม
คำตอบ:
มีเหตุผลสองสามข้อในการใช้คำสั่ง "goto" ที่ฉันรู้ (บางคนพูดสิ่งนี้แล้ว):
ออกจากฟังก์ชันอย่างหมดจด
บ่อยครั้งในฟังก์ชันคุณอาจจัดสรรทรัพยากรและจำเป็นต้องออกในหลาย ๆ ที่ โปรแกรมเมอร์สามารถลดความซับซ้อนของรหัสของพวกเขาโดยการวางรหัสการทำความสะอาดทรัพยากรในตอนท้ายของฟังก์ชั่นและ "ออกจากจุด" ของฟังก์ชั่นทั้งหมดจะได้รับฉลากการทำความสะอาด ด้วยวิธีนี้คุณไม่จำเป็นต้องเขียนโค้ดการล้างข้อมูลในทุก ๆ "จุดออก" ของฟังก์ชั่น
กำลังออกจากลูปซ้อนกัน
หากคุณอยู่ในลูปซ้อนกันและต้องการแยกลูปออกทั้งหมดลูปสามารถทำให้สิ่งนี้สะอาดและง่ายกว่าการใช้คำสั่ง break และ if-checks
การปรับปรุงประสิทธิภาพระดับต่ำ
นี่เป็นเพียงรหัสที่สมบูรณ์แบบ แต่คำสั่ง goto จะดำเนินการอย่างรวดเร็วและสามารถเพิ่มความสามารถให้คุณเมื่อเคลื่อนที่ผ่านฟังก์ชั่น อย่างไรก็ตามนี่เป็นดาบสองคมเนื่องจากคอมไพเลอร์โดยทั่วไปไม่สามารถปรับรหัสที่มี gotos ให้เหมาะสมได้
โปรดทราบว่าในตัวอย่างเหล่านี้ทั้งหมด gotos ถูก จำกัด ขอบเขตของฟังก์ชั่นเดียว
goto
ด้วยreturn
เป็นเพียงโง่ มันไม่ได้เป็น "refactoring" อะไรมันก็แค่ "เปลี่ยนชื่อ" เพื่อให้ผู้คนที่เติบโตขึ้นมาในgoto
สภาพแวดล้อมที่ -suppressed (เช่นเราทุกคน) goto
รู้สึกดีขึ้นเกี่ยวกับการใช้สิ่งที่มีคุณธรรมจะมีจำนวน ผมชอบมากที่จะเห็นวงที่ผมใช้มันและดูเล็ก ๆ น้อย ๆgoto
ซึ่งโดยตัวเองเป็นเพียงเครื่องมือgoto
กว่าคนเห็นต้องย้ายวงที่ไหนสักแห่งที่ไม่เกี่ยวข้องเพียงเพื่อหลีกเลี่ยงการ
break
, continue
, return
มีพื้นgoto
เพียงในบรรจุภัณฑ์ที่ดี
do{....}while(0)
ควรจะเป็นความคิดที่ดีกว่า goto ยกเว้นความจริงมันใช้งานได้ใน Java
ทุกคนที่ถูกต่อต้านไม่goto
ว่าโดยตรงหรือโดยอ้อมบทความGoToของ Edsger Dijkstra พิจารณาบทความที่เป็นอันตรายเพื่อยืนยันตำแหน่งของพวกเขา บทความของ Dijkstra ที่แย่มากไม่มีส่วนเกี่ยวข้องกับการใช้goto
คำสั่งในทุกวันนี้และสิ่งที่บทความบอกว่าไม่มีประโยชน์ในการเขียนโปรแกรมสมัยใหม่ goto
Verges มส์ -LESS ขณะนี้อยู่ในศาสนาลงขวาพระคัมภีร์ของ dictated จากที่สูงนักบวชชั้นสูงและหลบหลีก (หรือแย่ลง) ของคนนอกรับรู้
ลองใส่กระดาษของ Dijkstra เข้ากับบริบทเพื่อฉายแสงให้กับวัตถุ
เมื่อ Dijkstra เขียนบทความของเขาภาษายอดนิยมในเวลานั้นเป็นภาษาที่ไม่มีโครงสร้างเช่น BASIC, FORTRAN (ภาษาถิ่นก่อนหน้า) และภาษาแอสเซมบลีที่หลากหลาย เป็นเรื่องปกติสำหรับผู้ที่ใช้ภาษาระดับสูงกว่าในการกระโดดข้ามฐานรหัสของพวกเขาในเกลียวที่บิดเบี้ยวและมีการประมวลผลที่บิดเบี้ยวซึ่งก่อให้เกิดคำว่า "สปาเก็ตตี้โค้ด" คุณสามารถเห็นสิ่งนี้ได้โดยกระโดดข้ามไปที่เกม Trek คลาสสิกที่เขียนโดย Mike Mayfield และพยายามหาวิธีการทำงานของสิ่งต่าง ๆ ใช้เวลาสักครู่เพื่อดูว่า
นี่คือ "การใช้คำแถลงที่ไม่เป็นไปตามปกติ" ซึ่ง Dijkstra กำลังค้านกับในกระดาษของเขาในปี 1968 นี่คือสภาพแวดล้อมที่เขาอาศัยอยู่ในนั้นทำให้เขาเขียนบทความนั้น ความสามารถในการกระโดดไปทุกที่ที่คุณต้องการในรหัสของคุณ ณ จุดใด ๆ ที่คุณชอบคือสิ่งที่เขาวิจารณ์และเรียกร้องให้หยุด เมื่อเปรียบเทียบกับพลังของโลหิตจางgoto
ในภาษา C หรือภาษาสมัยใหม่อื่น ๆ
ฉันสามารถได้ยินเสียงสวดมนต์ที่ยกขึ้นของลัทธิเมื่อพวกเขาเผชิญหน้ากับคนนอกศาสนา "แต่" พวกเขาจะสวดมนต์ "คุณสามารถสร้างรหัสที่ยากมากที่จะอ่านด้วยgoto
ใน C. " โอ้ใช่? คุณสามารถสร้างโค้ดที่อ่านได้ยากโดยไม่จำเป็นgoto
เช่นกัน ชอบสิ่งนี้:
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
ไม่ใช่สิ่งที่goto
อยู่ในสายตาดังนั้นจะต้องอ่านง่ายใช่มั้ย หรือวิธีการเกี่ยวกับสิ่งนี้:
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*/
ไม่มีgoto
ทั้ง มันจะต้องสามารถอ่านได้
จุดของฉันกับตัวอย่างเหล่านี้คืออะไร? ไม่ใช่คุณลักษณะภาษาที่ทำให้รหัสไม่สามารถอ่านได้และไม่สามารถแก้ไขได้ ไม่ใช่ไวยากรณ์ที่ทำ มันเป็นโปรแกรมเมอร์ที่ไม่ดีที่ทำให้เกิดสิ่งนี้ และโปรแกรมเมอร์ที่ไม่ดีอย่างที่คุณเห็นในรายการข้างต้นสามารถทำให้คุณสมบัติภาษาใด ๆอ่านไม่ได้และใช้ไม่ได้ เหมือนfor
ห่วงที่นั่น (คุณเห็นพวกเขาใช่ไหม?)
ตอนนี้เพื่อความเป็นธรรมโครงสร้างภาษาบางอย่างง่ายต่อการถูกทารุณมากกว่าคนอื่น หากคุณเป็นโปรแกรมเมอร์ซีฉันจะลองใช้อีกนานกว่านี้ประมาณ 50% ของการใช้#define
งานนานก่อนที่ฉันจะทำสงครามต่อต้านgoto
!
ดังนั้นสำหรับผู้ที่ใส่ใจที่จะอ่านสิ่งนี้ไปไกลมีประเด็นสำคัญหลายประการที่ควรทราบ
goto
งบถูกเขียนขึ้นสำหรับสภาพแวดล้อมการเขียนโปรแกรมที่goto
เป็นจำนวนมาก
มากขึ้นอาจเป็นอันตรายมากกว่าที่เป็นอยู่ในภาษาที่ทันสมัยที่สุดที่ไม่ได้ประกอบgoto
เพราะสิ่งนี้มีเหตุผลมากพอ ๆ กับการพูดว่า "ฉันพยายามที่จะสนุกครั้งเดียว แต่ไม่ชอบเลยตอนนี้ฉันเลยสู้แล้ว"goto
ในรหัสที่ไม่สามารถแทนที่อย่างเพียงพอโดยการก่อสร้างอื่น ๆgodo
" สิ่งที่น่ารังเกียจที่เสมอเท็จdo
ห่วงเสียจากการใช้ในสถานที่ของbreak
เหล่านี้มักจะเลวร้ายยิ่งกว่าการใช้เหตุผลของgoto
goto
goto
จริงคืออะไร (ซึ่งเป็นคำถามที่โพสต์)
การปฏิบัติตามแนวปฏิบัติที่ดีที่สุดแบบสุ่มสี่สุ่มห้าไม่ใช่วิธีปฏิบัติที่ดีที่สุด แนวคิดของการหลีกเลี่ยงgoto
ข้อความเป็นรูปแบบหลักของการควบคุมการไหลคือการหลีกเลี่ยงการสร้างรหัสสปาเก็ต หากใช้อย่างไม่เหมาะสมในสถานที่ที่เหมาะสมบางครั้งอาจเป็นวิธีที่ง่ายและชัดเจนที่สุดในการแสดงความคิดเห็น Walter Bright ผู้สร้างคอมไพเลอร์ Zortech C ++ และภาษาการเขียนโปรแกรม D ใช้งานบ่อยๆ แต่ดูดี ถึงแม้จะมีgoto
คำสั่งรหัสของเขายังคงสามารถอ่านได้อย่างสมบูรณ์
บรรทัดล่าง: การหลีกเลี่ยงgoto
เพื่อหลีกเลี่ยงgoto
ไม่มีประโยชน์ สิ่งที่คุณต้องการหลีกเลี่ยงคือการสร้างรหัสที่อ่านไม่ได้ หากgoto
โค้ด -laden ของคุณสามารถอ่านได้แสดงว่าไม่มีอะไรผิดปกติ
เนื่องจากgoto
ทำให้การให้เหตุผลเกี่ยวกับการไหลของโปรแกรมอย่างหนัก1 (aka.“ สปาเก็ตตี้โค้ด”) goto
โดยทั่วไปจะใช้เพื่อชดเชยคุณสมบัติที่ขาดหายไปเท่านั้น: การใช้งานgoto
อาจเป็นที่ยอมรับได้จริง แต่ถ้าภาษานั้นไม่มีตัวแปรที่มีโครงสร้างมากกว่า เป้าหมายเดียวกัน ยกตัวอย่างของ Doubt:
กฎกับ goto ที่เราใช้คือ goto ก็โอเคสำหรับการกระโดดไปข้างหน้าไปยังจุดเดียวในการทำความสะอาด
สิ่งนี้เป็นจริง - เฉพาะในกรณีที่ภาษาไม่อนุญาตให้มีการจัดการข้อยกเว้นอย่างเป็นระบบด้วยรหัสการล้างข้อมูล (เช่น RAII หรือ finally
) ซึ่งทำงานได้ดีกว่าเดิม (เพราะมันถูกสร้างขึ้นเป็นพิเศษสำหรับการทำ) หรือเมื่อไม่มีเหตุผลที่ดี เพื่อใช้การจัดการข้อยกเว้นอย่างเป็นระบบ (แต่คุณจะไม่มีกรณีนี้ยกเว้นในระดับที่ต่ำมาก)
ในภาษาอื่นส่วนใหญ่การใช้งานที่ยอมรับได้เพียงอย่างเดียวgoto
คือออกจากลูปซ้อนกัน และถึงแม้จะเป็นการดีกว่าที่จะยกลูปด้านนอกให้เป็นวิธีการของตนเองและใช้return
แทน
นอกเหนือจากนั้นgoto
เป็นสัญญาณที่ความคิดไม่มากพอที่จะเข้าไปในส่วนของรหัส
1ภาษาสมัยใหม่ซึ่งรองรับgoto
การใช้งานข้อ จำกัด บางอย่าง (เช่นgoto
อาจไม่เข้าหรือออกจากฟังก์ชั่น) แต่ปัญหาพื้นฐานยังคงเหมือนเดิม
อนึ่งแน่นอนว่าสิ่งเดียวกันนี้เป็นจริงสำหรับคุณสมบัติภาษาอื่น ๆ โดยเฉพาะอย่างยิ่งข้อยกเว้น และโดยทั่วไปจะมีกฎที่เข้มงวดเพื่อใช้คุณสมบัติเหล่านี้เฉพาะที่ระบุไว้เช่นกฎที่จะไม่ใช้ข้อยกเว้นเพื่อควบคุมการไหลของโปรแกรมที่ไม่ได้รับการยกเว้น
finally
? ดังนั้นการใช้ข้อยกเว้นสำหรับสิ่งอื่นนอกเหนือจากการจัดการข้อผิดพลาดนั้นดี แต่การใช้goto
นั้นไม่ดี? ฉันคิดว่ามีข้อยกเว้นค่อนข้างเหมาะเจาะ
มีสิ่งหนึ่งที่เลวร้ายยิ่งกว่าgoto's
; การใช้ตัวดำเนินการ programflow อื่น ๆ อย่างแปลก ๆ เพื่อหลีกเลี่ยงการข้ามไป:
ตัวอย่าง:
// 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)
ฉันคิดว่าถือได้ว่าเป็นสำนวน คุณไม่ได้รับอนุญาตให้ไม่เห็นด้วย: D
goto after_do_block;
โดยไม่พูดอย่างนั้น มิฉะนั้น ... "ลูป" ที่ทำงานหนึ่งครั้งใช่ไหม ฉันจะเรียกการใช้โครงสร้างการควบคุมในทางที่ผิด
#define
มีมากน้อยกว่าการใช้goto
ครั้งเดียว: D
ในC # สวิตช์งบทำไม่อนุญาตให้ฤดูใบไม้ร่วงผ่าน ดังนั้นgotoจึงถูกใช้เพื่อถ่ายโอนการควบคุมไปยังเลเบลตัวพิมพ์ใหญ่หรือเลเบลเริ่มต้น
ตัวอย่างเช่น:
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;
}
แก้ไข: มีข้อยกเว้นหนึ่งข้อสำหรับกฎ "no fall-through" อนุญาตให้ใช้ Fall-through ได้หากคำสั่ง case ไม่มีรหัส
goto case 5:
ในกรณีที่ 1) ดูเหมือนว่าคำตอบของ Konrad Rudolph นั้นถูกต้องที่นี่: goto
กำลังชดเชยคุณสมบัติที่ขาดหายไป (และชัดเจนน้อยกว่าฟีเจอร์ที่แท้จริง) หากสิ่งที่เราต้องการจริงๆคือการล้มลงอาจเป็นค่าเริ่มต้นที่ดีที่สุดที่จะไม่ล้มเหลว แต่สิ่งที่ต้องการcontinue
ขออย่างชัดเจน
#ifdef TONGUE_IN_CHEEK
Perl มีgoto
ที่ช่วยให้คุณสามารถเรียกหางของคนจน :-P
sub factorial {
my ($n, $acc) = (@_, 1);
return $acc if $n < 1;
@_ = ($n - 1, $acc * $n);
goto &factorial;
}
#endif
goto
เอาล่ะเพื่อให้มีอะไรจะทำอย่างไรกับซี อย่างจริงจังยิ่งขึ้นฉันเห็นด้วยกับความคิดเห็นอื่น ๆ เกี่ยวกับการใช้goto
สำหรับการสะสางหรือเพื่อการใช้งานอุปกรณ์ของ Duffหรือสิ่งที่คล้ายกัน มันคือทั้งหมดที่เกี่ยวกับการใช้งานไม่ใช้ในทางที่ผิด
(ความคิดเห็นเดียวกันสามารถนำไปใช้กับlongjmp
ข้อยกเว้นcall/cc
และสิ่งที่คล้ายกัน --- พวกเขามีการใช้งานที่ถูกกฎหมาย แต่สามารถถูกทำร้ายได้ง่ายตัวอย่างเช่นการโยนข้อยกเว้นอย่างหมดจดเพื่อหลบหนีโครงสร้างการควบคุมที่ซ้อนกันลึก ๆ .)
ฉันเขียนภาษาแอสเซมบลีมากกว่าสองสามบรรทัดในช่วงหลายปีที่ผ่านมา ในที่สุดทุกภาษาระดับสูงรวบรวมลงไป gotos โอเคเรียกพวกเขาว่า "กิ่งไม้" หรือ "กระโดด" หรืออะไรก็ได้ แต่พวกมันกลับไปแล้ว ใครสามารถเขียนแอสเซมเบลอร์ goto น้อย
ตอนนี้แน่นอนคุณสามารถชี้ไปที่โปรแกรมเมอร์ Fortran, C หรือ BASIC ว่าการจลาจลกับ gotos เป็นสูตรสำหรับปาเก็ตตี้โบโลญญ่า คำตอบคือไม่ควรหลีกเลี่ยงพวกเขา แต่ใช้อย่างระมัดระวัง
มีดสามารถใช้ในการเตรียมอาหารฟรีใครบางคนหรือฆ่าคน เราจะทำโดยไม่มีมีดผ่านความกลัวของหลัง? ในทำนองเดียวกัน goto: ใช้อย่างไม่ระมัดระวังมันเป็นอุปสรรคต่อการใช้อย่างระมัดระวังจะช่วย
ดูเมื่อต้องการใช้ไปที่เมื่อเขียนโปรแกรมใน C :
แม้ว่าการใช้ goto นั้นเป็นวิธีการเขียนโปรแกรมที่ไม่ดีอยู่เสมอ (แน่นอนว่าคุณสามารถหาวิธีที่ดีกว่าในการทำ XYZ) แต่ก็มีบางครั้งที่มันไม่ใช่ตัวเลือกที่แย่จริงๆ บางคนอาจโต้แย้งว่าเมื่อมีประโยชน์มันเป็นตัวเลือกที่ดีที่สุด
สิ่งที่ฉันต้องพูดเกี่ยวกับ goto ส่วนใหญ่จะใช้กับ C เท่านั้นหากคุณใช้ C ++ ไม่มีเหตุผลที่ดีที่จะใช้ goto แทนข้อยกเว้น อย่างไรก็ตามใน C คุณไม่มีพลังของกลไกการจัดการข้อยกเว้นดังนั้นหากคุณต้องการแยกข้อผิดพลาดออกจากส่วนที่เหลือของตรรกะโปรแกรมของคุณและคุณต้องการหลีกเลี่ยงการเขียนโค้ดซ้ำหลายครั้งตลอดทั้งรหัสของคุณ จากนั้นข้ามไปก็เป็นทางเลือกที่ดี
ฉันหมายถึงอะไร คุณอาจมีรหัสที่มีลักษณะดังนี้:
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];
}
สิ่งนี้ใช้ได้จนกว่าคุณจะทราบว่าคุณต้องเปลี่ยนรหัสการล้างข้อมูลของคุณ จากนั้นคุณต้องผ่านและทำการเปลี่ยนแปลง 4 ครั้ง ตอนนี้คุณอาจตัดสินใจว่าคุณสามารถสรุปการล้างข้อมูลทั้งหมดในฟังก์ชันเดียวได้ นั่นไม่ใช่ความคิดที่เลว แต่หมายความว่าคุณจะต้องระวังพอยน์เตอร์ด้วย - ถ้าคุณวางแผนที่จะปล่อยพอยน์เตอร์ในฟังก์ชั่นการล้างข้อมูลของคุณไม่มีวิธีตั้งค่าให้ชี้ไปที่ NULL เว้นแต่ว่าคุณผ่านพอยน์เตอร์ไปยังพอยน์เตอร์ ในหลายกรณีคุณจะไม่ใช้ตัวชี้นั้นอีกเลยดังนั้นอาจไม่ใช่ปัญหาใหญ่ ในทางกลับกันถ้าคุณเพิ่มตัวชี้ใหม่ตัวจัดการไฟล์หรือสิ่งอื่น ๆ ที่จำเป็นต้องมีการล้างข้อมูลจากนั้นคุณจะต้องเปลี่ยนฟังก์ชันการล้างข้อมูลอีกครั้ง แล้วคุณจะต้องเปลี่ยนอาร์กิวเมนต์เป็นฟังก์ชันนั้น
โดยการใช้goto
งานก็จะเป็น
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;
}
ประโยชน์ของที่นี่คือรหัสของคุณต่อไปนี้มีสิทธิ์เข้าถึงทุกสิ่งที่จะต้องดำเนินการล้างข้อมูลและคุณได้จัดการเพื่อลดจำนวนจุดเปลี่ยนแปลงอย่างมาก ประโยชน์อีกอย่างก็คือคุณไม่ได้มีจุดออกหลายจุดสำหรับฟังก์ชั่นของคุณเพียงจุดเดียว ไม่มีโอกาสที่คุณจะกลับมาจากฟังก์ชั่นโดยไม่ตั้งใจโดยไม่ได้ทำความสะอาด
นอกจากนี้เนื่องจาก goto ถูกใช้เพื่อข้ามไปยังจุดเดียวเท่านั้นไม่ใช่ว่าคุณกำลังสร้างรหัสปาเก็ตตี้จำนวนมากที่กระโดดไปมาในความพยายามจำลองการเรียกใช้ฟังก์ชัน แต่ข้ามไปจริง ๆ แล้วช่วยเขียนโค้ดที่มีโครงสร้างมากขึ้น
ในคำที่goto
ควรใช้เท่าที่จำเป็นและเป็นทางเลือกสุดท้าย - แต่มีเวลาและสถานที่สำหรับมัน คำถามไม่ควร "คุณต้องใช้มัน" แต่ "มันเป็นตัวเลือกที่ดีที่สุด" เพื่อใช้งาน
หนึ่งในเหตุผลที่ข้ามไปไม่ดีนอกจากสไตล์การเขียนโค้ดคือคุณสามารถใช้มันเพื่อสร้างการทับซ้อนแต่ลูปที่ไม่ซ้อนกัน :
loop1:
a
loop2:
b
if(cond1) goto loop1
c
if(cond2) goto loop2
สิ่งนี้จะสร้างความแปลกประหลาด แต่โครงสร้างการไหลของการควบคุมที่ถูกกฎหมายซึ่งเป็นลำดับเช่น (a, b, c, b, a, b, a, b, ... ) เป็นไปได้ซึ่งทำให้แฮกเกอร์คอมไพเลอร์ไม่มีความสุข เห็นได้ชัดว่ามีเทคนิคการเพิ่มประสิทธิภาพที่ชาญฉลาดจำนวนหนึ่งซึ่งพึ่งพาโครงสร้างประเภทนี้ซึ่งไม่เกิดขึ้น (ฉันควรตรวจสอบสำเนาหนังสือมังกรของฉัน ... ) ผลลัพธ์ของสิ่งนี้อาจ (โดยใช้คอมไพเลอร์บางตัว) ว่าการเพิ่มประสิทธิภาพอื่นไม่ได้ทำเพื่อรหัสที่มีgoto
s
มันอาจจะมีประโยชน์ถ้าคุณรู้ว่ามัน "โอ้โดยวิธี" เกิดขึ้นเพื่อชักชวนคอมไพเลอร์ที่จะปล่อยรหัสได้เร็วขึ้น โดยส่วนตัวแล้วฉันต้องการที่จะพยายามอธิบายคอมไพเลอร์เกี่ยวกับสิ่งที่เป็นไปได้และสิ่งที่ไม่ได้ใช้เคล็ดลับเช่น goto แต่goto
ก่อนหน้านี้ฉันอาจลองใช้ก่อนที่จะแฮ็กแอสเซมเบลอร์
goto
มีประโยชน์คือมันช่วยให้คุณสร้างลูปแบบนี้ได้ซึ่งจะต้องใช้ตรรกะเชิงตรรกะอย่างอื่น ฉันยังต้องการยืนยันว่าหากเพิ่มประสิทธิภาพไม่ทราบวิธีการที่จะเขียนนี้แล้วดี การวนซ้ำเช่นนี้ไม่ควรกระทำเพื่อประสิทธิภาพหรือความสามารถในการอ่าน แต่เนื่องจากเป็นสิ่งที่ต้องเกิดขึ้นอย่างแน่นอน ในกรณีนี้ฉันไม่ต้องการให้เครื่องมือเพิ่มประสิทธิภาพคลำไปรอบ ๆ ด้วย
ฉันคิดว่ามันตลกที่บางคนจะไปไกลเท่าที่จะให้รายการกรณีที่ข้ามไปเป็นที่ยอมรับโดยบอกว่าการใช้อื่น ๆ ทั้งหมดเป็นสิ่งที่ยอมรับไม่ได้ คุณคิดว่าคุณรู้ทุกกรณีที่ goto เป็นตัวเลือกที่ดีที่สุดสำหรับการแสดงอัลกอริทึมหรือไม่?
เพื่ออธิบายฉันจะให้ตัวอย่างที่ยังไม่มีใครแสดงได้ที่นี่:
วันนี้ฉันกำลังเขียนโค้ดเพื่อแทรกองค์ประกอบในตารางแฮช ตารางแฮชคือแคชของการคำนวณก่อนหน้าซึ่งสามารถเขียนทับได้ตามประสงค์ (ส่งผลต่อประสิทธิภาพ แต่ไม่ถูกต้อง)
ที่ฝากข้อมูลของตารางแฮชแต่ละสล็อตมี 4 สล็อตและฉันมีเกณฑ์มากมายในการตัดสินใจว่าองค์ประกอบใดที่จะเขียนทับเมื่อที่เก็บข้อมูลเต็ม ตอนนี้นี่หมายถึงการผ่านสามถังผ่านไปเช่นนี้
// 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
ตอนนี้ถ้าฉันไม่ได้ใช้ goto รหัสนี้จะเป็นอย่างไร
บางสิ่งเช่นนี้
// 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
มันจะดูแย่ลงและแย่ลงหากมีการเพิ่มการส่งผ่านมากขึ้นในขณะที่เวอร์ชันที่มี goto จะรักษาระดับการเยื้องไว้ตลอดเวลาและหลีกเลี่ยงการใช้เก๊
ดังนั้นมีอีกกรณีที่ goto ทำให้โค้ดสะอาดขึ้นและง่ายต่อการเขียนและทำความเข้าใจ ... ฉันแน่ใจว่ามีอีกมากมายดังนั้นอย่าแสร้งว่ารู้ทุกกรณีที่ goto มีประโยชน์ใช้ดี ๆ ที่คุณไม่สามารถทำได้ ไม่คิด
goto
แต่ละฟังก์ชั่นอยู่ในระดับเดียวกันของสิ่งที่เป็นนามธรรม ว่ามันหลีกเลี่ยงgoto
เป็นโบนัส
container::iterator it = slot_p.find(hash_key); if (it != slot_p.end()) it->overwrite(hash_key); else it = slot_p.find_first_empty();
ฉันพบว่าโปรแกรมประเภทนั้นอ่านง่ายกว่ามาก แต่ละฟังก์ชั่นในกรณีนี้สามารถเขียนเป็นฟังก์ชั่นแท้ซึ่งง่ายกว่าที่จะให้เหตุผล ตอนนี้ฟังก์ชั่นหลักอธิบายว่าโค้ดทำหน้าที่อะไรเพียงแค่ชื่อของฟังก์ชั่นและถ้าคุณต้องการคุณสามารถดูคำจำกัดความของพวกเขาเพื่อดูว่ามันทำงานอย่างไร
กฎกับ goto ที่เราใช้คือ goto ก็โอเคสำหรับการกระโดดไปข้างหน้าไปยังจุดเดียวในการทำความสะอาด ในฟังก์ชั่นที่ซับซ้อนจริงๆเราผ่อนคลายกฎนั้นเพื่ออนุญาตให้ข้ามไปข้างหน้า ในทั้งสองกรณีเราจะหลีกเลี่ยงการซ้อนกันอย่างลึกซึ้งถ้าข้อความที่มักจะเกิดขึ้นกับการตรวจสอบรหัสข้อผิดพลาดซึ่งช่วยให้อ่านง่ายและบำรุงรักษา
ส่วนใหญ่อภิปรายรอบคอบและอย่างละเอียดของงบโกโตะใช้ถูกต้องตามกฎหมายของพวกเขาและสร้างทางเลือกที่สามารถนำมาใช้ในสถานที่ของ "งบกลับไปข้างคุณงามความดี" แต่สามารถทำร้ายเป็น "ได้อย่างง่ายดายเช่นงบข้ามไปเป็นโดนัลด์ Knuth บทความการเขียนโปรแกรมที่มีโครงสร้างที่มีงบโกโตะ " ในการสำรวจคอมพิวเตอร์เดือนธันวาคม 2517 (เล่มที่ 6 หมายเลข 4 หน้า 261 - 301)
ไม่น่าแปลกใจที่บางแง่มุมของกระดาษอายุ 39 ปีนี้มีการลงวันที่: การเพิ่มขึ้นของคำสั่งขนาดกำลังการผลิตทำให้การปรับปรุงประสิทธิภาพของ Knuth บางอย่างไม่สามารถสังเกตเห็นได้สำหรับปัญหาที่มีขนาดปานกลางและการสร้างภาษาโปรแกรมใหม่ (ตัวอย่างเช่นลองจับบล็อกเสริมโครงสร้างของ Zahn แม้ว่าจะไม่ค่อยได้ใช้ในทางนั้น) แต่ Knuth ครอบคลุมทุกด้านของการโต้แย้งและควรจะต้องอ่านก่อนที่จะมีใครซักคนปัญหาอีกครั้ง
ในโมดูล Perl บางครั้งคุณต้องการสร้างรูทีนย่อยหรือปิดทันที สิ่งคือเมื่อคุณสร้างรูทีนย่อยคุณจะไปยังรูทีนย่อยได้อย่างไร คุณสามารถเรียกมันได้ แต่ถ้ารูทีนย่อยใช้caller()
มันจะไม่มีประโยชน์เท่าที่ควร นั่นคือสิ่งที่การgoto &subroutine
เปลี่ยนแปลงจะมีประโยชน์
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.
}
คุณยังสามารถใช้รูปแบบนี้goto
เพื่อให้รูปแบบพื้นฐานของการเพิ่มประสิทธิภาพการโทรหาง
sub factorial($){
my($n,$tally) = (@_,1);
return $tally if $n <= 1;
$tally *= $n--;
@_ = ($n,$tally);
goto &factorial;
}
(ในPerl 5 เวอร์ชัน 16ที่น่าจะเขียนได้ดีกว่าgoto __SUB__;
)
มีโมดูลที่จะนำเข้าtail
ตัวดัดแปลงและหนึ่งที่จะนำเข้าrecur
หากคุณไม่ต้องการใช้รูปแบบ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
จะทำได้ดีกว่ากับคำหลักอื่น ๆเช่นเดียวกับredo
รหัสเล็กน้อย:
LABEL: ;
...
goto LABEL if $x;
{
...
redo if $x;
}
หรือไปที่last
โค้ดจากหลาย ๆ ที่:
goto LABEL if $x;
...
goto LABEL if $y;
...
LABEL: ;
{
last if $x;
...
last if $y
...
}
ถ้าเป็นเช่นนั้นทำไม
C ไม่มีการแบ่งหลายระดับ / ป้ายกำกับและกระแสการควบคุมทั้งหมดไม่สามารถจำลองได้อย่างง่ายดายด้วยการวนซ้ำและการตัดสินใจเบื้องต้น gotos ไปไกลเพื่อแก้ไขข้อบกพร่องเหล่านี้
บางครั้งมันชัดเจนกว่าที่จะใช้ตัวแปรธงบางชนิดเพื่อให้เกิดการแตกแบบหลอกหลายระดับ แต่มันก็ไม่ได้เหนือกว่า goto เสมอไป (อย่างน้อยก็ goto อนุญาตให้ใครกำหนดได้ง่ายว่าการควบคุมไปที่ใดซึ่งแตกต่างจากตัวแปร flag ) และบางครั้งคุณก็ไม่ต้องการจ่ายราคาประสิทธิภาพของธง / การตั้งค่าอื่น ๆ เพื่อหลีกเลี่ยงการข้ามไป
libavcodec เป็นโค้ดที่ไวต่อประสิทธิภาพ การแสดงออกโดยตรงของโฟลว์ควบคุมน่าจะเป็นลำดับความสำคัญเพราะมันจะมีแนวโน้มที่จะทำงานได้ดีขึ้น
เช่นเดียวกับที่ไม่มีใครใช้คำสั่ง "COME FROM" ....
ฉันพบว่าการใช้ {} ขณะที่ (เป็นเท็จ) เป็นการปฏิวัติอย่างเต็มที่ เป็นไปได้ที่จะโน้มน้าวฉันว่ามันเป็นสิ่งจำเป็นในบางกรณีที่แปลก แต่ไม่เคยว่ามันเป็นรหัสที่สะอาด
หากคุณต้องทำวนซ้ำเช่นนี้ทำไมไม่ทำให้การพึ่งพาตัวแปรแฟล็กชัดเจน?
for (stepfailed=0 ; ! stepfailed ; /*empty*/)
/*empty*/
จะเป็นอย่างนั้นstepfailed = 1
? ไม่ว่าในกรณีใดมันจะดีกว่าdo{}while(0)
อย่างไร? ในทั้งสองคุณต้องbreak
ออกจากมัน (หรือในของคุณstepfailed = 1; continue;
) ดูเหมือนไม่จำเป็นสำหรับฉัน
1) การใช้ข้ามไปที่พบบ่อยที่สุดที่ฉันรู้คือเลียนแบบการจัดการข้อยกเว้นในภาษาที่ไม่ได้นำเสนอคือใน C. (รหัสที่ได้รับจากนิวเคลียร์ข้างต้นเป็นเพียงแค่นั้น) ดูที่รหัสแหล่ง Linux และคุณ ' จะเห็นล้านล้าน gotos ใช้วิธีนั้น มีอยู่ประมาณ 100,000 gotos ในรหัสลินุกซ์ตามการสำรวจอย่างรวดเร็วดำเนินการในปี 2013: http://blog.regehr.org/archives/894 การใช้งานไปที่ถูกกล่าวถึงแม้จะอยู่ในคู่มือสไตล์ลินุกซ์การเข้ารหัส: https://www.kernel.org/doc/Documentation/CodingStyle เช่นเดียวกับการเขียนโปรแกรมเชิงวัตถุจำลองโดยใช้ structs ที่มีตัวชี้ฟังก์ชั่น goto มีสถานที่ในการเขียนโปรแกรม C ดังนั้นใครถูก: Dijkstra หรือ Linus (และเคอร์เนล Linux ทั้งหมด) ทฤษฎีกับการฝึกฝนโดยทั่วไป
อย่างไรก็ตามมี gotcha ตามปกติสำหรับไม่มีการสนับสนุนระดับคอมไพเลอร์และตรวจสอบการสร้าง / รูปแบบทั่วไป: มันง่ายกว่าที่จะใช้พวกเขาผิดและแนะนำข้อบกพร่องโดยไม่ต้องตรวจสอบเวลารวบรวม Windows และ Visual C ++ แต่ในโหมด C ให้การจัดการข้อยกเว้นผ่าน SEH / VEH ด้วยเหตุผลนี้: ข้อยกเว้นมีประโยชน์แม้จะอยู่นอกภาษา OOP เช่นในภาษาขั้นตอน แต่คอมไพเลอร์ไม่สามารถบันทึกเบคอนของคุณได้เสมอแม้ว่าจะให้การสนับสนุนทางไวยากรณ์สำหรับข้อยกเว้นในภาษา ลองพิจารณาเป็นตัวอย่างของข้อผิดพลาดที่มีชื่อเสียงของ Apple SSL "goto fail" ซึ่งทำซ้ำ goto หนึ่งรายการที่มีผลร้าย ( 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
คุณสามารถมีข้อผิดพลาดเดียวกันโดยใช้ข้อยกเว้นสนับสนุนคอมไพเลอร์เช่นใน C ++:
struct Fail {};
try {
if (something())
throw Fail();
throw Fail(); // copypasta bug
printf("Never reached\n");
}
catch (Fail&) {
// control jumps here
}
แต่ทั้งสองสายพันธุ์ของข้อผิดพลาดสามารถหลีกเลี่ยงได้หากคอมไพเลอร์วิเคราะห์และเตือนคุณเกี่ยวกับรหัสที่เข้าไม่ถึง ตัวอย่างเช่นการคอมไพล์ด้วย Visual C ++ ที่ระดับคำเตือน / W4 ค้นหาข้อบกพร่องในทั้งสองกรณี Java เช่นห้ามรหัสที่ไม่สามารถเข้าถึงได้ (ซึ่งสามารถหาได้!) ด้วยเหตุผลที่ดี: มันน่าจะเป็นข้อผิดพลาดในรหัสเฉลี่ยของ Joe ตราบใดที่การสร้าง goto ไม่อนุญาตให้เป้าหมายที่คอมไพเลอร์ไม่สามารถคิดออกได้ง่าย ๆ เช่น gotos ไปยังที่อยู่ที่คำนวณ (**) มันไม่ได้ยากสำหรับคอมไพเลอร์ในการค้นหาโค้ดที่เข้าไม่ถึงภายในฟังก์ชันที่มี gotos กว่าการใช้ Dijkstra - อนุมัติรหัส
(**) เชิงอรรถ: Gotos ไปยังหมายเลขบรรทัดที่คำนวณได้เป็นไปได้ใน Basic บางเวอร์ชันเช่น GOTO 10 * x โดยที่ x เป็นตัวแปร ค่อนข้างสับสนใน Fortran "computed goto" หมายถึงโครงสร้างที่เทียบเท่ากับคำสั่ง switch ใน C. Standard C ไม่อนุญาตให้มีการคำนวณ gotos ในภาษา แต่กลับไปที่ป้ายประกาศแบบคงที่ / syntactically อย่างไรก็ตาม GNU C มีส่วนขยายเพื่อรับที่อยู่ของป้ายกำกับ (ตัวดำเนินการนำหน้า &&) และยังอนุญาตให้ข้ามไปเป็นตัวแปรประเภทโมฆะ * ดูhttps://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.htmlสำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อย่อยที่ไม่ชัดเจนนี้ ส่วนที่เหลือของโพสต์นี้ไม่เกี่ยวข้องกับคุณลักษณะ GNU C ที่ปิดบัง
มาตรฐาน C (เช่นไม่คำนวณ) gotos มักจะไม่ได้เป็นเหตุผลที่รหัสไม่สามารถเข้าถึงได้ในเวลารวบรวม เหตุผลปกติคือรหัสตรรกะดังต่อไปนี้ ป.ร. ให้ไว้
int computation1() {
return 1;
}
int computation2() {
return computation1();
}
มันเป็นเรื่องยากสำหรับคอมไพเลอร์ที่จะหาโค้ดที่ไม่สามารถเข้าถึงได้ในโครงสร้าง 3 อย่างต่อไปนี้:
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&) {
}
}
(แก้ตัวสไตล์การเข้ารหัสที่เกี่ยวข้องกับรั้งของฉัน แต่ฉันพยายามเก็บตัวอย่างให้กะทัดรัดที่สุดเท่าที่จะทำได้)
Visual C ++ / W4 (แม้จะมี / Ox) ล้มเหลวในการค้นหารหัสที่ไม่สามารถเข้าถึงได้ในสิ่งเหล่านี้และเนื่องจากคุณอาจรู้ว่าปัญหาในการค้นหารหัสที่เข้าไม่ถึงนั้นไม่สามารถเข้าถึงได้โดยทั่วไป (หากคุณไม่เชื่อฉันเกี่ยวกับสิ่งนั้น: https://www.cl.cam.ac.uk/teaching/2006/OptComp/slides/lecture02.pdf )
ในฐานะที่เป็นปัญหาที่เกี่ยวข้อง C goto สามารถใช้เพื่อจำลองข้อยกเว้นเฉพาะภายในเนื้อหาของฟังก์ชัน ไลบรารี C มาตรฐานนำเสนอฟังก์ชัน setjmp () และ longjmp () สำหรับการจำลองการออก / ยกเว้นที่ไม่ใช่โลคอล แต่มีข้อเสียร้ายแรงบางประการเมื่อเทียบกับภาษาอื่น ๆ บทความ Wikipedia http://en.wikipedia.org/wiki/Setjmp.hอธิบายปัญหาหลังนี้ได้ค่อนข้างดี คู่ฟังก์ชั่นนี้ยังใช้งานได้บน Windows ( http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx ) แต่แทบจะไม่มีใครใช้พวกเขาที่นั่นเพราะ SEH / VEH เหนือกว่า แม้กระทั่ง Unix ฉันคิดว่า setjmp และ longjmp นั้นไม่ค่อยมีคนใช้
2) ฉันคิดว่าการใช้ข้ามครั้งที่สองที่ใช้กันทั่วไปของ goto ใน C คือการใช้การแบ่งหลายระดับหรือการดำเนินการหลายระดับต่อไปซึ่งเป็นกรณีการใช้งานที่ไม่มีข้อโต้แย้ง เรียกคืนได้ว่า Java ไม่อนุญาตให้ใช้ป้ายข้ามไป แต่อนุญาตให้แบ่งป้ายกำกับหรือทำป้ายกำกับต่อ ตามhttp://www.oracle.com/technetwork/java/simple-142616.htmlนี่เป็นกรณีการใช้งานทั่วไปของ gotos ใน C (90% พวกเขาพูด) แต่ในประสบการณ์ส่วนตัวของฉันรหัสระบบมีแนวโน้ม ใช้ gotos เพื่อจัดการข้อผิดพลาดบ่อยขึ้น บางทีในรหัสทางวิทยาศาสตร์หรือที่ระบบปฏิบัติการมีข้อยกเว้นการจัดการ (Windows) การออกหลายระดับเป็นกรณีการใช้งานที่โดดเด่น พวกเขาไม่ได้ให้รายละเอียดใด ๆ เกี่ยวกับบริบทของการสำรวจ
แก้ไขเพื่อเพิ่ม: ปรากฎว่ารูปแบบการใช้งานทั้งสองนี้พบได้ในหนังสือ C ของ Kernighan และ Ritchie รอบ ๆ หน้า 60 (ขึ้นอยู่กับรุ่น) สิ่งที่ควรทราบอีกประการหนึ่งคือกรณีใช้งานทั้งสองเกี่ยวข้องกับการส่งต่อไปข้างหน้าเท่านั้น และปรากฎว่า MISRA C 2012 edition (ต่างจากฉบับที่ 2004) อนุญาตให้ใช้งาน gotos ได้ตราบใดที่พวกมันเป็นรุ่นต่อไปเท่านั้น
บางคนบอกว่าไม่มีเหตุผลสำหรับการข้ามไปใน C ++ บางคนบอกว่าในกรณี 99% มีทางเลือกที่ดีกว่า นี่ไม่ใช่เหตุผลเพียงแค่การแสดงผลที่ไม่สมเหตุสมผล นี่เป็นตัวอย่างที่ดีที่ goto นำไปสู่รหัสที่ดีบางอย่างเช่นห่วง do-while ที่ปรับปรุงแล้ว:
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;
เปรียบเทียบกับรหัสฟรีข้ามไป:
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;
ฉันเห็นความแตกต่างเหล่านี้:
{}
จำเป็นต้องใช้บล็อกที่ซ้อนกัน(แม้ว่าจะdo {...} while
คุ้นเคยมากกว่า)loop
ตัวแปรพิเศษใช้ในสี่แห่งloop
loop
ไม่เก็บข้อมูลใด ๆ ก็แค่ควบคุมการไหลของการดำเนินการซึ่งเป็นที่เข้าใจได้น้อยกว่าฉลากที่เรียบง่ายมีอีกตัวอย่างหนึ่งคือ
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?
}
}
ทีนี้ลองกำจัด goto ที่ "ชั่วร้าย":
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);
}
คุณเห็นว่ามันเป็นประเภทเดียวกันกับการใช้ goto มันเป็นรูปแบบที่มีโครงสร้างที่ดีและไม่ได้ส่งต่อ goto มากเท่าการส่งเสริมตามวิธีที่แนะนำเท่านั้น แน่นอนคุณต้องการหลีกเลี่ยงรหัส "สมาร์ท" เช่นนี้:
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
}
}
ประเด็นก็คือว่า goto สามารถนำไปใช้ในทางที่ผิดได้ง่าย แต่ตัวของ goto นั้นไม่ควรตำหนิ โปรดทราบว่าฉลากมีขอบเขตฟังก์ชั่นใน C ++ ดังนั้นจึงไม่ก่อให้เกิดมลภาวะระดับโลกเช่นเดียวกับในแอสเซมบลีบริสุทธิ์ซึ่งลูปที่ซ้อนทับกันมีตำแหน่งอยู่และเป็นเรื่องธรรมดามาก - เช่นในรหัสต่อไปนี้สำหรับ 8051 โปรแกรมวนรอบส่วนฟ้าผ่า:
; 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
มีข้อได้เปรียบอีกประการหนึ่ง: goto สามารถใช้เป็นลูปที่มีชื่อเงื่อนไขและการไหลอื่น ๆ :
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)
หรือคุณสามารถใช้ goto ที่เทียบเท่ากับการเยื้องดังนั้นคุณไม่จำเป็นต้องแสดงความคิดเห็นหากคุณเลือกชื่อป้ายกำกับอย่างชาญฉลาด:
if(!valid) goto NOTVALID;
LOOPBACK:
// more than one page of code here
if(loop) goto LOOPBACK;
NOTVALID:;
ใน Perl ให้ใช้ label เพื่อ "goto" จาก loop - ใช้คำสั่ง "last" ซึ่งคล้ายกับการแตก
ทำให้สามารถควบคุมลูปซ้อนได้ดีขึ้น
รองรับฉลาก goto แบบดั้งเดิมด้วย แต่ฉันไม่แน่ใจว่ามีอินสแตนซ์มากเกินไปซึ่งเป็นวิธีเดียวที่จะบรรลุสิ่งที่คุณต้องการ - รูทีนย่อยและลูปควรเพียงพอสำหรับกรณีส่วนใหญ่
goto &subroutine
คือ ซึ่งเริ่มต้นรูทีนย่อยด้วย @_ ปัจจุบันขณะที่แทนที่รูทีนย่อยปัจจุบันในสแต็ก
ปัญหาของ 'goto' และอาร์กิวเมนต์ที่สำคัญที่สุดของการเคลื่อนไหว 'goto-less programming' คือถ้าคุณใช้มันบ่อยเกินไปรหัสของคุณถึงแม้ว่ามันอาจทำงานได้อย่างถูกต้องกลายเป็นอ่านไม่ได้ไม่สามารถอ่านไม่ได้ดูไม่ได้ ฯลฯ เคส 'goto' นำไปสู่รหัสสปาเก็ตตี้ โดยส่วนตัวฉันไม่สามารถนึกถึงเหตุผลที่ดีว่าทำไมฉันถึงใช้ 'โกโตะ'
goto
) การใช้งานของ @ cschol คล้ายกัน: แม้ว่าอาจจะไม่ได้ออกแบบภาษาในขณะนี้ (s) เขากำลังประเมินความพยายามของนักออกแบบโดยทั่วไป
goto
ยกเว้นในบริบทที่จะนำตัวแปรเข้ามามีอยู่นั้นมีราคาถูกกว่าการพยายามสนับสนุนโครงสร้างการควบคุมทุกชนิดที่บางคนอาจต้องการ การเขียนโค้ดด้วยgoto
อาจไม่ดีเท่าการใช้โครงสร้างอื่น แต่การสามารถเขียนโค้ดด้วยgoto
จะช่วยหลีกเลี่ยง "ช่องโหว่ในความหมาย" - โครงสร้างที่ภาษาไม่สามารถเขียนโค้ดที่มีประสิทธิภาพได้
goto
อยู่ในเว็บไซต์ตรวจสอบโค้ดการกำจัดgoto
ลอจิกที่ลดความซับซ้อนของโค้ด
แน่นอนว่าสามารถใช้ GOTO ได้ แต่มีสิ่งสำคัญมากกว่าสไตล์โค้ดหรือหากโค้ดนั้นเป็นหรือไม่สามารถอ่านได้ที่คุณต้องจำไว้เมื่อคุณใช้: รหัสภายในอาจไม่แข็งแรงเท่าคุณ คิด
ตัวอย่างเช่นดูตัวอย่างโค้ดสองรายการต่อไปนี้:
If A <> 0 Then A = 0 EndIf
Write("Value of A:" + A)
รหัสเทียบเท่ากับ GOTO
If A == 0 Then GOTO FINAL EndIf
A = 0
FINAL:
Write("Value of A:" + A)
สิ่งแรกที่เราคิดคือผลลัพธ์ของโค้ดทั้งสองจะเป็น "ค่าของ A: 0" (เราสมมติว่าการประมวลผลโดยไม่มีการขนานกันแน่นอน)
ไม่ถูกต้อง: ในตัวอย่างแรก A จะเป็น 0 เสมอ แต่ในตัวอย่างที่สอง (ด้วยคำสั่ง GOTO) A อาจไม่ใช่ 0 ทำไม
เหตุผลก็เพราะจากอีกจุดหนึ่งของโปรแกรมฉันสามารถแทรก a GOTO FINAL
โดยไม่ต้องควบคุมค่าของ A
ตัวอย่างนี้ชัดเจนมาก แต่เมื่อโปรแกรมซับซ้อนขึ้นความยากลำบากในการเห็นสิ่งเหล่านั้นจะเพิ่มขึ้น
วัสดุที่เกี่ยวข้องสามารถพบได้ในบทความที่มีชื่อเสียงจากนาย Dijkstra "คดีกับคำสั่งไปที่"
ฉันใช้ goto ในกรณีต่อไปนี้: เมื่อจำเป็นต้องกลับจาก funcions ในสถานที่ต่าง ๆ และก่อนที่จะส่งคืนบางสิ่งบางอย่างจำเป็นต้องทำ
ไม่ใช่รุ่น 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;
}
รุ่น 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;
}
รุ่นที่สองทำให้ง่ายขึ้นเมื่อคุณต้องการเปลี่ยนแปลงบางสิ่งในคำสั่งการจัดสรรคืน (แต่ละครั้งใช้ในรหัส) และลดโอกาสที่จะข้ามสิ่งเหล่านี้เมื่อเพิ่มสาขาใหม่ การย้ายพวกมันในฟังก์ชั่นจะไม่ช่วยได้ที่นี่เพราะการจัดสรรคืนสามารถทำได้ใน "ระดับ" ที่แตกต่างกัน
finally
บล็อกใน C #
finally
เท่านั้น) ในฐานะที่เป็นทางเลือกในการใช้goto
s แต่ไปร่วมกันออกจากจุดที่เสมอไม่ทั้งหมดขึ้นสะอาด แต่วิธีการล้างข้อมูลแต่ละวิธีสามารถจัดการค่าที่เป็นโมฆะหรือสะอาดอยู่แล้วหรือได้รับการปกป้องโดยการทดสอบตามเงื่อนไขดังนั้นจึงข้ามเมื่อไม่เหมาะสม
goto
s ที่ทั้งหมดไปที่จุดออกเดียวกันซึ่งมีตรรกะเดียวกัน (ซึ่งต้องเพิ่มพิเศษถ้าต่อทรัพยากรตามที่คุณพูด) แต่ไม่เป็นไรเมื่อใช้C
คุณถูกต้อง - ไม่ว่าด้วยเหตุผลใดก็ตามที่รหัสอยู่ใน C มันก็เกือบจะแน่นอนว่าเป็นการแลกเปลี่ยนที่โปรดปรานรหัส "โดยตรง" ที่สุด (ข้อเสนอแนะของฉันจัดการกับสถานการณ์ที่ซับซ้อนซึ่งทรัพยากรที่ให้ไว้อาจมีหรือไม่มีการจัดสรร แต่ใช่ overkill ในกรณีนี้)
มันมีประโยชน์สำหรับการประมวลผลสายอักขระที่ชาญฉลาดเป็นครั้งคราว
ลองนึกภาพตัวอย่างเช่น printf-esque:
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
}
}
คุณสามารถ refactor นั้นgoto handle_literal
เพื่อการเรียกใช้ฟังก์ชั่น แต่ถ้ามันมีการปรับเปลี่ยนตัวแปรท้องถิ่นที่แตกต่างกันหลายคุณจะต้องผ่านการอ้างอิงถึงแต่ละคนยกเว้นภาษาของคุณสนับสนุนการปิดที่ไม่แน่นอน คุณยังคงต้องใช้continue
คำสั่ง (ซึ่งเป็นรูปแบบของการข้ามไป) หลังจากการเรียกเพื่อให้ได้ความหมายเดียวกันถ้าตรรกะของคุณทำให้กรณีอื่นไม่ทำงาน
ฉันยังใช้ gotos อย่างรอบคอบใน lexers โดยทั่วไปแล้วสำหรับกรณีที่คล้ายกัน คุณไม่ต้องการเวลาส่วนใหญ่ แต่พวกมันก็ดีสำหรับกรณีแปลก ๆ เหล่านั้น