Q.2:- Define class and objects.
Explain with example the method
     of defining a class and constructor in C++.
                                  CLASS
     A class in C++ is a fundamental OOPs concept that enables you to
create user-defined data types. It acts as a blueprint or template to specify how
objects belonging to the class should be constructed and behave.
    ➢      A class encapsulates data members (attributes or properties) and
           member functions (methods or operations) that manipulate the data.
    ➢      In other words, a class in C++ acts like a template for building objects,
           where objects are instances.
    ➢      Each object has a unique set of data members and can be
           manipulated with the help of the class functions (i.e., class methods/
           member functions).
                                    OBJECT
      An object in C++ is a self-contained element that contains data and behavior.
It is an instance of a class that combines both data members and the class
methods/ functions that operate on that data into a single unit.
   ➢     The class functions, often referred to as methods, will allow the
         object to perform actions and manipulate its data.
   ➢     This bundling of data and methods within an object and class in C++
         programming supports code reuse and simpler maintenance,
         especially in large software systems.
   SYNTAX :-
  • Class_name: It is the name of the class for which the object is created
  • Object_name: It is the name of the object. Note that having a
    meaningful object name enhances code readability
                          ClassName _ObjectName;
Example of Class and Object in C++ :-
                     #include <iostream>
                     #include <string>
                     using namespace std;
                     class Person {
                     public:
                     string name;
                     int age;
                     void introduce()
                      {
                     cout << "Hi, my name is " << name << " and I am "
                            << age << " years old." << endl;
                      }
                     };
                     int main()
                     {
                     Person person1;
                     person1.name = "Vaibhav";
                     person1.age = 21;
                     person1.introduce();
                        return 0;
                     }
Output :-
Hi, my name is Vaibhav and I am 21 years old.
Q.3 Differentiate between :
  A. Single and Multiple Inheritance with suitable example
Single Inheritance :-
      Single inheritance is one in which the derived class inherits the single base
class either public, private, or protected access specifier. In single inheritance, the
derived class uses the features or members of the single base class. These base
class members can be accessed by a derived class or child class according to the
access specifier specified while inheriting the parent class or base class.
Syntax :-
Class DerivedClass_name : access_specifier Base_Class
{
   //Class's Body
};
Example :-
                             #include <iostream>
                             using namespace std;
                             class A {
                             public:
                             int k = 1000;
                             float salary = 80000;
                             };
                             class B : public A {
                             public:
                             float bonus = 8000;
                             void ts()
                             {
                            cout << "Total salary.." << (salary + bonus)
                            << endl;
                            }
                            };
                            int main()
                            {
                            B b1;
                            cout << "Salary:" << b1.salary << endl;
                            cout << "Bonus:" << b1.bonus << endl;
                            b1.ts();
                            return 0;
                            }
Output :-
Salary:80000
Bonus:8000
Total salary..88000
Multiple Inheritance :-
      Multiple inheritance is one in which the derived class acquires two or
more base classes. In multiple inheritance, the derived class is allowed to use
the joint features of the inherited base classes. Every base class is inherited by
the derived class by notifying the separate access specifier for each of them.
      The base class members can be accessed by the derived class or child
class according to the access specifier specified during inheriting the parent
class or base class.
Syntax :-
Class DerivedClass_name : access_specifier Base_Class1, access_specifier
Base_Class2
{
     //Class's Body
};
Example :-
             #include <iostream>
             using namespace std;
             class A {
             protected:
             int a;
             public:
             void get_a(int n) { a = n; }
             };
             class B {
             protected:
             int b;
             public:
             void get_b(int n) { b = n; }
             };
             class C : public A, public B {
             public:
             void display()
             {
             cout << "The value of a is : " << a << endl;
             cout << "The value of b is : " << b << endl;
             cout << "Product of a and b is : " << a * b;
             }
             };                                   Output :-
             int main()                           The value of a is :
             {                                    10
             C c;
             c.get_a(10);c.get_b(20);             The value of b is :
             c.display();                         20
             return 0;                            Product of a and b
             }                                    is : 200
  B.     Nested If-else and switch statement.
                            Nested if-else statement
     Nested if-else statements are those statements in which there is an if
statement inside another if else. We use nested if-else statements when we want
to implement multilayer conditions(condition inside the condition inside the
condition and so on). C++ allows any number of nesting levels.
       Example of nested if-else :-
                      #include<iostream>
                      using namespace std;
                      int main()
                      {
                         int a = 10;
                         int b = 2;
                         int c = 6;
                      if (a < b) {
                            if (c < b) {
                               printf("%d is the greatest", b);
                            }
                            else {
                               printf("%d is the greatest", c);
                            }
                         }
                         else {
                            if (c < a) {
                               printf("%d is the greatest", a);
                            }
                            else {
                               printf("%d is the greatest", c);
                            }
                         }
                         return 0;
                      }
