Introduction to OOP & Core Concepts
1. Limitations of Procedural Programming
In conventional procedural programming (like standard C or Pascal), programs are organized around actions and procedures (functions). Data is treated as a secondary entity that flows freely between functions via global and passed variables.
- Lack of Data Security: Global data can be inadvertently corrupted by any function in the program.
- Poor Real-World Modeling: Real-world entities have both state (attributes) and behavior (actions). Procedures separate them.
- Difficult Maintenance: Changing a core data structure requires modifying every function that touches that data.
2. The Genesis of C++
To overcome procedural limitations, Bjarne Stroustrup at Bell Laboratories developed C++ in 1979 (initially named βC with Classesβ and renamed to C++ in 1983). The ++ operator signifies that C++ is an incremental evolution of C, adding Object-Oriented capabilities while retaining C's blazing execution efficiency.
3. Fundamental Features of Object-Oriented Programming (OOP)
- Objects: Basic runtime entities in an object-oriented system that encapsulate state (data) and behavior (functions).
- Classes: The blueprint, template, or prototype from which individual objects are instantiated.
- Data Abstraction: Representing essential features of an entity while hiding underlying background complexities and implementation mechanics.
- Data Encapsulation: Wrapping data and functions into a single unit (class) and preventing unauthorized direct access (Data Hiding).
- Inheritance: The mechanism by which objects of one class acquire the properties and methods of another class, promoting code reuse.
- Polymorphism: The ability of a message or function to be displayed or executed in more than one form (Function Overloading, Operator Overloading, and Virtual Functions).
- Dynamic Binding: Linking a procedure call to the code to be executed at run-time rather than compile-time.
- Message Passing: Objects communicate with one another by sending and receiving specifications of requests (function invocations).
#include <iostream> #include <string> using namespace std; // Class encapsulating data and operations class BankAccount { private: string accountHolder; double balance; // Data Hiding: Cannot be modified directly outside public: // Constructor BankAccount(string holder, double initialDeposit) { accountHolder = holder; balance = initialDeposit; } void deposit(double amount) { if (amount > 0) { balance += amount; cout << "Deposited Rs " << amount << " | New Balance: Rs " << balance << endl; } } void displaySummary() const { cout << "Holder: " << accountHolder << " | Balance: Rs " << balance << endl; } }; int main() { BankAccount acc1("Simranjit Singh", 15000.0); acc1.displaySummary(); acc1.deposit(5000.0); return 0; }
List the four pillars of OOP (Abstraction, Encapsulation, Inheritance, Polymorphism) and give a real-world banking analogy for each pillar.
Procedure Oriented (C) vs OOP (C++)
1. Paradigm Architectural Differences
A programming paradigm determines how a software developer conceptualizes program execution, state changes, and logic flow:
| Dimension | Procedure Oriented (C) | Object Oriented (C++) |
|---|---|---|
| Core Philosophy | Emphasis on algorithms, procedures, and actions | Emphasis on data items and real-world objects |
| Architectural Approach | Top-Down Design (decomposes program into subroutines) | Bottom-Up Design (composes program from autonomous classes) |
| Data Security & Hiding | No data hiding; data moves freely; vulnerable to corruption | Data is private; access controlled by access specifiers |
| Code Reuse | Limited to calling shared library functions | Extensive through Inheritance and templates |
| Polymorphism | Not supported (every function must have a unique identifier) | Supported (Function Overloading, Operator Overloading, Virtual Functions) |
| Memory Management | Manual via malloc() and free() |
Type-safe via new, delete, and constructors/destructors |
2. Code Contrast: Modeling a Student Record
/* C Style (Procedural): Data separated from function */ struct StudentC { int id; float marks; }; void printStudentC(struct StudentC s) { printf("ID: %d, Marks: %.2f\n", s.id, s.marks); } /* C++ Style (OOP): Data and functions encapsulated in single unit */ class StudentCPP { private: int id; float marks; public: StudentCPP(int i, float m) : id(i), marks(m) {} void display() const { cout << "ID: " << id << ", Marks: " << marks << endl; } };
In university examinations, questions frequently state: βCompare and contrast Procedure-Oriented Programming and Object-Oriented Programming with diagrams.β Write a 5-point comparative summary emphasizing data hiding and bottom-up methodology.
C++ Program Structure & Components
1. Basic Components of a C++ Program
A C++ source file consists of standard structural elements:
- Preprocessor Directives: Lines starting with
#(e.g.#include <iostream>) processed before compilation starts. - Namespaces: Prevents identifier naming collisions. The directive
using namespace std;imports the Standard C++ namespace. - The
main()Entry Function: In modern C++,main()must return an integer (int main()). Returning 0 signifies success. - Standard Streams:
cout: Standard output stream associated with console (used with insertion operator<<).cin: Standard input stream associated with keyboard (used with extraction operator>>).cerr/clog: Unbuffered / buffered standard error streams.
2. Anatomy of a Clean C++ Program
// 1. Include header for console I/O #include <iostream> #include <iomanip> // For stream formatting manipulators // 2. Use standard namespace using namespace std; // 3. Global constant const double EXAM_PASS_THRESHOLD = 40.0; // 4. Main function entry point int main() { string studentName; double marks; cout << "Enter Student Name: "; cin >> studentName; cout << "Enter Marks Obtained: "; cin >> marks; cout << "\n=== Academic Status Evaluation ===" << endl; cout << "Student: " << studentName << endl; cout << "Score: " << fixed << setprecision(2) << marks << "%" << endl; cout << "Result: " << ((marks >= EXAM_PASS_THRESHOLD) ? "PASSED" : "REAPPEAR") << endl; return 0; }
Explain the role of stream manipulators: endl, setw(int), and setprecision(int). Write a short C++ snippet printing a tabular price list.
Compiling & Executing C++ Programs
1. The C++ Compilation & Build Process
C++ is a compiled language that translates high-level human-readable code directly into native machine code. The build pipeline consists of 4 distinct phases:
- Preprocessing (
g++ -E): Evaluates all#includeand#definedirectives, stripping comments and generating an expanded pure translation unit. - Compilation (
g++ -S): Parses the preprocessed code, checks grammar and syntax, and translates the code into assembly instructions specific to the CPU architecture. - Assembly (
g++ -c): Assembles assembly instructions into binary object code (.o/.obj) containing machine instructions with unresolved external addresses. - Linking (
g++): Links object files together with the C++ Standard Library runtime (libstdc++), resolving function addresses to produce the final executable.
2. Command-Line Compilation Using G++
# Basic compilation
g++ main.cpp -o main
# Recommended flags: Enable all warnings & strict C++ standard
g++ -Wall -Wextra -std=c++17 main.cpp -o main
# Execution
./main # On Linux / macOS
main.exe # On Windows
Compile a C++ program using the -c flag to generate an object file (program.o). Then invoke the linker separately to produce the final binary. Note the purpose of intermediate object files in large multi-file projects.
C++ Extensions: References & Inlines
1. Reference Variables in C++
A Reference Variable is an alias (an alternative name) for an existing variable. It is declared using the & symbol after the type:
int total = 100;
int &refTotal = total; // refTotal is an alias to total (same memory address)
- A reference must be initialized immediately upon declaration.
- A reference cannot be reseated to refer to another variable later.
- There is no such thing as a
NULLreference (making references safer than pointers).
2. Call by Reference Using C++ References
In C, passing by reference required explicit pointer syntax (&var and *ptr). In C++, functions can accept reference parameters cleanly:
#include <iostream> using namespace std; // Clean Call by Reference without pointer syntax void swapNumbers(int &x, int &y) { int temp = x; x = y; y = temp; } // Inline Function: Compiler replaces call with actual code inline inline int cube(int s) { return s * s * s; } int main() { int a = 10, b = 25; cout << "Before swap: a = " << a << ", b = " << b << endl; swapNumbers(a, b); cout << "After swap: a = " << a << ", b = " << b << endl; cout << "Cube of 5: " << cube(5) << endl; // Dynamic Memory Allocation via new and delete int *dynVal = new int(500); // Allocates heap memory and initializes cout << "Dynamic Heap Value: " << *dynVal << endl; delete dynVal; // Deallocates memory return 0; }
3. The Scope Resolution Operator (::)
In C++, if a local variable shadows a global variable of the same name, the global variable can be accessed using the Scope Resolution Operator: ::globalVar.
Write a program declaring a global variable int count = 50;. Inside main(), declare a local variable int count = 10;. Print both using the Scope Resolution Operator.
Defining Classes & Object Declaration
1. What is a Class?
A class is a user-defined blueprint or prototype that binds data variables (called data members) and the functions that manipulate them (called member functions) into a unified abstract data type.
2. General Class Syntax
class ClassName {
private:
// Variable and function declarations accessible only inside this class
public:
// Interface functions accessible from outside the class
protected:
// Accessible inside this class and by derived subclasses
}; // Note the required semicolon after the closing brace!
3. Declaration of Objects & Memory Allocation
When a class is defined, no memory is allocated for its variables. Memory is allocated only when an object (an instance of the class) is declared:
ClassName obj1, obj2; // Instantiates two distinct objects on the stack
#include <iostream> using namespace std; class Rectangle { private: double length; double width; public: void setDimensions(double l, double w) { if (l > 0 && w > 0) { length = l; width = w; } } double calculateArea() { return length * width; } }; int main() { Rectangle r1, r2; // Object declarations r1.setDimensions(10.0, 5.0); r2.setDimensions(8.0, 4.5); cout << "Area of Rectangle 1: " << r1.calculateArea() << endl; cout << "Area of Rectangle 2: " << r2.calculateArea() << endl; return 0; }
Define a class Circle with a private member radius. Provide public member functions to set the radius and calculate the circumference.
Access Specifiers & Member Access
1. Access Specifiers in C++
Access specifiers enforce the principle of Data Hiding and encapsulation:
private: Members declared private are accessible only by member functions inside the same class (and friend functions). By default, all members in a C++ class are private.public:Members declared public are accessible from any part of the program where the object is visible. Public functions form the class interface.protected:Members declared protected are inaccessible to outside callers, but are accessible by derived subclasses through inheritance (Unit-III).
2. Accessing Members from Objects (The Dot Operator)
Public members of an object are accessed using the dot (.) member selection operator:
objectName.publicMemberFunction();
Attempting to access a private member directly (e.g. r1.length = 50;) results in a compile-time error.
#include <iostream> using namespace std; class Employee { private: int empId; double basicSalary; // Protected against external tampering public: void initialize(int id, double salary) { empId = id; basicSalary = salary; } double getNetSalary() const { double hra = basicSalary * 0.20; // 20% HRA double da = basicSalary * 0.10; // 10% DA return basicSalary + hra + da; } void displayPaySlip() const { cout << "Employee ID: " << empId << endl; cout << "Basic: Rs " << basicSalary << " | Net Salary: Rs " << getNetSalary() << endl; } }; int main() { Employee e1; e1.initialize(501, 45000.0); e1.displayPaySlip(); // e1.basicSalary = 90000; // COMPILATION ERROR: 'basicSalary' is private! return 0; }
Explain the difference between a C++ struct and a C++ class. (Hint: What is the default access specifier in each?).
Member Functions & Array of Objects
1. Defining Member Functions: Inside vs. Outside Class
Member functions can be defined in two ways:
- Inside the Class Definition: Functions defined inside the class body are automatically treated by the compiler as
inlinefunctions. - Outside the Class Definition: Declared inside the class prototype, and defined outside using the Scope Resolution Operator (
::):return_type ClassName::functionName(parameter_list) { // function body }
2. Array of Objects
Just as we can create an array of integers, we can instantiate an array of user-defined class objects. Each element of the array is an independent object having its own state:
#include <iostream> #include <string> using namespace std; class Student { private: int rollNo; string name; float percentage; public: // Member function defined outside class void getData(int r, string n, float p); void putData() const; }; // Definition outside class using Scope Resolution Operator void Student::getData(int r, string n, float p) { rollNo = r; name = n; percentage = p; } void Student::putData() const { cout << "Roll: " << rollNo << " | Name: " << name << " | Percentage: " << percentage << "%" << endl; } int main() { // Array of 3 Student objects Student bcaBatch[3]; bcaBatch[0].getData(101, "Kuldeep", 89.5f); bcaBatch[1].getData(102, "Manpreet", 93.0f); bcaBatch[2].getData(103, "Gurvinder", 81.0f); cout << "=== PTU BCA Class Roster ===" << endl; for (int i = 0; i < 3; i++) { bcaBatch[i].putData(); } return 0; }
Define a class Book with bookId, title, and price. Create an array of 5 books and write a function that finds and displays the most expensive book in the array.
Constructors & Parameterized Constructors
1. Introduction to Constructors
A Constructor is a special member function whose primary responsibility is to initialize the data members of an object during instantiation.
- It has the exact same name as its enclosing class.
- It has no return type (not even
void). - It is invoked automatically whenever an object of that class is created.
- It should be placed in the
publicsection of the class so objects can be created freely.
2. Types of Constructors
- Default Constructor: A constructor that takes no arguments. If no constructors are written by the programmer, the compiler generates a default constructor automatically.
- Parameterized Constructor: Takes parameters to initialize custom initial values for each object instance.
- Multiple Constructors (Constructor Overloading): Defining multiple constructors with different argument lists in the same class.
#include <iostream> using namespace std; class ComplexNumber { private: double real; double imag; public: // 1. Default Constructor ComplexNumber() { real = 0.0; imag = 0.0; } // 2. Parameterized Constructor with two arguments ComplexNumber(double r, double i) { real = r; imag = i; } // 3. Overloaded Constructor with single argument ComplexNumber(double val) { real = val; imag = val; } void print() const { cout << real << " + " << imag << "i" << endl; } }; int main() { ComplexNumber c1; // Invokes default constructor ComplexNumber c2(4.5, 2.8); // Invokes parameterized constructor ComplexNumber c3(7.0); // Invokes single-argument constructor cout << "c1: "; c1.print(); cout << "c2: "; c2.print(); cout << "c3: "; c3.print(); return 0; }
Write a class Time with hours and minutes. Implement a default constructor setting time to 00:00, and a parameterized constructor that normalizes minutes if ≥ 60.
Copy Constructor & Dynamic Initialization
1. The Copy Constructor
A Copy Constructor initializes an object using another already-existing object of the same class:
ClassName(const ClassName &sourceObj);
ClassName(ClassName sourceObj)), passing the argument would itself require invoking the copy constructor, leading to an infinite recursion and compilation failure.
2. Shallow Copy vs. Deep Copy
- Shallow Copy: Bit-by-bit member copy (the default). If the class contains raw pointers to heap memory, both objects end up pointing to the same dynamic memory, causing double-free crashes!
- Deep Copy: Allocates a brand-new heap buffer and copies the actual values into the new buffer independently.
3. Dynamic Initialization of Objects
Dynamic initialization occurs when an object's initial values are provided at runtime through dynamic expressions, calculations, or user inputs.
#include <iostream> using namespace std; class IntegerArray { private: int *data; int size; public: // Parameterized Constructor IntegerArray(int s) { size = s; data = new int[size]; // Dynamic memory allocation for (int i = 0; i < size; i++) data[i] = 0; } // User-defined Copy Constructor (Deep Copy) IntegerArray(const IntegerArray &source) { size = source.size; data = new int[size]; // Allocate fresh memory buffer for (int i = 0; i < size; i++) { data[i] = source.data[i]; // Copy elements } } void set(int index, int value) { data[index] = value; } int get(int index) const { return data[index]; } // Destructor to free heap memory ~IntegerArray() { delete[] data; } }; int main() { IntegerArray arr1(3); arr1.set(0, 99); IntegerArray arr2 = arr1; // Invokes Copy Constructor arr2.set(0, 500); // Modifying arr2 does not affect arr1! cout << "arr1[0]: " << arr1.get(0) << " (remains 99)" << endl; cout << "arr2[0]: " << arr2.get(0) << " (modified to 500)" << endl; return 0; }
Explain the three situations where the copy constructor is automatically called: (1) Initializing one object from another, (2) Passing an object by value to a function, (3) Returning an object by value from a function.
Destructors in C++
1. Introduction to Destructors
A Destructor is a special member function that is executed automatically whenever an object goes out of scope or is explicitly destroyed via delete.
- It has the same name as the class, preceded by a tilde symbol (
~):~ClassName(). - It takes no arguments and returns no value.
- A class can have only one destructor (destructors cannot be overloaded).
2. The Order of Constructor and Destructor Execution
Constructors are executed in order of object creation. Destructors are executed in the exact Reverse Order (LIFO — Last In, First Out), mirroring the function call stack.
#include <iostream> using namespace std; class Tracer { private: int id; public: Tracer(int val) : id(val) { cout << "Constructing Object #" << id << endl; } ~Tracer() { cout << "Destructing Object #" << id << endl; } }; int main() { cout << "Entering main block..." << endl; Tracer t1(1); Tracer t2(2); { cout << "\nEntering inner block..." << endl; Tracer t3(3); cout << "Exiting inner block..." << endl; } // t3 goes out of scope and is destroyed here! cout << "\nExiting main block..." << endl; return 0; } // t2, then t1 destroyed here in reverse order
Explain Resource Acquisition Is Initialization (RAII). Write a class FileHandler that opens a file in its constructor and automatically calls fclose() in its destructor.
Inheritance Basics & Visibility Modes
1. Introduction to Inheritance
Inheritance is the mechanism of deriving a new class (the Derived Class or Subclass) from an existing class (the Base Class or Superclass). It models an IS-A relationship and eliminates redundant code.
class DerivedClass : visibility_mode BaseClass {
// derived class members
};
2. Visibility Modes in C++ Inheritance
The visibility mode determines the accessibility of base class members inside the derived class:
| Base Class Member | Public Inheritance (: public Base) |
Protected Inheritance (: protected Base) |
Private Inheritance (: private Base) |
|---|---|---|---|
private |
Not Inherited (Hidden) | Not Inherited (Hidden) | Not Inherited (Hidden) |
protected |
Becomes protected |
Becomes protected |
Becomes private |
public |
Becomes public |
Becomes protected |
Becomes private |
#include <iostream> #include <string> using namespace std; // Base Class class Person { protected: string name; int age; public: void setPersonData(string n, int a) { name = n; age = a; } }; // Public Derived Class class Student : public Person { private: int rollNo; public: void setStudentData(string n, int a, int r) { setPersonData(n, a); // Inherited base member rollNo = r; } void display() const { // Protected base members are directly accessible in derived class cout << "Name: " << name << " | Age: " << age << " | Roll No: " << rollNo << endl; } }; int main() { Student s1; s1.setStudentData("Maninder", 20, 105); s1.display(); return 0; }
Explain why private members of a base class are never directly accessible by derived class methods. How does protected solve this while still maintaining encapsulation from outside callers?
Types of Inheritance (Single, Multilevel, Hierarchical)
1. The Primary Inheritance Forms
- Single Inheritance: A derived class inherits from only one base class:
Class A → Class B - Multilevel Inheritance: A derived class acts as a base class for another derived class, forming an inheritance chain:
Class A → Class B → Class C - Hierarchical Inheritance: Multiple derived classes inherit from a single common base class:
Class A → Class BandClass A → Class C
2. Constructor Execution Order in Inheritance
Base class constructors are executed first, followed by intermediate derived classes, and finally the most derived class. Destructors execute in the exact reverse order.
#include <iostream> using namespace std; // Level 1: Grandparent Base Class class StudentInfo { protected: int rollNo; public: StudentInfo(int r) : rollNo(r) {} }; // Level 2: Parent Derived Class class ExamMarks : public StudentInfo { protected: float sub1, sub2; public: ExamMarks(int r, float m1, float m2) : StudentInfo(r), sub1(m1), sub2(m2) {} }; // Level 3: Child Derived Class class FinalReport : public ExamMarks { public: FinalReport(int r, float m1, float m2) : ExamMarks(r, m1, m2) {} void displayReportCard() const { float total = sub1 + sub2; cout << "Roll: " << rollNo << " | Sub1: " << sub1 << " | Sub2: " << sub2 << " | Total: " << total << endl; } }; int main() { FinalReport rpt(101, 85.0f, 92.0f); rpt.displayReportCard(); return 0; }
Implement Hierarchical Inheritance where a base class Employee is inherited by two derived classes: Manager and Developer. Each derived class should print its specific bonus calculation.
Multiple & Hybrid Inheritance (Diamond Problem)
1. Multiple Inheritance
In Multiple Inheritance, a derived class inherits directly from two or more distinct base classes:
class Derived : public BaseA, public BaseB { ... };
2. Hybrid Inheritance & The Diamond Problem
Hybrid Inheritance is formed by combining two or more inheritance types. A classic manifestation is Multipath Inheritance (The Diamond Problem):
Class A
/ Class B Class C
\ /
Class D
Here, Class D inherits Class A twice (once through B and once through C). This creates two identical copies of A's members inside D, causing compiler ambiguity: error: request for member 'x' is ambiguous.
3. Resolution: Virtual Base Classes
To resolve the ambiguity, classes B and C inherit Class A with the virtual keyword:
class B : virtual public A { ... };
class C : virtual public A { ... };
This guarantees that Class D receives only a single shared instance of Class A's members!
#include <iostream> using namespace std; class Student { protected: int rollNo; public: void setRoll(int r) { rollNo = r; } }; // Virtual inheritance prevents duplicate copies class AcademicTest : virtual public Student { protected: float theoryScore; public: void setTheory(float s) { theoryScore = s; } }; class SportsActivity : virtual public Student { protected: float sportsScore; public: void setSports(float s) { sportsScore = s; } }; class FinalResult : public AcademicTest, public SportsActivity { public: void displayScorecard() const { // Unambiguous access to rollNo thanks to virtual inheritance! cout << "Roll No: " << rollNo << endl; cout << "Theory: " << theoryScore << endl; cout << "Sports: " << sportsScore << endl; cout << "Total: " << (theoryScore + sportsScore) << endl; } }; int main() { FinalResult res; res.setRoll(108); res.setTheory(78.5f); res.setSports(15.0f); res.displayScorecard(); return 0; }
Explain how a Virtual Base Class pointer table operates under the hood to ensure only one base class subobject is initialized in memory.
Operator Overloading Fundamentals & Rules
1. What is Operator Overloading?
Operator Overloading is a compile-time polymorphic feature in C++ that allows existing C++ operators to be given user-defined meanings when applied to class objects (e.g. adding two ComplexNumber objects using the natural + operator).
2. General Syntax of an Operator Function
return_type operator op(argument_list) {
// operation logic
}
3. Strict Rules for Overloading Operators in C++
- Cannot Invent New Operators: Only existing C++ operators can be overloaded (e.g., you cannot invent
**for exponentiation). - Cannot Alter Precedence & Associativity: The order of operations and direction of evaluation remain immutable.
- Cannot Alter Number of Operands: A unary operator remains unary; a binary operator remains binary.
- At Least One Operand Must be a Class/User Type: You cannot redefine operators for primitive types (e.g., you cannot redefine
int + int).
Memorize these 5 operators:
- Scope Resolution Operator (
::) - Class Member Access / Dot Operator (
.) - Pointer-to-Member Operator (
.*) - Ternary Conditional Operator (
?:) - Sizeof Operator (
sizeof)
Discuss the difference between overloading an operator as a member function vs. overloading it as a non-member friend function. When is a friend function mandatory?
Overloading Unary & Binary Operators
1. Overloading Unary Operators
Unary operators operate on a single object. When overloaded as a member function, they take no arguments because they operate implicitly on *this:
#include <iostream> using namespace std; class Vector2D { private: int x, y; public: Vector2D(int xVal = 0, int yVal = 0) : x(xVal), y(yVal) {} // 1. Overload Unary Minus (-v) Vector2D operator-() const { return Vector2D(-x, -y); } // 2. Overload Binary Addition (v1 + v2) Vector2D operator+(const Vector2D &other) const { return Vector2D(x + other.x, y + other.y); } // 3. Overload Stream Insertion Operator (<<) using friend function friend ostream& operator<<(ostream &out, const Vector2D &v) { out << "(" << v.x << ", " << v.y << ")"; return out; } }; int main() { Vector2D v1(3, 4); Vector2D v2(1, 2); Vector2D vSum = v1 + v2; // Equivalent to: v1.operator+(v2) Vector2D vNeg = -v1; // Equivalent to: v1.operator-() cout << "v1: " << v1 << endl; cout << "v2: " << v2 << endl; cout << "v1 + v2: " << vSum << endl; cout << "-v1: " << vNeg << endl; return 0; }
Overload the relational equality operator == for a class Distance (feet and inches) to test if two distances are identical.
Polymorphism: Early vs Late Binding
1. The Two Faces of Polymorphism in C++
Polymorphism means βmany formsβ. In C++, polymorphism is realized across two major execution stages:
- Compile-Time Polymorphism (Static Binding / Early Binding): The compiler resolves which function or operator to invoke at compile time based on signatures and parameter types.
Examples: Function Overloading, Operator Overloading. Fast execution, but fixed at build time. - Run-Time Polymorphism (Dynamic Binding / Late Binding): The decision of which method implementation to execute is postponed until runtime based on the actual type of the object pointed to.
Examples: Virtual Functions.
2. Pointers to Derived Class Objects
In C++, a base class pointer can legally hold the address of any derived class object (an upcast). However, with early binding (default non-virtual methods), invoking a method via a base pointer executes the base class version regardless of the actual derived object pointed to!
#include <iostream> using namespace std; class Animal { public: void makeSound() const { cout << "Generic Animal sound (Early Binding)" << endl; } }; class Dog : public Animal { public: void makeSound() const { cout << "Woof! Woof!" << endl; } }; int main() { Dog myDog; Animal *ptr = &myDog; // Base class pointer holding Derived class object ptr->makeSound(); // PRINTS: "Generic Animal sound" (Calls Animal::makeSound) return 0; }
Dog::makeSound() through the base pointer, we must declare the method as virtual in the base class (explained in Lecture 18).
Explain the terms Static Binding and Dynamic Binding with respect to the CPU execution address resolution timeline.
Virtual Functions & Pure Virtual Functions
1. Virtual Functions (Enabling Late Binding)
A Virtual Function is a member function declared in a base class with the virtual keyword and overridden in derived classes. When invoked through a base pointer or reference, C++ uses Late Binding to execute the derived version corresponding to the actual object in memory.
2. Internal Mechanism: The vtable and vptr
- Virtual Table (
vtable): A static array of function pointers created by the compiler for each class containing virtual functions. - Virtual Pointer (
vptr): A hidden pointer inserted into every object instance pointing to its class'svtable. At runtime, calls are dispatched viavptr → vtable → target function.
3. Pure Virtual Functions
A Pure Virtual Function is a virtual function that has no implementation in the base class and is declared with = 0:
virtual void render() = 0; // Pure Virtual Function
#include <iostream> using namespace std; class GraphicShape { public: // Virtual function enables dynamic binding virtual void draw() const { cout << "Drawing a generic graphic shape." << endl; } // Virtual destructor ensures proper cleanup of derived objects virtual ~GraphicShape() {} }; class Circle : public GraphicShape { public: void draw() const override { cout << "Drawing Circle: O" << endl; } }; class Square : public GraphicShape { public: void draw() const override { cout << "Drawing Square: []" << endl; } }; int main() { GraphicShape *shapes[2]; shapes[0] = new Circle(); shapes[1] = new Square(); cout << "--- Runtime Polymorphic Dispatch ---\n"; for (int i = 0; i < 2; i++) { shapes[i]->draw(); // Dynamically dispatches to Circle::draw and Square::draw! } delete shapes[0]; delete shapes[1]; return 0; }
Why should the destructor of a base class always be declared virtual if it contains virtual functions? (What happens when deleting a derived object via a base pointer?).
Abstract Classes in C++
1. What is an Abstract Class?
An Abstract Class is a class that contains at least one pure virtual function. It serves as an architectural blueprint for a family of derived classes.
- You cannot instantiate objects of an abstract class directly (e.g.
Shape s;is a compiler error). - You can create pointers and references of an abstract class type to achieve generic polymorphic behavior.
- Any concrete derived class must override and implement all pure virtual functions; otherwise, the derived class also remains abstract!
#include <iostream> using namespace std; // Abstract Base Class class DatabaseConnection { public: // Pure Virtual Functions enforcing contract virtual void connect() = 0; virtual void disconnect() = 0; virtual ~DatabaseConnection() {} }; class MySQLConnection : public DatabaseConnection { public: void connect() override { cout << "Connecting to MySQL database server on port 3306..." << endl; } void disconnect() override { cout << "MySQL session closed cleanly." << endl; } }; int main() { // DatabaseConnection db; // ERROR: Cannot instantiate abstract class DatabaseConnection *conn = new MySQLConnection(); conn->connect(); conn->disconnect(); delete conn; return 0; }
Create an abstract class Shape with pure virtual functions area() and perimeter(). Implement concrete derived classes Rectangle and Triangle.
File Handling: Streams, Opening & Closing
1. The C++ Stream Class Hierarchy
File input/output in C++ is managed through the <fstream> library, built on an object-oriented stream hierarchy:
ifstream: Input file stream class (derived fromistream), used to read data from files.ofstream: Output file stream class (derived fromostream), used to write data to files.fstream: Input/Output file stream class (derived fromiostream), handles simultaneous read and write operations.
2. Opening and Closing Files
Files can be opened either via stream constructors or by calling the open() member method:
ofstream outFile("records.txt", ios::out | ios::app);
outFile.close();
3. File Opening Modes (ios:: flags)
| Mode Flag | Description |
|---|---|
ios::in | Open for reading (default for ifstream) |
ios::out | Open for writing, truncating existing content (default for ofstream) |
ios::app | Append mode; all writes happen at end of file |
ios::binary | Open in raw binary mode instead of text mode |
ios::trunc | Truncate existing file size to 0 bytes |
#include <iostream> #include <fstream> using namespace std; int main() { ofstream outFile; outFile.open("ptu_notes.txt", ios::out); if (!outFile.is_open()) { cerr << "Error: Failed to create or open file!" << endl; return 1; } outFile << "Punjab Technical University - BCA Curriculum" << endl; outFile << "C++ Object Oriented Programming Mastery" << endl; outFile.close(); // Flushes stream and closes file handle cout << "File created, written, and closed successfully." << endl; return 0; }
Write a program that tests if a file exists before opening it. If it exists, append a new timestamped log entry to the file without deleting previous contents.
Reading, Writing & Binary File Processing
1. Text File Reading & Line Processing
Reading text files is performed using formatted stream extraction (>>) or line-by-line reading with getline():
ifstream inFile("data.txt");
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
2. Binary File I/O: read() and write()
Binary file operations transfer raw memory bytes of entire objects directly without textual string formatting:
fileStream.write((char*)&object, sizeof(object));
fileStream.read((char*)&object, sizeof(object));
3. File Pointers: seekg(), seekp(), tellg(), tellp()
tellg()/tellp(): Returns current byte offset of input / output pointer.seekg(offset, origin): Moves input pointer (getpointer).seekp(offset, origin): Moves output pointer (putpointer).- Origins:
ios::beg(beginning),ios::cur(current),ios::end(end).
#include <iostream> #include <fstream> using namespace std; class StudentProfile { public: int rollNo; char name[30]; float gpa; }; int main() { StudentProfile sWrite = {101, "Amanpreet", 9.4f}; // 1. Write binary record to disk ofstream fOut("students.dat", ios::binary | ios::out); fOut.write((char*)&sWrite, sizeof(StudentProfile)); fOut.close(); // 2. Read binary record back from disk StudentProfile sRead; ifstream fIn("students.dat", ios::binary | ios::in); fIn.read((char*)&sRead, sizeof(StudentProfile)); fIn.close(); cout << "=== Binary Student Profile Read from Disk ===" << endl; cout << "Roll: " << sRead.rollNo << " | Name: " << sRead.name << " | GPA: " << sRead.gpa << endl; return 0; }
Write a program that uses seekg() and tellg() to compute the exact file size of any file in bytes without reading its full contents into memory.
Assignment 1: Marks of 6 Subjects (cin & cout)
cin and cout statements.
Program Logic
- Declare variables for 6 subjects (e.g.
sub1throughsub6or an array of size 6). - Prompt the user to enter marks for each subject using
cout <<and read values usingcin >>. - Calculate
totalMarks = sub1 + sub2 + sub3 + sub4 + sub5 + sub6;. - Compute percentage:
percentage = (totalMarks / 600.0) * 100.0;. - Display total marks and percentage formatted with 2 decimal places.
#include <iostream> #include <iomanip> using namespace std; int main() { float marks[6]; float totalMarks = 0.0f; float percentage; cout << "=== Enter Marks for 6 Subjects (out of 100) ===\n"; for (int i = 0; i < 6; i++) { cout << "Enter marks for Subject " << (i + 1) << ": "; cin >> marks[i]; totalMarks += marks[i]; } percentage = (totalMarks / 600.0f) * 100.0f; cout << "\n----------------------------------------\n"; cout << "Total Marks Obtained: " << totalMarks << " / 600\n"; cout << fixed << setprecision(2); cout << "Overall Percentage: " << percentage << "%\n"; cout << "Grade Status: " << ((percentage >= 40.0f) ? "Passed" : "Reappear") << "\n"; cout << "----------------------------------------\n"; return 0; }
Sample Console Output
=== Enter Marks for 6 Subjects (out of 100) ===
Enter marks for Subject 1: 85
Enter marks for Subject 2: 78
Enter marks for Subject 3: 92
Enter marks for Subject 4: 88
Enter marks for Subject 5: 74
Enter marks for Subject 6: 90
----------------------------------------
Total Marks Obtained: 507 / 600
Overall Percentage: 84.50%
Grade Status: Passed
----------------------------------------
Assignment 2: Swap via Reference Variables
Program Logic
- In C, swapping values required passing pointers (e.g.
swap(&a, &b)). - In C++, we use reference parameters:
void swapIntegers(int &x, int &y). xandybecome direct aliases for the caller's variables, modifying them without any pointer dereference syntax.
#include <iostream> using namespace std; // Function using reference variables as arguments void swapIntegers(int &x, int &y) { int temp = x; x = y; y = temp; } int main() { int first, second; cout << "Enter two integers: "; cin >> first >> second; cout << "\nBefore Swapping: first = " << first << ", second = " << second << endl; swapIntegers(first, second); cout << "After Swapping: first = " << first << ", second = " << second << endl; return 0; }
Sample Console Output
Enter two integers: 45 90
Before Swapping: first = 45, second = 90
After Swapping: first = 90, second = 45
Assignment 3: Largest of Three Numbers
#include <iostream> using namespace std; // Function returning the maximum of three numbers double findLargest(double a, double b, double c) { if (a >= b && a >= c) { return a; } else if (b >= a && b >= c) { return b; } else { return c; } } int main() { double n1, n2, n3; cout << "Enter three numbers: "; cin >> n1 >> n2 >> n3; double largest = findLargest(n1, n2, n3); cout << "The largest number among (" << n1 << ", " << n2 << ", " << n3 << ") is: " << largest << endl; return 0; }
Sample Console Output
Enter three numbers: 14.5 98.2 45.7
The largest number among (14.5, 98.2, 45.7) is: 98.2
Assignment 4: Factorial of a Number
#include <iostream> using namespace std; int main() { int num; unsigned long long factorial = 1; cout << "Enter a non-negative integer: "; cin >> num; if (num < 0) { cout << "Error! Factorial of a negative number does not exist." << endl; } else { for (int i = 1; i <= num; i++) { factorial *= i; } cout << "Factorial of " << num << " = " << factorial << endl; } return 0; }
Sample Console Output
Enter a non-negative integer: 7
Factorial of 7 = 5040
Assignment 5: Bank Account Class System
Data members:
a) Name of the depositor, b) Account Number, c) Withdrawal amount, d) Balance amount in the account.
Member Functions:
a) To assign initial values, b) To deposit an amount, c) To withdraw an amount after checking the balance, d) To display name and balance.
#include <iostream> #include <string> using namespace std; class BankAccount { private: string depositorName; long long accountNumber; double withdrawalAmount; double balanceAmount; public: // a) Assign initial values void assignInitialValues(string name, long long accNo, double initialBalance) { depositorName = name; accountNumber = accNo; balanceAmount = initialBalance; withdrawalAmount = 0.0; } // b) Deposit an amount void deposit(double amount) { if (amount > 0) { balanceAmount += amount; cout << "Successfully deposited Rs " << amount << endl; } else { cout << "Invalid deposit amount!\n"; } } // c) Withdraw an amount after checking balance void withdraw(double amount) { if (amount <= balanceAmount) { withdrawalAmount = amount; balanceAmount -= amount; cout << "Successfully withdrew Rs " << amount << endl; } else { cout << "Transaction Rejected: Insufficient balance! (Available: Rs " << balanceAmount << ")\n"; } } // d) Display name and balance void display() const { cout << "-------------------------------------\n"; cout << "Account Number: " << accountNumber << endl; cout << "Depositor Name: " << depositorName << endl; cout << "Current Balance: Rs " << balanceAmount << endl; cout << "-------------------------------------\n"; } }; int main() { BankAccount acc; acc.assignInitialValues("Karanveer Singh", 10023456789LL, 25000.0); acc.display(); acc.deposit(7500.0); acc.withdraw(12000.0); acc.withdraw(50000.0); // Demonstrates balance check acc.display(); return 0; }
Assignment 6: Bank System (Array of Objects)
n number of account holders using an array of objects.
#include <iostream> #include <string> using namespace std; class BankAccount { private: string name; int accNo; double balance; public: void input() { cout << "Enter Account Number: "; cin >> accNo; cout << "Enter Depositor Name: "; cin >> name; cout << "Enter Initial Balance: "; cin >> balance; } int getAccNo() const { return accNo; } void display() const { cout << "Acc No: " << accNo << " | Name: " << name << " | Balance: Rs " << balance << endl; } }; int main() { int n; cout << "Enter total number of account holders: "; cin >> n; // Dynamic array of objects for n holders BankAccount *customers = new BankAccount[n]; for (int i = 0; i < n; i++) { cout << "\nEnter details for Customer #" << (i + 1) << ":\n"; customers[i].input(); } cout << "\n=== Registered Bank Accounts Summary ===\n"; for (int i = 0; i < n; i++) { customers[i].display(); } delete[] customers; // Clean up allocated heap memory return 0; }
Assignment 7: Triangle Area Overloading
Mathematical Formulas Used
- Right-Angled Triangle:
area(base, height) = 0.5 * base * height - Equilateral Triangle:
area(side) = (sqrt(3) / 4) * side * side - Isosceles Triangle:
area(equalSide, base) = (base / 4.0) * sqrt(4 * a^2 - b^2)
#include <iostream> #include <cmath> using namespace std; // 1. Right-Angled Triangle Area double area(double base, double height) { return 0.5 * base * height; } // 2. Equilateral Triangle Area (single parameter) double area(double side) { return (sqrt(3.0) / 4.0) * side * side; } // 3. Isosceles Triangle Area (distinguished by float type tags or explicit formula) float area(float equalSide, float base) { return (base / 4.0f) * sqrt(4.0f * equalSide * equalSide - base * base); } int main() { cout << "Area of Right-Angled Triangle (b=6, h=8): " << area(6.0, 8.0) << endl; cout << "Area of Equilateral Triangle (side=5): " << area(5.0) << endl; cout << "Area of Isosceles Triangle (a=5, b=6): " << area(5.0f, 6.0f) << endl; return 0; }
Assignment 8: Publication, Book & Tape Hierarchy
Publication that stores the title (a string) and price (type float) of a publication.
Derive the following two classes from
Publication:
Book: adds a page count (int).Tape: adds a playing time in minutes (float).
get_data() function to get its data from the user and put_data() to display it. Write the main() function to test both classes.
#include <iostream> #include <string> using namespace std; class Publication { protected: string title; float price; public: void get_data() { cout << "Enter Title: "; cin.ignore(); getline(cin, title); cout << "Enter Price: Rs "; cin >> price; } void put_data() const { cout << "Title: " << title << " | Price: Rs " << price; } }; class Book : public Publication { private: int pageCount; public: void get_data() { Publication::get_data(); cout << "Enter Page Count: "; cin >> pageCount; } void put_data() const { Publication::put_data(); cout << " | Pages: " << pageCount << endl; } }; class Tape : public Publication { private: float playingTime; public: void get_data() { Publication::get_data(); cout << "Enter Playing Time (minutes): "; cin >> playingTime; } void put_data() const { Publication::put_data(); cout << " | Duration: " << playingTime << " mins" << endl; } }; int main() { Book b; Tape t; cout << "=== Enter Book Details ===\n"; b.get_data(); cout << "\n=== Enter Audio Tape Details ===\n"; t.get_data(); cout << "\n=== Publication Information Summary ===\n"; b.put_data(); t.put_data(); return 0; }
Assignment 9: Student, Exam & Result Multilevel
student, exam and result.
studenthas data members:rollno,name.examinheritsstudentand adds marks scored in 5 subjects.resultinheritsexamand has its own data members:total,avg.
#include <iostream> #include <string> using namespace std; // Base Class: student class Student { protected: int rollNo; string name; public: void getStudent(int r, string n) { rollNo = r; name = n; } void putStudent() const { cout << "Roll No: " << rollNo << " | Name: " << name << endl; } }; // Intermediate Class: exam inherits student class Exam : public Student { protected: float marks[5]; public: void getMarks(float m[]) { for (int i = 0; i < 5; i++) { marks[i] = m[i]; } } }; // Derived Class: result inherits exam class Result : public Exam { private: float total; float avg; public: void calculateAndDisplay() { total = 0.0f; for (int i = 0; i < 5; i++) { total += marks[i]; } avg = total / 5.0f; cout << "\n=== PTU BCA Academic Marksheet ===\n"; putStudent(); cout << "Marks: "; for (int i = 0; i < 5; i++) { cout << marks[i] << " "; } cout << "\nTotal: " << total << " / 500" << endl; cout << "Average: " << avg << "%" << endl; } }; int main() { Result student1; student1.getStudent(2026101, "Harshpreet Singh"); float m[5] = {88.5f, 92.0f, 79.5f, 85.0f, 91.0f}; student1.getMarks(m); student1.calculateAndDisplay(); return 0; }
Assignment 10: Overloading Unary ++ Operator
++ operator (demonstrating both prefix and postfix forms).
#include <iostream> using namespace std; class Counter { private: int count; public: Counter(int c = 0) : count(c) {} // Prefix form (++c): increments first, returns updated object Counter operator++() { ++count; return *this; } // Postfix form (c++): int dummy parameter distinguishes postfix Counter operator++(int) { Counter temp = *this; count++; return temp; // returns value prior to increment } void display() const { cout << "Count: " << count << endl; } }; int main() { Counter c1(5); cout << "Initial value: "; c1.display(); ++c1; cout << "After Prefix ++c1: "; c1.display(); c1++; cout << "After Postfix c1++: "; c1.display(); return 0; }
Assignment 11: Overloading Binary + Operator
+ operator to add two complex numbers.
#include <iostream> using namespace std; class Complex { private: float real; float imag; public: Complex(float r = 0.0f, float i = 0.0f) : real(r), imag(i) {} // Overloading binary + operator Complex operator+(const Complex &c) const { return Complex(real + c.real, imag + c.imag); } void display() const { cout << real << " + " << imag << "i" << endl; } }; int main() { Complex c1(3.5f, 2.5f); Complex c2(1.5f, 4.5f); Complex c3 = c1 + c2; // Invokes c1.operator+(c2) cout << "c1: "; c1.display(); cout << "c2: "; c2.display(); cout << "c1 + c2: "; c3.display(); return 0; }
Assignment 12: Virtual Functions Demonstration
#include <iostream> using namespace std; class MediaDevice { public: // Virtual function enables dynamic method dispatch virtual void play() const { cout << "Playing media on generic device." << endl; } virtual ~MediaDevice() {} }; class AudioPlayer : public MediaDevice { public: void play() const override { cout << "Playing MP3 Audio track (Stereo Output)" << endl; } }; class VideoPlayer : public MediaDevice { public: void play() const override { cout << "Playing MP4 Video (1080p Full HD)" << endl; } }; int main() { MediaDevice *devicePtr; AudioPlayer audio; VideoPlayer video; devicePtr = &audio; devicePtr->play(); // Dispatches to AudioPlayer::play devicePtr = &video; devicePtr->play(); // Dispatches to VideoPlayer::play return 0; }
Assignment 13: Abstract Classes Implementation
#include <iostream> using namespace std; // Abstract Base Class class Shape { public: // Pure Virtual Function virtual double calculateArea() const = 0; virtual void displayShape() const = 0; virtual ~Shape() {} }; class Rectangle : public Shape { private: double width, height; public: Rectangle(double w, double h) : width(w), height(h) {} double calculateArea() const override { return width * height; } void displayShape() const override { cout << "Rectangle (" << width << "x" << height << ") | Area: " << calculateArea() << endl; } }; int main() { Shape *s = new Rectangle(7.0, 4.0); s->displayShape(); delete s; return 0; }
Assignment 14: Read & Write from File
ofstream and ifstream.
#include <iostream> #include <fstream> #include <string> using namespace std; int main() { // 1. Writing data to file using ofstream ofstream outFile("assignment_test.txt"); if (!outFile) { cerr << "Error: Unable to open file for writing!\n"; return 1; } outFile << "Punjab Technical University - BCA Practical Assignment\n"; outFile << "Subject: Object Oriented Programming in C++\n"; outFile << "Status: File operations executed successfully.\n"; outFile.close(); cout << "Data written to file successfully.\n\n"; // 2. Reading data back from file using ifstream ifstream inFile("assignment_test.txt"); if (!inFile) { cerr << "Error: Unable to open file for reading!\n"; return 1; } cout << "=== Reading File Contents Line-by-Line ===\n"; string line; while (getline(inFile, line)) { cout << line << endl; } inFile.close(); return 0; }