الرجوع للقائمة الرئيسية (Back to Home)

Lecture 8: Run-time Polymorphism

المحاضرة الثامنة: تعدد الأشكال والدوال الافتراضية (Polymorphism & Virtual Functions)

الشرح التفصيلي لجميع شرائح المحاضرة (Slide-by-Slide) مدعوم بشرح الأكواد سطراً بسطر (Line-by-Line Code Explanation)

Slide 3: تذكير بمفهوم الـ Polymorphism (تعدد الأشكال)

Slide 3

كلمة Polymorphism معناها "القدرة على اتخاذ أشكال متعددة". في البرمجة، معناها إن نفس الدالة (نفس الاسم) ممكن تشتغل بطرق مختلفة تماماً حسب الـ Object اللي بيناديها.

تعدد الأشكال بينقسم لنوعين رئيسيين:

  • Compile-Time Polymorphism (المبكر/الثابت): وده بيحصل وقت الترجمة. زي الـ (Function Overloading) والـ (Operator Overloading).
  • Run-Time Polymorphism (المتأخر/الديناميكي): وده موضوع محاضرتنا النهاردة! بيحصل وقت تشغيل البرنامج، وبيعتمد على الوراثة (Inheritance) والبوينترز (Pointers) والدوال الافتراضية (Virtual Functions).

Slide 5: مؤشر الأب لكائن الابن (Base Pointer to Derived Object)

Slide 5

عشان نفهم الـ Run-Time Polymorphism، لازم نفهم قاعدة ذهبية وغريبة شوية في الـ C++:

القاعدة: ينفع جداً تعمل Pointer من نوع الكلاس الأب (Base)، وتخليه يشاور على Object من نوع الكلاس الابن (Derived)!

ليه؟ لأننا قلنا قبل كده إن الابن Is-A الأب (السيارة هي مركبة). فمنطقي إن بوينتر المركبة يقدر يشاور على سيارة.

Animal *ptr; // Pointer of Base class Dog d; // Object of Derived class ptr = &d; // VALID! Base pointer holding Derived address

Slide 6: مشكلة الربط المبكر (The Problem: Early Binding)

Slide 6

طيب إيه المشكلة في إن بوينتر الأب يشاور على الابن؟ المشكلة بتظهر لما الاتنين (الأب والابن) يكون عندهم دالة بنفس الاسم (Overriding)، مثلاً دالة speak().

لما تيجي تقول للبوينتر ptr->speak()، الكومبايلر بيتصرف بغباء شوية:

الكومبايلر بيبص على نوع البوينتر (اللي هو Animal) ومش بيبص على محتوى البوينتر الحقيقي (اللي هو Dog). فبيروح ينفذ دالة الأب!

المشكلة دي اسمها Early Binding (الربط المبكر) أو Static Binding، لأن الكومبايلر بيربط الدالة بناءً على نوع المتغير وقت الترجمة (Compile time).

Slide 9: الحل السحري - الدوال الافتراضية (Virtual Functions)

Slide 9

عشان نكبر الكومبايلر إنه يبص على الكائن الحقيقي اللي البوينتر بيشاور عليه وقت التشغيل (عشان ينفذ دالة Dog مش Animal)، بنستخدم الكلمة السحرية virtual.

مجرد ما نكتب كلمة virtual قبل اسم الدالة في الكلاس الأب، الكومبايلر بيفهم إنه لازم يأجل قراره لحد وقت التشغيل (Run-time). ده اللي بنسميه Late Binding أو Dynamic Binding.

Slide 11: كود الدوال الافتراضية (Virtual Function Syntax)

Slide 11

تعالوا نشوف إزاي كلمة واحدة بتغير مسار البرنامج بالكامل:

