ทำ {…} while (เท็จ)


125

ฉันกำลังดูรหัสบางอย่างโดยบุคคลและสังเกตเห็นว่าเขาดูเหมือนจะมีรูปแบบในฟังก์ชั่นของเขา:

<return-type> function(<params>)
{
 <initialization>

 do
 {
   <main code for function>
 }
 while(false);

 <tidy-up & return>
}

มันไม่เลวแปลกกว่านั้น (รหัสจริงค่อนข้างเรียบร้อยและไม่น่าแปลกใจ) ไม่ใช่สิ่งที่ฉันเคยเห็นมาก่อนและฉันสงสัยว่ามีใครสามารถคิดตรรกะเบื้องหลังมันได้หรือไม่ - อาจมีพื้นหลังเป็นภาษาอื่น?


2
คุณได้ลองเปลี่ยนเป็นเวอร์ชัน "ปกติ" และดูว่ายังรวบรวมอยู่หรือไม่? หากคอมไพล์ไม่สำเร็จเมื่อเขียนตามปกติข้อผิดพลาดของคอมไพเลอร์อาจให้เบาะแสว่าเหตุใดจึงทำเช่นนี้
Mark Byers

11
ทำไมไม่ถาม "บุคคล" แทนที่จะขอให้เราคาดเดาความตั้งใจของเขา?

1
บางทีศาสตราจารย์ของเขาต้องการให้เขาใช้ a do...while()และเขาก็ใช้รหัสนั้นเป็นเทมเพลตฟังก์ชันตั้งแต่นั้นมา
Hogan

6
@ นีล ... ฉันต้องการหลีกเลี่ยงการดูโง่เมื่อเผชิญหน้ากับเขาและเขาอธิบายการใช้งาน C ++ ขั้นสูงที่ฉันไม่เคยคิด
คุณบอย

6
ดูเหมือนจะมีคำตอบที่แตกต่างกันอยู่แล้วคุณสามารถถามผู้เขียนต้นฉบับได้ไหมว่าพวกเขามีเจตนาอย่างไรและโพสต์กลับ
nevets1219

คำตอบ:


175

คุณสามารถbreakออกจากdo{...}while(false).


95
+1 เพราะนั่นอาจเป็นเจตนาของรหัส แต่การทำอะไรแบบนี้เป็นแค่ goto ที่ปลอมตัวมาอย่างงี่เง่า หากคุณคิดว่า goto เป็นเครื่องมือที่เหมาะสมสำหรับงานคุณควรใช้ # $ (* # @ goto.
dsimcha

56
มันเป็นมากกว่า goto ปลอมตัว มันคือ goto ที่ถูก จำกัด (ที่มีโครงสร้าง)
Thomas Eding

19
“ ถูก จำกัด ” ในทางใด? การกระโดดไปข้างหน้าเท่านั้นแทบจะไม่เป็น "ข้อ จำกัด " goto คือ goto และการแต่งตัวเพื่อให้ดูเหมือนว่ามันไม่มีใครแย่ไปกว่าการใช้ goto ในตอนแรก
อานนท์.

45
@Anon: การกระโดดไปข้างหน้าเป็นข้อ จำกัด ในการโกโตะและการกระโดดออกไปถือเป็นข้อ จำกัด อย่างแน่นอน ปัญหาที่แท้จริงของ gotos คือรหัสสปาเก็ตตี้และขีด จำกัด การก้าวไปข้างหน้าและก้าวออกไปนั้นยิ่งใหญ่มาก
David Thornley

33
ลูปที่แท้จริงไม่ใช่โกโตอย่างมีความหมาย เงื่อนไขไม่ใช่ goto ตามความหมาย "Go to the end of the function and do the cleanup code" คือคำว่า goto ใช้ gotos เมื่อใช้ความหมายอย่าแต่งสิ่งที่แตกต่างทางความหมายเพราะคุณกลัว
อานนท์.

126

หลายคนชี้ให้เห็นว่ามักใช้กับตัวแบ่งเป็นวิธีการเขียน "goto" ที่น่าอึดอัด นั่นอาจเป็นความจริงถ้าเขียนลงในฟังก์ชันโดยตรง

ในมาโคร OTOH do { something; } while (false)เป็นวิธีที่สะดวกในการบังคับใช้อัฒภาคหลังจากการเรียกใช้มาโครโดยไม่อนุญาตให้ใช้โทเค็นอื่น ๆ

และความเป็นไปได้อีกประการหนึ่งก็คือครั้งหนึ่งเคยมีการวนซ้ำอยู่ที่นั่นหรือคาดว่าจะมีการเพิ่มการวนซ้ำในอนาคต (เช่นในการพัฒนาที่ขับเคลื่อนด้วยการทดสอบการทำซ้ำไม่จำเป็นต้องผ่านการทดสอบ แต่ในทางเหตุผลมันก็สมเหตุสมผลที่จะวนซ้ำที่นั่นถ้า ฟังก์ชั่นที่จำเป็นต้องค่อนข้างกว้างกว่าที่จำเป็นในปัจจุบัน)


18
+1 สำหรับการกล่าวถึงยูทิลิตี้นี้ในมาโคร ฉันแปลกใจที่ไม่มีใครพูดถึงเรื่องนั้น!
Nick Meyer

