ตัวดำเนินการเชิงตรรกะที่ลัดวงจรได้รับคำสั่งหรือไม่? และลำดับการประเมินผล?


140

มาตรฐาน ANSI กำหนดให้ตัวดำเนินการเชิงตรรกะต้องทำการลัดวงจรใน C หรือ C ++ หรือไม่?

ฉันสับสนเพราะฉันจำได้ว่าหนังสือ K&R บอกว่ารหัสของคุณไม่ควรขึ้นอยู่กับการดำเนินการเหล่านี้เพราะไฟฟ้าลัดวงจรเพราะอาจไม่ได้ ใครช่วยกรุณาชี้ให้เห็นว่าในมาตรฐานมันกล่าวว่าตรรกะ ops จะลัดวงจรเสมอ? ฉันสนใจ C ++ เป็นส่วนใหญ่คำตอบสำหรับ C ก็ยอดเยี่ยมเช่นกัน

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

มาตรฐานระบุลำดับการประเมินผลของนิพจน์นี้หรือไม่

if( functionA() && functionB() && functionC() ) cout<<"Hello world";

12
Carefull: มันเป็นความจริงสำหรับประเภท POD แต่ถ้าคุณโอเวอร์โหลดตัวดำเนินการ && หรือตัวดำเนินการ | | สำหรับชั้นเรียนเหล่านี้ฉันไม่ได้ทำซ้ำไม่ทางลัด นี่คือเหตุผลที่แนะนำว่าคุณไม่ได้กำหนดโอเปอเรเตอร์เหล่านี้สำหรับคลาสของคุณเอง
Martin York

ฉันนิยามผู้ประกอบการเหล่านี้เมื่อไม่นานมานี้เมื่อฉันสร้างคลาสที่จะดำเนินการพีชคณิตแบบบูลพื้นฐาน อาจเป็นไปได้ว่าควรแสดงความคิดเห็นเตือน "นี่เป็นการทำลายการลัดวงจรและการประเมินทางซ้าย - ขวา!" ในกรณีที่ฉันลืมสิ่งนี้ นอกจากนี้ยังมากเกินไป * / + และทำให้พวกเขาเป็นคำพ้องความหมายของพวกเขา :-)
โจ Pineda

การเรียกใช้ฟังก์ชั่นใน if-block นั้นไม่ใช่วิธีการเขียนโปรแกรมที่ดี ให้ประกาศตัวแปรที่เก็บค่าส่งคืนของเมธอดเสมอและใช้ใน if-block
SR Chaitanya

6
@SRChaitanya นั่นไม่ถูกต้อง สิ่งที่คุณอธิบายโดยพลการว่าเป็นวิธีปฏิบัติที่ไม่ดีจะทำอยู่ตลอดเวลาโดยเฉพาะอย่างยิ่งกับฟังก์ชั่นที่ส่งคืน booleans ดังที่นี่
มาร์ควิสแห่ง Lorne

คำตอบ:


154

ใช่จำเป็นต้องมีคำสั่งลัดวงจรและการประเมินผลสำหรับผู้ประกอบการ||และ&&ทั้งในมาตรฐาน C และ C ++

มาตรฐาน C ++ บอกว่า (ควรมีข้อเทียบเท่าในมาตรฐาน C):

1.9.18

ในการประเมินผลของการแสดงออกต่อไปนี้

a && b
a || b
a ? b : c
a , b

ใช้ความหมายในตัวของผู้ประกอบการในการแสดงออกเหล่านี้มีจุดลำดับหลังจากการประเมินผลของการแสดงออกครั้งแรก (12)

ใน C ++ มีกับดักพิเศษ: ลัดวงจรไม่ไม่ได้นำไปใช้กับประเภทที่ผู้ประกอบการเกินและ||&&

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

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


3
ไม่ทราบว่าการลัดวงจรไม่สามารถใช้ได้กับการใช้งาน ops แบบลอจิกมากเกินไปนั่นเป็นสิ่งที่น่าสนใจ คุณช่วยเพิ่มการอ้างอิงถึงมาตรฐานหรือแหล่งที่มาได้ไหม ฉันไม่ไว้ใจคุณเพียงแค่ต้องการเรียนรู้เพิ่มเติมเกี่ยวกับเรื่องนี้
Joe Pineda

4
ใช่นั่นเป็นเหตุผล มันทำหน้าที่เป็นอาร์กิวเมนต์ของโอเปอเรเตอร์ && (a, b) มันเป็นการใช้งานของมันที่บอกว่าเกิดอะไรขึ้น
Johannes Schaub - litb