Output :-
10 is the greatest
                                  Switch statement
      In C++, the switch case statement provides a convenient way to execute
different blocks of code based on the value of a single expression. It is similar to
the if-else-if ladder, where we have multiple choices, and a block of statements
is executed based on which choice we make. It's particularly useful when dealing
with multiple possible values for a variable and simplifies the code compared to
using multiple nested if-else in C++.
     Example of Switch :-
                 #include <iostream>
                 using namespace std;
                 int main()
                 {
                   char x = 'A';
                   switch (x) {
                   case 'A':
                      cout << "Choice is A";
                      break;
                   case 'B':
                      cout << "Choice is B";
                      break;
                   case 'C':
                      cout << "Choice is C";
                      break;
                   default:
                      cout << "Choice other than A, B and C";
                      break;
                   }
                   return 0;
                 }
Output :-
Choice is A
  C. Overloading and overriding.
                                  Overloading
      The ability to declare numerous functions with the same name but
different parameters (types of parameters) is known as function overloading in
C++. In other words, the function name is the same, but the function definition
differs.
         • Meaning, when a function is overloaded with many different jobs but
           has the same name for all, it is called function overloaded in C++.
         • This is a feature of object-oriented programming and is built on the
           idea of polymorphism. It allows using a single function name to
           denote many behaviors depending on the parameters given.
                                      Overriding
      Function overriding is a feature that allows a derived class to provide a new
or custom implementation for a function that has already been defined in its base
class. This is important for building hierarchical relationships between classes,
where derived classes inherit characteristics and behaviour from their base
classes but can also customize or extend those behaviours as needed.
   • When a derived class (child class) overrides a function from its base class
      (parent class), it provides a new definition for that function with the same
      function name and signature, i.e., the function prototype.
   • This allows instances of the derived class to respond to method calls with
      their unique behaviour, even when treated as instances of the base class.
Q.4 :- Write a program in C++ which demonstrates the              use
    of inheritance.
              #include <iostream>
              using namespace std;
              class Animal {
              public:
                 Animal(const string& name) : name(name) {}
                 virtual void sound() const {
                    cout << name << " makes a sound." << endl;
                 }
                 string getName() const {
                    return name;
                 }
              private:
                 string name;
              };
              class Dog : public Animal {
              public:
                 Dog(const string& name) : Animal(name) {}
                 void sound() const override {
                    cout << getName() << " says Woof!" << endl;
                 }
              };
              class Cat : public Animal {
              public:
                 Cat(const string& name) : Animal(name) {}
                 void sound() const override {
                    cout << getName() << " says Meow!" << endl;
                 }
              };
              int main() {
                 Animal* animal1 = new Dog("Buddy");
                 Animal* animal2 = new Cat("Whiskers");
                 animal1->sound();
                 animal2->sound();
                 delete animal1;        OUTPUT :-
                 delete animal2;        Buddy says Woof!
                 return 0; }            Whiskers says Meow!
Q.5 :- Explain the various access specifier in C++ and their visibility
     in the class with example.
C++ has three access specifiers that define the visibility of class members:
Public
      Members are accessible from anywhere in the program, including outside
the class and its subclasses. Public members are useful for designing interfaces
that can be used by other classes or code sections.
Private
      Members are only accessible within the class they are declared in. Private
members are not inherited in classes, and they cannot be accessed by derived
classes or other parts of the program.
Protected
      Members are not accessible from outside the class, but they can be
accessed in inherited classes. Protected members are kept hidden from the rest
of the program, but they can be accessed by any subclass of that class.
     Access specifiers are keywords that ensure encapsulation and data hiding,
which are important principles of object-oriented programming.
EXAMPLE :-
           #include <iostream>
           using namespace std;
           class Base {
           public:
              int publicVar;
           protected:
              int protectedVar;
           private:
              int privateVar;
           public:
              void setPrivateVar(int value) {
                privateVar = value;
              }
              void showPrivateVar() {
                cout << "Private Variable: " << privateVar << endl;
              }
           };
        class Derived : public Base {
        public:
           void accessBaseMembers() {
             publicVar = 10;
             protectedVar = 20;
                  cout << "Public Variable: " << publicVar << endl;
             cout << "Protected Variable: " << protectedVar << endl;
           }
        };
        int main() {
           Base baseObj;
           baseObj.publicVar = 5;
           cout << "Public Variable (Base): " << baseObj.publicVar << endl;
           baseObj.setPrivateVar(25);
           baseObj.showPrivateVar();
           Derived derivedObj;
           derivedObj.accessBaseMembers();
              return 0;
        }
