การเชื่อมโยงแบบคงที่ล่าช้าใน PHP การผูกแบบคงที่ช่วงปลาย การผูกแบบคงที่และไดนามิก


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

เกิดอะไรขึ้นในโค้ดด้านล่าง:

การเชื่อมโยงแบบคงที่หรือการเชื่อมโยงแบบไดนามิก?
ความหลากหลายนี้คืออะไร?

คลาส Animal ( void eat() ( System.out.println("Animal is eating"); ) ) คลาส Dog ขยาย Animal ( void eat() ( System.out.println("Dog is eating"); ) ) คงที่สาธารณะ เป็นโมฆะ main (String args) ( Animal a=new Animal(); a.eat(); )


2018-05-20 10:33


2018-05-20 10:46

ตรวจสอบว่าคลาสพนักงานนี้มีฟังก์ชัน abstract Earning() และทุกคลาสมีการใช้งาน toString()

พนักงาน พนักงาน = พนักงานใหม่; // เริ่มต้นอาร์เรย์ด้วยพนักงานพนักงาน = ใหม่ SalriedEmployee(); พนักงาน = พนักงานรายชั่วโมงใหม่ (); พนักงาน = CommissionEmployee ใหม่ (); พนักงาน = BasePlusCommissionEmployee ใหม่ (); สำหรับ (พนักงาน currentEmployee: พนักงาน)( System.out.println(currentEmployee); // เรียกใช้ toString System.out.printf("earned $%,.2f%n", currentEmployee.earnings()); )

การเรียก toString และวิธีการรับรายได้ทั้งหมดได้รับการแก้ไข ณ เวลาดำเนินการ ขึ้นอยู่กับประเภทของออบเจ็กต์ที่พนักงานปัจจุบันอยู่

กระบวนการนี้เรียกว่าการรวมแบบไดนามิกหรือการผูกล่าช้า

Late Static Binding (LSB) เป็นหัวข้อสนทนาที่ร้อนแรงในช่วงสามปีที่ผ่านมาในแวดวงการพัฒนา PHP (และในที่สุดเราก็ได้รับมันใน PHP 5.3) แต่ทำไมมันถึงจำเป็น? ในบทความนี้เราจะมาดูกันว่าเป็นอย่างไร การผูกแบบคงที่ล่าช้าสามารถทำให้โค้ดของคุณง่ายขึ้นอย่างมาก

ในการประชุมนักพัฒนา PHP ที่จัดขึ้นที่ปารีสในเดือนพฤศจิกายน พ.ศ. 2548 หัวข้อของการเชื่อมโยงแบบสแตติกช่วงหลังได้รับการพูดคุยอย่างเป็นทางการโดยทีมพัฒนาหลัก พวกเขาเห็นพ้องที่จะปฏิบัติตามพร้อมกับหัวข้ออื่น ๆ อีกมากมายที่อยู่ในวาระการประชุม รายละเอียดจะต้องได้รับการตกลงผ่านการอภิปรายแบบเปิด

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

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

Class Beer ( const NAME = "Beer!"; public function getName() ( return self::NAME; ) ) class Ale ขยาย Beer ( const NAME = "Ale!"; ) $beerDrink = new Beer; $aleDrink = เบียร์ใหม่; echo "เบียร์คือ:" . $beerDrink->getName() "\n"; echo "เอลคือ:" . $aleDrink->getName() "\n";

รหัสนี้จะสร้างผลลัพธ์ดังต่อไปนี้:

เบียร์คือ: เบียร์! เอลคือ: เบียร์!

ระดับ เบียร์วิธีการสืบทอด รับชื่อ(), แต่ในขณะเดียวกัน ตัวเองยังคงชี้ไปที่คลาสที่ใช้งาน (ในกรณีนี้คือ class เบียร์). สิ่งนี้ยังคงอยู่ใน PHP 5.3 แต่มีการเพิ่มคำนี้เข้าไป คงที่. ลองดูตัวอย่างอีกครั้ง:

Class Beer ( const NAME = "Beer!"; ฟังก์ชั่นสาธารณะ getName() ( return self::NAME; ) ฟังก์ชั่นสาธารณะ getStaticName() ( return static::NAME; ) ) class Ale ขยาย Beer ( const NAME = "Ale!" ; ) $beerDrink = เบียร์ใหม่; $aleDrink = เบียร์ใหม่; echo "เบียร์คือ:" . $beerDrink->getName() "\n"; echo "เอลคือ:" . $aleDrink->getName() "\n"; echo "เบียร์คือความจริง:" . $beerDrink->getStaticName() "\n"; echo "เอลคือจริงๆ:" . $aleDrink->getStaticName() "\n";

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