10
litb: เป็นไปไม่ได้ที่จะส่งผ่าน b ไปยังผู้ให้บริการ && (a, b) โดยไม่ต้องประเมิน และไม่มีวิธีการยกเลิกการประเมิน b เพราะคอมไพเลอร์ไม่สามารถรับประกันได้ว่าจะไม่มีผลข้างเคียง
jmucchiello

2
ฉันพบว่ามันเศร้า ฉันคิดว่าฉันควรนิยามผู้ประกอบการ && และ || ใหม่ และพวกเขายังคงกำหนดอย่างเต็มที่คอมไพเลอร์จะตรวจสอบและประเมินผลให้พวกเขาลัดวงจร: หลังจากทั้งหมดเพื่อที่จะไม่เกี่ยวข้องเป็นพวกเขารับประกันไม่มีผลข้างเคียง!
Joe Pineda

2
@ โจ: แต่ค่าตอบแทนและข้อโต้แย้งของผู้ประกอบการอาจเปลี่ยนจากบูลีนเป็นอย่างอื่น ฉันเคยใช้ตรรกะ "พิเศษ" กับค่าสาม ("จริง", "เท็จ" และ "ไม่รู้จัก") ค่าส่งคืนนั้นถูกกำหนดไว้แล้ว แต่พฤติกรรมการลัดวงจรไม่เหมาะสม
Alex B

70

การประเมินการลัดวงจรและคำสั่งของการประเมินเป็นมาตรฐานความหมายในทั้ง C และ C ++

หากไม่เป็นเช่นนั้นรหัสเช่นนี้จะไม่ใช่สำนวนสามัญ

   char* pChar = 0;
   // some actions which may or may not set pChar to something
   if ((pChar != 0) && (*pChar != '\0')) {
      // do something useful

   }

ส่วนที่6.5.13 ตรรกะและตัวดำเนินการของข้อกำหนด C99 (ลิงก์ PDF)กล่าวว่า

(4) ซึ่งแตกต่างจากไบนารี & โอเปอเรเตอร์ bitwise ผู้ประกอบการ && รับประกันการประเมินจากซ้ายไปขวา มีจุดลำดับหลังจากการประเมินผลของตัวถูกดำเนินการแรก หากตัวถูกดำเนินการตัวแรกเปรียบเทียบเท่ากับ 0 ตัวถูกดำเนินการตัวที่สองจะไม่ถูกประเมิน

ในทำนองเดียวกันส่วนที่6.5.14 ตัวดำเนินการตรรกะหรือพูดว่า

(4) ต่างจาก bitwise | ผู้ประกอบการ | | ผู้ประกอบการรับประกันการประเมินผลจากซ้ายไปขวา; มีจุดลำดับหลังจากการประเมินผลของตัวถูกดำเนินการแรก หากตัวถูกดำเนินการตัวแรกเปรียบเทียบไม่เท่ากันกับ 0 ตัวถูกดำเนินการตัวที่สองจะไม่ถูกประเมิน

ถ้อยคำที่คล้ายกันนี้สามารถพบได้ในมาตรฐาน C ++ ตรวจสอบหัวข้อ 5.14 ในสำเนาฉบับร่างนี้ ในฐานะที่เป็นตัวตรวจสอบบันทึกในคำตอบอื่นถ้าคุณแทนที่ && หรือ | | ดังนั้นตัวถูกดำเนินการทั้งสองจะต้องได้รับการประเมินเนื่องจากจะกลายเป็นการเรียกใช้ฟังก์ชันปกติ


อาสิ่งที่ฉันกำลังมองหา! ตกลงดังนั้นทั้งคำสั่งการประเมินผลและการลัดวงจรได้รับคำสั่งตาม ANSI-C 99! ฉันชอบที่จะเห็นการอ้างอิงที่เทียบเท่าสำหรับ ANSI-C ++ แต่ฉันเกือบ 99% จะต้องเหมือนกัน
Joe Pineda

ยากที่จะหาลิงค์ฟรีที่ดีสำหรับมาตรฐาน C ++ ได้เชื่อมโยงกับสำเนาฉบับร่างที่ฉันพบด้วย googling
พอลดิกสัน

เป็นจริงสำหรับประเภท POD แต่ถ้าคุณใช้งานโอเปอเรเตอร์มากเกินไป && หรือโอเปอเรเตอร์ || นี่ไม่ใช่ทางลัด
30932 Martin York

1
ใช่มันเป็นเรื่องที่น่าสนใจที่จะทราบว่าสำหรับบูลคุณจะรับประกันการประเมินผลและพฤติกรรมการลัดวงจร เนื่องจากคุณไม่สามารถโอเวอร์โหลดโอเปอเรเตอร์ && สำหรับสองประเภทในตัว คุณต้องการอย่างน้อยหนึ่งประเภทที่ผู้ใช้กำหนดในตัวถูกดำเนินการเพื่อให้มันทำงานแตกต่างกัน
Johannes Schaub - litb

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

