UNIT 3: Object-Oriented Programming in C++
I. OOP Fundamentals & C++ Overview
Advantages of OOP Paradigm
-
Modularity: Code is organized into self-contained objects.
-
Reusability: Classes can be reused via inheritance.
-
Data Security: Encapsulation protects internal data.
-
Easy Maintenance: Changes in one object rarely affect others.
-
Scalability: Complex systems can be built from simpler objects.
Core Features of OOP (4 Pillars)
-
Encapsulation: Bundling data (attributes) and functions (methods) that operate on that data into a single unit (class). Access is controlled via access specifiers.
-
Inheritance: Mechanism where a new class (derived/child) acquires properties and behaviors of an existing class (base/parent). Promotes code reuse.
-
Polymorphism: "Many forms." Ability of an object to take different forms.
-
Compile-time (Static): Function/Operator Overloading.
-
Run-time (Dynamic): Virtual Functions.
-
-
Abstraction: Hiding implementation details, showing only essential features. Achieved via abstract classes/interfaces.
C++ vs C Language
| Feature | C Language | C++ Language |
|---|---|---|
| Paradigm | Procedural | Multi-paradigm (Procedural + OOP) |
| Data Security | No access control | private, public, protected |
| Functions | Standalone | Member functions of classes |
| Inheritance | Not supported | Supported (Single, Multiple, etc.) |
| Polymorphism | Not supported | Supported (Virtual functions) |
| Input/Output | printf(), scanf() |
cin, cout (stream-based) |
| Memory | malloc(), calloc(), free() |
new, delete operators |
| Error Handling | Error codes, setjmp/longjmp |
Exceptions (try, catch, throw) |
| Type Checking | Less strict | Strict (function prototyping) |
C++ Specifics: Variable Declaration Anywhere
-
Significance: Variables can be declared at the point of their first use within any block
{ }. -
Benefit: Improves code readability, reduces scope of variables (localizes them), and prevents accidental use before initialization.
void func() { int x = 10; // Declaration at point of use // ... use x for (int i = 0; i < x; i++) { // i declared in for-loop scope // ... } }
II. Classes, Objects, and Access Control
Class vs Structure in C++
| Feature | class |
struct |
|---|---|---|
| Default Access | private |
public |
| Primary Use | For objects with data hiding & methods | For passive data structures (POD) |
| Inheritance | Can have base classes | Can have base classes |
| Member Functions | Can have constructors, destructors, methods | Can have constructors, destructors, methods (since C++) |
[!TIP] In modern C++, the only technical difference is the default access specifier. Use
classfor data abstraction/hiding,structfor simple data aggregates.
Access Specifiers
-
private: Accessible only within the class itself. -
public: Accessible from anywhere. -
protected: Accessible within the class and its derived classes.
Access Specifiers with Respect to Inheritance
| Base Specifier | Derived Class Access (if inherited publicly) |
Derived Class Access (if inherited protectedly) |
Derived Class Access (if inherited privately) |
|---|---|---|---|
public |
public |
protected |
private |
protected |
protected |
protected |
private |
private |
Inaccessible | Inaccessible | Inaccessible |
Mechanism of Accessing Members
-
Inside
main(): Create an object. Use dot operator (.).ClassName obj; obj.publicMember = 10; obj.publicFunction(); -
Inside member function of same class: Direct access by name.
void ClassName::func() { privateMember = 5; // Direct access publicMember = 10; } -
Inside member function of another class:
-
If member is
public: Use object of the first class with.. -
If member is
private/protected: Not accessible unlessfriendrelationship exists.
-
Constructors & Destructors
-
Constructor: Special member function with same name as class, no return type. Called automatically when an object is created. Purpose: Initialize object's data members.
-
Destructor: Special member function with ~ClassName, no return type, no parameters. Called automatically when object goes out of scope or is
deleted. Purpose: Clean up resources (e.g.,deletedynamic memory).
Types of Constructors:
-
Default Constructor: Takes no arguments.
ClassName(); -
Parameterized Constructor: Takes arguments.
ClassName(int a, int b); -
Copy Constructor: Takes reference to object of same class.
ClassName(const ClassName &obj);
Copy Constructor & Its Use:
-
Purpose: Initialize a new object as a copy of an existing object.
-
When Called:
-
Object initialization:
ClassName obj2 = obj1; -
Passing object by value to a function.
-
Returning object by value from a function.
-
-
Example Program:
class Box { int length; public: Box(int l) : length(l) {} // Parameterized Box(const Box &b) { // Copy Constructor length = b.length; cout << "Copy Constructor called" << endl; } }; int main() { Box b1(10); // Parameterized Box b2 = b1; // Copy Constructor called return 0; }
Constructor Overloading & Chaining:
-
Overloading: Multiple constructors with different parameters.
-
Chaining (C++11 onwards): One constructor calling another using member initializer list.
class Demo { int a, b; public: Demo() : Demo(0, 0) { cout << "Default called\n"; } // Chains to 2-arg Demo(int x) : Demo(x, 0) { cout << "1-arg called\n"; } // Chains to 2-arg Demo(int x, int y) : a(x), b(y) { // Ultimate initializer cout << "2-arg called\n"; } };
Object Management
Array of Objects:
class Student {
int rollno;
public:
void setRoll(int r) { rollno = r; }
void display() { cout << rollno; }
};
int main() {
Student s[3]; // Array of 3 Student objects
for(int i=0; i<3; i++) s[i].setRoll(i+1);
for(int i=0; i<3; i++) s[i].display();
return 0;
}
Array of Objects using Pointers (Dynamic Allocation):
int n = 5;
Student *arr = new Student[n]; // Allocates array on heap
for(int i=0; i<n; i++) arr[i].setRoll(i+1);
// ... use arr
delete[] arr; // Crucial: delete[] for arrays
III. Inheritance
Inheritance: Concept & Importance
-
Concept: Creating new classes (derived) from existing ones (base). Derived inherits base's members.
-
Importance:
-
Code Reusability: Avoid rewriting common code.
-
Extensibility: Easily add new features to existing classes.
-
Logical Hierarchy: Models real-world "is-a" relationships (e.g.,
Dogis-aAnimal). -
Transitivity: If
CinheritsB, andBinheritsA, thenCalso inheritsA.
-
Types of Inheritance
| Type | Description | Example |
|---|---|---|
| Single | One derived from one base. | class Derived : public Base |
| Multilevel | Chain: Base -> Intermediate -> Derived. | A -> B -> C |
| Multiple | One derived from multiple bases. | class Derived : public Base1, public Base2 |
| Hierarchical | One base, multiple derived classes. | A -> B, A -> C |
| Hybrid | Combination of two or more types. | A -> B (Single), A -> C (Hierarchical) |
Difference: Multilevel vs Multiple Inheritance
| Aspect | Multilevel Inheritance | Multiple Inheritance |
|---|---|---|
| Base Classes | One base per level. Chain structure. | Two or more base classes for a single derived. |
| Complexity | Simpler, linear hierarchy. | High complexity (Diamond problem, ambiguity). |
| Example | GrandParent -> Parent -> Child |
Child : public Father, public Mother |
[!TIP] Why Multiple Inheritance Can Induce Complexity?
- Ambiguity: If two base classes have a member with same name, which one does derived access? (e.g.,
Father::namevsMother::name).
- Diamond Problem: Derived inherits two copies of a common base (via two paths). Causes duplication and inconsistency.
- Constructor/Destructor Order: More complex initialization/destruction sequence.
- Increased Coupling: Tightly binds derived to multiple base implementations.
Constructor Behavior in Inheritance
Order of Constructor Calls:
-
Base class constructor (of top-most base) is called first.
-
Then, intermediate base class constructors (in order of inheritance declaration).
-
Finally, derived class constructor is called.
Destructor order is exact reverse.
Example:
class A { public: A() { cout << "A ctor\n"; } };
class B : public A { public: B() { cout << "B ctor\n"; } };
class C : public B { public: C() { cout << "C ctor\n"; } };
// Output: A ctor -> B ctor -> C ctor
Initializing Base Class Members:
-
Use member initializer list in derived constructor.
-
If base has no default constructor, derived must explicitly call a base constructor in its initializer list.
class Base { int x; public: Base(int a) : x(a) {} // No default ctor }; class Derived : public Base { int y; public: Derived(int a, int b) : Base(a), y(b) {} // MUST call Base(a) };
Ambiguity in Multiple Inheritance & Diamond Problem
-
Problem: If two base classes (
B1,B2) inherit from a common base (A), and a derived class (D) inherits from bothB1andB2, thenDcontains two copies ofA's members (viaB1::AandB2::A). This is ambiguous and wasteful.A / \ B1 B2 \ / D <-- D has two A subobjects! -
Resolution: Virtual Base Classes
-
Declare base class (
A) asvirtualwhen inherited byB1andB2. -
Ensures only one shared instance of
Aexists inD.
class A { /* ... */ }; class B1 : virtual public A { /* ... */ }; // virtual inheritance class B2 : virtual public A { /* ... */ }; class D : public B1, public B2 { /* ... */ }; // D has only ONE A -
IV. Polymorphism
Compile-Time Polymorphism (Static Binding)
-
Function Overloading: Multiple functions with same name but different parameters (type/number).
void print(int i) { cout << i; } void print(double d) { cout << d; } void print(char* s) { cout << s; } -
Operator Overloading: Redefining operator behavior for user-defined types.
-
Unary Operator Example (
++prefix):class Counter { int count; public: Counter& operator++() { // Prefix: ++obj ++count; return *this; // Return reference to modified object } }; -
Pros: Intuitive syntax for custom types (e.g.,
c1 + c2). -
Cons: Can make code obscure if overused/abused; cannot change operator precedence/associativity.
-
Run-Time Polymorphism (Dynamic Binding)
-
Virtual Functions: Declared with
virtualkeyword in base class. Allows overriding in derived classes. Calls are resolved at runtime based on object's actual type.class Base { public: virtual void show() { cout << "Base\n"; } }; class Derived : public Base { public: void show() override { cout << "Derived\n"; } // Override }; int main() { Base *b = new Derived(); b->show(); // Output: "Derived" (Late binding) } -
Pure Virtual Function & Abstract Class:
-
virtual void func() = 0;(Pure virtual). -
Class containing at least one pure virtual is abstract.
-
Cannot instantiate abstract class objects.
-
Purpose: Define interface/template for derived classes.
class Shape { public: virtual double area() = 0; // Pure virtual }; class Circle : public Shape { double r; public: double area() override { return 3.14*r*r; } }; // Shape s; // ERROR: Abstract class Circle c; // OK -
How Polymorphism is Achieved:
| Type | Mechanism | Binding Time | Example |
|---|---|---|---|
| Compile-time | Overloading | Early/Static (at compile) | Function overloading, Operator overloading |
| Run-time | Overriding via virtual |
Late/Dynamic (at runtime) | Virtual functions, Pure virtual functions |
Binding in Polymorphism:
-
Early Binding (Static): Function call linked to function definition at compile time. For non-virtual, overloaded functions.
-
Late Binding (Dynamic): Function call linked to function definition at runtime. For
virtualfunctions. Requires vtable (virtual table) mechanism.
V. Advanced C++ Features
Friend Functions & Classes
-
Friend Function: Non-member function granted access to private/protected members of a class.
-
Declared with
friendkeyword inside the class. -
Not a member of the class (cannot use
thispointer). -
Application: Operator overloading for symmetric operations (e.g.,
ostream &operator<<), utility functions needing deep access.
class Box { int length; public: Box(int l) : length(l) {} friend void showLength(Box b); // Friend declaration }; void showLength(Box b) { // Definition (not member) cout << b.length; // Access private member } -
Static Members and Functions
-
Static Data Member: Shared by all objects of the class. One copy exists. Must be defined outside class.
class Counter { static int count; // Declaration public: Counter() { count++; } static int getCount() { return count; } }; int Counter::count = 0; // Definition & initialization -
Static Member Function: Can access only static members of class. Called using class name (
ClassName::func()), nothispointer.
Templates
-
Function Template: Blueprint for generating functions for different types.
template <typename T> T max(T a, T b) { return (a > b) ? a : b; } // Instantiations: max<int>(1,2), max<double>(1.1, 2.2) -
Overloaded Function Templates: Multiple templates with same name but different template parameters or function parameters.
template <typename T> T max(T a, T b) { /* ... */ } template <typename T, typename U> auto max(T a, U b) -> decltype(a+b) { /* ... */ } // Different signature
Exception Handling
-
Keywords:
-
try: Block where exceptions might occur. -
catch: Handler for specific exception type. -
throw: Raises an exception.
try { if (error) throw "Error message"; // Throw } catch (const char* e) { // Catch cout << "Caught: " << e; } -
-
Synchronous vs Asynchronous Exceptions:
| | Synchronous | Asynchronous | | :--- | :--- | :--- | | Cause | Directly from
throwstatement withintryblock. | External events (hardware failure, signals, interrupts). | | Handling | Handled by nearest matchingcatch. | Cannot be handled by C++try-catch. Requires OS/hardware support. | | Example |throw -1;| Division by zero (hardware),SIGSEGVsignal. |
Inline Functions
-
Purpose: Suggest compiler to replace function call with function body to avoid call overhead. Use for small, frequently called functions.
-
Syntax: Define function inside class definition or use
inlinekeyword.class Calc { public: inline int add(int a, int b) { return a+b; } // Inline member }; -
Macros vs Inline Functions:
| Feature | Macros (
#define) | Inline Functions | | :--- | :--- | :--- | | Type Safety | No (textual substitution) | Yes (type-checked by compiler) | | Debugging | Difficult (no symbol) | Easier (symbol exists) | | Scope | Global (preprocessor) | Respects scope & access control | | Parameters | No evaluation safety (e.g.,#define SQR(x) x*x) | Safe evaluation (arguments evaluated once) | | Return Type | No return type check | Has return type |
VI. Memory Management & I/O
Memory Management: new / delete
-
new: Allocates memory on heap, returns pointer. Calls constructor.int *p = new int(10); // Single int int *arr = new int[5]; // Array ClassName *obj = new ClassName(args); // Object -
delete: Frees memory allocated bynew. Calls destructor.delete p; // Single object delete[] arr; // Array (MUST use []!) delete obj; -
Clearing Memory: After
delete, set pointer tonullptrto avoid dangling pointer.p = nullptr; // Safe
Call by Reference & Return by Reference
-
Call by Reference (
&): Function receives alias to original variable. Changes affect original. Avoids copy overhead for large objects.void increment(int &x) { x++; } // Reference parameter int main() { int a = 5; increment(a); // a becomes 6 } -
Return by Reference: Function returns reference to a variable (usually a class member). Must not return reference to local variable (dangling reference).
class Array { int data[100]; public: int& get(int index) { return data[index]; } // Returns reference }; int main() { Array arr; arr.get(0) = 100; // Direct assignment via returned reference }
Input/Output in C++
-
cin/cout(Stream-based - Preferred):int x; string s; cin >> x >> s; // Formatted input cout << "Value: " << x; // Formatted output -
scanf/printf(C-style - Still supported):int x; scanf("%d", &x); // Requires address-of operator (&) printf("%d", x);
[!TIP]
cin/coutare type-safe, extensible (via overloading), and integrate with C++ I/O library (fstream).scanf/printfare faster but error-prone (format specifier mismatch).
File I/O (fstream):
#include <fstream>
int main() {
// Writing
ofstream outFile("data.txt");
outFile << "Hello File";
outFile.close();
// Reading
ifstream inFile("data.txt");
string line;
getline(inFile, line);
inFile.close();
return 0;
}
Pointer Debugging
-
Dangling Pointer: Pointer pointing to deleted/invalid memory.
int *p = new int(5); delete p; // p is now dangling (points to freed memory) *p = 10; // ERROR: Undefined behavior! -
Memory Leak: Allocated memory (
new) never deallocated (delete). Lost pointer to block.void leak() { int *p = new int[100]; // No delete[] p; -> Memory leak on function exit } -
Debugging Tips:
-
Initialize pointers to
nullptr. -
After
delete, set pointer tonullptr. -
Use smart pointers (
unique_ptr,shared_ptr) for automatic management. -
Tools: Valgrind (Linux), AddressSanitizer.
-
VII. Practical Programming Applications
Validation of Examination Marks
class Student {
int marks;
public:
void setMarks(int m) {
if (m >= 65 && m <= 100) marks = m;
else { cout << "Invalid marks!\n"; marks = 0; }
}
int getMarks() { return marks; }
};
Grade Calculation System
class Student {
int m1, m2, m3;
public:
void setMarks(int a, int b, int c) { m1=a; m2=b; m3=c; }
void getResults() {
double avg = (m1+m2+m3)/3.0;
char grade;
if (avg >= 80) grade = 'A';
else if (avg >= 65) grade = 'B';
else if (avg >= 50) grade = 'C';
else if (avg >= 40) grade = 'D';
else grade = 'E';
cout << "Grade: " << grade << endl;
}
};
Complex Number Arithmetic
class Complex {
double real, imag;
public:
Complex(double r=0, double i=0) : real(r), imag(i) {}
Complex operator+(const Complex& c) {
return Complex(real + c.real, imag + c.imag);
}
Complex operator-(const Complex& c) {
return Complex(real - c.real, imag - c.imag);
}
Complex operator*(const Complex& c) {
return Complex(
real*c.real - imag*c.imag,
real*c.imag + imag*c.real
);
}
void display() { cout << real << " + " << imag << "i\n"; }
};
Inheritance-Based Designs
Employee (Derived from Person):
class Person {
string name;
public:
Person(string n) : name(n) {}
string getName() { return name; }
};
class Employee : public Person {
double salary;
int year;
string insuranceNo;
public:
Employee(string n, double s, int y, string ins)
: Person(n), salary(s), year(y), insuranceNo(ins) {}
bool equals(Employee& e) {
return getName() == e.getName() && insuranceNo == e.insuranceNo;
}
};
SurveyOperator (Derived from Person):
class SurveyOperator : public Person {
int numCalls, successCalls;
double totalTime;
bool available;
public:
SurveyOperator(string name, int nc, int sc, double t, bool av)
: Person(name), numCalls(nc), successCalls(sc), totalTime(t), available(av) {}
// ... other methods (GetNumberOfCalls, SetAvailable, MakeCall)
};
Array-Based Fee Calculation
int main() {
int branchId[] = {1, 2, 1, 3, 2}; // Example branch IDs
double fees[] = {50000, 60000, 70000}; // Index: branchId-1
double total = 0;
for (int id : branchId) {
total += fees[id-1];
}
cout << "Total Fees: " << total;
return 0;
}
VIII. OOP Design Concepts
Encapsulation vs Information Hiding
| Encapsulation | Information Hiding |
|---|---|
| Bundling data & methods into a single unit (class). | Hiding internal implementation details, exposing only interface. |
How: Using class/struct. |
How: Using private/protected members, public interface. |
| Focus: Organization. | Focus: Protection & abstraction. |
| Example: All members in one class. | Example: private data, public getter/setter methods. |
OOP Techniques for Handling System Complexity
-
Abstraction: Model essential features, ignore irrelevant details.
-
Encapsulation: Contain complexity within well-defined boundaries.
-
Modularity: Break system into manageable, independent modules (classes).
-
Hierarchy (Inheritance): Organize classes into logical, reusable hierarchies.
-
Separation of Interface & Implementation: Users interact with interface, not implementation.
Overloading vs Overriding
| Feature | Overloading | Overriding |
|---|---|---|
| Definition | Same function name, different parameters in same class. | Same function signature in base & derived classes. |
| Scope | Single class (or namespace). | Inheritance hierarchy (base & derived). |
| Binding | Early/Static binding (compile-time). | Late/Dynamic binding (runtime) if virtual. |
| Purpose | Provide multiple ways to call similar operation. | Change/specialize base class behavior in derived. |
| Example | void print(int); void print(double); |
virtual void show(); in base, void show() override; in derived |
[!TIP] Binding in Polymorphism:
- Overloading: Compiler decides which function to call based on argument types at compile time.
- Overriding (with
virtual): Runtime decides which function to call based on object's actual type using vtable.