Virtual function
        
            - First we will go through a problem scenario then we will see how to overcome it.
 
        
            - When show() function is called. Now question is what will be the outcome???
- This program prints "we are in base class yeah", although the pointer was storing the address of derived
                class. 
- So this was the problem, now this problem can be resolved using "virtual function".
- While covering this problem there are many topics that are also involved in this like- late binding, we
                will also cover them.
            Virtual function
            
                - A virtual function is a member function in a base class that can be overridden by a derived class to
                    provide a different implementation. It allows the derived class to define its own version of the
                    function, providing flexibility and extensibility in the object-oriented hierarchy.
- The virtual keyword is used to declare a function as virtual in the base class.
- Virtual functions enable dynamic polymorphism, which means that a function call is resolved
                    dynamically at runtime based on the actual object type rather than the declared type. This allows
                    objects of different derived classes to be treated uniformly through a base class pointer or
                    reference, simplifying code and improving flexibility.
- When a virtual function is called through a base class pointer or reference, the function that is
                    invoked is determined at runtime based on the type of the actual object being pointed to or
                    referenced. This is known as dynamic or late binding.
- Virtual functions are typically used in scenarios where different derived classes need to provide
                    their own implementation of a specific behavior defined in the base class. The base class provides a
                    common interface through virtual functions, while the derived classes override these functions to
                    customize their behavior.
- It is important to note that virtual functions should be declared in the base class and overridden
                    in the derived classes using the override keyword to ensure proper overriding and to indicate the
                    intention of overriding the base class function.
Let's resolve the problem
            
                
                
                   
#include <iostream>
using namespace std;
class base_class
{
public:
    virtual void show()
    {
        cout << "we are in base class yeah";
    }
};
class derived_class : public base_class
{
public:
    void show()
    {
        cout << "we are in derived class yeah";
    }
};
int main()
{
    base_class *basePtr;
    derived_class objOfDerivedC;
    basePtr = &objOfDerivedC;
    basePtr->show();
    return 0;
}
                   
               
             
            Output ↓
            
                
we are in derived class yeah
                
            
            
                - Just including 'virtual' in definition of show() function of base class, the problem is resolved.
                
 
        
            Early binding & late binding
            
                Early binding (static binding)
                
                    - Early binding occurs when the function call is resolved at compile-time.
- The compiler determines which function to call based on the static type of the object. It is
                        also known as static binding because the binding between the function call and the function
                        implementation is done before the program is executed.
Program to demonstrate early binding
                
                    
                    
                   
#include <iostream>
using namespace std;
class Shape
{
public:
    void draw()
    {
        cout << "Drawing Shape." << endl;
    }
};
class Circle : public Shape
{
public:
    void draw()
    {
        cout << "Drawing Circle." << endl;
    }
};
int main()
{
    Shape shape;
    Circle circle;
    Shape *shapePtr = &shape;
    shapePtr->draw(); // Calls Shape's draw() function (early binding)
    shapePtr = &circle;
    shapePtr->draw(); // Calls Shape's draw() function (early binding)
    return 0;
}
                   
               
                 
                
                    - Just because we are not using virutal function this becomes example of early binding.
 
            
                Late binding (Dynamic binding)
                
                    - Late binding occurs when the function call is resolved at runtime. The compiler defers the
                        binding of the function call to the actual object until runtime. It is also known as dynamic
                        binding or virtual binding.
                    
                    
                       
#include <iostream>
using namespace std;
class Shape
{
public:
    virtual void draw()
    {
        cout << "Drawing Shape." << endl;
    }
};
class Circle : public Shape
{
public:
    void draw()
    {
        cout << "Drawing Circle." << endl;
    }
};
int main()
{
    Shape *shapePtr;
    Shape shape;
    Circle circle;
    shapePtr = &shape;
    shapePtr->draw(); // Calls Shape's draw() function (late binding)
    shapePtr = &circle;
    shapePtr->draw(); // Calls Circle's draw() function (late binding)
    return 0;
}
                       
                   
                 
             
         
        
            Pure virtual functions
            
            Example ↓
            
                
                
                   
#include <iostream>
using namespace std;
class Shape
{
public:
    virtual void draw() = 0; // Pure virtual function
};
class Circle : public Shape
{
public:
    void draw() override // overide is keyword which ensure we are overriding function in derived class from base class.
    {
        cout << "Drawing Circle." << endl;
    }
};
class Rectangle : public Shape
{
public:
    void draw() override
    {
        cout << "Drawing Rectangle." << endl;
    }
};
int main()
{
    Circle circle;
    Rectangle rectangle;
    circle.draw();    // Calls Circle's draw() function
    rectangle.draw(); // Calls Rectangle's draw() function
    return 0;
}
                   
               
             
         
        
            Abstract classes
            
                - An abstract class is a class that contains at least one pure virtual function.
- Abstract classes cannot be instantiated directly; they serve as base classes for derived classes.
                
- Derived classes must provide implementations for all pure virtual functions in the abstract base
                    class.
Example program ↓
            
                
                
                   
#include <iostream>
using namespace std;
class Shape // abstract class
{
public:
    virtual void draw() = 0; // Pure virtual function
};
class Circle : public Shape
{
public:
    void draw() override
    {
        cout << "Drawing Circle." << endl;
    }
};
class Rectangle : public Shape
{
public:
    void draw() override
    {
        cout << "Drawing Rectangle." << endl;
    }
};
int main()
{
    Circle circle;
    Rectangle rectangle;
    circle.draw();
    rectangle.draw();
    return 0;
}
                   
               
             
         
        
            How Runtime polymorphism and Compile-time polymorphism is related to early and late binding??
            
                Runtime polymorphism
                
                    - Runtime polymorphism, also known as dynamic polymorphism, is achieved through the use of virtual
                        functions.
- It allows the selection of the appropriate function implementation at runtime based on the
                        actual object type. This is possible because virtual functions are resolved dynamically (late
                        binding).
- The binding of the function call to its implementation occurs at runtime, depending on the type
                        of the object being referenced or pointed to.
 
            
                Compile-time Polymorphism
                
                    - Compile-time polymorphism, also known as static polymorphism, is achieved through function
                        overloading and templates. In function overloading, multiple functions with the same name but
                        different parameter lists can be defined. The appropriate function is selected based on the
                        static types of the arguments at compile time.
 
            
                - So, late binding is associated with runtime polymorphism, where the appropriate function
                    implementation is determined at runtime based on the actual object type. Early binding is associated
                    with compile-time polymorphism, where the function call is resolved at compile time based on the
                    static types of the objects involved.