0% found this document useful (0 votes)
3 views26 pages

Assignment - 6

The document explains operator overloading in C++, defining it as a way to redefine operators for user-defined data types, along with rules governing its implementation. It also covers the differences between unary and binary operator overloading, the concept of inheritance and its advantages in OOP, the role of virtual functions in supporting runtime polymorphism, and the usage of new and delete operators for dynamic memory management. Additionally, it introduces class and function templates as a means to create generic programs that can work with various data types.

Uploaded by

24bit136
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views26 pages

Assignment - 6

The document explains operator overloading in C++, defining it as a way to redefine operators for user-defined data types, along with rules governing its implementation. It also covers the differences between unary and binary operator overloading, the concept of inheritance and its advantages in OOP, the role of virtual functions in supporting runtime polymorphism, and the usage of new and delete operators for dynamic memory management. Additionally, it introduces class and function templates as a means to create generic programs that can work with various data types.

Uploaded by

24bit136
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 26

ASSIGNMENT – 6

Q1. Define operator overloading. What are the rules for operator
overloading in C++?
Operator Overloading: Operator overloading is a type of polymorphism in C++ that allows you
to redefine the way operators work for user-defined data types (classes). It enables operators to
have special meanings when applied to objects of a class, making code more intuitive and
readable by allowing operations to be performed on objects using familiar operator syntax.

Rules for Operator Overloading:

1.​ Only Existing Operators Can Be Overloaded: You can only overload existing operators in
C++. You cannot create new operators.
2.​ Precedence and Associativity Cannot Change: The precedence and associativity of an
operator cannot be changed through overloading.
3.​ Arity Cannot Change: The number of operands an operator takes (its arity) cannot be
changed. For example, a binary operator (like +) cannot be overloaded to work with
three operands.
4.​ Cannot Overload Certain Operators: Some operators cannot be overloaded:
○​ . ​ (dot operator)
○​ .* ​ ​ (pointer-to-member operator)
○​ :: ​ ​ (scope resolution operator)
○​ ?: ​ ​ (ternary conditional operator)
○​ sizeof ​ ​ (sizeof operator)
○​ typeid ​ ​ (RTTI operator)
5.​ Member Function vs. Non-Member (Friend) Function:
○​ Binary operators (except (), [], ->, =, and conversion operators) can be
overloaded either as a member function or a non-member (friend) function.
○​ Unary operators can be overloaded as member functions (taking no arguments)
or non-member (friend) functions (taking one argument).
○​ Operators (), [], ->, and = must be overloaded as member functions.
○​ Conversion operators must be overloaded as member functions.
○​ The << and >> operators (for I/O) are typically overloaded as non-member friend
functions because their left-hand operand is usually an ostream or istream object,
not an object of the user-defined class.
6.​ At Least One Operand Must Be User-Defined: When overloading an operator, at least
one of its operands must be an object of a user-defined class (or a reference to it). You
cannot overload operators for built-in data types alone (e.g., you cannot change how int
+ int works).
7.​ No Default Arguments: Overloaded operators cannot have default arguments.
8.​ Inheritance: Overloaded operators are not automatically inherited by derived classes.
Each derived class must explicitly overload operators if needed.
Q2. What is the difference between overloading unary and binary
operators in C++?
The difference between overloading unary and binary operators lies in their arity (number of
operands) and how they are typically implemented as member functions versus non-member
(friend) functions.

Unary Operator Overloading:

●​ Arity: Unary operators operate on a single operand. Examples include ++ (increment), --


(decrement), - (unary minus), ! (logical NOT), * (dereference), & (address-of).
●​ Implementation as Member Function: When overloaded as a member function, a unary
operator takes no explicit arguments. The operand on which the operator operates is the
object itself (implicitly this).
○​ Syntax (as member): ReturnType operatorOp();
○​ Example: Complex operator-(); (for unary minus)
●​ Implementation as Non-Member (Friend) Function: When overloaded as a non-member
(friend) function, a unary operator takes one explicit argument, which is the operand.
○​ Syntax (as non-member): ReturnType operatorOp(const ClassType& obj);
○​ Example: friend Complex operator-(const Complex& obj);
●​ Postfix ++ and -- (Special Case): For postfix increment/decrement, a dummy int
argument is used in the signature to differentiate it from the prefix version.
○​ Syntax (postfix member): ReturnType operatorOp(int);
○​ Example: Complex operator++(int);