7
ใช่สิ่งที่มาโครเป็นการใช้งานสิ่งนี้อย่างสมบูรณ์แบบ แน่นอนมาโครนอกมันโง่ ... ;)
jalf

12
มันไม่ได้น่าอึดอัด แต่มีประโยชน์เพราะมันเป็นScoped goto นั่นหมายความว่าตัวแปรใด ๆ ที่คุณประกาศใน do loop จะถูกทำลายในขณะที่ goto ไม่ทำเช่นนั้น
Ana Betts

9
@ พอล: ไม่มีอะไรป้องกันไม่ให้คุณเพิ่มวงเล็บรอบ ๆ คำสั่งเพื่อบังคับให้เกิดพฤติกรรมนี้ด้วย goto
erikkallen

5
@Paul: การออกจากบล็อกทำให้ตัวแปรโลคัลถูกทำลายใน C ++ เหมือนกับการหยุดพัก และในตัวแปร C ไม่ได้ถูกทำลายจริงๆพื้นที่สแต็กของพวกมันจะถูกเรียกคืน
Ben Voigt

25

การหยุดพักเป็น goto น่าจะเป็นคำตอบ แต่ฉันจะหยิบยกแนวคิดอื่น ๆ

บางทีเขาอาจต้องการมีตัวแปรที่กำหนดในพื้นที่และใช้โครงสร้างนี้เพื่อรับขอบเขตใหม่

โปรดจำไว้ว่าในขณะที่ C ++ ล่าสุดอนุญาตให้ใช้ได้{...}ทุกที่ แต่ก็ไม่ได้เป็นเช่นนั้นเสมอไป


30
เขาสามารถใช้วงเล็บปีกกาในกรณีนั้นได้
Nemanja Trifunovic

2
@Nemanja คุณจะประหลาดใจกับจำนวนนักพัฒนาที่ไม่รู้และลองทำอะไรที่เกี่ยวข้องกับสิ่งที่โฮแกนแนะนำ
Polaris878

8
@Polaris @Nemanja ไม่ได้จนกว่าฉันจะเขียนโปรแกรมใน C / C ++ เหมือน 4 ปีที่ฉันคิดว่าคุณสามารถสร้างขอบเขตท้องถิ่นใหม่{} ได้ทุกที่ .. สิ่งนี้มีประโยชน์อย่างยิ่งในswitch-caseรหัส
Earlz

1
@Nemanja: ฉันไม่รู้แน่ชัดว่าเมื่อไร แต่ฉันแน่ใจว่า{...}ทุกที่นั้นมีการพัฒนาที่ทันสมัยกว่าใน C ++ (จำไว้ว่า C ++ ตัวแรกที่ฉันใช้นั้นถูกนำไปใช้กับตัวประมวลผลล่วงหน้าและไม่อนุญาตให้ใช้งานสมัยใหม่นี้) ผู้เขียนเป็นเพียงโรงเรียนเก่า
Hogan

2
ไม่อนุญาตให้จัดฟันทุกที่เมื่อไหร่ ฉันเริ่มเขียนโปรแกรมเมื่อ 15 ปีที่แล้วและได้รับอนุญาตแล้ว (ทั้งในหนังสือเรียนและในทุกคอมไพเลอร์ที่ฉันลอง)
erikkallen

20

ฉันเห็นว่ามันใช้เป็นรูปแบบที่มีประโยชน์เมื่อมีจุดออกที่เป็นไปได้หลายจุดสำหรับฟังก์ชัน แต่จำเป็นต้องใช้รหัสการล้างข้อมูลเดียวกันเสมอไม่ว่าฟังก์ชันจะออกอย่างไร

มันสามารถทำให้ต้นไม้ if / else-if ที่น่าเบื่อหน่ายง่ายต่อการอ่านมากโดยเพียงแค่ต้องทำลายทุกครั้งที่ถึงจุดออกพร้อมกับตรรกะที่เหลือในบรรทัดหลังจากนั้น

รูปแบบนี้ยังมีประโยชน์ในภาษาที่ไม่มีคำสั่ง goto บางทีนั่นอาจเป็นสิ่งที่โปรแกรมเมอร์ดั้งเดิมได้เรียนรู้รูปแบบ


15
จากนั้นใช้ goto ที่ซื่อสัตย์ตรงไปตรงมาแทน goto ปลอมตัวบาง ๆ
dsimcha

4
ฉันชอบทางนี้ดีกว่า ง่ายต่อการอ่านและไม่ถือเป็นตราบาปของ goto
Cameron

8
gotos นั้นอ่านง่ายอย่างสมบูรณ์แบบหากใช้อย่าง จำกัด และในพื้นที่ พวกเขาได้รับความอัปยศจากด้านหลังเมื่อพวกเขาเป็นรูปแบบหลักของการควบคุมการไหลและกำลังกระโดดข้ามเส้นหลายร้อยเส้น
dsimcha

13
การไม่ใช้ goto เนื่องจาก "ความอัปยศ" เป็นสัญญาณที่แน่นอนของการเขียนโปรแกรมลัทธิขนส่งสินค้า
อานนท์.

6
ไม่ต้องพูดถึงว่ารหัสการล้างข้อมูลจำนวนมากเป็นกลิ่นเหม็น นี่คือ C ++ รหัสการล้างข้อมูลโดยปกติควรอยู่ในตัวทำลายที่เรียกว่าระหว่างการประมวลผล RAII
David Thornley

