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

Polymorphism คืออะไร C++?
In C++โพลีมอร์ฟิซึมทำให้ฟังก์ชันสมาชิกทำงานแตกต่างกันไปตามวัตถุที่เรียกใช้ โพลีมอร์ฟิซึมเป็นคำภาษากรีกที่หมายถึงการมีหลายรูปแบบ เกิดขึ้นเมื่อมีลำดับชั้นของ ชั้นเรียน มีความสัมพันธ์ทางกรรมพันธุ์
ตัวอย่างเช่น สมมติว่าเรามีฟังก์ชัน makeSound() เมื่อแมวเรียกใช้ฟังก์ชันนี้ มันจะส่งเสียงร้องเหมียว เมื่อวัวเรียกใช้ฟังก์ชันเดียวกัน มันจะส่งเสียงวัว
แม้ว่าเราจะมีฟังก์ชันเดียว แต่ก็มีพฤติกรรมที่แตกต่างกันภายใต้สถานการณ์ที่ต่างกัน ฟังก์ชันมีหลายรูปแบบ ดังนั้นเราจึงประสบความสำเร็จในความหลากหลาย
เมื่อมีแนวคิดหลักที่ชัดเจนแล้ว C++ จัดพฤติกรรมนี้ออกเป็นสองประเภทที่แตกต่างกัน
ประเภทของความหลากหลาย
C++ รองรับความหลากหลายสองประเภท:
- พหุสัณฐานเวลาคอมไพล์ และ
- ความหลากหลายแบบรันไทม์
หมวดหมู่แรกจะจัดการกับการเรียกใช้ฟังก์ชันทั้งหมดก่อนที่โปรแกรมจะเริ่มทำงาน
ความหลากหลายของเวลาคอมไพล์
คุณเรียกใช้ฟังก์ชันโอเวอร์โหลดโดยการจับคู่จำนวนและประเภทของอาร์กิวเมนต์ ข้อมูลจะปรากฏในระหว่างเวลาคอมไพล์ ซึ่งหมายความว่า C++ คอมไพเลอร์จะเลือกฟังก์ชันที่ถูกต้องในเวลาคอมไพล์
ความแปรปรวนร่วมในเวลาคอมไพล์ทำได้โดยการโอเวอร์โหลดฟังก์ชันและการโอเวอร์โหลดตัวดำเนินการ
ฟังก์ชั่นโอเวอร์โหลด
ฟังก์ชั่นโอเวอร์โหลดเกิดขึ้นเมื่อเรามีฟังก์ชั่นมากมายที่มีชื่อคล้ายกันแต่มีข้อโต้แย้งต่างกัน อาร์กิวเมนต์อาจแตกต่างกันในแง่ของจำนวนหรือประเภท
1 ตัวอย่าง
#include <iostream> using namespace std; void test(int i) { cout << " The int is " << i << endl; } void test(double f) { cout << " The float is " << f << endl; } void test(char const *ch) { cout << " The char* is " << ch << endl; } int main() { test(5); test(5.5); test("five"); return 0; }
Output:
นี่คือภาพหน้าจอของรหัส:
Code คำอธิบาย:
- รวมไฟล์ส่วนหัว iostream ลงในโค้ดของเรา เราจะสามารถใช้ฟังก์ชั่นของมันได้
- รวมเนมสเปซมาตรฐานไว้ในโค้ดของเรา เราจะสามารถใช้คลาสของมันได้โดยไม่ต้องเรียกมัน
- สร้างฟังก์ชันชื่อ test ที่รับพารามิเตอร์จำนวนเต็ม i { เป็นจุดเริ่มต้นของการทดสอบเนื้อความของฟังก์ชัน
- คำสั่งที่จะดำเนินการหากมีการเรียกใช้/เรียกใช้การทดสอบฟังก์ชันข้างต้น
- ส่วนท้ายของการทดสอบการทำงานข้างต้น
- สร้างฟังก์ชันชื่อ test ที่ใช้พารามิเตอร์ float f { เป็นจุดเริ่มต้นของการทดสอบเนื้อความของฟังก์ชัน
- คำสั่งที่จะดำเนินการหากมีการเรียกใช้/เรียกใช้การทดสอบฟังก์ชันข้างต้น
- ส่วนท้ายของการทดสอบการทำงานข้างต้น
- สร้างฟังก์ชันชื่อ test ที่รับพารามิเตอร์อักขระ ch { เป็นจุดเริ่มต้นของการทดสอบเนื้อความของฟังก์ชัน
- คำสั่งที่จะดำเนินการหากมีการเรียกใช้/เรียกใช้การทดสอบฟังก์ชันข้างต้น
- ส่วนท้ายของการทดสอบการทำงานข้างต้น
- เรียกใช้ฟังก์ชัน main() { เป็นจุดเริ่มต้นของเนื้อความของฟังก์ชัน
- เรียกใช้การทดสอบฟังก์ชันและส่ง 5 ให้เป็นค่าของอาร์กิวเมนต์ ซึ่งจะเรียกใช้ฟังก์ชันทดสอบที่ยอมรับอาร์กิวเมนต์จำนวนเต็ม ซึ่งก็คือฟังก์ชันทดสอบแรก
- เรียกใช้การทดสอบฟังก์ชันและส่ง 5.5 ให้เป็นค่าของอาร์กิวเมนต์ สิ่งนี้จะเรียกใช้ฟังก์ชันทดสอบที่ยอมรับอาร์กิวเมนต์ float นั่นคือฟังก์ชันทดสอบที่สอง
- เรียกใช้การทดสอบฟังก์ชันและส่งค่าห้าให้เป็นค่าของอาร์กิวเมนต์ ซึ่งจะเรียกใช้ฟังก์ชันทดสอบที่ยอมรับอาร์กิวเมนต์อักขระ ซึ่งก็คือฟังก์ชันทดสอบตัวที่สาม
- โปรแกรมจะต้องส่งคืนค่าหากทำงานได้สำเร็จ
- ส่วนท้ายของฟังก์ชัน main()
เรามีฟังก์ชันสามฟังก์ชันที่มีชื่อเดียวกันแต่อาร์กิวเมนต์ต่างกัน เราบรรลุถึงความหลากหลายแล้ว
Operaทอร์โอเวอร์โหลด
In Operator Overloading เราให้คำจำกัดความใหม่แก่ C++ ผู้ประกอบการนอกจากนี้ยังเปลี่ยนวิธีการทำงานของตัวดำเนินการด้วย ตัวอย่างเช่น เราสามารถกำหนดตัวดำเนินการ + เพื่อเชื่อมต่อสตริงสองสตริงเข้าด้วยกัน เรารู้จักมันในฐานะตัวดำเนินการบวกสำหรับการบวกค่าตัวเลข หลังจากที่เรากำหนดแล้ว เมื่อวางไว้ระหว่างจำนวนเต็ม มันจะบวกกัน เมื่อวางไว้ระหว่างสตริง มันจะเชื่อมต่อสตริงเหล่านั้นเข้าด้วยกัน
2 ตัวอย่าง
#include<iostream> using namespace std; class ComplexNum { private: int real, over; public: ComplexNum(int rl = 0, int ov = 0) { real = rl; over = ov; } ComplexNum operator + (ComplexNum const &obj) { ComplexNum result; result.real = real + obj.real; result.over = over + obj.over; return result; } void print() { cout << real << " + i" << over << endl; } }; int main() { ComplexNum c1(10, 2), c2(3, 7); ComplexNum c3 = c1+c2; c3.print(); }
Output:
นี่คือภาพหน้าจอของรหัส:
Code คำอธิบาย:
- รวมไฟล์ส่วนหัว iostream ลงในโปรแกรมของเราเพื่อใช้ฟังก์ชันต่างๆ
- รวมเนมสเปซมาตรฐานไว้ในโปรแกรมของเราเพื่อใช้คลาสโดยไม่ต้องเรียกมัน
- สร้างคลาสชื่อ ComplexNum เครื่องหมาย { จะเป็นจุดเริ่มต้นของตัวคลาส
- ใช้ตัวแก้ไขการเข้าถึงส่วนตัวเพื่อทำเครื่องหมายตัวแปรว่าเป็นส่วนตัว ซึ่งหมายความว่าสามารถเข้าถึงได้จากภายในชั้นเรียนเท่านั้น
- กำหนดตัวแปรจำนวนเต็มสองตัว จริงและมากกว่า
- ใช้ตัวแก้ไขการเข้าถึงแบบสาธารณะ (public access modifier) เพื่อกำหนดให้คอนสตรัคเตอร์เป็นแบบสาธารณะ ซึ่งหมายความว่าจะสามารถเข้าถึงได้แม้จากภายนอกคลาส
- สร้างตัวสร้างคลาสและการเริ่มต้นตัวแปร
- เริ่มต้นค่าของตัวแปรจริง
- เริ่มต้นค่าของตัวแปรทับ
- จุดสิ้นสุดของตัวสร้าง
- เราจำเป็นต้องแทนที่ความหมายของตัวดำเนินการ +
- สร้างผลลัพธ์ชนิดข้อมูลประเภท ComplexNum
- ใช้ตัวดำเนินการ + กับตัวเลขเชิงซ้อน บรรทัดนี้จะเพิ่มส่วนจริงของตัวเลขเข้ากับส่วนจริงของตัวเลขอื่น
- ใช้ตัวดำเนินการ + กับตัวเลขเชิงซ้อน บรรทัดนี้จะเพิ่มส่วนจินตภาพของตัวเลขเข้ากับส่วนจินตภาพของตัวเลขอื่น
- โปรแกรมจะคืนค่าของผลลัพธ์ของตัวแปรเมื่อดำเนินการสำเร็จ
- สิ้นสุดนิยามความหมายใหม่ของตัวดำเนินการ + นั่นคือการโอเวอร์โหลด
- เรียกเมธอด print()
- พิมพ์ตัวเลขเชิงซ้อนใหม่หลังจากการบวกบนคอนโซล
- ส่วนท้ายของฟังก์ชัน print()
- จุดสิ้นสุดของเนื้อหาคลาส ComplexNum
- เรียกใช้ฟังก์ชัน main()
- ส่งค่าทั้งส่วนจริงและส่วนเชิงซ้อนที่ต้องการบวก โดยส่วนแรกของ c1 จะถูกบวกกับส่วนแรกของ c2 นั่นคือ 10+3 ส่วนที่สองของ c1 จะถูกบวกกับส่วนที่สองของ c นั่นคือ 2+7
- ดำเนินการโดยใช้ตัวดำเนินการโอเวอร์โหลด + และเก็บผลลัพธ์ไว้ในตัวแปร c3
- พิมพ์ค่าของตัวแปร c3 บนคอนโซล
- ส่วนท้ายของฟังก์ชัน main()
ในขณะที่โพลีมอร์ฟิซึมในขั้นตอนการคอมไพล์จะตัดสินใจตั้งแต่เนิ่นๆ โพลีมอร์ฟิซึมประเภทที่สองจะเลื่อนการเลือกไปจนถึงขั้นตอนการทำงาน
ความแตกต่างของรันไทม์
สิ่งนี้เกิดขึ้นเมื่อเมธอดของอ็อบเจ็กต์ถูกเรียกใช้/เรียกใช้ระหว่างรันไทม์ แทนที่จะเป็นในช่วงเวลาคอมไพล์ ความหลากหลายแบบรันไทม์ทำได้โดยการเอาชนะฟังก์ชัน ฟังก์ชันที่จะเรียกใช้/เรียกใช้จะถูกสร้างขึ้นในระหว่างรันไทม์
การเอาชนะฟังก์ชัน
การแทนที่ฟังก์ชันเกิดขึ้นเมื่อฟังก์ชันของคลาสพื้นฐานได้รับคำจำกัดความใหม่ในคลาสที่ได้รับ ในเวลานั้น เราสามารถพูดได้ว่าฟังก์ชันพื้นฐานถูกแทนที่แล้ว
ตัวอย่างเช่น:
#include <iostream> using namespace std; class Mammal { public: void eat() { cout << "Mammals eat..."; } }; class Cow: public Mammal { public: void eat() { cout << "Cows eat grass..."; } }; int main(void) { Cow c = Cow(); c.eat(); return 0; }
Output:
นี่คือภาพหน้าจอของรหัส:
Code คำอธิบาย:
- นำเข้าไฟล์ส่วนหัว iostream ลงในโปรแกรมของเราเพื่อใช้ฟังก์ชันต่างๆ
- รวมเนมสเปซมาตรฐานไว้ในโปรแกรมของเราเพื่อใช้คลาสโดยไม่ต้องเรียกมัน
- สร้างคลาสชื่อ Mammal { ถือเป็นจุดเริ่มต้นของเนื้อหาคลาส
- ใช้ตัวแก้ไขการเข้าถึงสาธารณะเพื่อตั้งค่าฟังก์ชันที่เรากำลังจะสร้างให้เข้าถึงได้แบบสาธารณะ จะสามารถเข้าถึงได้จากภายนอกชั้นเรียนนี้
- สร้างฟังก์ชั่นสาธารณะชื่อกิน { เป็นจุดเริ่มต้นของเนื้อหาฟังก์ชัน
- พิมพ์คำสั่งที่เพิ่มเข้าไปในฟังก์ชัน cout เมื่อเรียกใช้ฟังก์ชัน eat()
- ส่วนท้ายของฟังก์ชัน eat()
- ปิดท้ายด้วยคลาส Mammal
- สร้างคลาสชื่อ Cow ที่สืบทอดคลาส Mammal Cow เป็นคลาสที่ได้รับ ในขณะที่ Mammal เป็นคลาสพื้นฐาน { ถือเป็นจุดเริ่มต้นของคลาสนี้
- ใช้ตัวแก้ไขการเข้าถึงสาธารณะเพื่อทำเครื่องหมายฟังก์ชันที่เรากำลังจะสร้างว่าเข้าถึงได้แบบสาธารณะ จะสามารถเข้าถึงได้จากภายนอกชั้นเรียนนี้
- แทนที่ฟังก์ชัน eat() ที่กำหนดไว้ในคลาสพื้นฐาน { เป็นจุดเริ่มต้นของเนื้อหาฟังก์ชัน
- คำสั่งที่จะพิมพ์บนคอนโซลเมื่อมีการเรียกใช้ฟังก์ชันนี้
- ส่วนท้ายของฟังก์ชัน eat()
- จบร่างของคลาสวัว
- เรียกใช้ฟังก์ชัน main() { ถือเป็นจุดเริ่มต้นของเนื้อหาของฟังก์ชันนี้
- สร้างอินสแตนซ์ของคลาส Cow และตั้งชื่อให้ว่า c
- เรียกใช้ฟังก์ชัน eat() ที่กำหนดไว้ในคลาส Cow
- โปรแกรมจะต้องส่งคืนค่าเมื่อดำเนินการเสร็จสิ้น
- จุดสิ้นสุดของฟังก์ชัน main()
C++ ฟังก์ชั่นเสมือน
ฟังก์ชันเสมือนเป็นอีกวิธีหนึ่งในการนำโพลีมอร์ฟิซึมแบบรันไทม์มาใช้ใน C++เป็นฟังก์ชันพิเศษที่กำหนดในคลาสฐานและกำหนดใหม่ในคลาสที่สืบทอดมา หากต้องการประกาศฟังก์ชันเสมือน คุณควรใช้คีย์เวิร์ดเสมือน คีย์เวิร์ดควรอยู่ก่อนการประกาศฟังก์ชันในคลาสฐาน
หากคลาสฟังก์ชันเสมือนได้รับการสืบทอดมา คลาสเสมือนจะกำหนดฟังก์ชันเสมือนใหม่เพื่อให้เหมาะกับความต้องการ ตัวอย่างเช่น:
#include <iostream> using namespace std; class ClassA { public: virtual void show() { cout << "The show() function in base class invoked..." << endl; } }; class ClassB :public ClassA { public: void show() { cout << "The show() function in derived class invoked..."; } }; int main() { ClassA* a; ClassB b; a = &b; a->show(); }
Output:
นี่คือภาพหน้าจอของรหัส:
Code คำอธิบาย:
- รวมไฟล์ส่วนหัว iostream ไว้ในโค้ดเพื่อใช้ฟังก์ชันต่างๆ
- รวมเนมสเปซมาตรฐานไว้ในโค้ดของเราเพื่อใช้คลาสโดยไม่ต้องเรียกมัน
- สร้างคลาสชื่อ ClassA
- ใช้ตัวแก้ไขการเข้าถึงสาธารณะเพื่อทำเครื่องหมายสมาชิกชั้นเรียนว่าเข้าถึงได้แบบสาธารณะ
- สร้างฟังก์ชันเสมือนชื่อ show() มันจะเป็นงานสาธารณะ
- ข้อความที่จะพิมพ์เมื่อเรียกใช้ show() endl คือ C++ คำสำคัญซึ่งหมายถึงบรรทัดสิ้นสุด มันเลื่อนเคอร์เซอร์ของเมาส์ไปที่บรรทัดถัดไป
- ส่วนท้ายของฟังก์ชันเสมือน show()
- ส่วนท้ายของคลาส ClassA
- การสร้างคลาสใหม่ชื่อ ClassB ที่สืบทอดคลาส ClassA ClassA กลายเป็นคลาสพื้นฐานในขณะที่ ClassB กลายเป็นคลาสที่ได้รับ
- ใช้ตัวแก้ไขการเข้าถึงสาธารณะเพื่อทำเครื่องหมายสมาชิกชั้นเรียนว่าเข้าถึงได้แบบสาธารณะ
- กำหนดฟังก์ชันเสมือน show() ที่ได้รับในคลาสพื้นฐานใหม่
- ข้อความที่จะพิมพ์บนคอนโซลเมื่อมีการเรียกใช้ฟังก์ชัน show() ที่กำหนดไว้ในคลาสที่ได้รับ
- ส่วนท้ายของฟังก์ชัน show()
- ส่วนท้ายของคลาสที่ได้รับ ClassB
- เรียกใช้ฟังก์ชัน main() ควรเพิ่มตรรกะของโปรแกรมภายในเนื้อหา
- สร้างตัวแปรพอยน์เตอร์ชื่อ a มันชี้ไปที่คลาสชื่อ ClassA
- สร้างอินสแตนซ์ของคลาสชื่อ ClassB อินสแตนซ์ได้รับชื่อ b
- กำหนดค่าที่จัดเก็บไว้ในที่อยู่ b ในตัวแปร a
- เรียกใช้ฟังก์ชัน show() ที่กำหนดไว้ในคลาสที่ได้รับ มีผลผูกพันล่าช้า
- ส่วนท้ายของฟังก์ชัน main()
เมื่อได้อธิบายกลไกทั้งสองแล้ว ตารางด้านล่างจะแสดงความแตกต่างระหว่างกลไกทั้งสองแบบ
ความแตกต่างระหว่างคอมไพล์กับเวลา ความแตกต่างระหว่างรันไทม์
นี่คือข้อแตกต่างที่สำคัญระหว่างทั้งสอง:
| ความหลากหลายของเวลาคอมไพล์ | ความหลากหลายแบบรันไทม์ |
|---|---|
| เรียกอีกอย่างว่า การจับตัวในช่วงต้น หรือ โพลีมอร์ฟิซึมแบบคงที่ | เรียกอีกอย่างว่า การจับตัวแบบล่าช้า/แบบไดนามิก หรือ โพลีมอร์ฟิซึมแบบไดนามิก |
| เมธอดนี้ถูกเรียก/เรียกใช้ระหว่างเวลาคอมไพล์ | วิธีการนี้ถูกเรียก/เรียกใช้ในระหว่างรันไทม์ |
| ใช้งานผ่านการโอเวอร์โหลดฟังก์ชันและการโอเวอร์โหลดตัวดำเนินการ | ดำเนินการผ่านวิธีการแทนที่และฟังก์ชันเสมือน |
| ตัวอย่าง วิธีการโอเวอร์โหลด หลายวิธีอาจมีชื่อคล้ายกันแต่มีจำนวนหรือประเภทอาร์กิวเมนต์ต่างกัน | ตัวอย่าง การเอาชนะวิธีการ หลายวิธีอาจมีชื่อคล้ายกันและมีต้นแบบเดียวกัน |
| การประมวลผลเร็วขึ้นเนื่องจากการค้นหาวิธีการเกิดขึ้นในระหว่างการคอมไพล์ | การทำงานช้าลงเนื่องจากการค้นหาวิธีการเกิดขึ้นในระหว่างการทำงาน |
| Less มีความยืดหยุ่นในการแก้ไขปัญหาเนื่องจากทุกอย่างเป็นที่ทราบในระหว่างเวลาคอมไพล์ | มีความยืดหยุ่นมากในการแก้ไขปัญหาที่ซับซ้อนเนื่องจากมีการค้นพบวิธีการระหว่างรันไทม์ |







