optional · CSS study notes
2. Object-Oriented Programming
FPSC CSS Computer Science past papers (2016–2026) — 41 questions: 8 MCQs, 33 subjective parts. Newest first.
Part A — Subjective Questions
2026 · Paper-I · Q4(a) (6 marks)
Question
Why cannot we instantiate an abstract class directly, even though it may contain complete functions?
Solution
An abstract class in C++ is any class that declares at least one pure virtual function (a function declared with = 0), regardless of how many other fully-implemented (complete) member functions it may also contain. The presence of even a single pure virtual function means the class's interface is deliberately left incomplete — that particular function has no implementation for the class to fall back on, so the class as a whole does not represent a fully-usable, concrete type.
If the compiler allowed an object of this class to be created, and code then called that unimplemented pure virtual function on the object, there would be no function body to execute — an undefined, unsafe operation. To prevent this, the C++ standard makes it a compile-time error to instantiate any class containing an unoverridden pure virtual function; the class can only be brought to life indirectly, by having a derived class supply a concrete implementation for every pure virtual function, after which THAT derived class becomes instantiable. This is precisely how abstract classes are used to force a common interface/contract onto all derived classes while still allowing the base class to provide shared, complete helper functions to those derived classes.
2026 · Paper-I · Q4(b) (6 marks)
Question
A class library contains many book objects. Should this be modeled using inheritance or composition? Why?
Solution
This should be modeled using composition, not inheritance.
Inheritance models an 'is-a' relationship — a derived class IS a more specific version of its base class (e.g., a NovelBook is-a Book). But a Library is NOT a kind of Book; a library simply CONTAINS/owns many book objects. This is a 'has-a' relationship, which is exactly what composition represents: the Library class holds a collection (e.g., a vector<Book> or vector<Book*>) of Book objects as member data, rather than the Library class inheriting from Book.
class Book { /* title, author, ISBN, etc. */ };
class Library {
vector<Book> books; // composition: Library HAS-A collection of Books
public:
void addBook(const Book& b) { books.push_back(b); }
int totalBooks() const { return books.size(); }
};
Using inheritance here would be conceptually wrong (a Library does not share the Book interface or behave as a Book) and would also be impractical, since inheritance models a one-parent relationship with a single conceptual type, not a collection of many independent objects. Composition correctly captures both the ownership and the one-to-many nature of the relationship.
2026 · Paper-I · Q4(c) (8 marks)
Question
Demonstrate method overriding using a base class Account and a derived class Current Account. Make balance a protected data member.
Solution
#include <iostream>
using namespace std;
class Account {
protected:
double balance;
public:
Account(double b) : balance(b) {}
virtual void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
cout << "Withdrew " << amount << ". New balance: " << balance << endl;
} else {
cout << "Insufficient funds." << endl;
}
}
virtual void display() const {
cout << "Account balance: " << balance << endl;
}
};
class CurrentAccount : public Account {
double overdraftLimit;
public:
CurrentAccount(double b, double odLimit) : Account(b), overdraftLimit(odLimit) {}
// Overriding withdraw() to allow overdraft up to a limit
void withdraw(double amount) override {
if (amount <= balance + overdraftLimit) {
balance -= amount; // balance can legally go negative up to -overdraftLimit
cout << "Withdrew " << amount << " (overdraft allowed). New balance: " << balance << endl;
} else {
cout << "Withdrawal exceeds overdraft limit." << endl;
}
}
};
int main() {
Account* acc = new CurrentAccount(1000, 500); // balance=1000, overdraft up to 500
acc->withdraw(1300); // uses CurrentAccount's overridden withdraw() via virtual dispatch
acc->display(); // uses Account's display() (not overridden)
delete acc;
return 0;
}
Since balance is declared protected in Account, the derived CurrentAccount class can access and modify it directly. withdraw() is declared virtual in the base class and overridden in the derived class with a different rule (allowing overdraft); calling it through a base-class pointer (acc->withdraw(...)) correctly dispatches to CurrentAccount::withdraw() at runtime — this is method overriding combined with runtime polymorphism.
2025 · Paper-I · Q3(a) (6 marks)
Question
What is the difference between shallow copy and deep copy of creating objects in C++. Discuss the cases where a specific copy should be used by providing examples.
Solution
A shallow copy copies an object's member values field-by-field; if a member is a pointer, only the pointer (address) is copied, so both the original and the copy end up pointing to the same dynamically allocated memory. This is what the compiler-generated default copy constructor/assignment operator does. A deep copy instead allocates new memory for pointer members and copies the pointed-to data itself, so each object owns an independent copy.
Example:
class Buffer {
int* data;
public:
Buffer(int size) { data = new int[size]; }
// Shallow copy (default, dangerous):
// Buffer(const Buffer&) = default;
// Deep copy (safe):
Buffer(const Buffer& other) {
data = new int[10];
for (int i = 0; i < 10; i++) data[i] = other.data[i];
}
~Buffer() { delete[] data; }
};
If shallow copy is used here, when both objects are destroyed the destructor calls delete[] data twice on the same memory (double free / dangling pointer, undefined behavior).
When to use which:
- Shallow copy is fine (and faster, uses less memory) when objects are meant to share the same underlying resource, e.g., reference-counted smart pointers, or read-only shared data.
- Deep copy is required whenever a class owns a dynamically-allocated resource exclusively (e.g., a String class holding a char*, a container class), so that copies are independent and destructors don't double-free memory.
2025 · Paper-I · Q3(c) (8 marks)
Question
Explain the role of pure virtual functions in implementing abstract classes in C++. How do they differ from virtual functions? Give an example of C++ classes where both types of functions are used.
Solution
A pure virtual function is a virtual function declared with = 0 and (usually) no implementation in the base class: virtual void draw() = 0;. Declaring even one pure virtual function makes the class abstract — it cannot be instantiated directly, and any concrete derived class must override every pure virtual function to become instantiable. Pure virtual functions are used to define a common interface/contract that all derived classes must implement.
A plain virtual function, by contrast, has a body in the base class; a derived class MAY override it to change behaviour, but does not have to — the base implementation is used if it doesn't. A class with only ordinary virtual functions (no pure virtual ones) can still be instantiated.
Example combining both:
class Shape {
public:
virtual double area() const = 0; // pure virtual: must be implemented
virtual void display() const { // ordinary virtual: has default body
cout << "Area = " << area() << endl;
}
virtual ~Shape() {}
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { return 3.1416 * radius * radius; }
// display() not overridden -> uses Shape's version, calls Circle::area() via polymorphism
};
Shape cannot be instantiated (Shape s; is a compile error) because area() is pure virtual, but Circle can be instantiated because it supplies area(). display() demonstrates a normal virtual function reused as-is via runtime polymorphism.
2025 · Paper-I · Q5(a) (10 marks)
Question
Demonstrate the use of stream insertion and extraction operators for creating custom objects. Provide a suitable coding example for illustrating the functional difference between them.
Solution
The stream insertion operator << sends (outputs) an object's data to a stream (e.g., cout, a file), while the stream extraction operator >> reads (inputs) data from a stream into an object. In C++ both must be overloaded as free functions (often declared friend) because the left-hand operand is the stream object (ostream/istream), not the class itself.
#include <iostream>
using namespace std;
class Point {
int x, y;
public:
Point(int a = 0, int b = 0) : x(a), y(b) {}
friend ostream& operator<<(ostream& out, const Point& p) {
out << "(" << p.x << ", " << p.y << ")";
return out; // enables chaining: cout << p1 << p2;
}
friend istream& operator>>(istream& in, Point& p) {
cout << "Enter x and y: ";
in >> p.x >> p.y;
return in;
}
};
int main() {
Point p1(2, 3), p2;
cout << "p1 = " << p1 << endl; // uses operator<<
cin >> p2; // uses operator>>
cout << "p2 = " << p2 << endl;
return 0;
}
Functional difference: operator<< takes the object by const reference (it only reads it to display) and returns ostream&; operator>> takes the object by non-const reference (it must modify it while reading) and returns istream&. Both return the stream reference so multiple insertions/extractions can be chained, e.g., cout << p1 << p2;.
2025 · Paper-I · Q5(b) (10 marks)
Question
Implement a Book Management System consisting of three different classes. The book class should have data members such as book name, ISBN number, and publication year. The author class should have attributes such as author name, and author email. The publisher class contains attributes such as publisher name, publisher email and publisher address. Each book has an author and a publisher. The author and publisher are created and destroyed with books. When the book is deleted, the corresponding author and publisher should also be deleted. Write a C++ code of this system and explain how the composition relationship is used in this system.
Solution
#include <iostream>
#include <string>
using namespace std;
class Author {
string name, email;
public:
Author(string n, string e) : name(n), email(e) {
cout << "Author " << name << " created.\n";
}
~Author() { cout << "Author " << name << " destroyed.\n"; }
void display() const { cout << "Author: " << name << " (" << email << ")\n"; }
};
class Publisher {
string name, email, address;
public:
Publisher(string n, string e, string a) : name(n), email(e), address(a) {
cout << "Publisher " << name << " created.\n";
}
~Publisher() { cout << "Publisher " << name << " destroyed.\n"; }
void display() const { cout << "Publisher: " << name << ", " << email << ", " << address << "\n"; }
};
class Book {
string bookName, isbn;
int year;
Author* author; // owned sub-object (composition)
Publisher* publisher; // owned sub-object (composition)
public:
Book(string bn, string isbnNo, int y, string aName, string aEmail,
string pName, string pEmail, string pAddr)
: bookName(bn), isbn(isbnNo), year(y) {
author = new Author(aName, aEmail);
publisher = new Publisher(pName, pEmail, pAddr);
}
~Book() {
delete author; // author's lifetime is tied to the book
delete publisher; // publisher's lifetime is tied to the book
cout << "Book " << bookName << " deleted.\n";
}
void display() const {
cout << "Book: " << bookName << ", ISBN: " << isbn << ", Year: " << year << endl;
author->display();
publisher->display();
}
};
int main() {
Book* b = new Book("C++ Primer", "978-0321714114", 2012,
"Stanley Lippman", "lippman@mail.com",
"Addison-Wesley", "info@aw.com", "Boston, USA");
b->display();
delete b; // destroying the Book automatically destroys its Author and Publisher
return 0;
}
Composition explanation: Book has-a Author and has-a Publisher by ownership (a strong "whole-part" relationship, not mere inheritance or aggregation). The Author and Publisher objects are created inside the Book's constructor and are stored as pointers that only the Book manages; when the Book's destructor runs, it explicitly deletes both, so the sub-objects' lifetimes are entirely dependent on and bound to the containing Book — the defining characteristic of composition (as opposed to aggregation, where the parts could outlive the whole).
2024 · Paper-I · Q5(a) (6 marks)
Question
Show the output of the following code? public class Test { public static void main(String[] args) { Double x = 3.5; System.out.println(x.intValue()); System.out.println(x.compareTo(4.5)); } }
Solution
x is a Double object wrapping 3.5.
x.intValue() truncates the decimal part, returning 3.
x.compareTo(4.5) compares 3.5 to 4.5; since 3.5 is less than 4.5, compareTo returns a negative value (in Java's Double implementation, this returns -1).
Output:
3
-1
2024 · Paper-I · Q5(b) (6 marks)
Question
Illustrate the difference between overriding and overloading by the piece of pseudocode or program.
Solution
Overloading: multiple methods share the same name but differ in their parameter list (number/type of parameters), all defined in the same class; the compiler decides which one to call based on the arguments used, at compile time (static/early binding).
Overriding: a subclass provides its own implementation of a method that already exists in its superclass, with the exact same signature (name, parameters, return type); the version that executes is decided at run time based on the actual object type (dynamic/late binding), and it requires inheritance.
class Calculator {
// Overloading - same name "add", different parameter types
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
}
class Animal {
void sound() { System.out.println("Animal makes a sound"); }
}
class Dog extends Animal {
// Overriding - same signature as Animal.sound(), redefines behavior
@Override
void sound() { System.out.println("Dog barks"); }
}
Calling calc.add(2,3) vs calc.add(2.5,3.5) picks a different overloaded method at compile time; calling sound() on an Animal reference that actually holds a Dog object executes Dog.sound() at run time - demonstrating overriding.
2024 · Paper-I · Q5(c) (8 marks)
Question
How do you prevent a class from being extended? How do you prevent a method from being overridden? Exemplify with simple piece of code.
Solution
In Java, the final keyword applied to a class prevents it from being subclassed/extended, and applied to a method prevents it from being overridden in a subclass:
final class Vehicle {
// cannot be extended by any other class
}
class Base {
final void display() { // cannot be overridden by subclasses
System.out.println("Base display");
}
}
In C++ (C++11 onward), the same effect is achieved with the final specifier:
class Vehicle final {
// cannot be inherited from
};
class Base {
public:
virtual void display() final { // cannot be overridden in derived classes
}
};
Attempting to write class SportsCar : public Vehicle {} (C++) or class SportsCar extends Vehicle {} (Java) in either case produces a compile-time error, since Vehicle is marked final; likewise, attempting to override display() in a derived/subclass produces a compile-time error.
2023 · Paper-I · Q3(b) (6 marks)
Question
Write a note on the use of overloaded operators. Discuss with examples.
Solution
Operator overloading in C++ lets a class redefine the meaning of standard operators (+, -, ==, <<, etc.) so that they work intuitively with objects of user-defined types, improving code readability by letting objects be manipulated with natural mathematical/logical notation instead of explicit method calls.
class Complex {
double re, im;
public:
Complex(double r = 0, double i = 0) : re(r), im(i) {}
// overload + so Complex objects can be added with '+'
Complex operator+(const Complex &c) const {
return Complex(re + c.re, im + c.im);
}
// overload == for equality comparison
bool operator==(const Complex &c) const {
return re == c.re && im == c.im;
}
void display() const { cout << re << " + " << im << "i" << endl; }
};
int main() {
Complex a(2,3), b(1,4);
Complex c = a + b; // calls operator+
c.display(); // 3 + 7i
cout << (a == b); // calls operator==, prints 0 (false)
}
Overloading must preserve the operator's usual arity (e.g., binary + still takes two operands) and is resolved at compile time based on the operand types, making code using custom types (vectors, matrices, complex numbers, strings) as easy to write as code using built-in types.
2023 · Paper-I · Q4(a) (8 marks)
Question
What is Polymorphism? Explain Ad-Hoc Polymorphism, Parametric Polymorphism & Subtyping with suitable coding examples.
Solution
Polymorphism ("many forms") is the ability of the same interface/operation to behave differently depending on the type of data it operates on. There are three main kinds:
- Ad-hoc polymorphism: the same function/operator name behaves differently for different, specifically-enumerated types, resolved at compile time (function/operator overloading).
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
- Parametric polymorphism: a single piece of code works uniformly for any type, using a type parameter rather than being written separately per type (generic programming/templates).
template <typename T>
T maxVal(T a, T b) { return (a > b) ? a : b; }
// works for int, double, or any type supporting operator>
- Subtyping (inclusion) polymorphism: a function/pointer/reference declared for a base type can operate on any derived-type object, with the actual behavior selected at run time via virtual functions.
class Shape { public: virtual void draw() { cout << "Shape"; } };
class Circle : public Shape { public: void draw() override { cout << "Circle"; } };
Shape* s = new Circle();
s->draw(); // prints "Circle" - resolved at run time
2023 · Paper-I · Q4(b) (6 marks)
Question
Discuss Late Binding with a short program.
Solution
Late binding (dynamic binding) means the specific function implementation to execute for a call is determined at run time, based on the actual (dynamic) type of the object, rather than at compile time based on the declared (static) type of the pointer/reference. In C++, this is achieved using virtual functions.
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() { cout << "Some generic sound" << endl; }
};
class Dog : public Animal {
public:
void sound() override { cout << "Bark" << endl; }
};
int main() {
Animal* a = new Dog();
a->sound(); // late binding: resolved at run time -> prints "Bark"
delete a;
return 0;
}
Even though a is statically typed as Animal*, the call a->sound() is late-bound (via the vtable mechanism) to Dog::sound(), because that is the actual run-time type of the object it points to. This is the foundation of run-time polymorphism, contrasted with early (static) binding used for non-virtual functions, which is resolved purely at compile time.
2023 · Paper-I · Q4(c) (6 marks)
Question
Differentiate between Abstraction & Encapsulation.
Solution
Abstraction is a design-level concept: it means hiding unnecessary implementation complexity and exposing only the essential features/behavior of an object to the outside world ("what an object does"). It is achieved using abstract classes, interfaces, or simply well-designed public method signatures.
Encapsulation is an implementation-level mechanism: it means bundling data (attributes) and the methods that operate on that data together into a single unit (a class), and restricting direct external access to the internal state ("how it is hidden"), typically using access specifiers like private/protected, exposing controlled access only through public getter/setter methods.
In short: abstraction focuses on hiding complexity from the user's perspective (interface design), while encapsulation focuses on hiding/protecting data from unauthorized or unintended access (implementation protection). Abstraction is usually achieved through abstract classes/interfaces, whereas encapsulation is achieved through access modifiers within a class. They work together: encapsulation is often the mechanism used to enforce abstraction.
2022 · Paper-I · Q5(a) (10 marks)
Question
Define a class called Circle using C++. An object of the class Circle can be created using any of the following ways (implement all the ways): (1) by default, the circle will be of radius 1 inch and centered at (0,0); (2) with a given value for radius, centered at (0,0); (3) by providing the center coordinates and the radius is assumed to be 1; (4) by providing the radius and the coordinates for the center; (5) by providing another Circle object to initialize. In the main function (driver program), separately create 5 different objects of the Circle class using a different way of object creation (mentioned above) for each of them.
Solution
Each of the five ways corresponds to a distinct constructor overload, distinguished by its parameter list (number/order of arguments):
#include <iostream>
using namespace std;
class Circle {
private:
double x, y; // center coordinates
double radius;
public:
// 1. default: radius 1, centered at (0,0)
Circle() : x(0), y(0), radius(1) {}
// 2. given radius, centered at (0,0)
Circle(double r) : x(0), y(0), radius(r) {}
// 3. given center coordinates, radius assumed 1
Circle(double cx, double cy) : x(cx), y(cy), radius(1) {}
// 4. given radius and center coordinates
Circle(double r, double cx, double cy) : x(cx), y(cy), radius(r) {}
// 5. copy constructor - initialize from another Circle object
Circle(const Circle &c) : x(c.x), y(c.y), radius(c.radius) {}
void display() const {
cout << "Center: (" << x << ", " << y << "), Radius: " << radius << endl;
}
};
int main() {
Circle c1; // way 1: default
Circle c2(5.0); // way 2: radius only
Circle c3(2.0, 3.0); // way 3: center only, radius=1
Circle c4(4.0, 1.0, 1.0); // way 4: radius + center
Circle c5(c4); // way 5: copy constructor
c1.display();
c2.display();
c3.display();
c4.display();
c5.display();
return 0;
}
Because each constructor has a unique arity/signature (0, 1, 2, 3 arguments, and a const-reference to Circle), the compiler can unambiguously pick the right overload for each object creation.
2022 · Paper-I · Q5(b) (10 marks)
Question
What is a virtual function in C++? Using an example, explain how does a virtual function mechanism work.
Solution
A virtual function is a member function declared in a base class using the keyword virtual, which a derived class can override with its own implementation. When called through a base-class pointer or reference, the version that actually executes is determined at run time based on the real (dynamic) type of the object, not the static type of the pointer - this is called dynamic (late) binding, and it is the mechanism behind run-time polymorphism in C++.
Internally, the compiler gives every class with virtual functions a hidden "vtable" (virtual table) - an array of function pointers, one per virtual function. Each object of such a class carries a hidden vptr pointing to its class's vtable. A call through a base pointer is translated into an indirect call via the vptr/vtable, so it always lands on the function of the object's actual class.
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() { cout << "Some generic animal sound" << endl; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Bark!" << endl; }
};
class Cat : public Animal {
public:
void speak() override { cout << "Meow!" << endl; }
};
int main() {
Animal* a1 = new Dog();
Animal* a2 = new Cat();
a1->speak(); // prints "Bark!"
a2->speak(); // prints "Meow!"
delete a1; delete a2;
return 0;
}
Even though both pointers are declared as Animal*, each call resolves to the overridden method of the object it actually points to, because speak() is virtual.
2021 · Paper-I · Q4(a) (8 marks)
Question
Explain object oriented programming paradigm. Write a detailed note on any two of the principles of object oriented programming paradigm.
Solution
Object-Oriented Programming (OOP) is a programming paradigm that structures software as a collection of interacting 'objects' — instances of classes that bundle together data (attributes/state) and the behaviour (methods) that operates on that data — rather than as a sequence of procedures acting on separate data, as in procedural programming. It is built on four main principles: Encapsulation, Inheritance, Polymorphism, and Abstraction.
Encapsulation: bundles an object's data and the methods operating on it together, and restricts direct external access to the internal state using access modifiers (private/protected), exposing only a controlled public interface (getters/setters). This protects data integrity — invalid states cannot be set directly — and hides implementation details so they can change later without breaking client code.
Inheritance: allows a new class (derived/child) to acquire the attributes and methods of an existing class (base/parent), establishing an 'is-a' relationship and enabling code reuse. For example, class SavingsAccount : public Account reuses Account's common fields/behaviour while adding interest-specific functionality, avoiding duplicating the shared logic.
2021 · Paper-I · Q4(b) (6 marks)
Question
Why do we need interfaces in OOP? How does it help in achieving abstraction?
Solution
An interface defines a contract — a set of method signatures — that any implementing class must fulfil, without specifying HOW those methods are implemented. Interfaces are needed because: (1) many object-oriented languages (Java, C#) do not allow a class to inherit from multiple classes, but a class CAN implement multiple interfaces, giving multiple-inheritance-like flexibility without its complications (e.g. the diamond problem); (2) they enable loose coupling — client code can be written against the interface TYPE rather than a specific concrete class, so different implementations can be swapped in without changing the calling code, and unit tests can substitute mock implementations easily. This helps achieve abstraction because the interface exposes only WHAT operations are available (the method signatures) while completely hiding HOW each implementing class carries them out — client code interacts purely with the abstract contract, never needing to know or depend on the concrete implementation details behind it.
2021 · Paper-I · Q6(b) (6 marks)
Question
Differentiate between overloading and overriding with the help of an example.
Solution
Overloading (compile-time/static polymorphism): defining multiple methods with the SAME name in the SAME class (or scope), differing in their parameter list (number, type, or order of parameters). The compiler decides which version to call based on the arguments used at the call site.
class Calculator {
public:
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
};
Overriding (runtime/dynamic polymorphism): a derived (child) class provides its OWN implementation of a method that is already defined (as virtual in C++) in its base class, with the SAME signature. The version actually executed is determined at runtime based on the object's actual type.
class Animal { public: virtual void sound() { cout << "Some sound"; } };
class Dog : public Animal { public: void sound() override { cout << "Bark"; } };
Key difference: overloading is resolved at compile time based on argument types within one class; overriding is resolved at runtime based on the actual object type across a base-derived class hierarchy.
2020 · Paper-I · Q2(c) (5 marks)
Question
Explain the concept of abstract class with an example
Solution
An abstract class is a class that cannot be instantiated on its own and is designed only to be inherited from. It typically contains at least one pure virtual function (in C++, declared as virtual returnType func() = 0;) that has no implementation in the abstract class itself — derived classes are FORCED to override and implement it before they can be instantiated. Abstract classes are used to define a common interface/contract and shared partial implementation for a family of related classes, supporting polymorphism.
Example:
class Shape {
public:
virtual double area() = 0; // pure virtual -> Shape is abstract
virtual void draw() { cout << "Drawing a shape" << endl; }
};
class Circle : public Shape {
double radius;
public:
Circle(double r): radius(r) {}
double area() override { return 3.14159 * radius * radius; }
};
Here Shape cannot be instantiated directly (Shape s; is a compile error), but Circle can be, since it implements area(). A Shape* pointer can then point to any concrete subclass, calling the correct overridden area() at runtime (polymorphism).
2020 · Paper-I · Q4(a) (8 marks)
Question
Write a program to perform mathematical operations of addition, subtraction and multiplication on complex numbers. Each operation should be supported by a separate method.
Solution
#include <iostream>
using namespace std;
class Complex {
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
Complex add(const Complex& o) { return Complex(real + o.real, imag + o.imag); }
Complex subtract(const Complex& o) { return Complex(real - o.real, imag - o.imag); }
Complex multiply(const Complex& o) {
return Complex(real * o.real - imag * o.imag, real * o.imag + imag * o.real);
}
void display() {
cout << real << (imag >= 0 ? " + " : " - ") << (imag >= 0 ? imag : -imag) << "i" << endl;
}
};
int main() {
Complex c1(3, 4), c2(1, 2);
cout << "Sum: "; c1.add(c2).display();
cout << "Difference: "; c1.subtract(c2).display();
cout << "Product: "; c1.multiply(c2).display();
return 0;
}
For (3+4i) and (1+2i): Sum = 4 + 6i, Difference = 2 + 2i, Product = (3·1−4·2) + (3·2+4·1)i = -5 + 10i.
2020 · Paper-I · Q4(b) (4 marks)
Question
How object encapsulation is useful? Explain.
Solution
Encapsulation bundles an object's data (attributes) together with the methods that operate on that data, and restricts direct external access to the internal state (typically via private/protected access modifiers), exposing only a controlled public interface (getters/setters/public methods). It is useful because: (1) it protects data integrity — an object's internal invariants can only be changed through validated methods, preventing invalid states (e.g. a negative balance); (2) it hides implementation details, so the internal representation can be changed later without breaking client code that only uses the public interface (information hiding); (3) it reduces coupling between modules, since other code depends only on the stable public interface, not internal fields; and (4) it makes large systems easier to maintain and debug, since bugs affecting an object's state are confined to that object's own methods.
2020 · Paper-I · Q5(a) (5 marks)
Question
A university maintains records for students, Faculty, and academic record. Following three classes are part of the system: Student(ID,Name,Age,Address,Contact,Program,CGPA), Teachers(ID,Name,Age,Address,Highest Degree,Subjects,Salary), Courses(Semester,Course Code,Student ID,Teacher ID,Grade). All the data is stored in files. Draw a class diagram to represent the three classes and their relationships
Solution
Class diagram (described, since it cannot be drawn graphically):
+-------------------+ +--------------------+ +--------------------+
| Student | | Course | | Teacher |
+-------------------+ +--------------------+ +--------------------+
| -ID: int |1 *| -Semester: int |* 1| -ID: int |
| -Name: string |--------| -CourseCode: string |--------| -Name: string |
| -Age: int | | -StudentID: int (FK)| | -Age: int |
| -Address: string | | -TeacherID: int (FK)| | -Address: string |
| -Contact: string | | -Grade: string | | -HighestDegree |
| -Program: string | +--------------------+ | -Subjects: string |
| -CGPA: float | | -Salary: float |
+-------------------+ +--------------------+
Course is an associative/junction class: each Student can be linked to many Course records (1..) and each Teacher can be linked to many Course records (1..), so Course realises a many-to-many relationship between Student and Teacher, carrying its own attributes (Semester, Grade) — exactly analogous to a 'gerund'/associative entity in ER modelling.
2020 · Paper-I · Q5(b) (15 marks)
Question
Write C++ programs to compute following: i. Add a student ii. Add a course iii. Find a student with respect to CGPA iv. Add a Teacher v. Update a student
Solution
Since 'all the data is stored in files', each operation is implemented using binary file I/O (fstream) on fixed-size struct records:
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
struct Student { int ID; char Name[50]; int Age; char Address[100];
char Contact[20]; char Program[30]; float CGPA; };
struct Teacher { int ID; char Name[50]; int Age; char Address[100];
char HighestDegree[30]; char Subjects[100]; float Salary; };
struct Course { int Semester; char CourseCode[10]; int StudentID; int TeacherID; char Grade[3]; };
// i. Add a student
void addStudent(const Student& s) {
ofstream fout("students.dat", ios::binary | ios::app);
fout.write(reinterpret_cast<const char*>(&s), sizeof(Student));
}
// ii. Add a course
void addCourse(const Course& c) {
ofstream fout("courses.dat", ios::binary | ios::app);
fout.write(reinterpret_cast<const char*>(&c), sizeof(Course));
}
// iii. Find a student with respect to CGPA
void findStudentByCGPA(float targetCGPA) {
Student s; ifstream fin("students.dat", ios::binary); bool found = false;
while (fin.read(reinterpret_cast<char*>(&s), sizeof(Student))) {
if (s.CGPA == targetCGPA) {
cout << "ID:" << s.ID << " Name:" << s.Name << " CGPA:" << s.CGPA << endl;
found = true;
}
}
if (!found) cout << "No student found with that CGPA" << endl;
}
// iv. Add a Teacher
void addTeacher(const Teacher& t) {
ofstream fout("teachers.dat", ios::binary | ios::app);
fout.write(reinterpret_cast<const char*>(&t), sizeof(Teacher));
}
// v. Update a student (by ID, updates CGPA as an example field)
void updateStudent(int id, float newCGPA) {
fstream f("students.dat", ios::binary | ios::in | ios::out);
Student s;
while (f.read(reinterpret_cast<char*>(&s), sizeof(Student))) {
if (s.ID == id) {
s.CGPA = newCGPA;
f.seekp(-(int)sizeof(Student), ios::cur);
f.write(reinterpret_cast<char*>(&s), sizeof(Student));
break;
}
}
}
int main() {
addStudent({1, "Ali Khan", 20, "Lahore", "03001234567", "BSCS", 3.5f});
addCourse({5, "CS301", 1, 10, "A"});
findStudentByCGPA(3.5f);
addTeacher({10, "Dr. Ahmed", 45, "Lahore", "PhD", "Databases", 150000});
updateStudent(1, 3.8f);
return 0;
}
Each function corresponds to one required operation, all persisting to their respective binary files as the question specifies data is file-based.
2019 · Paper-I · Q4(a) (10 marks)
Question
How do the OOP paradigm can be associated with the real-world problems? Explain.
Solution
Object-oriented programming models software as a collection of interacting 'objects', each combining state (attributes) and behaviour (methods) — mirroring how real-world entities naturally are: a car has attributes (color, speed) and behaviours (accelerate, brake). This natural correspondence gives OOP several advantages when solving real-world problems:
- Classes map directly onto real-world entity types (Customer, Order, Account), so system analysis and design vocabulary matches the domain, easing communication with stakeholders.
- Inheritance mirrors natural taxonomies/specialisation (e.g. SavingsAccount is-a Account), enabling reuse and hierarchical organisation the same way real-world categories are organised.
- Encapsulation mirrors the real-world idea that an object's internal workings are hidden and it is only interacted with through a well-defined interface (e.g. you drive a car through pedals/steering without knowing engine internals).
- Polymorphism mirrors how different real objects can respond differently to the same general action (e.g. 'make a sound' behaves differently for a Dog and a Cat object).
- Association/aggregation/composition relationships (has-a, part-of) model real-world relationships between entities (a Car has an Engine; a University has Departments). Because of this direct mapping, OOP makes large, complex real-world systems easier to model, understand, extend and maintain than purely procedural, function-centric approaches.
2019 · Paper-I · Q4(b) (10 marks)
Question
Discuss critical reasons given by the professionals for not supporting the OOP paradigm.
Solution
Criticisms levelled against OOP by practitioners include:
- Performance overhead: dynamic dispatch (virtual functions), extra indirection, and object header metadata add runtime and memory overhead compared to plain procedural code, which matters in performance-critical/embedded systems.
- Over-engineering/complexity: deep inheritance hierarchies and excessive abstraction (design-pattern overuse) can make code harder to read, trace and debug than a straightforward procedural solution.
- Poor fit for some domains: purely mathematical/scientific computation and simple data-transformation pipelines are often more naturally and efficiently expressed in a procedural or functional style, where OOP adds ceremony without benefit.
- Fragile base class problem: changes to a base class can silently break derived classes, especially across module/library boundaries.
- State management: mutable object state distributed across many interacting objects makes reasoning about program correctness and concurrency harder than with stateless functional approaches.
- Steeper learning curve and design overhead: correctly identifying classes, responsibilities and relationships (good OOD) takes real design skill; poorly designed OOP code can be worse than well-written procedural code.
- Testing difficulty: hidden state and complex object graphs can make unit testing and mocking harder than testing pure functions. These are reasons some professionals prefer procedural, functional, or multi-paradigm approaches for specific classes of problems rather than defaulting to OOP.
2018 · Paper-I · Q4(a) (5 marks)
Question
Consider the inheritance hierarchy shown below (A is the base class; B and C derive from A; D and E derive from C). Each part of this question is independent. In which class(es) would it make most sense to have protected members? Which class(es) would be able to access those protected members directly?
Solution
It makes most sense to declare protected members in base classes that are designed to be extended by derived classes but should hide their internals from the outside world - here that is class A (the root, extended by B and C) and class C (itself extended by D and E).
- Protected members declared in class A would be directly accessible by A itself and by ALL classes that derive from A, directly or transitively: B, C, D, and E.
- Protected members declared in class C would be directly accessible by C itself and by classes deriving from C: D and E (but NOT by A or B, since they are not descendants of C).
In general, protected access is used precisely when a class wants to share implementation details with its subclass family while still keeping those details inaccessible to unrelated/external code (unlike public members).
2018 · Paper-I · Q4(b) (5 marks)
Question
Which class(es) can access private members of class C directly?
Solution
Only class C itself can directly access its own private members. Private members are not inherited-in-an-accessible-way by any derived class - even D and E (which derive directly from C) cannot access C's private members directly; they can only access them indirectly through C's own public or protected member functions (getters/setters), unless C explicitly declares D, E, or some other class/function as a 'friend', which is not indicated here. So the answer is: C only (plus any friend classes/functions of C, if explicitly declared, which none are in this hierarchy).
2018 · Paper-I · Q4(c) (5 marks)
Question
Suppose class C contains a pure virtual function. Suppose we wish to instantiate objects of this hierarchy. Which class(es) are or could be abstract and which are concrete?
Solution
A class containing a pure virtual function that is not overridden/implemented within it is an abstract class and cannot be instantiated.
- Class C itself is abstract (it declares the pure virtual function without implementing it).
- Classes D and E (deriving from C) are abstract UNLESS they each provide a concrete override/implementation of that pure virtual function - if they do override it, they become concrete and can be instantiated; if they don't, they remain abstract too (the 'pure-ness' propagates down until someone implements it).
- Class A is concrete, since the pure virtual function is introduced in C, not in A, and A has no relationship requiring it to implement anything from C's branch.
- Class B is also concrete, for the same reason (B is a sibling of C under A, unrelated to C's pure virtual function).
Summary: C is abstract; A and B are concrete; D and E are concrete only if they implement the pure virtual function, otherwise they remain abstract.
2018 · Paper-I · Q4(d) (5 marks)
Question
Consider the following list of classes: Car, SteeringWheel, Vehicle, Van, Minivan, AudioSystem, ParkingLot. Your task is to describe all of the is-a and has-a relationships between these classes. Include an inheritance hierarchy for all classes that fit. Fill in the table with is-a or has-a relationship while leaving the cells empty where no relation is applicable.
Solution
Inheritance hierarchy (is-a relationships):
Vehicle
/ |
Car Van (other vehicle types)
|
MiniVan
- Car is-a Vehicle
- Van is-a Vehicle
- MiniVan is-a Van (and transitively is-a Vehicle)
Composition (has-a relationships):
- Vehicle has-a SteeringWheel (every vehicle in this hierarchy has one)
- Vehicle has-a AudioSystem (typically included, so inherited conceptually by Car, Van, MiniVan)
- ParkingLot has-a Vehicle (a parking lot contains/holds multiple vehicle instances, e.g. Car, Van, MiniVan parked in it)
Filled relationship table (row -> column): | Vehicle | Car | Van | MiniVan | SteeringWheel | AudioSystem | ParkingLot Vehicle | | | | | has-a | has-a | Car | is-a | | | | | | Van | is-a | | | | | | MiniVan | is-a | | is-a | | | | SteeringWheel| | | | | | | AudioSystem | | | | | | | ParkingLot | | has-a| has-a| has-a | | |
(All other cells are left empty, as no direct is-a or has-a relationship applies between those pairs - e.g. SteeringWheel and AudioSystem have no relation to each other.)
2016 · Paper-I · Q2(c) (8 marks)
Question
Suppose you are trying to build a Student Registration System using object oriented approach. Objective of the system is to store information of course registration of students and their course results. In C++ syntax define following classes with suitable attributes each having getdata() and showdata() member functions along with suitable constructor functions; Person, Teacher, Student, Course, Section, Allocation (containing information about allocation of a course to a teacher in a semester), Registration (containing information about registration of a student in course, this class will also save course result information of the student). You can add more classes which you think suitable for the system.
Solution
class Person {
protected:
int id;
char name[50];
char address[100];
public:
Person() {}
Person(int i, string n, string a) { id=i; }
void getdata();
void showdata();
};
class Teacher : public Person {
char designation[30];
float salary;
public:
Teacher() {}
void getdata();
void showdata();
};
class Student : public Person {
char rollNo[20];
char program[30];
public:
Student() {}
void getdata();
void showdata();
};
class Course {
char courseCode[10];
char title[50];
int creditHours;
public:
Course() {}
Course(string code, string t, int ch) {}
void getdata();
void showdata();
};
class Section {
char sectionId[10];
Course course;
int semester;
public:
Section() {}
void getdata();
void showdata();
};
class Allocation {
Teacher teacher;
Section section;
char semesterName[20];
public:
Allocation() {}
void getdata();
void showdata();
};
class Registration {
Student student;
Section section;
float marksObtained;
char grade[3];
public:
Registration() {}
void getdata();
void showdata();
};
Design notes: Person is the common base class holding shared attributes; Teacher and Student inherit from it (avoids duplication - a core OOP inheritance principle). Allocation links a Teacher to a Section for a semester, while Registration links a Student to a Section and stores the result, satisfying the system's stated objectives.
2016 · Paper-I · Q3(c) (8 marks)
Question
Suppose you are trying to build a Library Information System using object oriented approach. System should be able to keep track of books issued and returned. It should be able to calculate fine if a book is late. Currently you are required to design classes i.e. not to write C++ code but describe which class you will define, which data members you will add, which member functions you will include in those classes. Define only suitable classes and suitable attributes (data members) in C++ syntax. Names of some classes are like, Student, Teacher, Book, IssueBook etc. You can (but not required to) extend this list of four classes if you think of some other suitable classes.
Solution
class Person {
protected:
int id;
char name[50];
char contactNo[15];
};
class Student : public Person {
char rollNo[20];
char department[30];
};
class Teacher : public Person {
char designation[30];
};
class Book {
char isbn[20];
char title[50];
char author[50];
int totalCopies;
int availableCopies;
};
class IssueBook {
Book book;
Person* borrower; // Student or Teacher
char issueDate[12];
char dueDate[12];
char returnDate[12];
bool isReturned;
};
class Fine {
IssueBook issueRecord;
int daysLate;
float amount;
float perDayRate;
};
Rationale: Person is a base class shared by Student and Teacher (both can borrow books) - a clean use of inheritance to avoid duplicating id/name/contact fields. Book holds catalogue information and tracks how many copies are currently available. IssueBook records one borrowing transaction (who borrowed what, when, and whether it was returned). Fine is a separate class tied to an IssueBook record so that the late-fee calculation (daysLate * perDayRate) is cleanly encapsulated and can be reused/extended (e.g. different rates per book category) without cluttering IssueBook itself.
2016 · Paper-I · Q4(c) (8 marks)
Question
Suppose you are designing software for a Medical Store, using object oriented techniques. Decide and define classes (like 'Person', 'Customer', 'Salesman', 'Purchase' etc.) and their attributes in C++ or Java syntax. You are not required to write any member functions and main programme. You must use inheritance where suitable.
Solution
class Person {
protected:
int id;
char name[50];
char phone[15];
char address[100];
};
class Customer : public Person {
char customerType[20]; // walk-in / regular
};
class Salesman : public Person {
float salary;
char shift[10];
};
class Medicine {
char medicineCode[15];
char name[50];
char manufacturer[50];
float unitPrice;
int stockQuantity;
char expiryDate[12];
};
class Purchase {
Customer customer;
Salesman servedBy;
char purchaseDate[12];
float totalAmount;
};
class PurchaseItem {
Purchase purchase;
Medicine medicine;
int quantity;
float subTotal;
};
class Supplier {
char supplierName[50];
char contactNo[15];
};
class StockPurchase {
Supplier supplier;
Medicine medicine;
int quantityReceived;
char dateReceived[12];
};
Customer and Salesman both inherit common personal-detail attributes (id, name, phone, address) from the base class Person, avoiding duplication - the required use of inheritance. Purchase represents one sale transaction (linking a Customer and the Salesman who served them); PurchaseItem records each medicine sold within that transaction so a single purchase can contain multiple medicines. Supplier and StockPurchase extend the design to also model restocking of medicines.
Part B — MCQs
2026 · Paper-I · Q4 (MCQ)
A class needs to allow different implementations of a method. What concept to use?
- A. Inheritance
- B. Polymorphism
- C. Encapsulation
- D. None of these
Answer: B
Explanation: Polymorphism allows the same method name/signature to have different implementations in different classes (e.g., via overriding), so the correct version runs depending on the actual object type.
2026 · Paper-I · Q5 (MCQ)
What is the purpose of using virtual functions in inheritance?
- A. To prevent overriding
- B. To hide members
- C. To enable polymorphism
- D. None of these
Answer: C
Explanation: Virtual functions allow a call through a base-class pointer/reference to resolve to the derived class's overridden implementation at runtime, which is the mechanism that enables runtime polymorphism.
2026 · Paper-I · Q9 (MCQ)
Which operator is typically overloaded for stream insertion?
- A. <<
- B. >>
- C. +
- D. None of these
Answer: A
Explanation: The << (left shift) operator is conventionally overloaded in C++ as the stream insertion operator to send data out to an ostream such as cout.
2026 · Paper-I · Q10 (MCQ)
What does the Standard Template Library (STL) provide in C++?
- A. Pre-defined classes for GUI
- B. Pre-defined functions for math operations
- C. Pre-defined containers and algorithms
- D. None of these
Answer: C
Explanation: The STL provides generic, reusable container classes (vector, list, map, set, etc.) along with a rich set of generic algorithms (sort, find, etc.) and iterators to work with them.
2025 · Paper-I · Q7 (MCQ)
Which programming language uses the concept of friend function?
- A. Python
- B. Java
- C. C#
- D. None of these
Answer: D
Explanation: The friend function/class mechanism, which lets a non-member function access private/protected members, is a C++-specific feature; Python, Java and C# do not support it.
2024 · Paper-I · Q6 (MCQ)
Which was the first purely object-oriented programming language developed?
- A. Java
- B. C++
- C. SmallTalk
- D. None of these
Answer: C
Explanation: Smalltalk, developed in the 1970s, was designed from the ground up as a purely object-oriented language, where everything (including primitives) is an object, predating both C++ and Java.
2024 · Paper-I · Q7 (MCQ)
Which language does not allow for inheritance in all four forms?
- A. Kotlin
- B. Java
- C. C++
- D. None of these
Answer: B
Explanation: Java does not support multiple inheritance of classes (one of the four classical inheritance forms: single, multilevel, hierarchical, multiple), allowing multiple inheritance only through interfaces, whereas C++ supports all four forms directly for classes.
2024 · Paper-I · Q8 (MCQ)
Which programming language allows for polymorphism but not classes?
- A. C++ programming language
- B. Java programming language
- C. Ada programming language
- D. None of these
Answer: C
Explanation: Ada supports polymorphism through generics (parametric polymorphism) and tagged types without relying on a traditional class-based object model like C++ or Java.