12

ฉันเคยเห็นรหัสแบบนั้นเพื่อให้คุณสามารถใช้breakเป็นgotoประเภทต่างๆ


10

นี่เป็นเพียงการบิดเบือนwhileเพื่อให้ได้สัญศาสตร์goto tidy-upโดยไม่ต้องใช้คำว่า goto

มันเป็นรูปแบบที่ไม่ดีเพราะเมื่อคุณใช้อื่น ๆลูปภายในภายนอกกลายเป็นคลุมเครือให้ผู้อ่าน "นี่มันควรจะไปที่ทางออกแล้วหรือนี่มันตั้งใจจะหลุดจากวงในเท่านั้น"whilebreaks


10

ฉันคิดว่ามันสะดวกกว่าที่จะเขียนแทนbreak goto endคุณไม่จำเป็นต้องคิดชื่อสำหรับป้ายกำกับซึ่งทำให้ความตั้งใจชัดเจนขึ้น: คุณไม่ต้องการข้ามไปยังป้ายกำกับที่มีชื่อเฉพาะ คุณต้องการออกไปจากที่นี่

ยังมีโอกาสที่คุณจะต้องจัดฟันด้วย นี่คือdo{...}while(false);เวอร์ชัน:

do {
   // code
   if (condition) break; // or continue
   // more code
} while(false);

และนี่คือวิธีที่คุณจะต้องแสดงออกหากคุณต้องการใช้goto:

{
   // code
   if (condition) goto end;
   // more code
}
end:

ฉันคิดว่าความหมายของเวอร์ชันแรกนั้นเข้าใจง่ายกว่ามาก นอกจากนี้ยังเขียนได้ง่ายขึ้นขยายง่ายขึ้นแปลเป็นภาษาที่ไม่รองรับได้ง่ายขึ้นgotoฯลฯ


ความกังวลที่กล่าวถึงบ่อยที่สุดเกี่ยวกับการใช้ก็คือว่ามันปลอมตัวไม่ดีbreak gotoแต่ที่จริงแล้วbreakมีความคล้ายคลึงกันมากขึ้นในการreturn: gotoคำแนะนำทั้งสองกระโดดออกมาจากบล็อกของรหัสซึ่งเป็นโครงสร้างที่สวยมากเมื่อเปรียบเทียบกับ อย่างไรก็ตามทั้งสองคำแนะนำอนุญาตให้มีทางออกหลายจุดในบล็อกรหัสซึ่งอาจทำให้สับสนได้ในบางครั้ง ท้ายที่สุดฉันจะพยายามหาวิธีแก้ปัญหาที่ชัดเจนที่สุดไม่ว่าจะอยู่ในสถานการณ์ใด


โอ้ฉันเพิ่งสังเกตว่าฉันไม่เข้าใจคำถามทั้งหมดก่อนที่จะตอบ ฉันคิดว่ามันเกี่ยวกับการใช้งานdo{ ... }while(false);โดยทั่วไป try{ ... }finally{ ... }แต่จริงๆแล้วมันเป็นเรื่องของการใช้มันจะเลียนแบบชนิดของ
Robert

การสังเกตที่ดีของเส้นขนานระหว่างการหยุดพักและการกลับมาแทนที่จะเป็นไป ยอมรับว่าทางออกที่ชัดเจนดีที่สุด ในหลาย ๆ กรณีอาจชัดเจนกว่าที่จะห่อหุ้มโค้ดเช่นนี้ในฟังก์ชันแยกต่างหากของตัวเองที่ใช้การส่งคืนแทนที่จะทำลายแล้วทำการล้างข้อมูลในฟังก์ชันการโทร
persiflage

7

เคล็ดลับนี้ใช้โดยโปรแกรมเมอร์ที่ขี้อายเกินกว่าที่จะใช้gotoรหัสที่ชัดเจน ผู้เขียนโค้ดด้านบนต้องการให้สามารถข้ามไปยังจุด "ล้างข้อมูลและส่งคืน" ได้โดยตรงจากตรงกลางของโค้ด gotoแต่พวกเขาไม่ต้องการที่จะใช้ป้ายและชัดเจน แต่พวกเขาสามารถใช้breakภายในร่างกายของวงจร "ปลอม" ข้างต้นเพื่อให้ได้ผลเช่นเดียวกัน


7

ถือเป็นเรื่องธรรมดามาก ในC ฉันพยายามคิดว่าถ้าคุณต้องการโกหกตัวเองแบบว่า "ฉันไม่ได้ใช้กgoto" คิดเกี่ยวกับมันจะไม่มีอะไรผิดปกติกับการgotoใช้ในทำนองเดียวกัน ในความเป็นจริงมันจะลดระดับการเยื้องด้วย

ที่กล่าวว่าฉันสังเกตเห็นบ่อยครั้งที่do..whileลูปนี้มักจะโตขึ้น แล้วพวกเขาก็จะได้ifs and elses เข้าไปข้างในทำให้โค้ดนั้นอ่านไม่ได้จริง ๆ นับประสาอะไรกับการทดสอบ