OUTPUT :-
             Public Variable (Base): 5
             Private Variable: 25
             Public Variable: 10
             Protected Variable: 20
Q.6 :- Write a program in C++ to Store Information of a Student in
     a Structure.
             #include <iostream>
             using namespace std;
             struct Student {
                string name;
                int rollNumber;
                float marks;
             };
             int main() {
                Student student;
                cout << "Enter student's name: ";
                getline(cin, student.name);
                cout << "Enter roll number: ";
                cin >> student.rollNumber;
                cout << "Enter marks: ";
                cin >> student.marks;
                cout << "\nStudent Information:" << endl;
                cout << "Name: " << Vaibhav<< endl;
                cout << "Roll Number: " << 1476133 << endl;
                cout << "Marks: " << 79.5<< endl;
                 return 0;
             }
OUTPUT :-
             Student Information:
             Name: Vaibhav
             Roll Number: 1476133
             Marks: 79.5
Q.7 :- Explain Message passing and Dynamic Binding.
                            Message Passing
     Objects communicate with one another by sending and receiving infor-
mation. A message for an object is a request for the execution of a procedure
and therefore will invoke a function in the receiving object that generates the
desired results. Message passing involves specifying the name of the object, the
name of the function, and the information to be sent.
     Example :-
           #include <iostream>
           using namespace std;
           class Car {
           public:
              void displaySpeed(int speed) {
                cout << "The car is moving at " << speed << " km/h." << endl;
              }
           };
           int main() {
              Car myCar;
           int currentSpeed = 100;
              myCar.displaySpeed(currentSpeed);
              return 0;
           }
OUTPUT :- The car is moving at 100 km/h.
                            Dynamic Binding
       In dynamic binding, the code to be executed in response to the function
call is decided at runtime. C++ has virtual functions to support this. Because dy-
namic binding is flexible, it avoids the drawbacks of static binding, which con-
nected the function call and definition at build time.
Example :-
                  #include <iostream>
                  using namespace std;
                  class GFG {
                  public:
                     void call_Function()
                     {
                       print();
              }
              void print()
              {
                cout << "Printing the Base class Content" << endl;
              }
            };
            class GFG2 : public GFG
            {
            public:
               void print()
               {
                 cout << "Printing the Derived class Content" << endl;
               }
            };
            int main()
            {
               GFG* geeksforgeeks = new GFG();
               geeksforgeeks->call_Function();
               GFG* geeksforgeeks2 = new GFG2();
               geeksforgeeks2->call_Function();
               delete geeksforgeeks;
               delete geeksforgeeks2;
               return 0;
            }
OUTPUT :-   Printing the Base class Content
            Printing the Base class Content
Q.8 :- Write a short note :
     (a) File streams (b) C++ garbage collection
     (c) Runtime polymorphism (d)Dynamic Memory Allocation
          (e) Data hiding (f)Inline functions & Template function
a) File Streams
      In C++, file streams are used for input and output operations with files. The
fstream library provides classes such as ifstream (input file stream), ofstream
(output file stream), and fstream (for both input and output). These classes allow
programs to read from and write to files efficiently.
b) C++ Garbage Collection
      C++ does not have automatic garbage collection like some modern
languages. Memory allocated using new must be manually deallocated using
delete. Failure to do so can lead to memory leaks. Smart pointers (std::unique_ptr,
std::shared_ptr) introduced in C++11 help manage memory automatically by
releasing unused memory when objects go out of scope.
c) Runtime Polymorphism
      Runtime polymorphism in C++ is achieved using inheritance and virtual
functions. It allows objects of derived classes to be treated as objects of a base
class, with the correct derived class method invoked at runtime. This is enabled
through the virtual table (vtable) mechanism.
d) Dynamic Memory Allocation
       Dynamic memory allocation allows programs to allocate memory during
runtime using new for single objects or arrays and delete to free the memory.
Functions like malloc() and free() from C are also available but lack type safety.
Efficient memory management is critical to avoid leaks and crashes.
e) Data Hiding
      Data hiding is an OOP concept in C++ where access to class members is
restricted using access specifiers like private and protected. It ensures that
internal details of a class are hidden from outside code, promoting encapsulation
and protecting the integrity of the data.
f) Inline Functions & Template Functions
Inline Functions: These are small functions defined using the inline keyword. The
      compiler attempts to replace the function call with the actual code to
      reduce function call overhead, though this is not guaranteed.
Template Functions: These enable generic programming by allowing a single
    function definition to work with different data types. Template functions use
    the template keyword and are resolved at compile time.