Binary Operator Overloading:

●​ Arity: Binary operators operate on two operands. Examples include + (addition), -


(subtraction), * (multiplication), / (division), % (modulo), == (equality), << (insertion), >>
(extraction), etc.
●​ Implementation as Member Function: When overloaded as a member function, a binary
operator takes one explicit argument (the right-hand operand). The left-hand operand is
the object itself (implicitly this).
○​ Syntax (as member): ReturnType operatorOp(const ClassType& rightOperand);
○​ Example: Complex operator+(const Complex& other);
●​ Implementation as Non-Member (Friend) Function: When overloaded as a non-member
(friend) function, a binary operator takes two explicit arguments (the left-hand and
right-hand operands).
○​ Syntax (as non-member): ReturnType operatorOp(const ClassType1&
leftOperand, const ClassType2& rightOperand);
○​ Example: friend Complex operator+(const Complex& c1, const Complex& c2);
●​ Common Use Case: For << and >> (stream operators), they must be overloaded as
non-member (friend) functions because the left operand is typically an ostream or
istream object, which is not an instance of your class.
Q3. What is inheritance? Explain its advantages in OOP.
Inheritance: Inheritance is one of the fundamental concepts of Object-Oriented Programming
(OOP) where a new class (called a derived class or subclass) is created from an existing class
(called a base class or superclass). The derived class inherits attributes (data members) and
behaviors (member functions) from the base class. This mechanism promotes code reusability.

Advantages of Inheritance in OOP:

1.​ Code Reusability: This is the primary advantage. Instead of writing the same code
(attributes and methods) for multiple classes, you can define common functionalities in a
base class and reuse them in derived classes. This saves development time and effort.
2.​ Reduced Code Duplication: By reusing code, the amount of redundant code in a
system is significantly reduced. This makes the codebase smaller, cleaner, and easier to
manage.
3.​ Improved Maintainability: When common logic is centralized in a base class, changes
or bug fixes need to be made in only one place (the base class). These changes
automatically propagate to all derived classes, simplifying maintenance and reducing the
risk of inconsistencies.
4.​ Enhanced Extensibility: Inheritance makes it easier to extend the functionality of an
application. New features or specialized behaviors can be added by creating new
derived classes without modifying the existing base class, adhering to the Open/Closed
Principle (open for extension, closed for modification).
5.​ Polymorphism: Inheritance is a prerequisite for achieving run-time polymorphism
(dynamic polymorphism) through virtual functions. This allows objects of different derived
classes to be treated as objects of a common base class, enabling more flexible and
generic code.
6.​ Better Code Organization and Structure: Inheritance helps in creating a clear,
hierarchical structure among classes. This logical organization makes the program
easier to understand, design, and manage, reflecting real-world relationships more
naturally.
7.​ Data Hiding (Encapsulation): While derived classes inherit members, access specifiers
(private, protected, public) still control visibility. protected members allow derived
classes to access them while keeping them hidden from external users, maintaining
encapsulation.
8.​ Abstraction: Inheritance can be used in conjunction with abstract classes and pure
virtual functions to define interfaces and enforce certain behaviors among derived
classes, pushing common design to the base class while leaving specific implementation
details to derived classes.
Q4. What are virtual functions? How do they support runtime
polymorphism?
Virtual Functions: A virtual function is a member function of a base class that you expect to be
redefined (overridden) in derived classes. When you call a virtual function through a pointer or
reference to a base class object, the specific version of the function that is executed depends on
the actual type of the object being pointed to or referenced at runtime, rather than the type of
the pointer/reference itself.
To make a function virtual, you use the virtual keyword before its declaration in the base class. If
a derived class explicitly redefines a virtual function, it's good practice to use the override
keyword (C++11 onwards) to indicate that it's overriding a base class virtual function.

How Virtual Functions Support Runtime Polymorphism:

Runtime polymorphism, also known as dynamic polymorphism or late binding, is the ability of an
object to take on many forms at runtime. Virtual functions are the core mechanism in C++ that
enables this:

1.​ Base Class Pointer/Reference: The key to runtime polymorphism is to use a pointer or
a reference to the base class to point to or refer to an object of a derived class.
Animal* myAnimal = new Dog();
2.​ Dynamic Dispatch (V-table): When a virtual function is called through a base class
pointer or reference, C++ uses a mechanism called "dynamic dispatch" or "late binding."
This typically involves a virtual table (v-table).
○​ Every class that has virtual functions (or inherits them) gets a v-table, which is a
table of function pointers.
○​ Every object of such a class gets a hidden pointer, often called a v-pointer (vptr),
which points to its class's v-table.
○​ When a virtual function is called via a base pointer/reference, the compiler
doesn't directly call a specific function. Instead, it looks at the object's vptr, finds
the corresponding v-table, and then looks up the correct function address in that
v-table based on the function's signature.
○​ Because the vptr points to the v-table of the actual object type (e.g., Dog's v-table
even if the pointer is Animal*), the correct overridden function in the derived class
is invoked at runtime.

Example Scenario (Conceptual):

#include<iostream>
Using namespace std;
class Animal {
public:
virtual void makeSound() {
cout << "Animal makes a sound" << endl;
}
};
class Dog : public Animal {
public:
void makeSound() override {
cout << "Dog barks" << endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
cout << "Cat meows" << endl;
}
};
int main() {
Animal* animals[3];
animals[0] = new Animal();
animals[1] = new Dog();
animals[2] = new Cat();
for (int i = 0; i < 3; ++i) {
animals[i]->makeSound();
return 0;
}

Output:
Animal makes a sound
Dog barks
Cat meows

In this example, even though animals is an array of Animal pointers, the makeSound() call for
each element correctly dispatches to the makeSound() of Animal, Dog, or Cat based on the
actual object type at runtime. This dynamic behavior is the essence of runtime polymorphism
supported by virtual functions.
Q5. How do new and delete operators work in C++? Write a
program to demonstrate their usage.
new and delete Operators in C++:​
new and delete are operators in C++ used for dynamic memory allocation and deallocation at
runtime, primarily on the heap (or free store).

●​ new Operator:​

○​ The new operator is used to allocate memory dynamically for an object or an


array of objects.
○​ It returns a pointer to the newly allocated memory.
○​ If memory allocation fails, new throws a std::bad_alloc exception (by default). You
can use new(std::nothrow) to return nullptr on failure instead of throwing an
exception.
○​ When allocating for a class object, new not only allocates memory but also
automatically calls the constructor of that class to initialize the object.
○​ Syntax: pointer_variable = new DataType; or pointer_variable = new
DataType[size];
●​ delete Operator:​

○​ The delete operator is used to deallocate memory that was previously allocated
using new.
○​ It takes a pointer to the memory block as an operand.
○​ Deallocating memory frees it up, making it available for other programs or
subsequent allocations.
○​ If the memory being deallocated was for a class object, delete automatically calls
the destructor of that class before deallocating the memory.
○​ It's crucial to delete memory once it's no longer needed to prevent memory leaks.
○​ Deleting a nullptr is safe and does nothing. Deleting memory that was not
allocated with new or deleting the same memory twice leads to undefined
behavior.
○​ Syntax: delete pointer_variable; or delete[] pointer_variable; (for arrays)

Program to demonstrate their usage:


#include <iostream>
#include <string>
using namespace std;
class MyClass {
public:
int id;
string name;
MyClass(int i, string n) : id(i), name(n) {
cout << "Constructor called for MyClass (ID: " << id << ")" << endl;
}
~MyClass() {
cout << "Destructor called for MyClass (ID: " << id << ")" << endl;
}
void display() {
cout << "MyClass Object - ID: " << id << ", Name: " << name << endl;
}
};
int main() {
cout << "--- Demonstrating 'new' for single object ---" << endl;
int* ptrInt = new int(100);
cout << "Dynamically allocated int: " << *ptrInt << endl;
MyClass* objPtr = new MyClass(1, "ObjectOne");
objPtr->display();
cout << "\n--- Demonstrating 'delete' for single object ---" << endl;
delete ptrInt;
ptrInt = nullptr;
delete objPtr;
objPtr = nullptr;
cout << "\n--- Demonstrating 'new' for array of objects ---" << endl;
int arraySize = 3;
double* ptrArrayDouble = new double[arraySize];
for (int i = 0; i < arraySize; ++i) {
ptrArrayDouble[i] = (i + 1) * 10.5;
cout << "Array element " << i << ": " << ptrArrayDouble[i] << endl;
}
MyClass* classArrayPtr = new MyClass[arraySize]{
MyClass(10, "ArrayObj1"),
MyClass(20, "ArrayObj2"),
MyClass(30, "ArrayObj3")
};
for (int i = 0; i < arraySize; ++i) {
classArrayPtr[i].display();
}
cout << "\n--- Demonstrating 'delete[]' for array of objects ---" << endl;
delete[] ptrArrayDouble;
ptrArrayDouble = nullptr;
delete[] classArrayPtr;
classArrayPtr = nullptr;
cout << "\n--- Program End ---" << endl;
return 0;
}
Output:

--- Demonstrating 'new' for single object ---


Dynamically allocated int: 100
Constructor called for MyClass (ID: 1)
MyClass Object - ID: 1, Name: ObjectOne

--- Demonstrating 'delete' for single object ---


Destructor called for MyClass (ID: 1)

--- Demonstrating 'new' for array of objects ---


Array element 0: 10.5
Array element 1: 21
Array element 2: 31.5
Constructor called for MyClass (ID: 10)
Constructor called for MyClass (ID: 20)
Constructor called for MyClass (ID: 30)
MyClass Object - ID: 10, Name: ArrayObj1
MyClass Object - ID: 20, Name: ArrayObj2
MyClass Object - ID: 30, Name: ArrayObj3

--- Demonstrating 'delete[]' for array of objects ---


Destructor called for MyClass (ID: 30)
Destructor called for MyClass (ID: 20)
Destructor called for MyClass (ID: 10)

--- Program End ---


Q6. Explain class templates and function templates with suitable
examples.
Templates in C++: Templates are a powerful feature in C++ that allow you to write generic
programs. The idea is to pass data types as parameters so that you don't need to write the
same code for different data types. Templates are often referred to as "parameterized types."

Function Templates:​
A function template is a blueprint for creating functions that can operate on different data types
without being rewritten for each type. Instead of writing separate functions for int, float, double,
etc., you write one function template that takes a type parameter. The compiler then generates
specific versions of the function for the data types used in the calls.

Syntax:​
template <typename T>

ReturnType functionName(T param1, T param2) {


// function body using T
}
Or template <class T> can also be used.

Example:​
#include <iostream>​
using namespace std;​
template <typename T>​
T findMaximum(T a, T b) {​
return (a > b) ? a : b;​
}​
int main() {​
cout << "Max of 5 and 10 (int): " << findMaximum(5, 10) << endl;​
cout << "Max of 3.5 and 7.2 (double): " << findMaximum(3.5, 7.2) << endl;​
cout << "Max of 'Z' and 'A' (char): " << findMaximum('Z', 'A') << endl;​
return 0;​
}

Class Templates:​
A class template is a blueprint for creating classes that can work with different data types. It
allows you to define a class structure (data members and member functions) once, and then
use that structure with any data type. This is particularly useful for generic data structures like
stacks, queues, lists, or pairs, where the underlying type of elements can vary.
Syntax:​
template <typename T>
class ClassName {
// class members using T

};

Example:

#include <iostream>
using namespace std;
template <typename T>
class Pair {
private:
T first;
T second;
public:
Pair(T f, T s) : first(f), second(s) {}
void display() {
cout << "First: " << first << ", Second: " << second << endl;
}
T getMax() {
return (first > second) ? first : second;
}
};
int main() {
Pair<int> intPair(10, 20);
cout << "Int Pair: ";
intPair.display();
cout << "Max in Int Pair: " << intPair.getMax() << endl;
Pair<double> doublePair(15.5, 12.3);
cout << "Double Pair: ";
doublePair.display();
cout << "Max in Double Pair: " << doublePair.getMax() << endl;
Pair<char> charPair('X', 'Y');
cout << "Char Pair: ";
charPair.display();
cout << "Max in Char Pair: " << charPair.getMax() << endl;
return 0;
}
Q7. Define exception handling. What are the basics of exception
handling in C++?
Exception Handling: Exception handling in C++ is a powerful mechanism that allows
programs to detect and respond to exceptional conditions (errors or unusual events) that occur
during runtime, without crashing or terminating abruptly. It separates the error-handling code
from the normal program logic, making the code cleaner, more robust, and easier to maintain.

Basics of Exception Handling in C++:


C++ exception handling is built upon three keywords: try, throw, and catch.

1.​ try block:


○​ The try block encloses the code segment that might cause an exception.
○​ If an exceptional condition occurs within the try block, an exception is thrown.
○​ If no exception is thrown, the try block completes normally, and the catch blocks
associated with it are skipped.
○​ Syntax:​
try {
// Code that might throw an exception
}

2.​ throw statement:​

○​ When an error condition is detected within a try block (or a function called from
within a try block), an exception is thrown using the throw keyword.
○​ The throw statement can throw any type of data, including primitive types (like int,
char*, double) or objects of user-defined classes.
○​ When an exception is thrown, the program control immediately transfers from the
throw point to the appropriate catch handler. The normal execution flow is
interrupted, and the stack is unwound (destructors of local objects are called).
○​ Syntax: throw exception_object; or throw value;​

3.​ catch block:​

○​ The catch block (also known as an exception handler) is where the exception is
caught and handled.
○​ A catch block immediately follows a try block. It specifies the type of exception it
can handle in its parameter list.
○​ If the type of the thrown exception matches the type in a catch block's parameter,
that catch block is executed.
○​ Multiple catch blocks can be associated with a single try block to handle different
types of exceptions.
○​ A catch(...) block (ellipsis) can be used to catch any type of exception (a
"catch-all" handler). It should be placed last.
○​ Syntax:​
catch (ExceptionType e) {
// Code to handle the exception
}
catch (...) { // Catch-all
// Handle any other exception
}​

Basic Flow:
1.​ Code within a try block executes.
2.​ If an abnormal condition occurs, a throw statement sends an exception object.
3.​ The C++ runtime system looks for a catch block that can handle the type of the thrown
exception.
4.​ If a matching catch block is found, control jumps to that catch block. The code inside the
catch block executes, handling the error.
5.​ After the catch block completes, execution resumes at the statement immediately
following the catch block (or try-catch construct).
6.​ If no matching catch block is found after searching through the current function's catch
blocks and unwinding the call stack, the program terminates (often by calling
std::terminate()).

Advantages:
●​ Separation of Concerns: Separates error-handling code from normal logic.
●​ Robustness: Allows graceful handling of errors, preventing program crashes.
●​ Clarity: Makes the code more readable by centralizing error management.
Q8. Write a program to overload the << and >> operators for input
and output in a class.
Code:

#include <iostream>
#include <string>
using namespace std;
class Book {
private:
string title;
string author;
int publicationYear;
public:
Book() : title(""), author(""), publicationYear(0) {}
Book(string t, string a, int y) : title(t), author(a), publicationYear(y) {}
friend ostream& operator<<(ostream& os, const Book& book);
friend istream& operator>>(istream& is, Book& book);
};
ostream& operator<<(ostream& os, const Book& book) {
os << "Title: \"" << book.title << "\", Author: " << book.author << ", Year: " <<
book.publicationYear;
return os;
}
istream& operator>>(istream& is, Book& book) {
cout << "Enter Title: ";
getline(is >> ws, book.title);
cout << "Enter Author: ";
getline(is >> ws, book.author);
cout << "Enter Publication Year: ";
is >> book.publicationYear;
return is;
}
int main() {
Book book1("The Great Gatsby", "F. Scott Fitzgerald", 1925);
cout << "Book 1 Details: " << book1 << endl;
Book book2;
cout << "\nEnter details for Book 2:" << endl;
cin >> book2;
cout << "\nBook 2 Details: " << book2 << endl;
return 0;
}
Output:

Book 1 Details: Title: "The Great Gatsby", Author: F. Scott Fitzgerald, Year: 1925

Enter details for Book 2:


Enter Title: My New Book
Enter Author: Jane Doe
Enter Publication Year: 2023

Book 2 Details: Title: "My New Book", Author: Jane Doe, Year: 2023
Q9. Write a program to overload binary + and unary - operators.

Code:

#include <iostream>
using namespace std;
class Vector2D {
private:
double x;
double y;
public:
Vector2D(double valX = 0.0, double valY = 0.0) : x(valX), y(valY) {}
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
Vector2D operator-() const {
return Vector2D(-x, -y);
}
void display() const {
cout << "(" << x << ", " << y << ")" << endl;
}
};
int main() {
Vector2D v1(2.5, 3.0);
Vector2D v2(1.0, -1.5);
cout << "Vector 1: ";
v1.display();
cout << "Vector 2: ";
v2.display();
Vector2D sum = v1 + v2;
cout << "Sum (V1 + V2): ";
sum.display();
Vector2D negativeV1 = -v1;
cout << "Negative V1 (-V1): ";
negativeV1.display();
Vector2D negativeV2 = -v2;
cout << "Negative V2 (-V2): ";
negativeV2.display();
return 0;
}

Output:
Vector 1: (2.5, 3)
Vector 2: (1, -1.5)
Sum (V1 + V2): (3.5, 1.5)
Negative V1 (-V1): (-2.5, -3)
Negative V2 (-V2): (-1, 1.5)
Q10. Write a program demonstrating multilevel and multiple
inheritance.

Code:
#include <iostream>
#include <string>
using namespace std;
class Grandparent {
public:
string grandName;
Grandparent(string gn) : grandName(gn) {
cout << "Grandparent constructor: " << grandName << endl;
}
void displayGrandparent() {
cout << "Grandparent Name: " << grandName << endl;
}
~Grandparent() {
cout << "Grandparent destructor: " << grandName << endl;
}
};
class Parent : public Grandparent {
public:
string parentName;
Parent(string gn, string pn) : Grandparent(gn), parentName(pn) {
cout << "Parent constructor: " << parentName << endl;
}
void displayParent() {
displayGrandparent();
cout << "Parent Name: " << parentName << endl;
}
~Parent() {
cout << "Parent destructor: " << parentName << endl;
}
};
class Child : public Parent {
public:
string childName;
Child(string gn, string pn, string cn) : Parent(gn, pn), childName(cn) {
cout << "Child constructor: " << childName << endl;
}
void displayChild() {
displayParent();
cout << "Child Name: " << childName << endl;
}
~Child() {
cout << "Child destructor: " << childName << endl;
}
};
class SkillA {
public:
string skillAName;
SkillA(string sn) : skillAName(sn) {
cout << "SkillA constructor: " << skillAName << endl;
}
void showSkillA() {
cout << "Has Skill A: " << skillAName << endl;
}
~SkillA() {
cout << "SkillA destructor: " << skillAName << endl;
}
};
class SkillB {
public:
string skillBName;
SkillB(string sn) : skillBName(sn) {
cout << "SkillB constructor: " << skillBName << endl;
}
void showSkillB() {
cout << "Has Skill B: " << skillBName << endl;
}
~SkillB() {
cout << "SkillB destructor: " << skillBName << endl;
}
};
class Expert : public Child, public SkillA, public SkillB {
public:
string expertName;
Expert(string gn, string pn, string cn, string sa, string sb, string en)
: Child(gn, pn, cn), SkillA(sa), SkillB(sb), expertName(en) {
cout << "Expert constructor: " << expertName << endl;
}
void displayExpert() {
cout << "\n--- Expert Details (" << expertName << ") ---" << endl;
displayChild();
showSkillA();
showSkillB();
}
~Expert() {
cout << "Expert destructor: " << expertName << endl;
}
};
int main() {
cout << "--- Demonstrating Multilevel Inheritance ---" << endl;
Child myChild("Grand John", "Parent Jane", "Child Jim");
myChild.displayChild();
cout << "\n--- Demonstrating Multiple Inheritance ---" << endl;
Expert professional("Grand Alice", "Parent Bob", "Child Charlie", "Coding", "Design", "Ava
The Expert");
professional.displayExpert();
cout << "\n--- Program End ---" << endl;
return 0;
}

Output:

--- Demonstrating Multilevel Inheritance ---


Grandparent constructor: Grand John
Parent constructor: Parent Jane
Child constructor: Child Jim
Grandparent Name: Grand John
Parent Name: Parent Jane
Child Name: Child Jim

--- Demonstrating Multiple Inheritance ---


Grandparent constructor: Grand Alice
Parent constructor: Parent Bob
Child constructor: Child Charlie
SkillA constructor: Coding
SkillB constructor: Design
Expert constructor: Ava The Expert

--- Expert Details (Ava The Expert) ---


Grandparent Name: Grand Alice
Parent Name: Parent Bob
Child Name: Child Charlie
Has Skill A: Coding
Has Skill B: Design

--- Program End ---


Expert destructor: Ava The Expert
SkillB destructor: Design
SkillA destructor: Coding
Child destructor: Child Charlie
Parent destructor: Parent Bob
Grandparent destructor: Grand Alice
Child destructor: Child Jim
Parent destructor: Parent Jane
Grandparent destructor: Grand John
Q11. Write a program to demonstrates the basic use of a pure
virtual function.
Code:

#include <iostream>
#include <string>
using namespace std;
class Shape {
public:
string name;
Shape(string n) : name(n) {
cout << "Shape constructor: " << name << endl;
}
virtual double calculateArea() = 0;
virtual void displayInfo() {
cout << "This is a " << name << "." << endl;
}
virtual ~Shape() {
cout << "Shape destructor: " << name << endl;
}
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : Shape("Circle"), radius(r) {
cout << " Circle constructor: radius " << radius << endl;
}
double calculateArea() override {
return 3.14159 * radius * radius;
}
void displayInfo() override {
cout << "This is a Circle with radius " << radius << "." << endl;
}
~Circle() override {
cout << " Circle destructor: radius " << radius << endl;
}
};
class Rectangle : public Shape {
private:
double length;
double width;
public:
Rectangle(double l, double w) : Shape("Rectangle"), length(l), width(w) {
cout << " Rectangle constructor: length " << length << ", width " << width << endl;
}
double calculateArea() override {
return length * width;
}
void displayInfo() override {
cout << "This is a Rectangle with length " << length << " and width " << width << "." <<
endl;
}
~Rectangle() override {
cout << " Rectangle destructor: length " << length << ", width " << width << endl;
}
};
int main() {
Shape* shapePtr1 = new Circle(5.0);
Shape* shapePtr2 = new Rectangle(4.0, 6.0);
cout << "\n--- Displaying Info and Area ---" << endl;
shapePtr1->displayInfo();
cout << "Area: " << shapePtr1->calculateArea() << endl;
shapePtr2->displayInfo();
cout << "Area: " << shapePtr2->calculateArea() << endl;
cout << "\n--- Cleaning up ---" << endl;
delete shapePtr1;
shapePtr1 = nullptr;
delete shapePtr2;
shapePtr2 = nullptr;
cout << "\n--- Program End ---" << endl;
return 0;
}

Output:

Shape constructor: Circle


Circle constructor: radius 5
Shape constructor: Rectangle
Rectangle constructor: length 4, width 6

--- Displaying Info and Area ---


This is a Circle with radius 5.
Area: 78.5397
This is a Rectangle with length 4 and width 6.
Area: 24

--- Cleaning up ---


Circle destructor: radius 5
Shape destructor: Circle
Rectangle destructor: length 4, width 6
Shape destructor: Rectangle

--- Program End ---


Q12. Write a program to implement a function template for finding
the maximum of two values.
Code:

#include <iostream>
#include <string>
using namespace std;
template <typename T>
T findMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
cout << "Maximum of 10 and 20 (int): " << findMax(10, 20) << endl;
cout << "Maximum of 3.14 and 2.71 (double): " << findMax(3.14, 2.71) << endl;
cout << "Maximum of 'A' and 'Z' (char): " << findMax('A', 'Z') << endl;
string s1 = "apple";
string s2 = "banana";
cout << "Maximum of \"" << s1 << "\" and \"" << s2 << "\" (string): " << findMax(s1, s2) <<
endl;
return 0;
}

Output:

Maximum of 10 and 20 (int): 20


Maximum of 3.14 and 2.71 (double): 3.14
Maximum of 'A' and 'Z' (char): Z
Maximum of "apple" and "banana" (string): banana
Q13.Write a program to demonstrate throwing and catching of
exceptions.
Code:

#include <iostream>
#include <string>
using namespace std;
double divide(int numerator, int denominator) {
if (denominator == 0) {
throw "Division by zero is not allowed!";
}
if (numerator < 0) {
throw string("Numerator cannot be negative for this operation.");
}
if (numerator > 1000) {
throw 999;
}
return static_cast<double>(numerator) / denominator;
}
int main() {
cout << "--- Demonstrating Exception Handling ---" << endl;
try {
cout << "Attempting division (10, 2): " << divide(10, 2) << endl;
cout << "Attempting division (5, 0): ";
cout << divide(5, 0) << endl;
}
catch (const char* msg) {
cout << "Caught C-style string exception: " << msg << endl;
}
try {
cout << "\nAttempting division (-10, 2): ";
cout << divide(-10, 2) << endl;
}
catch (const string& msg) {
cout << "Caught std::string exception: " << msg << endl;
}
try {
cout << "\nAttempting division (1500, 50): ";
cout << divide(1500, 50) << endl;
}
catch (int errorCode) {
cout << "Caught int exception (Error Code): " << errorCode << endl;
}
try {
cout << "\nAttempting division (25, 5): " << divide(25, 5) << endl;
}
catch (...) {
cout << "Caught an unknown exception type." << endl;
}
cout << "\n--- Program continues after exceptions ---" << endl;
return 0;
}

Output:

--- Demonstrating Exception Handling ---


Attempting division (10, 2): 5
Attempting division (5, 0): Caught C-style string exception: Division by zero is not allowed!

Attempting division (-10, 2): Caught std::string exception: Numerator cannot be negative for this
operation.

Attempting division (1500, 50): Caught int exception (Error Code): 999

Attempting division (25, 5): 5

--- Program continues after exceptions ---


Q14. Write a program to demonstrate division by zero using
exception handling.
Code:
#include <iostream>
using namespace std;
double safeDivide(double numerator, double denominator) {
if (denominator == 0) {
throw "Error: Division by zero is undefined.";
}
return numerator / denominator;
}
int main() {
cout << "--- Division by Zero Exception Handling ---" << endl;
double num1 = 10.0;
double den1 = 2.0;
double num2 = 15.0;
double den2 = 0.0;
double num3 = 7.0;
double den3 = 0.0;
try {
cout << num1 << " / " << den1 << " = " << safeDivide(num1, den1) << endl;
cout << num2 << " / " << den2 << " = ";
cout << safeDivide(num2, den2) << endl;
}
catch (const char* errorMessage) {
cout << errorMessage << endl;
}
cout << "\nAttempting another division by zero in a new try block:" << endl;
try {
cout << num3 << " / " << den3 << " = ";
cout << safeDivide(num3, den3) << endl;
}
catch (const char* errorMessage) {
cout << errorMessage << endl;
}
cout << "\nProgram continues after handling exceptions." << endl;
return 0;
}

Output:

--- Division by Zero Exception Handling ---


10 / 2 = 5
15 / 0 = Error: Division by zero is undefined.

Attempting another division by zero in a new try block:


7 / 0 = Error: Division by zero is undefined.
Program continues after handling exceptions.
Q15. Write a program that throws and catches an object of a
user-defined exception class.

Code:

#include <iostream>
#include <string>
using namespace std;
class CustomDivideByZeroException {
private:
string message;
public:
CustomDivideByZeroException(string msg) : message(msg) {}
const char* what() const {
return message.c_str();
}
};
double safeDivideCustom(double numerator, double denominator) {
if (denominator == 0) {
throw CustomDivideByZeroException("Custom Error: Cannot divide by zero!");
}
return numerator / denominator;
}
int main() {
cout << "--- User-Defined Exception Class Demo ---" << endl;
double num1 = 10.0;
double den1 = 2.0;
double num2 = 15.0;
double den2 = 0.0;
double num3 = 20.0;
double den3 = 0.0;
try {
cout << num1 << " / " << den1 << " = " << safeDivideCustom(num1, den1) << endl;
cout << num2 << " / " << den2 << " = ";
cout << safeDivideCustom(num2, den2) << endl;
}
catch (const CustomDivideByZeroException& e) {
cout << "Caught CustomDivideByZeroException: " << e.what() << endl;
}
cout << "\nAttempting another division by zero with user-defined exception:" << endl;
try {
cout << num3 << " / " << den3 << " = ";
cout << safeDivideCustom(num3, den3) << endl;
}
catch (const CustomDivideByZeroException& e) {
cout << "Caught CustomDivideByZeroException: " << e.what() << endl;
}
cout << "\nProgram continues after handling custom exceptions." << endl;
return 0;
}

Output:

--- User-Defined Exception Class Demo ---


10 / 2 = 5
15 / 0 = Caught CustomDivideByZeroException: Custom Error: Cannot divide by zero!

Attempting another division by zero with user-defined exception:


20 / 0 = Caught CustomDivideByZeroException: Custom Error: Cannot divide by zero!

Program continues after handling custom exceptions.

You might also like