ผู้ที่do..whileมีความตั้งใจที่ปกติจะทำสะอาดขึ้น โดยทั้งหมดเป็นไปได้ฉันต้องการใช้RAIIและกลับมาก่อนเวลาจากฟังก์ชันสั้น ๆ ในทางกลับกันCไม่ได้อำนวยความสะดวกให้คุณมากเท่ากับC ++ทำให้เป็นdo..whileหนึ่งในวิธีที่ดีที่สุดในการล้างข้อมูล


6

ดูเหมือนโปรแกรมเมอร์ C ใน C ++ ตัวแปรอัตโนมัติมีตัวทำลายที่คุณใช้ในการล้างข้อมูลดังนั้นจึงไม่ควรมีสิ่งใดที่จำเป็นในการจัดเตรียมก่อนที่จะส่งคืน ในภาษา C คุณไม่มีสำนวนRAIIนี้ดังนั้นหากคุณมีรหัสการล้างข้อมูลทั่วไปคุณอาจใช้รหัสgotoนี้หรือใช้การวนซ้ำแบบครั้งเดียวตามด้านบน

ข้อเสียเปรียบหลักเมื่อเทียบกับสำนวน C ++ คือจะไม่เป็นระเบียบเรียบร้อยหากมีข้อยกเว้นเกิดขึ้นในร่างกาย C ไม่มีข้อยกเว้นดังนั้นนี่จึงไม่ใช่ปัญหา แต่จะทำให้เป็นนิสัยที่ไม่ดีใน C ++


4

คำอธิบายหลายประการ อันแรกเป็นแบบทั่วไปอันที่สองเฉพาะสำหรับมาโครตัวประมวลผลล่วงหน้า C ที่มีพารามิเตอร์:

การควบคุมการไหล

ฉันเคยเห็นสิ่งนี้ใช้ในรหัส C ธรรมดา โดยพื้นฐานแล้วมันเป็น goto เวอร์ชันที่ปลอดภัยกว่าเนื่องจากคุณสามารถแยกออกได้และหน่วยความจำทั้งหมดจะได้รับการทำความสะอาดอย่างเหมาะสม

ทำไมบางสิ่งบางอย่างถึงgotoดี? ถ้าคุณมีโค้ดที่เกือบทุกบรรทัดสามารถส่งคืนข้อผิดพลาดได้ แต่คุณต้องตอบสนองต่อทั้งหมดในลักษณะเดียวกัน (เช่นโดยการส่งข้อผิดพลาดไปยังผู้โทรของคุณหลังจากล้างข้อมูล) โดยปกติแล้วจะสามารถอ่านได้มากกว่าเพื่อหลีกเลี่ยงif( error ) { /* cleanup and error string generation and return here */ }as หลีกเลี่ยงการทำซ้ำรหัสการล้างข้อมูล

อย่างไรก็ตามใน C ++ คุณมีข้อยกเว้น + RAII สำหรับจุดประสงค์นี้ดังนั้นฉันคิดว่ารูปแบบการเข้ารหัสไม่ดี

การตรวจสอบอัฒภาค

หากคุณลืมเครื่องหมายอัฒภาคหลังจากการเรียกใช้แมโครที่เหมือนฟังก์ชันอาร์กิวเมนต์อาจหดตัวในลักษณะที่ไม่ต้องการและรวบรวมเป็นไวยากรณ์ที่ถูกต้อง ลองนึกภาพมาโคร

#define PRINT_IF_DEBUGMODE_ON(msg) if( gDebugModeOn ) printf("foo");

ที่เรียกโดยบังเอิญว่าเป็น

if( foo )
    PRINT_IF_DEBUGMODE_ON("Hullo\n")
else
    doSomethingElse();

"else" จะถูกพิจารณาว่ามีความเกี่ยวข้องgDebugModeOnดังนั้นเมื่อfooใดfalseที่การย้อนกลับที่แน่นอนของสิ่งที่ตั้งใจไว้จะเกิดขึ้น

กำหนดขอบเขตสำหรับตัวแปรชั่วคราว

เนื่องจาก do / while มีวงเล็บปีกกาตัวแปรชั่วคราวจึงมีขอบเขตที่กำหนดไว้อย่างชัดเจนจึงไม่สามารถหลีกเลี่ยงได้

หลีกเลี่ยงคำเตือน "อัฒภาคที่อาจไม่ต้องการ"

มาโครบางตัวเปิดใช้งานเฉพาะในรุ่นแก้จุดบกพร่องเท่านั้น คุณกำหนดพวกเขาเช่น:

#if DEBUG
#define DBG_PRINT_NUM(n) printf("%d\n",n);
#else
#define DBG_PRINT_NUM(n) 
#endif

ตอนนี้ถ้าคุณใช้สิ่งนี้ในรีลีสบิลด์ภายในเงื่อนไขจะคอมไพล์

if( foo )
    ;

คอมไพเลอร์หลายคนเห็นว่าสิ่งนี้เหมือนกับไฟล์

if( foo );

ซึ่งมักเขียนโดยบังเอิญ. คุณจึงได้รับคำเตือน do {} while (false) ซ่อนสิ่งนี้จากคอมไพลเลอร์และได้รับการยอมรับว่าเป็นสิ่งที่บ่งบอกว่าคุณไม่ต้องการทำอะไรเลยที่นี่

การหลีกเลี่ยงการจับเส้นตามเงื่อนไข