ภายใน ความแตกต่างหลัก (และในความเป็นจริง สาเหตุที่การเชื่อมโยงถูกเรียกว่าล่าช้า) ระหว่างวิธีการเข้าถึงทั้งสองวิธีนี้ก็คือ PHP จะกำหนดค่าสำหรับ ตัวเอง::NAMEระหว่าง "การรวบรวม" (เมื่ออักขระ PHP ถูกแปลงเป็นรหัสเครื่องที่จะถูกประมวลผลโดยเอ็นจิ้น Zend) และสำหรับ คงที่::NAMEค่าจะถูกกำหนดเมื่อเริ่มต้น (ในขณะที่รหัสเครื่องถูกดำเนินการในเอ็นจิ้น Zend)

นี่เป็นอีกหนึ่งเครื่องมือสำหรับนักพัฒนา PHP ในส่วนที่สอง เราจะมาดูกันว่าสามารถนำมาใช้ให้เกิดประโยชน์ได้อย่างไร

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

ใน Java ตัวแปรวัตถุเป็นแบบ polymorphic ตัวอย่างเช่น:
class King ( โมฆะสาธารณะคงที่ main (String args) ( King king = new King() ; king = new AerysTargaryen() ; king = new RobertBaratheon() ; ) ) คลาส RobertBaratheon ขยาย King ( ) คลาส AerysTargaryen ขยาย King ( )
ตัวแปรประเภท King สามารถอ้างอิงถึงวัตถุประเภท King หรือวัตถุของคลาสย่อยของ King ก็ได้
ลองใช้ตัวอย่างต่อไปนี้:

class King ( public void Speech() ( System .out .println ("I"m the King of the Andals!" ) ; ) public void Speech(สตริงคำพูด) ( System .out .println ("Wise man says: " + ใบเสนอราคา); ) คำพูดที่เป็นโมฆะสาธารณะ (บูลีนพูดเสียงดัง) ( ถ้า (พูดเสียงดัง) ระบบ .out .println ( "ฉันคือราชาแห่งอันดาลส์!!!11") ; else System .out .println ("i"m... the king..." ) ; ) ) คลาส AerysTargaryen ขยาย King ( @Override public void Speech() ( System .out .println ("Burn they all... " ) ; ) @Override คำพูดเป็นโมฆะสาธารณะ(ใบเสนอราคาสตริง) ( System .out .println (quotation+ " ... และตอนนี้เผามันทั้งหมด!" ) ; ) ) class Kingdom ( public static void main(String args) ( King king = ใหม่ AerysTargaryen() ; king.speech ("Homo homini lupus est" ) ; ) )
จะเกิดอะไรขึ้นเมื่อมีการเรียกใช้เมธอดที่เป็นของออบเจ็กต์กษัตริย์?
1. คอมไพลเลอร์จะตรวจสอบประเภทอ็อบเจ็กต์ที่ประกาศและชื่อเมธอด โดยระบุชื่อเมธอดทั้งหมดด้วยชื่อคำพูดในคลาส AerusTargarien และวิธีการสาธารณะทั้งหมดคำพูดในซูเปอร์คลาสเอรัส ทาร์แกเรียน. คอมไพเลอร์ตอนนี้รู้ตัวเลือกที่เป็นไปได้เมื่อเรียกใช้เมธอด
2. คอมไพลเลอร์จะกำหนดประเภทของอาร์กิวเมนต์ที่ส่งผ่านไปยังเมธอด หากพบวิธีการเดียวที่มีลายเซ็นตรงกับอาร์กิวเมนต์ การโทรจะเกิดขึ้นกระบวนการนี้king.speech("ตุ๊ด homini lupus est") คอมไพเลอร์จะเลือกวิธีการคำพูด (คำพูดสตริง), แต่ไม่ คำพูด().
หากคอมไพเลอร์พบหลายวิธีด้วยพารามิเตอร์ที่เหมาะสม (หรือไม่มีเลย) ข้อความแสดงข้อผิดพลาดจะปรากฏขึ้น



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