19

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

if(ptr && ptr->value) { 
    ...
}

เหมือนกันสำหรับผู้ใช้เครื่องหมายจุลภาค:

// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b()); 

หนึ่งกล่าวว่าระหว่างถูกดำเนินการทางซ้ายและขวาของ&&, ||, ,และระหว่างครั้งแรกและครั้งที่สอง / ถูกดำเนินการในสามของ?:(ผู้ประกอบการตามเงื่อนไข) เป็น "จุดตามลำดับ" ผลข้างเคียงใด ๆ จะได้รับการประเมินอย่างสมบูรณ์ก่อนถึงจุดนั้น ดังนั้นนี่ปลอดภัย:

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1

โปรดทราบว่าผู้ใช้เครื่องหมายจุลภาคไม่ต้องสับสนกับเครื่องหมายจุลภาคที่ใช้ในการแยกสิ่งต่าง ๆ :

// order of calls to a and b is unspecified!
function(a(), b());

มาตรฐาน C ++ บอกไว้ใน5.14/1:

กลุ่มตัวดำเนินการ && จากซ้ายไปขวา ตัวถูกดำเนินการทั้งสองถูกแปลงเป็นประเภทบูลโดยนัย (ข้อ 4) ผลลัพธ์จะเป็นจริงถ้าตัวถูกดำเนินการทั้งคู่เป็นจริงและเท็จ ซึ่งแตกต่างจาก &, & & รับประกันการประเมินผลจากซ้ายไปขวา: ตัวถูกดำเนินการที่สองจะไม่ได้รับการประเมินหากตัวถูกดำเนินการแรกเป็นเท็จ

และใน5.15/1:

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

มันบอกว่าสำหรับทั้งสองข้างถัดไป:

ผลลัพธ์ที่ได้คือบูล ผลข้างเคียงทั้งหมดของการแสดงออกครั้งแรกยกเว้นการทำลายขมับ (12.2) เกิดขึ้นก่อนการแสดงออกครั้งที่สองจะถูกประเมิน

นอกจากนั้น1.9/18พูดว่า

ในการประเมินผลของแต่ละการแสดงออก

  • a && b
  • a || b
  • a ? b : C
  • a , b

ใช้ความหมายในตัวของตัวดำเนินการในนิพจน์เหล่านี้ (5.14, 5.15, 5.16, 5.18) มีจุดลำดับหลังจากการประเมินผลของนิพจน์แรก


10

ส่งตรงจาก K&R เก่าที่ดี:

C รับประกัน&&และ||ประเมินจากซ้ายไปขวา - ในไม่ช้าเราจะเห็นกรณีที่มีความสำคัญ


3
K&R 2nd edition p40 "นิพจน์ที่เชื่อมโยงโดย && หรือ || จะได้รับการประเมินจากซ้ายไปขวาและการประเมินจะหยุดทันทีที่ทราบความจริงหรือเท็จของผลลัพธ์โปรแกรม C ส่วนใหญ่ใช้คุณสมบัติเหล่านี้" ฉันไม่พบข้อความที่ยกมาของคุณที่ใดก็ได้ในหนังสือ นี่มาจากรุ่นที่ล้าสมัยครั้งแรกหรือเปล่า? โปรดระบุที่คุณพบข้อความนี้
Lundin

1
ตกลงปรากฎว่าคุณกำลังพูดถึงการกวดวิชาโบราณนี้ มันมาจากปี 1974 และไม่เกี่ยวข้องอย่างมาก
Lundin

6

ระวังให้มาก

สำหรับประเภทพื้นฐานสิ่งเหล่านี้คือตัวดำเนินการทางลัด

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

สำหรับoperator &&และoperator ||สำหรับประเภทพื้นฐานลำดับการประเมินผลจากซ้ายไปขวา (ไม่เช่นนั้นการตัดสั้นจะเป็นเรื่องยาก :-) แต่สำหรับผู้ประกอบการที่ใช้งานเกินพิกัดที่คุณกำหนด ไม่ได้กำหนด.


1
ผู้ประกอบการมากไปไม่มีอะไรเกี่ยวข้องกับประเภทที่เป็น POD หรือไม่ ในการกำหนดฟังก์ชั่นโอเปอเรเตอร์อย่างน้อยหนึ่งอาร์กิวเมนต์ต้องเป็นคลาส (หรือ struct หรือ union) หรือ enum หรือการอ้างอิงไปยังหนึ่งในนั้น การเป็น POD หมายความว่าคุณสามารถใช้ memcpy กับมันได้
Derek Ledbetter

