Lecture 1 / 35
Lecture 01 Β· Unit-I: OOP Principles [CO1]

Introduction to OOP & Core Concepts

[CO1] Course Outcome OOP Principles Abstraction & Encapsulation

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)

  1. Objects: Basic runtime entities in an object-oriented system that encapsulate state (data) and behavior (functions).
  2. Classes: The blueprint, template, or prototype from which individual objects are instantiated.
  3. Data Abstraction: Representing essential features of an entity while hiding underlying background complexities and implementation mechanics.
  4. Data Encapsulation: Wrapping data and functions into a single unit (class) and preventing unauthorized direct access (Data Hiding).
  5. Inheritance: The mechanism by which objects of one class acquire the properties and methods of another class, promoting code reuse.
  6. 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).
  7. Dynamic Binding: Linking a procedure call to the code to be executed at run-time rather than compile-time.
  8. Message Passing: Objects communicate with one another by sending and receiving specifications of requests (function invocations).
oop_concept_demo.cpp
#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;
}
🎯 Practice Challenge 0.1

List the four pillars of OOP (Abstraction, Encapsulation, Inheritance, Polymorphism) and give a real-world banking analogy for each pillar.

Lecture 02 Β· Unit-I: OOP Principles [CO1]

Procedure Oriented (C) vs OOP (C++)