class Base { public: // Adding 'virtual' tells compiler to use Late Binding virtual void show() { cout << "Show from Base" << endl; } }; class Derived : public Base { public: // Overridden function void show() { cout << "Show from Derived" << endl; } }; int main() { Base *ptr; // Base pointer Derived obj; // Derived object ptr = &obj; // Pointing to derived ptr->show(); // Without virtual -> prints "Base" (Early Binding) // WITH virtual -> prints "Derived" (Late Binding / True Polymorphism) }

Quiz 1: Virtual Functions & Binding

1. If a base class pointer points to a derived class object, and calls a NON-virtual overridden function, whose function is actually executed?

2. What is another term for Run-time Polymorphism achieved through virtual functions?

Slide 16: الدوال الافتراضية البحتة (Pure Virtual Functions)

Slide 16

أحياناً الكلاس الأب بيكون مجرد فكرة عامة، ومينفعش نكتب جواه implementation للدالة أصلاً. (مثلاً: كلاس Shape وفيه دالة draw(). هترسم إيه وإنت لسه متعرفش هو مربع ولا دايرة؟)

هنا بنستخدم Pure Virtual Function (دالة افتراضية بحتة / نقية). دي دالة إحنا بنجبر الكومبايلر إنه ميكونش ليها أي جسم (No Body) في الكلاس الأب.

// Syntax of Pure Virtual Function (Notice the '= 0') virtual void draw() = 0;

علامة = 0 مش معناها إن قيمتها صفر، دي مجرد علامة (Syntax) بتفهم الكومبايلر إن "الدالة دي فاضية، والأبناء هم اللي لازم وحتماً يكتبوا الكود بتاعها".

Slide 18: الكلاس المجرد (Abstract Classes)

Slide 18

أي كلاس يحتوي على دالة واحدة على الأقل من نوع Pure Virtual Function بيتحول أوتوماتيك لشيء اسمه Abstract Class (كلاس مجرد).

خصائص الكلاس المجرد (أسئلة امتحانات):

  1. ممنوع الاستنساخ: مستحيل (Error) إنك تكريت Object مباشر من الـ Abstract Class (مينفعش تقول Shape s;). لأنه كلاس غير مكتمل وفيه دوال فاضية!
  2. الإجبار للأبناء: أي كلاس يورث من الـ Abstract Class لازم (Must) يعمل Overriding لكل الـ Pure Virtual Functions ويكتب الكود بتاعها، وإلا الابن نفسه هيتحول لـ Abstract Class هو كمان!
  3. مسموح بالبوينترز: بالرغم إنك متقدرش تعمل Object، إنت تقدر جداً تعمل Pointer من الـ Abstract Class عشان يشاور على الأبناء (وده أساس تعدد الأشكال!).

Slide 20: كود الكلاس المجرد (Abstract Class Example)

Slide 20

تطبيق عملي على الكلاس المجرد والدوال البحتة:

class Shape { // This is an Abstract Class public: // Pure Virtual Function virtual void draw() = 0; }; class Circle : public Shape { public: // Must implement draw() void draw() { cout << "Drawing a Circle" << endl; } }; class Rectangle : public Shape { public: void draw() { cout << "Drawing a Rectangle" << endl; } }; int main() { // Shape s; // ❌ ERROR: Cannot instantiate Abstract Class Shape *arr[2]; // ✅ VALID: Array of Base Pointers arr[0] = new Circle(); arr[1] = new Rectangle(); // Magic of Polymorphism: same function call does different things! arr[0]->draw(); // Prints: Drawing a Circle arr[1]->draw(); // Prints: Drawing a Rectangle }

Final Quiz: Abstract Classes

1. Which of the following defines an Abstract Class in C++?

2. What happens if a Derived class inherits from an Abstract Base class but fails to implement (override) one of the pure virtual functions?

الملخص الشامل لأهم التريكات (Summary & Pro Tips)

سؤال مقالي متوقع للامتحان (Essay Question)

Question: Differentiate between Early Binding and Late Binding in C++. Explain the role of the `virtual` keyword, Pure Virtual Functions, and Abstract Classes. Provide a code example showing polymorphic behavior.

🖥️ What is the output of the following code?

Q1:

class Base { public: virtual void show() { cout << "Base "; } }; class Derived : public Base { public: void show() { cout << "Derived "; } }; int main() { Base *ptr = new Derived(); ptr->show(); }

Q2:

class Animal { public: void sound() { cout << "Animal "; } }; class Cat : public Animal { public: void sound() { cout << "Meow "; } }; int main() { Animal *ptr = new Cat(); ptr->sound(); }

✍️ Write a line of code for the following statements

Q1: Declare a Pure Virtual Function called calculateArea that returns a float in a class Shape.

Q2: Create an array of 3 pointers to the base class Shape.

Q3: Given Shape *arr[3] and derived classes Circle and Square, assign a new Circle object to the first element and a Square object to the second element.

🔍 Based on the following code, answer all the following questions

class Vehicle { public: virtual void move() = 0; }; class Car : public Vehicle { public: void move() { cout << "Driving on road" << endl; } }; int main() { Vehicle v; Vehicle *ptr = new Car(); ptr->move(); }

a) What is the error in this code?

b) If we remove the error line, what is the output?

c) What is the benefit of having Vehicle *ptr point to new Car()?