มาโครจากตัวอย่างก่อนหน้านี้:

if( foo )
    DBG_PRINT_NUM(42)
doSomething();

ตอนนี้ในการสร้างการดีบักเนื่องจากเรารวมอัฒภาคเป็นนิสัยการคอมไพล์นี้ใช้ได้ดี อย่างไรก็ตามในการสร้างรุ่นนี้จะกลายเป็น:

if( foo )

doSomething();

หรือจัดรูปแบบให้ชัดเจนยิ่งขึ้น

if( foo )
    doSomething();

ซึ่งไม่ใช่สิ่งที่ตั้งใจไว้เลย การเพิ่ม do {... } ในขณะที่ (false) รอบ ๆ มาโครจะเปลี่ยนอัฒภาคที่หายไปให้เป็นข้อผิดพลาดในการคอมไพล์

นั่นหมายความว่าอย่างไรสำหรับ OP?

โดยทั่วไปคุณต้องการใช้ข้อยกเว้นใน C ++ สำหรับการจัดการข้อผิดพลาดและเทมเพลตแทนมาโคร อย่างไรก็ตามในกรณีที่หายากมากที่คุณยังต้องการมาโคร (เช่นเมื่อสร้างชื่อคลาสโดยใช้การวางโทเค็น) หรือ จำกัด ไว้ที่ C ธรรมดานี่เป็นรูปแบบที่มีประโยชน์


เกี่ยวกับการกำหนดขอบเขตคุณไม่จำเป็นต้องใช้อุปกรณ์วนซ้ำ "การตกแต่ง" x =1; y = 2; { int tmp = y; y = x; x = tmp; }บล็อกเป็นกฎหมาย:
chepner

บล็อกที่ไม่มีการตกแต่งไม่ได้บังคับว่ามีอัฒภาคที่หายไปซึ่งอาจมีผลข้างเคียงที่ไม่ต้องการ ทำ {} while (); ต้องการเครื่องหมายอัฒภาคและด้วยเหตุนี้เช่นไม่ทำให้คำสั่งต่อไปนี้ถูกดึงเข้าไปใน "if" หากมาโครกำหนดว่าไม่มีอะไรในบิลด์รีลีสดังตัวอย่างของฉันด้านบน
uliwitness

ไม่สามารถหลีกเลี่ยงได้โดยเพียงแค่ให้คำจำกัดความที่ดีกว่าสำหรับมาโคร #define DBG_PRINT_NUM(n) {}.
chepner

1
ไม่เพราะ {} เป็นคำสั่งแบบเต็มกล่าวคือเทียบเท่ากับ ";" ดังนั้นถ้าคุณเขียนif(a) DBG_PRINT_NUM(n); else other();มันจะไม่รวบรวม เท่านั้นif(a) {} else other();หรือif(a) ; else other();ถูกต้อง แต่if(a) {}; else other();ไม่ใช่เพราะนั่นทำให้อนุประโยค "if" ประกอบด้วยสองคำสั่ง
uliwitness

2

บางทีมันอาจจะถูกใช้เพื่อให้breakสามารถใช้ภายในเพื่อยกเลิกการเรียกใช้โค้ดเพิ่มเติมได้ทุกเมื่อ:

do {
    if (!condition1) break;
    some_code;
    if (!condition2) break;
    some_further_code;
    // …
} while(false);

7
การทำเช่นนี้ดูเหมือนเป็นการพยายามหลีกเลี่ยงการใช้gotoเพียงเพราะมีคนได้ยินว่า "ไม่ดี"
อานนท์.

1
บางที แต่ C ++ มีข้อยกเว้นสำหรับเหตุผลนี้
TED


2

มันง่ายมาก: เห็นได้ชัดว่าคุณสามารถกระโดดออกจากลูปปลอมได้ตลอดเวลาโดยใช้breakคำสั่ง นอกจากนี้doบล็อกยังเป็นขอบเขตแยกต่างหาก (ซึ่งสามารถทำได้ด้วย{ ... }เท่านั้น)

ในสถานการณ์เช่นนี้ควรใช้ RAII (วัตถุจะทำลายโดยอัตโนมัติอย่างถูกต้องเมื่อฟังก์ชันสิ้นสุดลง) โครงสร้างอื่นที่คล้ายกันคือการใช้goto- ใช่ฉันรู้ว่ามันชั่วร้ายแต่สามารถใช้เพื่อมีรหัสล้างข้อมูลทั่วไปได้ดังนี้:

<return-type> function(<params>)
{
 <initialization>

 <main code for function using "goto error;" if something goes wrong>

 <tidy-up in success case & return>

 error:

 <commmon tidy-up actions for error case & return error code or throw exception>
}

(นอกเหนือจาก: โครงสร้าง do-while-false ถูกใช้ใน Lua เพื่อหาcontinueคำสั่งที่ขาดหายไป)


2

ผู้ตอบหลายคนให้เหตุผลdo{(...)break;}while(false)ว่า ฉันอยากจะเติมเต็มภาพด้วยอีกตัวอย่างในชีวิตจริง

ในรหัสต่อไปนี้ฉันต้องตั้งค่าตัวนับoperationตามที่อยู่ที่ชี้ไปโดยdataตัวชี้ เนื่องจากสวิตช์เคสสามารถใช้ได้เฉพาะกับประเภทสเกลาร์ก่อนอื่นฉันจึงทำแบบนี้อย่างไม่มีประสิทธิภาพ