[CO1] Course Outcome Paradigm Comparison C vs 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_vs_cpp_contrast.cpp
/* 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;
    }
};
🎯 Practice Challenge 1.1

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.

Lecture 03 Β· Unit-I: OOP Principles [CO1]

C++ Program Structure & Components

[CO1] Course Outcome Program Structure Streams: cin & cout

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

structure_demo.cpp
// 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;
}
🎯 Practice Challenge 2.1

Explain the role of stream manipulators: endl, setw(int), and setprecision(int). Write a short C++ snippet printing a tabular price list.

Lecture 04 Β· Unit-I: OOP Principles [CO1]

Compiling & Executing C++ Programs

[CO1] Course Outcome Build Process g++ Toolchain

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:

  1. Preprocessing (g++ -E): Evaluates all #include and #define directives, stripping comments and generating an expanded pure translation unit.
  2. Compilation (g++ -S): Parses the preprocessed code, checks grammar and syntax, and translates the code into assembly instructions specific to the CPU architecture.
  3. Assembly (g++ -c): Assembles assembly instructions into binary object code (.o / .obj) containing machine instructions with unresolved external addresses.
  4. 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
πŸ’‘ Why C++ is Faster than Java/Python
Unlike Java (which compiles to intermediate Bytecode run inside a JVM) or Python (interpreted dynamically), C++ compiles directly into native machine instructions for the host CPU without runtime interpretation or garbage-collection pauses.
🎯 Practice Challenge 3.1

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.

Lecture 05 Β· Unit-I: OOP Principles [CO1]

C++ Extensions: References & Inlines

[CO1] Course Outcome Reference Variables Inline Functions & new/delete

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 NULL reference (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:

references_and_inlines.cpp
#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.

🎯 Practice Challenge 4.1

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.

Lecture 06 Β· Unit-II: Classes & Objects [CO2]

Defining Classes & Object Declaration

[CO2] Course Outcome Class Specification Object Instantiation

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
πŸ“˜ How Memory is Allocated to Objects
Each object gets its own separate copy of data members so that every object can maintain its own independent state. However, member functions are stored in memory only once and shared by all objects of the class, conserving valuable RAM.
class_definition.cpp
#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;
}
🎯 Practice Challenge 5.1

Define a class Circle with a private member radius. Provide public member functions to set the radius and calculate the circumference.

Lecture 07 Β· Unit-II: Classes & Objects [CO2]

Access Specifiers & Member Access

[CO2] Course Outcome Data Hiding Private, Public & Protected

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.

access_specifiers_demo.cpp
#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;
}
🎯 Practice Challenge 6.1

Explain the difference between a C++ struct and a C++ class. (Hint: What is the default access specifier in each?).

Lecture 08 Β· Unit-II: Classes & Objects [CO2]

Member Functions & Array of Objects

[CO2] Course Outcome Scope Resolution (::) 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 inline functions.
  • 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:

array_of_objects.cpp
#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;
}
🎯 Practice Challenge 7.1

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.

Lecture 09 Β· Unit-II: Constructors [CO2]

Constructors & Parameterized Constructors

[CO2] Course Outcome Object Initialization Constructor Overloading

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 public section 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.
constructors_demo.cpp
#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;
}
🎯 Practice Challenge 8.1

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.

Lecture 10 Β· Unit-II: Constructors [CO2]

Copy Constructor & Dynamic Initialization

[CO2] Course Outcome Copy Constructor Deep vs Shallow Copy

1. The Copy Constructor

A Copy Constructor initializes an object using another already-existing object of the same class:

ClassName(const ClassName &sourceObj);
⚠️ Why Pass by Reference (const ClassName &) is Mandatory
If a copy constructor accepted its argument by value (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.

copy_constructor_deep.cpp
#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;
}
🎯 Practice Challenge 9.1

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.

Lecture 11 Β· Unit-II: Constructors [CO2]

Destructors in C++

[CO2] Course Outcome Memory Deallocation RAII Principle

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.

destructor_lifo.cpp
#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
🎯 Practice Challenge 10.1

Explain Resource Acquisition Is Initialization (RAII). Write a class FileHandler that opens a file in its constructor and automatically calls fclose() in its destructor.

Lecture 12 Β· Unit-III: Inheritance [CO3]

Inheritance Basics & Visibility Modes

[CO3] Course Outcome Code Reusability 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
inheritance_basic.cpp
#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;
}
🎯 Practice Challenge 11.1

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?

Lecture 13 Β· Unit-III: Inheritance [CO3]

Types of Inheritance (Single, Multilevel, Hierarchical)

[CO3] Course Outcome Inheritance Hierarchies Constructor Delegation

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 B and Class 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.

multilevel_inheritance.cpp
#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;
}
🎯 Practice Challenge 12.1

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.

Lecture 14 Β· Unit-III: Inheritance [CO3]

Multiple & Hybrid Inheritance (Diamond Problem)

[CO3] Course Outcome Multipath Inheritance Virtual Base Class

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!

diamond_virtual_base.cpp
#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;
}
🎯 Practice Challenge 13.1

Explain how a Virtual Base Class pointer table operates under the hood to ensure only one base class subobject is initialized in memory.

Lecture 15 Β· Unit-III: Operator Overloading [CO3]

Operator Overloading Fundamentals & Rules

[CO3] Course Outcome Compile-Time Polymorphism Overloading 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).
⚠️ Operators That CANNOT Be Overloaded in C++
In university examinations, questions consistently ask: β€œWhich operators cannot be overloaded in C++?”
Memorize these 5 operators:
  1. Scope Resolution Operator (::)
  2. Class Member Access / Dot Operator (.)
  3. Pointer-to-Member Operator (.*)
  4. Ternary Conditional Operator (?:)
  5. Sizeof Operator (sizeof)
🎯 Practice Challenge 14.1

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?

Lecture 16 Β· Unit-III: Operator Overloading [CO3]

Overloading Unary & Binary Operators

[CO3] Course Outcome Unary & Binary Overload Friend Functions

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:

unary_binary_overloading.cpp
#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;
}
🎯 Practice Challenge 15.1

Overload the relational equality operator == for a class Distance (feet and inches) to test if two distances are identical.

Lecture 17 Β· Unit-IV: Polymorphism [CO5]

Polymorphism: Early vs Late Binding

[CO5] Course Outcome Binding Mechanisms Base Class Pointers

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!

early_binding_problem.cpp
#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;
}
πŸ’‘ How to Achieve Late Binding
To force C++ to inspect the runtime object and call Dog::makeSound() through the base pointer, we must declare the method as virtual in the base class (explained in Lecture 18).
🎯 Practice Challenge 16.1

Explain the terms Static Binding and Dynamic Binding with respect to the CPU execution address resolution timeline.

Lecture 18 Β· Unit-IV: Polymorphism [CO5]

Virtual Functions & Pure Virtual Functions

[CO5] Course Outcome Virtual Table (vtable) 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's vtable. At runtime, calls are dispatched via vptr → 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
virtual_functions_demo.cpp
#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;
}
🎯 Practice Challenge 17.1

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?).

Lecture 19 Β· Unit-IV: Polymorphism [CO5]

Abstract Classes in C++

[CO5] Course Outcome Pure Interfaces Abstract Classes

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!
abstract_class_demo.cpp
#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;
}
🎯 Practice Challenge 18.1

Create an abstract class Shape with pure virtual functions area() and perimeter(). Implement concrete derived classes Rectangle and Triangle.

Lecture 20 Β· Unit-IV: File Handling [CO5]

File Handling: Streams, Opening & Closing

[CO5] Course Outcome fstream Library File Modes

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 from istream), used to read data from files.
  • ofstream: Output file stream class (derived from ostream), used to write data to files.
  • fstream: Input/Output file stream class (derived from iostream), 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::inOpen for reading (default for ifstream)
ios::outOpen for writing, truncating existing content (default for ofstream)
ios::appAppend mode; all writes happen at end of file
ios::binaryOpen in raw binary mode instead of text mode
ios::truncTruncate existing file size to 0 bytes
open_close_demo.cpp
#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;
}
🎯 Practice Challenge 19.1

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.

Lecture 21 Β· Unit-IV: File Handling [CO5]

Reading, Writing & Binary File Processing

[CO5] Course Outcome Formatted & Binary I/O Stream Pointers

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 (get pointer).
  • seekp(offset, origin): Moves output pointer (put pointer).
  • Origins: ios::beg (beginning), ios::cur (current), ios::end (end).
binary_file_record.cpp
#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;
}
🎯 Practice Challenge 20.1

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.

Practical Assignment 01 Β· Basic I/O

Assignment 1: Marks of 6 Subjects (cin & cout)

Practical Lab Streams: cin / cout
πŸ“‹ Problem Statement
Write a C++ program to enter marks of 6 different subjects and find out the total marks and percentage using cin and cout statements.

Program Logic

  1. Declare variables for 6 subjects (e.g. sub1 through sub6 or an array of size 6).
  2. Prompt the user to enter marks for each subject using cout << and read values using cin >>.
  3. Calculate totalMarks = sub1 + sub2 + sub3 + sub4 + sub5 + sub6;.
  4. Compute percentage: percentage = (totalMarks / 600.0) * 100.0;.
  5. Display total marks and percentage formatted with 2 decimal places.
assignment_01.cpp
#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
----------------------------------------
Practical Assignment 02 Β· References

Assignment 2: Swap via Reference Variables

Practical Lab Call by Reference
πŸ“‹ Problem Statement
Write a function using reference variables as arguments to swap the values of a pair of integers.

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).
  • x and y become direct aliases for the caller's variables, modifying them without any pointer dereference syntax.
assignment_02.cpp
#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
Practical Assignment 03 Β· Functions

Assignment 3: Largest of Three Numbers

Practical Lab Conditional Logic
πŸ“‹ Problem Statement
Write a C++ function to find the largest of three numbers.
assignment_03.cpp
#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
Practical Assignment 04 Β· Loops

Assignment 4: Factorial of a Number

Practical Lab Factorial Calculation
πŸ“‹ Problem Statement
Write a C++ program to find the factorial of an input number.
assignment_04.cpp
#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
Practical Assignment 05 Β· Classes & Objects

Assignment 5: Bank Account Class System

Practical Lab Data Encapsulation
πŸ“‹ Problem Statement
Define a class to represent a bank account which includes the following members:
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.
assignment_05.cpp
#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;
}
Practical Assignment 06 Β· Array of Objects

Assignment 6: Bank System (Array of Objects)

Practical Lab Array of Objects
πŸ“‹ Problem Statement
Write the above bank account program for handling n number of account holders using an array of objects.
assignment_06.cpp
#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;
}
Practical Assignment 07 Β· Function Overloading

Assignment 7: Triangle Area Overloading

Practical Lab Function Overloading
πŸ“‹ Problem Statement
Write a C++ program to compute the area of a right-angled triangle, equilateral triangle, and isosceles triangle using the function overloading concept.

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)
assignment_07.cpp
#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;
}
Practical Assignment 08 Β· Inheritance

Assignment 8: Publication, Book & Tape Hierarchy

Practical Lab Hierarchical Inheritance
πŸ“‹ Problem Statement
Consider a publishing company that markets both book and audio cassette versions of its works. Create a class 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).
Each class should have get_data() function to get its data from the user and put_data() to display it. Write the main() function to test both classes.
assignment_08.cpp
#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;
}
Practical Assignment 09 Β· Multilevel Inheritance

Assignment 9: Student, Exam & Result Multilevel

Practical Lab Multilevel Inheritance
πŸ“‹ Problem Statement
Consider an example of declaring the examination result. Design three classes: student, exam and result.
  • student has data members: rollno, name.
  • exam inherits student and adds marks scored in 5 subjects.
  • result inherits exam and has its own data members: total, avg.
assignment_09.cpp
#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;
}
Practical Assignment 10 Β· Operator Overloading

Assignment 10: Overloading Unary ++ Operator

Practical Lab Unary ++ Operator
πŸ“‹ Problem Statement
Write a C++ program for overloading the Unary ++ operator (demonstrating both prefix and postfix forms).
assignment_10.cpp
#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;
}
Practical Assignment 11 Β· Operator Overloading

Assignment 11: Overloading Binary + Operator

Practical Lab Binary + Operator
πŸ“‹ Problem Statement
Write a C++ program for overloading the Binary + operator to add two complex numbers.
assignment_11.cpp
#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;
}
Practical Assignment 12 Β· Virtual Functions

Assignment 12: Virtual Functions Demonstration

Practical Lab Dynamic Binding
πŸ“‹ Problem Statement
Write a C++ program demonstrating the use of Virtual Functions for runtime polymorphism.
assignment_12.cpp
#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;
}
Practical Assignment 13 Β· Abstract Classes

Assignment 13: Abstract Classes Implementation

Practical Lab Pure Virtual Function
πŸ“‹ Problem Statement
Write a C++ program implementing an Abstract Class with pure virtual functions.
assignment_13.cpp
#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;
}
Practical Assignment 14 Β· File Handling

Assignment 14: Read & Write from File

Practical Lab File Streams
πŸ“‹ Problem Statement
Write a C++ program to read and write data from a file using ofstream and ifstream.
assignment_14.cpp
#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;
}