และนั่นคือสิ่งที่ฉันพูด หากคุณทำมากเกินไป && สำหรับชั้นเรียนของคุณมันเป็นเพียงวิธีการโทร ดังนั้นคุณไม่สามารถพึ่งพาลำดับของการประเมินผลของพารามิเตอร์ เห็นได้ชัดว่าคุณไม่สามารถโหลดมากเกินไป && สำหรับประเภท POD
Martin York

3
คุณใช้คำว่า "ประเภท POD" ไม่ถูกต้อง คุณสามารถโหลดมากเกินไป && สำหรับโครงสร้างคลาสสหภาพหรือ Enum ใด ๆ POD หรือไม่ก็ได้ คุณไม่สามารถโหลดมากเกินไป && หากทั้งสองฝ่ายเป็นประเภทตัวเลขหรือพอยน์เตอร์
Derek Ledbetter

ฉันใช้ POD เป็น (char / int / float ฯลฯ ) ไม่ใช่ POD aggrigate (ซึ่งเป็นสิ่งที่คุณกำลังพูดถึง) และมักจะอ้างถึงแยกต่างหากหรือมากกว่าอย่างชัดเจนเพราะมันไม่ได้อยู่ในประเภท
มาร์ตินยอร์

2
คุณหมายถึง "ประเภทพื้นฐาน" แต่เขียนว่า "ประเภท POD" หรือไม่?
Öö Tiib

0

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

สำหรับความสำคัญเมื่อคุณมีบางสิ่งบางอย่างเช่นA op1 B op2 Cคุณสามารถสิ่งที่กลุ่มเป็นอย่างใดอย่างหนึ่งหรือ(A op1 B) op2 C A op1 (B op2 C)หากop1มีลำดับความสำคัญสูงกว่าop2คุณจะได้รับนิพจน์แรก มิฉะนั้นคุณจะได้อันดับที่สอง

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

ในกรณีนี้โดยเฉพาะอย่างยิ่ง&&มีความสำคัญสูงกว่าดังนั้นการแสดงออกจะได้รับการประเมินว่าเป็น||(a != "" && it == seqMap.end()) || isEven

ลำดับตัวเองคือ "ซ้ายไปขวา" ในรูปแบบต้นไม้นิพจน์ a != "" && it == seqMap.end()ดังนั้นครั้งแรกที่เราจะประเมิน isEvenถ้ามันเป็นความจริงนิพจน์ทั้งหมดเป็นความจริงมิฉะนั้นเราจะไป ขั้นตอนการทำซ้ำตัวเองซ้ำภายในซ้ำซากซ้ายของหลักสูตร


เกร็ดเล็กเกร็ดน้อยที่น่าสนใจ แต่แนวคิดของความเป็นผู้นำมีรากฐานมาจากสัญกรณ์คณิตศาสตร์ สิ่งเดียวกันที่เกิดขึ้นในa*b + cที่มีความสำคัญสูงกว่า*+

ที่น่าสนใจยิ่ง / ปิดบังสำหรับการแสดงออก unparenthasiszed A1 op1 A2 op2 ... opn-1 Anที่ผู้ประกอบการทุกคนมีความสำคัญเดียวกันจำนวนของต้นไม้แสดงออกไบนารีที่เราสามารถสร้างจะได้รับจากที่เรียกว่าหมายเลขคาตาลัน สำหรับขนาดใหญ่nเหล่านี้เติบโตอย่างรวดเร็ว d


ทั้งหมดนี้ถูกต้อง แต่มันเกี่ยวกับความสำคัญของผู้ปฏิบัติงานและการเชื่อมโยงไม่เกี่ยวกับลำดับการประเมินผลและการเรียนระยะสั้น สิ่งเหล่านั้นแตกต่างกัน
โทมัส Padron-McCarthy

0

ถ้าคุณเชื่อใจวิกิพีเดีย:

[ &&และ||] มีความแตกต่างทางความหมายจากตัวดำเนินการ bit-wise & และ | เพราะพวกมันจะไม่ประเมินค่าตัวถูกดำเนินการที่เหมาะสมหากผลลัพธ์สามารถกำหนดได้จากด้านซ้ายเพียงอย่างเดียว

C (ภาษาการเขียนโปรแกรม)


11
ทำไมต้องเชื่อวิกิเมื่อเรามีมาตรฐาน!
30932 Martin York

1
ถ้าคุณเชื่อถือวิกิพีเดีย'วิกิพีเดียไม่ได้เป็นแหล่งที่เชื่อถือได้'
มาร์ควิสแห่ง Lorne

นี่เป็นความจริงตราบใดที่มันดำเนินไป แต่ไม่สมบูรณ์เนื่องจากตัวดำเนินการที่โอเวอร์โหลดใน C ++ นั้นไม่ได้เป็นแบบลัดวงจร
Thomas Padron-McCarthy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.