if (data == &array[o1])
    operation = O1;
else if (data == &array[o2])
    operation = O2;
else if (data == &array[on])
    operation = ON;

Log("operation:",operation);

แต่เนื่องจาก Log () และโค้ดส่วนที่เหลือซ้ำกันสำหรับค่าการดำเนินการใด ๆ ที่เลือกฉันจึงหลงทางว่าจะข้ามการเปรียบเทียบส่วนที่เหลืออย่างไรเมื่อพบที่อยู่แล้ว และนี่คือจุดที่do{(...)break;}while(false)มีประโยชน์

do {
    if (data == &array[o1]) {
        operation = O1;
        break;
    }
    if (data == &array[o2]) {
        operation = O2;
        break;
    }
    if (data == &array[on]) {
        operation = ON;
        break;
    }
} while (false);

Log("operation:",operation);

บางคนอาจสงสัยว่าทำไมเขาถึงทำแบบเดียวกันกับbreakในifแถลงการณ์ไม่ได้เช่น:

if (data == &array[o1])
{
    operation = O1;
    break;
}
else if (...)

breakปฏิสัมพันธ์ แต่เพียงผู้เดียวกับการปิดล้อมอยู่ใกล้ห่วงหรือสวิทช์ไม่ว่าจะเป็นfor, whileหรือdo .. whileประเภทจึงน่าเสียดายที่จะไม่ทำงาน


1

ผู้เขียนอายุเท่าไร?

ฉันถามเพราะครั้งหนึ่งฉันเจอรหัส Fortran แบบเรียลไทม์ที่ทำแบบนั้นย้อนกลับไปในช่วงปลายยุค 80 ปรากฎว่าเป็นวิธีที่ดีมากในการจำลองเธรดบนระบบปฏิบัติการที่ไม่มี คุณเพียงแค่ใส่โปรแกรมทั้งหมด (ตัวกำหนดตารางเวลาของคุณ) ในลูปแล้วเรียก "เธรด" รูทีน "ทีละรายการรูทีนเธรดเองก็คือลูปที่วนซ้ำจนกว่าจะมีเงื่อนไขใดเงื่อนไขหนึ่งเกิดขึ้น (โดยมากจะเป็นจำนวนหนึ่ง เวลาผ่านไปแล้ว) มันคือ "การทำงานหลายอย่างแบบร่วมมือกัน" ซึ่งขึ้นอยู่กับแต่ละเธรดที่จะเลิกใช้ CPU ทุก ๆ ครั้งเพื่อไม่ให้คนอื่นอดอาหารคุณสามารถซ้อนการเรียกโปรแกรมย่อยแบบวนซ้ำเพื่อจำลองลำดับความสำคัญของเธรด วงดนตรี


1

นอกเหนือจาก 'goto Example' ที่กล่าวไปแล้วสำนวน do ... ในขณะที่ (0) บางครั้งใช้ในนิยามมาโครเพื่อจัดเตรียมวงเล็บในนิยามและยังคงให้คอมไพเลอร์ทำงานโดยเพิ่มเซมิโคลอนต่อท้าย การโทรแบบมาโคร

http://groups.google.com/group/comp.soft-sys.ace/browse_thread/thread/52f670f1292f30a4?tvc=2&q=while+(0)


0

ฉันเห็นด้วยกับผู้โพสต์ส่วนใหญ่เกี่ยวกับการใช้โกโตะที่ปลอมตัวบาง ๆ มาโครยังได้รับการกล่าวถึงว่าเป็นแรงจูงใจที่เป็นไปได้สำหรับการเขียนโค้ดในรูปแบบ

ฉันยังเห็นโครงสร้างนี้ที่ใช้ในสภาพแวดล้อม C / C ++ แบบผสมเป็นข้อยกเว้นของคนยากจน "do {} while (false)" กับ "break" สามารถใช้เพื่อข้ามไปยังจุดสิ้นสุดของบล็อกโค้ดได้หากพบข้อยกเว้นในลูป

ฉันยังใช้โครงสร้างนี้ในร้านค้าที่มีการบังคับใช้อุดมการณ์ "ผลตอบแทนเดียวต่อฟังก์ชัน" อีกครั้งนี่เป็นการแทนที่ "goto" ที่ชัดเจน - แต่แรงจูงใจคือการหลีกเลี่ยงจุดส่งคืนหลายจุดไม่ให้ "ข้าม" โค้ดและดำเนินการตามจริงต่อไปภายในฟังก์ชันนั้น


0

ฉันทำงานกับ Adobe InDesign SDK และตัวอย่าง InDesign SDK มีเกือบทุกฟังก์ชันที่เขียนแบบนี้ เนื่องมาจากว่าฟังก์ชันมักจะยาวมาก ที่คุณต้องทำ QueryInterface (... ) เพื่อรับอะไรก็ได้จากโมเดลวัตถุแอปพลิเคชัน ดังนั้นโดยปกติแล้วทุก QueryInterface จะตามมาด้วยifไม่เป็นไปด้วยดีทำลาย


0