4. เครื่องเสมือนจะสร้างตารางวิธีการไว้ล่วงหน้าสำหรับแต่ละคลาสที่แสดงรายการลายเซ็นของวิธีการทั้งหมดและวิธีการจริงที่จะเรียกใช้
ตารางวิธีการเรียนกษัตริย์ดูเหมือนว่า:
  • คำพูด() - กษัตริย์.คำพูด()
  • คำพูด (คำพูดสตริง) -กษัตริย์.คำพูด (คำพูดสตริง)
  • กษัตริย์.คำพูด (บูลีนพูดเสียงดัง)
และสำหรับชั้นเรียนนั้นAerysTargaryen - แบบนี้:
  • คำพูด() -แอรีส ทาร์แกเรียน . คำพูด()
  • คำพูด (คำพูดสตริง) -แอรีส ทาร์แกเรียน. คำพูด (คำพูดสตริง)
  • คำพูด(บูลีนพูดเสียงดัง) -กษัตริย์. คำพูด (บูลีนพูดเสียงดัง)
วิธีการที่สืบทอดมาจาก Object จะถูกละเว้นในตัวอย่างนี้
เมื่อโทรกษัตริย์.คำพูด() :
  1. มีการกำหนดประเภทที่แท้จริงของตัวแปรกษัตริย์ . ในกรณีนี้ก็คือแอรีส ทาร์แกเรียน.
  2. เครื่องเสมือนกำหนดคลาสที่เป็นของวิธีการคำพูด()
  3. วิธีการนี้เรียกว่า
