C++ OOP Exam Solutions and Concepts
C++ OOP Exam Solutions and Concepts
UNIT-I
● Class: A class is a blueprint, template, or prototype from which objects are created. It is
a logical construct that defines the properties (data members) and behaviors (member
functions/methods) that all objects of that type will have.
○ Nature: Abstract.
○ Memory: No memory is allocated when a class is defined.
● Object: An object is a real-world entity and an instance of a class. Once a class is
defined, you can create multiple objects from it, each holding its own copy of the class's
properties.
○ Nature: Concrete.
○ Memory: Memory is allocated when an object is instantiated, and it stores the
actual data.
○ Analogy: A class is like the blueprint for a car, while an object is the actual car
built from that blueprint.
Procedural Programming (POP) and Object-Oriented Programming (OOP) are two distinct
programming paradigms.
Feature Procedure Oriented Object Oriented
Programming (POP) Programming (OOP)
Focus Focuses on procedures Focuses on objects. The
(functions). The program is program is organized around
divided into functions. data and objects.
Design Approach Follows a top-down approach. Follows a bottom-up
approach.
Data & Access Data is often stored in global Data is encapsulated within
variables, making it accessible objects. Objects control
from anywhere in the program. access to their data through
Data security is low. methods, ensuring high data
integrity and security.
Data Hiding Not supported. Supported (through access
specifiers like private and
protected).
Reusability Limited reusability; complex High reusability through
functionalities might require concepts like Inheritance.
code duplication.
Polymorphism Not directly supported. Supported (through function
overloading, overriding, and
virtual functions).
Examples C, Pascal, FORTRAN. C++, Java, Python, C#.
What is a Structure?
A structure (struct) in C++ is a user-defined data type that allows you to group related
variables of different data types under a single name. Each variable within a structure is
called a member.
● Key Difference from Class: By default, members of a C++ struct are public, whereas
members of a C++ class are private.
Structure members are accessed using the dot operator (.) when operating on a direct
structure variable or using the arrow operator (->) when operating on a pointer to a
structure.
Example:
C++
#include <iostream>
// Structure Definition
struct Student {
int roll_no;
std::string name;
};
int main() {
// 1. Access using a direct variable (Dot operator)
Student s1;
s1.roll_no = 101; // Accessing member using dot operator
[Link] = "Alice"; // Accessing member using dot operator
std::cout << "Student: " << [Link] << ", Roll No: " << s1.roll_no << std::endl;
// 2. Access using a pointer (Arrow operator)
Student* s2_ptr = new Student;
s2_ptr->roll_no = 102; // Accessing member using arrow operator
s2_ptr->name = "Bob"; // Accessing member using arrow operator
std::cout << "Student: " << s2_ptr->name << ", Roll No: " << s2_ptr->roll_no << std::endl;
delete s2_ptr;
return 0;
}
2. (b) Define the 'this' pointer, with an example, indicate the steps
involved in referring to members of the invoking object.
The this pointer is a special, constant pointer that is automatically passed as a hidden
argument to every non-static member function of a class.
● Purpose: It holds the address of the object on which the member function is invoked
(the invoking object).
● Function: It enables the member function to access the specific data members and call
other member functions of that particular object.
When a member function is called on an object, the following steps happen implicitly:
1. Implicit Argument Passing: The address of the invoking object is automatically passed
as a hidden argument (the this pointer) to the member function.
2. Member Access: Inside the member function, every direct reference to a non-static
member variable (e.g., age or name) is implicitly translated by the compiler into an
expression using the this pointer (e.g., this->age or this->name).
3. Explicit Use Cases: While the compiler performs the access implicitly, the this pointer
is explicitly used in C++ for scenarios like:
○ Distinguishing between a member variable and a local variable/parameter that
have the same name (name conflict).
○ Returning a reference or pointer to the invoking object from a function (often
used for chaining function calls).
C++
#include <iostream>
#include <string>
class Student {
private:
std::string name;
int roll_no;
public:
// Constructor to initialize members
Student(std::string name, int roll_no) {
// Explicitly using 'this' to distinguish between the member
// variable (this->name) and the parameter (name)
this->name = name;
this->roll_no = roll_no;
}
void displayInfo() const {
// Implicit use: 'name' is internally translated to 'this->name'
std::cout << "Name: " << name << ", Roll No: " << roll_no << std::endl;
}
// Function that returns a reference to the invoking object
Student& updateRollNo(int new_roll_no) {
// Use 'this' to modify the member of the invoking object
this->roll_no = new_roll_no;
return *this; // Return the object pointed to by 'this'
}
};
int main() {
Student s("Chandan", 44003);
[Link](); // Invoked on object 's', so 'this' inside points to 's'
// Chaining function calls using the returned '*this'
[Link](1605)
.displayInfo(); // First updateRollNo is called, then displayInfo on the updated 's'
return 0;
}
UNIT-II
Operator Overloading is a feature in C++ that allows the redefinition or customization of the
behavior of existing operators (like +, -, *, ++, etc.) when used with user-defined data types
(classes or structs). It allows objects of a class to interact using traditional operators, making
the code more intuitive and readable.
Key Fundamentals:
1. Syntax: Operator overloading is achieved by defining a special function called an
operator function. The general syntax is:
C++
ReturnType operatorSymbol (ArgumentList) { ... }
○ operator is a keyword.
○ Symbol is the operator being overloaded (e.g., +, *, <<).
2. Cannot Invent New Operators: Only pre-existing C++ operators can be overloaded.
3. At least one User-Defined Operand: An overloaded operator function must have at
least one operand that is of a user-defined type (class or struct).
4. Preserved Arity and Precedence: The precedence, associativity, and arity (number of
operands) of the original operator cannot be changed.
5. Non-Overloadable Operators: A few operators cannot be overloaded, notably the
Scope Resolution Operator (::), Member Access Operator (.), Member Pointer Selector
(.*), and the Ternary/Conditional Operator (?:).
The following program overloads the binary operator + to add two Complex number objects.
C++
#include <iostream>
class Complex {
private:
float real;
float imag;
public:
// Constructor
Complex(float r = 0.0, float i = 0.0) : real(r), imag(i) {}
// Overload the binary '+' operator as a member function
Complex operator+(const Complex& other) {
// 'this' (left operand) is implicitly the object on which the function is called (c1)
// 'other' (right operand) is the argument (c2)
// Create a new Complex object with the sum of real and imaginary parts
Complex temp;
[Link] = this->real + [Link];
[Link] = this->imag + [Link];
return temp;
}
void display() const {
std::cout << real;
if (imag >= 0) {
std::cout << " + " << imag << "i";
} else {
std::cout << " - " << -imag << "i";
}
std::cout << std::endl;
}
};
int main() {
Complex c1(10, 5);
Complex c2(5, 3);
// This statement calls the overloaded operator+: Complex c3 = [Link]+(c2);
Complex c3 = c1 + c2;
std::cout << "Complex Number 1: ";
[Link](); // 10 + 5i
std::cout << "Complex Number 2: ";
[Link](); // 5 + 3i
std::cout << "Sum (c1 + c2): ";
[Link](); // 15 + 8i
return 0;
}
4. What is the ambiguity that arises in multiple inheritance? Discuss
with examples, the implications of deriving a class from an existing
class by the 'public' and 'protected' access specifiers.
Multiple Inheritance is a feature where a derived class inherits from two or more base
classes.
The primary ambiguity that arises is known as the "Diamond Problem" or Inheritance
Ambiguity, which occurs when:
1. Two or more base classes have member functions or data members with the exact
same name.
2. A derived class inherits from these multiple base classes.
3. An object of the derived class attempts to access that similarly named member.
In this scenario, the compiler cannot determine which base class's member should be
accessed, leading to ambiguity and a compilation error.
Example of Ambiguity:
C++
class BaseA {
public:
void print() { /* A's print */ }
};
class BaseB {
public:
void print() { /* B's print */ }
};
class Derived : public BaseA, public BaseB {
// Derived has inherited two functions named print()
};
int main() {
Derived d;
// ERROR: Ambiguous call to 'print()'.
// Compiler doesn't know whether to call BaseA::print or BaseB::print.
// [Link]();
// Resolution: Use the scope resolution operator (::)
[Link]::print(); // OK: Explicitly calls A's function
[Link]::print(); // OK: Explicitly calls B's function
return 0;
}
Note: The Diamond Problem (where a class inherits a member multiple times from a common
ancestor via two paths) is a more complex type of ambiguity which is resolved using Virtual
Inheritance.
When a derived class inherits from a base class, the visibility mode (using public, protected,
or private keywords) determines how the inherited members of the base class will be
accessible in the derived class and by outside objects.
Base Class Access Visibility Mode Derived Class Access Accessible by Object
of Derived Class?
public public Remains public Yes
protected public Remains protected No
private public Remains private No
public protected Becomes protected No
protected protected Remains protected No
public private Becomes private No
C++
#include <iostream>
class Base {
public:
int pub_val = 1;
protected:
int pro_val = 2;
private:
int pri_val = 3;
};
// 1. Public Derivation
class PublicDerived : public Base {
public:
void accessBaseMembers() {
std::cout << "Public Derived can access: " << std::endl;
std::cout << " pub_val: " << pub_val << std::endl; // OK (public -> public)
std::cout << " pro_val: " << pro_val << std::endl; // OK (protected -> protected)
// std::cout << pri_val << std::endl; // ERROR: private members are never inherited
}
};
// 2. Protected Derivation
class ProtectedDerived : protected Base {
public:
void accessBaseMembers() {
std::cout << "Protected Derived can access: " << std::endl;
std::cout << " pub_val: " << pub_val << std::endl; // OK (public -> protected)
std::cout << " pro_val: " << pro_val << std::endl; // OK (protected -> protected)
}
};
int main() {
PublicDerived pd;
ProtectedDerived prd;
// Public Derivation Test
std::cout << "--- Public Derivation ---" << std::endl;
[Link]();
std::cout << "Access via object: pd.pub_val = " << pd.pub_val << std::endl; // OK (public
member)
// pd.pro_val; // ERROR: protected in derived class
// Protected Derivation Test
std::cout << "--- Protected Derivation ---" << std::endl;
[Link]();
// prd.pub_val; // ERROR: pub_val is 'protected' in ProtectedDerived
return 0;
}
UNIT-III
A Pure Virtual Function is a virtual function declared in a base class that has no definition
(no implementation) in the base class. It is declared by assigning = 0 to its declaration: virtual
ReturnType functionName(Arguments) = 0;.
● Abstract Class: A class containing at least one pure virtual function is known as an
Abstract Base Class.
● Implication: You cannot create objects of an abstract class.
● Purpose: The pure virtual function forces all derived classes to provide their own
implementation for that function, thus defining a common interface that all derived
classes must adhere to.
C++ Program Example (Shape Interface):
C++
#include <iostream>
#include <cmath>
// Abstract Base Class: Shape
class Shape {
public:
// Pure Virtual Function (Interface): Forces derived classes to implement area()
virtual double area() = 0;
// Regular Virtual Function
virtual void displayInfo() {
std::cout << "This is a generic shape." << std::endl;
}
// A pure virtual function requires a virtual destructor for proper cleanup
virtual ~Shape() {}
};
// Derived Class 1: Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// Must override the pure virtual function 'area'
double area() override {
return M_PI * radius * radius;
}
void displayInfo() override {
std::cout << "Circle with radius " << radius << std::endl;
}
};
// Derived Class 2: Rectangle
class Rectangle : public Shape {
private:
double length;
double width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
// Must override the pure virtual function 'area'
double area() override {
return length * width;
}
};
int main() {
// Shape s; // ERROR: Cannot create object of abstract class 'Shape'
// Use Base Class Pointer for Polymorphism
Shape* s_ptr1 = new Circle(5.0);
Shape* s_ptr2 = new Rectangle(4.0, 6.0);
std::cout << "--- Polymorphic Behavior ---" << std::endl;
// Calls Circle::area()
std::cout << "Area of Circle: " << s_ptr1->area() << std::endl;
// Calls Rectangle::area()
std::cout << "Area of Rectangle: " << s_ptr2->area() << std::endl;
// Calls Circle::displayInfo() (Demonstrates virtual function call)
s_ptr1->displayInfo();
delete s_ptr1;
delete s_ptr2;
return 0;
}
6. (a) What is sequential access file? How to read and update data in
sequential access file?
A sequential access file is a type of file organization where data records are stored and
retrieved one after another in a linear sequence.
● Order of Access: To access the $n$-th data item, you must first read (or skip) the
previous $n-1$ data items in the file.
● Starting Point: Reading a sequential file typically begins from the start of the file.
● Example: A simple text file where lines are processed one by one is a common example
of a sequential file.
Reading data involves opening the file and reading data items iteratively until the end of the
file is reached.
Steps to Read:
1. Open the File: Create an object of the ifstream (input file stream) class and open the
file in read mode.
2. Read Iteratively: Use the stream extraction operator (>>) or functions like getline() or
read() to read the data one record or item at a time.
3. Check for EOF: The reading process continues in a loop until the End-Of-File (EOF)
marker is detected (e.g., by checking the stream state).
4. Close the File: Close the file to release the resources.
C++ Reading Example (assuming file is [Link]):
C++
#include <fstream>
#include <iostream>
int main() {
std::ifstream file("[Link]"); // Open file for reading
int number;
while (file >> number) { // Reads sequentially until EOF is reached
std::cout << "Read: " << number << std::endl;
}
[Link]();
return 0;
}
Updating a record in a sequential file is generally inefficient and involves a multi-step process
because you cannot directly jump to a record and overwrite it without affecting the
surrounding data, especially if the record size changes.
Steps to Update (Simulated Update):
1. Open Source and Temporary Files: Open the original file in input mode (ifstream) and
create a new temporary file in output mode (ofstream).
2. Sequential Read and Write: Read records sequentially from the source file.
3. Identify and Modify:
○ If the current record is not the one to be updated, write it directly to the
temporary file.
○ If the current record is the one to be updated, read it, perform the necessary
modification, and then write the modified record to the temporary file.
4. Close and Replace: After reaching EOF, close both files. Then, delete the original
source file and rename the temporary file to the name of the original file. This
effectively updates the data by replacing the entire file.
6. (b) Discuss about stream input, stream output, stream
manipulators and stream error states.
In C++, I/O operations are handled using the concept of streams. A stream is an abstraction
that represents a flow of data between a program and an I/O device (like a keyboard, screen,
or file).
● Stream Input (istream): A stream that facilitates the flow of data from an input source
to the program.
○ Operator: Uses the extraction operator (>>).
○ Common Objects: std::cin (standard input, usually keyboard), std::ifstream (file
input stream).
○ Example: std::cin >> var;
● Stream Output (ostream): A stream that facilitates the flow of data from the program
to an output destination.
○ Operator: Uses the insertion operator (<<).
○ Common Objects: std::cout (standard output, usually screen), std::cerr (standard
error), std::ofstream (file output stream).
○ Example: std::cout << "Value: " << var;
Stream Manipulators
Stream Manipulators are functions or objects used to modify the state of an I/O stream,
thereby changing how the data is formatted, read, or written. They are typically used with the
insertion (<<) or extraction (>>) operators.
Type Manipulator Header Description Example
No Arg std::endl <iostream> Inserts a newline cout << "Hi" <<
character and endl;
flushes the
output buffer.
std::hex, std::oct, <iostream> Sets the numeric cout << hex <<
std::dec base for integer 100; (Outputs 64)
I/O.
std::fixed, <iostream> Sets the cout << fixed <<
std::scientific floating-point 12.34;
notation.
With Arg std::setw(int w) <iomanip> Sets the minimum cout << setw(10)
field width for the << 123;
next output
operation.
std::setfill(char c) <iomanip> Sets the character cout << setfill('*')
used to fill the << 123;
empty space
when setw is
used.
std::setprecision(i <iomanip> Sets the number cout <<
nt p) of digits to be setprecision(2) <<
displayed for 3.14159;
floating-point
values.
Every stream object maintains a set of internal flags (error states) to monitor the status of the
I/O operations. These flags can be checked using member functions to determine the success
or failure of an operation.
Function Error State Description
good() goodbit Returns true if no errors or
end-of-file condition has been
reached. The stream is ready
for I/O.
fail() failbit or badbit Returns true if an operation
failed (e.g., trying to read an
integer but encountering a
letter). Indicates a potential
data issue.
bad() badbit Returns true if the stream is
corrupted or an irreparable
error occurred (e.g., a file
cannot be opened).
eof() eofbit Returns true if the end of the
input stream (End-Of-File) has
been reached.
UNIT-IV
#include <iostream>
// 1. Function Template: Can handle any type T
template <typename T>
void displayMax(T a, T b) {
std::cout << "Template (Generic): Max is " << (a > b ? a : b) << std::endl;
}
// 2. Non-Template Function: Specific overload for 'int' type
void displayMax(int a, int b) {
std::cout << "Non-Template (Specific): Max is " << (a > b ? a : b) << std::endl;
}
int main() {
// Case 1: Call with 'int' arguments
// The non-template function is preferred over the template specialization
// because a non-template function takes precedence in overload resolution.
displayMax(10, 20); // Output: Non-Template (Specific)...
// Case 2: Call with 'double' arguments
// No specific non-template function for 'double', so the template is used.
displayMax(10.5, 5.5); // Output: Template (Generic)...
// Case 3: Call with 'char' arguments
// No specific non-template function for 'char', so the template is used.
displayMax('a', 'z'); // Output: Template (Generic)...
return 0;
}
Class Template
A Class Template is a design for creating generic classes that can work with any data type. It
allows the class structure to be defined once, and the compiler then generates specific class
types when an object is instantiated with a concrete data type.
● Syntax: Similar to function templates, a class template uses the template keyword
followed by template parameters.
C++
template <typename T>
class MyGenericClass {
T data;
// ... member functions using T ...
};
● Instantiation: To use a class template, you must specify the actual data type(s) inside
angle brackets when creating an object.
○ Example: MyGenericClass<int> int_obj; and MyGenericClass<std::string>
string_obj;
● Use Case: The most common example is the C++ Standard Template Library (STL)
containers, such as std::vector<T>, std::list<T>, and std::map<Key, Value>.
In addition to type parameters (like typename T or class T), C++ templates can also accept
non-type parameters. A non-type template parameter is a placeholder for a constant value
that must be known at compile time.
● Types: Non-type parameters can be integral types (like int, long), enumeration types,
pointers or references to objects/functions, and, since C++20, floating-point and literal
class types.
● Syntax: The type of the parameter is explicitly specified:
C++
template <typename T, int N> // N is a non-type parameter
class Array { ... };
● Purpose: Non-type parameters are primarily used when a class needs to fix a size or
value that must be a compile-time constant.
Example using Non-Type Parameter:
The standard library class std::array and std::bitset heavily rely on non-type parameters.
C++
#include <iostream>
// Class Template with a non-type parameter 'Size'
template <typename T, int Size>
class StaticArray {
private:
T arr[Size]; // The size of the array is fixed at compile time
public:
void printSize() const {
std::cout << "Array size: " << Size << std::endl;
}
// ... other array-like methods ...
};
int main() {
// Instantiation 1: T=int, Size=10
StaticArray<int, 10> arr1;
[Link](); // Output: Array size: 10
// Instantiation 2: T=double, Size=5
StaticArray<double, 5> arr2;
[Link](); // Output: Array size: 5
// The compiler generates two completely separate classes: StaticArray<int, 10> and
StaticArray<double, 5>
return 0;
}
A User Defined Exception (or custom exception) is an exception class created by the
programmer to handle application-specific error conditions that are not covered by the
standard C++ exceptions (like std::bad_alloc or std::out_of_range).
● Implementation: Custom exceptions are typically implemented as a class that inherits
from the standard C++ exception base class, std::exception, or one of its derived
classes (like std::runtime_error).
● Benefit: By inheriting from std::exception, the custom exception can be caught using a
general catch (const std::exception& e) block and can utilize the standard what()
method to return a descriptive error message.
Example Structure:
C++
#include <exception>
#include <string>
class NegativeValueError : public std::exception {
public:
const char* what() const noexcept override {
return "Error: Value cannot be negative.";
}
};
// This custom exception can be thrown and caught like any standard exception.
User-defined exceptions are required when the program logic demands specific error states
that provide more context and meaning than a generic exception:
1. Handling Business Rule Violations: When an input or operation violates a rule
essential to the application's domain.
○ Scenario: A bank account class where a withdrawal request exceeds the account
balance. The standard library has no exception for this, so a
InsufficientFundsException is thrown.
2. Custom Data Validation: When validating complex data structures or specific ranges.
○ Scenario: In a date class, a function attempts to set the day of the month to 31 for
February. A InvalidDateException would be required.
3. Abstraction of Low-Level Errors: To translate low-level system or library errors into a
high-level, application-specific failure.
○ Scenario: A network communication class catches a low-level socket error and
re-throws it as a more meaningful NetworkTimeoutException or
ConnectionLostException that the higher-level code can easily understand and
handle.
4. Error Categorization: To allow different catch blocks to handle specific errors
differently without having to inspect the error message string.
○ Scenario: Throwing UserNotFoundException vs. DatabaseConnectionException
allows the program to either prompt the user to register or retry the database
connection.
8. (b) Draw a comparison between Error and Exception and also draw
a comparison between Exceptions and Inheritance.