หลายคนได้กล่าวถึงความคล้ายคลึงกันระหว่างโครงสร้างนี้และ a gotoแล้วและแสดงความพึงพอใจต่อ goto บางทีภูมิหลังของบุคคลนี้อาจรวมถึงสภาพแวดล้อมที่ goto ถูกห้ามอย่างเคร่งครัดตามแนวทางการเขียนโค้ด?


0

อีกเหตุผลหนึ่งที่ฉันคิดได้ก็คือมันตกแต่งเครื่องมือจัดฟันในขณะที่ฉันเชื่อว่าการจัดฟันแบบเปล่ามาตรฐาน C ++ ที่ใหม่กว่านั้นไม่โอเค (ISO C ไม่ชอบ) มิฉะนั้นให้เงียบเครื่องวิเคราะห์แบบคงที่เช่นผ้าสำลี

ไม่แน่ใจว่าทำไมคุณถึงต้องการมันอาจจะเป็นขอบเขตที่เปลี่ยนแปลงหรือได้เปรียบด้วยดีบักเกอร์

ดูTrivial Do While loopและวงเล็บปีกกาเป็นสิ่งที่ดีจาก C2

เพื่อชี้แจงคำศัพท์ของฉัน (ซึ่งฉันเชื่อว่าเป็นไปตามการใช้งานมาตรฐาน):

การจัดฟันแบบเปล่า :

init();
...
{
c = NULL;
mkwidget(&c);
finishwidget(&c);
}
shutdown();

เครื่องมือจัดฟันเปล่า (NOP):

{}

เช่น

while (1)
   {}  /* Do nothing, endless loop */

บล็อก :

if (finished)
{
     closewindows(&windows);
     freememory(&cache);
}

ซึ่งจะกลายเป็น

if (finished)
     closewindows(&windows);
freememory(&cache);

หากมีการถอดวงเล็บปีกกาออกไปการเปลี่ยนแปลงขั้นตอนการดำเนินการไม่ใช่แค่ขอบเขตของตัวแปรในระบบเท่านั้น ดังนั้นจึงไม่ใช่ 'อิสระ' หรือ 'เปล่า'

อาจใช้วงเล็บปีกกาเปล่าหรือบล็อกเพื่อแสดงถึงส่วนใด ๆ ของรหัสที่อาจเป็นไปได้สำหรับฟังก์ชัน (อินไลน์) ที่คุณต้องการทำเครื่องหมาย แต่จะไม่ปรับโครงสร้างในเวลานั้น


จริงๆ? ที่น่าเสียดาย หนึ่งในไม่กี่สิ่งที่ฉันชอบเกี่ยวกับ C คือให้คุณประกาศขอบเขตที่ซ้อนกันใหม่ได้ทุกที่
TED

1
การจัดฟันแบบเปล่าบางครั้งมีประโยชน์ในการแก้ไขข้อขัดข้องแปลก ๆ ดูblogs.msdn.com/oldnewthing/archive/2004/05/20/135841.aspx
Brian

1
ใน C ++ บล็อก (เช่น "วงเล็บปีกกาเปล่า" ของคุณ) สามารถใช้ได้ทุกที่ที่อนุญาตให้ใช้คำสั่งเดียว
Ben Voigt

@BenVoigt วงเล็บปีกกาว่างคือ NOP แตกต่างจาก "วงเล็บปีกกาเปล่า" ซึ่งเป็นบล็อกที่เพิ่มรอบลำดับคำแนะนำเชิงเส้น เช่น `printf (" Hello "); {พุทชาร์ (','); putchar (0x20); } printf ("world! \ n"); `โดยที่วงเล็บปีกกาไม่ได้เป็นส่วนหนึ่งของการควบคุมวงหรือกิ่ง
mctylr

@mctylr: ฉันไม่ได้พูดถึงการจัดฟันเปล่า
Ben Voigt

0

เป็นวิธีที่ออกแบบมาเพื่อเลียนแบบGOTOเนื่องจากทั้งสองเหมือนกันจริง:

// NOTE: This is discouraged!
do {
    if (someCondition) break;
    // some code be here
} while (false);
// more code be here

และ:

// NOTE: This is discouraged, too!
if (someCondition) goto marker;
// some code be here
marker:
// more code be here

ในทางกลับกันทั้งสองอย่างนี้ควรจะทำด้วยifs:

if (!someCondition) {
    // some code be here
}
// more code be here

แม้ว่าการซ้อนจะดูน่าเกลียดไปหน่อยถ้าคุณเปลี่ยนสายข้างหน้ายาวGOTOๆ ให้เป็นifs ที่ซ้อนกัน คำตอบที่แท้จริงคือการปรับโครงสร้างที่เหมาะสมแม้ว่าจะไม่เลียนแบบโครงสร้างภาษาโบราณ

หากคุณพยายามทับศัพท์อัลกอริทึมที่มีGOTOs อยู่ในนั้นอย่างสิ้นหวังคุณอาจทำได้ด้วยสำนวนนี้ แน่นอนว่ามันไม่ได้มาตรฐานและเป็นตัวบ่งชี้ที่ดีว่าคุณไม่ได้ยึดมั่นอย่างใกล้ชิดกับสำนวนภาษาที่คาดหวังไว้

ฉันไม่ทราบว่ามีภาษาคล้าย C ใด ๆ ที่ do / while เป็นวิธีแก้ปัญหาสำหรับทุกสิ่งจริงๆ