เชื่อมโยงทุกวิถีทางเข้าด้วยกันชวาดำเนินการหลายรูปแบบผ่านการผูกล่าช้าการเชื่อมโยงแบบไดนามิกมีคุณลักษณะที่สำคัญประการหนึ่ง: อนุญาตแก้ไขโปรแกรมโดยไม่ต้องคอมไพล์รหัสใหม่ นี่คือสิ่งที่โปรแกรมทำขยายได้แบบไดนามิก ( ขยายได้).
จะเกิดอะไรขึ้นหากคุณเรียกใช้เมธอดที่ถูกผูกไว้แบบไดนามิกของวัตถุที่สร้างขึ้นในตัวสร้าง? ตัวอย่างเช่น:
คลาสคิง ( คิง () ( System . out . println ( "เรียกตัวสร้างคิง" ) ; คำพูด () ; //แทนที่วิธี polymorphic ใน AerysTargaryen) public void Speech() ( System .out .println ("I"m the King of the Andals!" ) ; ) ) คลาส AerysTargaryen ขยาย King ( private String allowanceName; AerysTargaryen() ( System .out .println ( "โทรหาผู้สร้าง Aerys Targaryen") ; ชื่อเหยื่อ = "ลีอันนา สตาร์ค" ; คำพูด() ; ) @Override public void Speech() ( System .out .println ("Burn " + allowanceName + "!" ) ; ) ) class Kingdom ( public static void main(String args) ( King king = new AerysTargaryen() ; ) ) ผลลัพธ์:

โทรหาผู้สร้างคิง เบิร์นโมฆะ! โทรหาผู้สร้าง Aerys Targaryen เบิร์น ลีอันนา สตาร์ค!
ตัวสร้างของคลาสพื้นฐานจะถูกเรียกเสมอระหว่างการสร้างคลาสที่ได้รับ การเรียกจะเลื่อนสายการสืบทอดขึ้นโดยอัตโนมัติ เพื่อให้ในที่สุดตัวสร้างของคลาสพื้นฐานทั้งหมดตลอดสายการสืบทอดจะถูกเรียกในที่สุด
ซึ่งหมายความว่าเมื่อเรียกตัวสร้างใหม่ AerysTargaryen() จะถูกเรียกว่า:
  1. วัตถุใหม่ ()
  2. คิงองค์ใหม่()
  3. ใหม่แอรีสทาร์แกเรียน()
ตามคำนิยาม งานของนักออกแบบคือการทำให้วัตถุมีชีวิตชีวา ภายในตัวสร้างใดๆ วัตถุสามารถสร้างขึ้นได้เพียงบางส่วนเท่านั้น ทั้งหมดที่ทราบก็คือวัตถุคลาสพื้นฐานได้รับการเตรียมใช้งานแล้ว หาก Constructor เป็นเพียงอีกก้าวหนึ่งของการสร้างคลาสอ็อบเจ็กต์ที่ได้มาจากคลาสของ Constructor นี้ ส่วนที่ "ได้มา" ยังไม่ได้รับการเริ่มต้น ณ เวลาที่ Constructor ปัจจุบันถูกเรียก

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

ผลลัพธ์ของโปรแกรมถูกกำหนดโดยการดำเนินการของอัลกอริธึมการเริ่มต้นวัตถุ:

  1. หน่วยความจำที่จัดสรรสำหรับวัตถุใหม่เต็มไปด้วยเลขศูนย์ไบนารี
  2. ตัวสร้างคลาสฐานถูกเรียกตามลำดับที่อธิบายไว้ก่อนหน้านี้ ณ จุดนี้จะมีการเรียกเมธอดที่ถูกแทนที่คำพูด() (ใช่ ก่อนที่จะเรียกตัวสร้างคลาสแอรีส ทาร์แกเรียน) โดยที่พบว่าตัวแปรนั้นชื่อเหยื่อเป็นโมฆะ เพราะระยะแรก
  3. ตัวเริ่มต้นสมาชิกคลาสจะถูกเรียกตามลำดับที่ถูกกำหนดไว้
  4. เนื้อความของตัวสร้างคลาสที่ได้รับถูกดำเนินการ
โดยเฉพาะอย่างยิ่งเนื่องจากปัญหาด้านพฤติกรรมดังกล่าว จึงควรปฏิบัติตามกฎต่อไปนี้ในการเขียนตัวสร้าง:
- ดำเนินการในตัวสร้างเฉพาะการกระทำที่จำเป็นและง่ายที่สุดเพื่อเริ่มต้นวัตถุ
- ถ้าเป็นไปได้ ให้หลีกเลี่ยงการเรียกวิธีการที่ไม่ได้กำหนดไว้ส่วนตัวหรือขั้นสุดท้าย (ซึ่งในบริบทนี้ก็เป็นสิ่งเดียวกัน)
วัสดุที่ใช้:
  1. เอคเคล บี.-คิดในชวา , ฉบับพิมพ์ครั้งที่ 4 - บทที่ 8
  2. เคย์ เอส. ฮอร์สต์แมนน์, แกรี่ คอร์เนล -คอร์ Java 1 - บทที่ 5
  3. วิกิพีเดีย

ผูกพัน- การทดแทนการเรียกใช้ฟังก์ชันเฉพาะเป็นรหัสโปรแกรม - วิธีการเรียน. เหมาะสมสำหรับคลาสที่ได้รับเท่านั้น

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

หากการเลือกฟังก์ชั่นเสร็จสิ้นในเวลารวบรวม เรากำลังเผชิญกับ การเชื่อมโยงแบบคงที่.

ในกรณีนี้ ฟังก์ชัน - วิธีการของคลาสฐาน - จะถูกเรียกสำหรับตัวชี้ไปยังคลาสฐาน แม้ว่าตัวชี้ไปยังคลาสฐานจะถูกกำหนดค่าของที่อยู่ของอินสแตนซ์ของคลาสที่ได้รับก็ตาม

หากทำการเลือกฟังก์ชั่นในขั้นตอนการทำงานของโปรแกรม เรากำลังเผชิญอยู่ การเชื่อมโยงแบบไดนามิก.

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

ฟังก์ชั่นเสมือน

ตามค่าเริ่มต้น คลาสที่ได้รับจะมีการเชื่อมโยงแบบคงที่ ถ้าจะใช้การเชื่อมโยงแบบไดนามิกสำหรับวิธีการเรียนใด ๆ วิธีการดังกล่าวจะต้องได้รับการประกาศ เสมือน .

ฟังก์ชั่นเสมือน:

    มีคีย์เวิร์ดเสมือนในต้นแบบในคลาสพื้นฐาน

    ฟังก์ชั่นสมาชิกคลาสบังคับ:

    คลาสที่ได้รับมาทั้งหมดจะต้องมีต้นแบบเดียวกัน (ไม่จำเป็นต้องระบุคำว่า virtual ในคลาสที่ได้รับมา)

หากวิธีการใดๆ ในคลาสที่ได้รับมีชื่อเหมือนกับในคลาสพื้นฐาน แต่มีรายการพารามิเตอร์ที่แตกต่างกัน แสดงว่ามีฟังก์ชันโอเวอร์โหลด

ตัวอย่าง: ชั้นเรียนจุดและวงกลม

การพิมพ์โมฆะเสมือน ();

คลาสวงกลม: จุดสาธารณะ (

พิมพ์เป็นโมฆะ(); // คุณสามารถพิมพ์โมฆะเสมือน ();

จุดโมฆะ::พิมพ์()

ศาล<< "Point (" << x << ", " << y << ")";

เป็นโมฆะวงกลม::พิมพ์()

ศาล<< "Circle with center in "; Point::print();

ศาล<< "and radius " << rad;

การใช้งาน:

จุด p1(3,5), p2(1,1), *pPtr;

วงกลม c1(1), c2(p2, 1);

pPtr = pPtr->พิมพ์(); // รับ: จุด (3, 5)

pPtr = pPtr->พิมพ์(); // รับ:

วงกลมโดยมีศูนย์กลางอยู่ที่จุด (1, 1) และรัศมี 1

ตัวอย่างการผูกแบบไดนามิก: รายการ

การใช้การเชื่อมโยงแบบไดนามิกที่พบบ่อยที่สุดคือกับคลาสคอนเทนเนอร์ที่มีตัวชี้ไปยังคลาสฐาน คลาสคอนเทนเนอร์ดังกล่าวสามารถรวมข้อมูลที่เกี่ยวข้องกับทั้งคลาสพื้นฐานและคลาสที่ได้รับใดๆ

ลองพิจารณาตัวอย่าง - รายการที่มีทั้งจุดและวงกลม

// ตัวสร้าง

รายการ():ข้อมูล(NULL), ถัดไป(NULL)()

รายการ (จุด *p):ข้อมูล(p), ถัดไป(NULL)()

รายการ():หัว(โมฆะ)()

การแทรกเป็นโมฆะ (จุด *p)(p->ถัดไป = head; head = p;)

รายการโมฆะ::พิมพ์()

สำหรับ(รายการ *cur = head; cur; cur = cur->ถัดไป)(

cur -> ข้อมูล -> พิมพ์ ();

ศาล<< endl;

การใช้คลาส:

จุด *p = จุดใหม่ (1,2);

mylist.insert(พี);

p = วงจรใหม่ (1,2,1);

mylist.insert(พี);

วงกลมโดยมีศูนย์กลางอยู่ที่จุด (1, 2) และรัศมี 1

การผูกใน C ++

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

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

ตัวอย่างเช่น พิจารณาคำอธิบายของคลาสและตัวแปรโกลบอลต่อไปนี้: คลาส Mammal

printf("พูดไม่ได้");

คลาสสุนัข: สัตว์เลี้ยงลูกด้วยนมสาธารณะ

printf("ว้าว ว้าว");

printf("ว้าว ว้าว เช่นกัน");

สัตว์เลี้ยงลูกด้วยนม *fido = สุนัขตัวใหม่;

นิพจน์ fred.speak() พิมพ์ว่า "cant speak" แต่การเรียก fido->speak() ก็จะพิมพ์ "cant speak" ด้วยเช่นกัน เนื่องจากวิธีการที่สอดคล้องกันในคลาส Mammal ไม่ได้ถูกประกาศให้เป็นเสมือน คอมไพลเลอร์ไม่อนุญาตให้ใช้นิพจน์ fido->bark() แม้ว่าประเภทไดนามิกสำหรับ fido จะเป็น Dog ก็ตาม อย่างไรก็ตาม ประเภทคงที่ของตัวแปรเป็นเพียงคลาส Mammal เท่านั้น

ถ้าเราเพิ่มคำว่าเสมือน:

โมฆะเสมือนพูด ()

printf("พูดไม่ได้");

จากนั้นเราจะได้ผลลัพธ์ที่คาดหวังที่เอาต์พุตสำหรับนิพจน์ fido->speak()

การเปลี่ยนแปลงล่าสุดในภาษา C++ คือการเพิ่มสิ่งอำนวยความสะดวกในการจดจำคลาสไดนามิกของอ็อบเจ็กต์ พวกเขาสร้างระบบ RTTI (การระบุประเภทรันไทม์)

ในระบบ RTTI แต่ละคลาสมีโครงสร้างที่เกี่ยวข้องประเภท typeinfo ซึ่งเข้ารหัสข้อมูลต่างๆ เกี่ยวกับคลาส ฟิลด์ข้อมูลชื่อ ซึ่งเป็นหนึ่งในฟิลด์ข้อมูลของโครงสร้างนี้ มีชื่อคลาสเป็นสตริงข้อความ ฟังก์ชัน typeid สามารถใช้เพื่อแยกวิเคราะห์ข้อมูลประเภทข้อมูล ดังนั้นคำสั่งต่อไปนี้จะพิมพ์สตริง "Dog" ซึ่งเป็นชนิดข้อมูลไดนามิกสำหรับ fido ในตัวอย่างนี้ จำเป็นต้องยกเลิกการอ้างอิงตัวแปร fido pointer เพื่อให้อาร์กิวเมนต์เป็นค่าที่ตัวชี้อ้างถึง แทนที่จะเป็นตัวชี้เอง:

ศาล<< «fido is a» << typeid(*fido).name() << endl;

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

ถ้า (typeid(*fido).before (typeid(fred)))…

ถ้า (typeid(เฟรด).ก่อน (typeid(สาว)))…

ก่อนระบบ RTTI เคล็ดลับการเขียนโปรแกรมมาตรฐานคือการเขียนโค้ดอย่างชัดเจนในลำดับชั้นของคลาสถึงวิธีที่จะเป็นอินสแตนซ์ ตัวอย่างเช่น ในการทดสอบค่าของตัวแปรประเภท Animal เพื่อดูว่าเป็นประเภท Cat หรือประเภท Dog เราสามารถกำหนดระบบของวิธีการได้ดังต่อไปนี้:

isaDog เสมือน int ()

int เสมือน isaCat()

คลาสสุนัข: สัตว์เลี้ยงลูกด้วยนมสาธารณะ

isaDog เสมือน int ()

คลาสแมว: สัตว์เลี้ยงลูกด้วยนมสาธารณะ

int เสมือน isaCat()

ตอนนี้คุณสามารถใช้คำสั่ง fido->isaDog() เพื่อตรวจสอบว่าค่าปัจจุบันของ fido เป็นค่าประเภท Dog หรือไม่ หากส่งคืนค่าที่ไม่ใช่ศูนย์ ประเภทของตัวแปรก็สามารถแปลงเป็นประเภทข้อมูลที่ต้องการได้

ด้วยการส่งคืนพอยน์เตอร์แทนที่จะเป็นจำนวนเต็ม เราจะรวมการทดสอบคลาสย่อยและการคัดเลือกประเภท สิ่งนี้คล้ายกับอีกส่วนหนึ่งของระบบ RTTI ที่เรียกว่า dynamic_cast ซึ่งเราจะอธิบายโดยย่อ หากฟังก์ชันในคลาส Mammal ส่งกลับตัวชี้ไปที่ Dog จะต้องประกาศคลาส Dog ไว้ก่อนหน้านี้ ผลลัพธ์ของการมอบหมายอาจเป็นตัวชี้ว่างหรือการอ้างอิงที่ถูกต้องไปยังคลาส Dog ดังนั้น จะต้องตรวจสอบผลลัพธ์ แต่เราขจัดความจำเป็นในการร่ายแบบ สิ่งนี้แสดงในตัวอย่างต่อไปนี้:

สุนัขคลาส; // คำอธิบายเบื้องต้น

สุนัขเสมือน* isaDog()

แมวเสมือน * isaCat()

คลาสสุนัข: สัตว์เลี้ยงลูกด้วยนมสาธารณะ

สุนัขเสมือน* isaDog()

คลาสแมว: สัตว์เลี้ยงลูกด้วยนมสาธารณะ

แมวเสมือน * isaCat()

ผู้ดำเนินการ lassie = fido->isaDog(); ตอนนี้เราจะทำมันเสมอ ด้วยเหตุนี้ lassie จึงถูกตั้งค่าเป็นค่าที่ไม่ใช่ศูนย์เฉพาะในกรณีที่ fido มีคลาส Dog แบบไดนามิก หาก Dog ไม่ได้เป็นเจ้าของ fido lassie จะถูกกำหนดให้เป็นตัวชี้ว่าง

lassie = fido->isaDog();

... // fido เป็นประเภท Dog จริงๆ

... // การมอบหมายงานไม่ทำงาน

... // fido ไม่ใช่ประเภท Dog

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

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

// แปลงเฉพาะในกรณีที่ fido เป็นสุนัข

ลาสซี่ = dynamic_cast< Dog* >(ฟิโด้);

// จากนั้นตรวจสอบว่าการร่ายสำเร็จหรือไม่

มีการเพิ่มประเภทการหล่ออีกสามประเภทใน C ++ (static_cast, const_cast และ reตีความ_cast) แต่จะใช้ในกรณีพิเศษและดังนั้นจึงไม่ได้อธิบายไว้ที่นี่ โปรแกรมเมอร์ได้รับการสนับสนุนให้ใช้มันเป็นตัวเลือกที่ปลอดภัยกว่าแทนที่จะใช้กลไกการหล่อแบบเก่า

2. ส่วนการออกแบบ