ทำ {…} 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.