คุณอาจปรับเปลี่ยนความยุ่งเหยิงทั้งหมดให้เป็นสิ่งที่สมเหตุสมผลมากขึ้นเพื่อให้สำนวนมากขึ้นและอ่านง่ายขึ้น


1
อลันdo..while(false)จะไม่ได้สำหรับการทำงาน "ไม่ได้กำหนด" จำนวนครั้งที่มันเป็นสำหรับการทำงานครั้งเดียว
Dmitry

2
กำลังเรียกใช้จำนวนครั้งที่ไม่ได้กำหนดหากคุณมีcontinueคำสั่งเงื่อนไขในตอนท้ายเหมือนที่ฉันแสดง โดยundefinedฉันหมายความว่าคุณไม่รู้ว่ามันทำงานมากกว่าหนึ่งครั้งหรือไม่เว้นแต่คุณจะสามารถคาดเดาได้ว่าจะตรงตามเงื่อนไขหรือไม่ หากไม่มีcontinues ใด ๆในนั้นdo..while(false)จะทำงานครั้งเดียว แต่ยังไม่มีbreaks อยู่ด้วยก็while(true)จะทำงานตลอดไปดังนั้น "การทำงานเริ่มต้น" จึงไม่เกี่ยวข้องกับการทำความเข้าใจสิ่งที่สามารถทำได้กับลูป
Alan Plum

จะมีประโยชน์เมื่อคุณกำหนดมาโครที่มีหลายคำสั่ง หากคุณรวมไว้ใน do / while (false) คุณสามารถใช้มาโครในคำสั่ง if / else เหมือนกับการเรียกฟังก์ชันอื่น ๆ ดูnoveltheory.com/TechPapers/ while.htmสำหรับคำอธิบาย
John Paquin

5
continueบางคนไม่เข้าใจความหมายของ continueไม่วนซ้ำ " continueคำสั่งจะเกิดขึ้นเฉพาะในคำสั่งวนซ้ำและทำให้การควบคุมส่งผ่านไปยังส่วนต่อเนื่องของการวนซ้ำของคำสั่งการวนซ้ำที่ล้อมรอบที่เล็กที่สุดนั่นคือไปยังจุดสิ้นสุดของลูป"
Ben Voigt

-1 คำสั่งสองอันดับแรกไม่เหมือนกันสำหรับเหตุผลที่ @BenVoigt ชี้ให้เห็น
cmh

0

ผู้เขียนโค้ดบางรายต้องการให้มีเพียงทางออกเดียว / กลับจากฟังก์ชันของตน การใช้หุ่นจำลองทำ {.... } while (false); ช่วยให้คุณ "แยกออก" จากการวนซ้ำแบบจำลองเมื่อคุณทำเสร็จแล้วและยังคงได้รับผลตอบแทนเพียงครั้งเดียว

ฉันเป็นคนเขียนโค้ดจาวาดังนั้นตัวอย่างของฉันจะเป็นอย่างไร

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class p45
{
    static List<String> cakeNames = Arrays.asList("schwarzwald torte", "princess", "icecream");
    static Set<Integer> forbidden = Stream.of(0, 2).collect(Collectors.toSet());

    public static  void main(String[] argv)
    {
        for (int i = 0; i < 4; i++)
        {
            System.out.println(String.format("cake(%d)=\"%s\"", i, describeCake(i)));
        }
    }


    static String describeCake(int typeOfCake)
    {
        String result = "unknown";
        do {
            // ensure type of cake is valid
            if (typeOfCake < 0 || typeOfCake >= cakeNames.size()) break;

            if (forbidden.contains(typeOfCake)) {
                result = "not for you!!";
                break;
            }

            result = cakeNames.get(typeOfCake);
        } while (false);
        return result;
    }
}


-1

นี่เป็นเรื่องน่าขบขัน อาจมีรอยแตกในวงดังที่คนอื่นพูด ฉันจะทำแบบนี้:

while(true)
{
   <main code for function>
   break; // at the end.
}

2
ด้วยศักยภาพของการวนซ้ำตลอดไป? do..while(false)ออกเสมอwhile(true)มีความเสี่ยงมากกว่า
Dmitry

1
while(true)เป็นสำนวนที่ถูกต้องในภาษาส่วนใหญ่ คุณมักจะพบในแอปพลิเคชัน GUI เป็นลูปหลักของโปรแกรม เนื่องจากโดยพื้นฐานแล้วคุณถือว่าโปรแกรมไม่ควรตายจนกว่าจะมีการบอกให้ทำเช่นนั้นdo..while(false)จะทำให้เกิดตรรกะที่สร้างขึ้นทุกประเภท วิธีนี้อาจเพิ่มขึ้นจาก POV ที่สมบูรณ์แบบ แต่มันง่ายกว่าในเชิงความหมายและทำให้เกิดข้อผิดพลาดน้อยกว่าสำหรับโปรแกรมเมอร์ที่เป็นมนุษย์ (ขออภัย Skynet)
Alan Plum

2
@dmitry do{...}while(false)เหมือนกับwhile(true){ .. break;}
N 1.1

4
@ N1.1: ไม่ใช่ต่อหน้าcontinueพวกเขาไม่เหมือนกัน
Ben Voigt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.