Why Object-Oriented Programming?
Before OOP, large programs were written as long sequences of procedures — fine for small scripts, but unmanageable as software grew. Object-Oriented Programming (OOP) organises code around objects: self-contained units that bundle related data and behaviour together.
OOP is the dominant paradigm in the industry. Companies like TCS, Infosys, Wipro, and virtually every product company interviewing in India (Google, Amazon, Microsoft, Flipkart) expect candidates to understand OOP deeply. C++ supports OOP natively and adds the performance characteristics that Java and Python cannot match.
The four pillars of OOP are:
| Pillar | Meaning |
|---|---|
| Encapsulation | Bundling data and methods; hiding internal state |
| Abstraction | Exposing only what is necessary |
| Inheritance | Deriving new classes from existing ones |
| Polymorphism | One interface, many implementations |
This chapter focuses on encapsulation — the foundation on which the other three rest.
The class Keyword
A class is a blueprint. An object is a concrete instance created from that blueprint. The syntax mirrors a struct, but with one key default difference: members are private by default in a class (public by default in a struct).
class Student {
// members go here
};
Objects are created like any other variable:
Student s1; // default-constructed object
Student s2, s3; // two more objects
Access Specifiers
Access specifiers control which parts of the program can read or modify a class member.
| Specifier | Who Can Access |
|---|---|
public | Any code anywhere |
private | Only member functions and friend functions of this class |
protected | Member functions, friend functions, and derived classes |
class Employee {
public:
string name; // accessible from anywhere
private:
double salary; // only accessible inside the class
protected:
int employeeId; // accessible in derived classes too
};
The golden rule: data members should almost always be private; public exposure is provided through methods.
Member Variables and Methods
A class combines member variables (data) and member functions (methods) in one unit.
#include <iostream>
#include <string>
using namespace std;
class Rectangle {
private:
double width;
double height;
public:
// Method to set dimensions
void setDimensions(double w, double h) {
width = w;
height = h;
}
double area() const {
return width * height;
}
double perimeter() const {
return 2 * (width + height);
}
void display() const {
cout << "Width: " << width
<< ", Height: " << height
<< ", Area: " << area() << "\n";
}
};
int main() {
Rectangle room;
room.setDimensions(5.0, 3.5);
room.display(); // Width: 5, Height: 3.5, Area: 17.5
return 0;
}
The const after a method signature means the method does not modify any member variables — always mark read-only methods as const.
The this Pointer
Inside every non-static member function, the compiler implicitly passes a pointer called this that points to the current object. You rarely need to use this explicitly, but it is essential in two scenarios:
- Resolving name ambiguity when a parameter has the same name as a member variable.
- Returning the current object from a method (method chaining).
class Circle {
private:
double radius;
public:
void setRadius(double radius) {
// 'radius' (parameter) shadows the member
this->radius = radius; // this->radius is the member
}
// Method chaining: return *this
Circle& scale(double factor) {
radius *= factor;
return *this;
}
double area() const {
return 3.14159 * radius * radius;
}
};
int main() {
Circle c;
c.setRadius(5.0);
cout << c.scale(2.0).area() << "\n"; // area of circle with r=10
return 0;
}
Getters and Setters
Keeping data private and exposing it via getter (accessor) and setter (mutator) methods is the standard encapsulation pattern. It lets you add validation, logging, or computation without changing how external code uses the class.
class Temperature {
private:
double celsius;
public:
// Setter with validation
void setCelsius(double c) {
if (c < -273.15) {
cout << "Temperature below absolute zero!\n";
return;
}
celsius = c;
}
// Getter
double getCelsius() const {
return celsius;
}
// Derived getter — no extra storage needed
double getFahrenheit() const {
return (celsius * 9.0 / 5.0) + 32.0;
}
};
Static Members
A static member variable is shared by all instances of the class — there is exactly one copy regardless of how many objects exist. A static member function can be called without any object and can only access other static members.
#include <iostream>
using namespace std;
class Employee {
private:
string name;
static int count; // ONE shared copy across all Employee objects
public:
Employee(string n) : name(n) {
count++;
}
static int getCount() {
return count;
}
string getName() const { return name; }
};
// Static member must be defined outside the class
int Employee::count = 0;
int main() {
cout << "Employees: " << Employee::getCount() << "\n"; // 0
Employee e1("Anjali");
Employee e2("Ravi");
Employee e3("Preethi");
cout << "Employees: " << Employee::getCount() << "\n"; // 3
return 0;
}
Static members are useful for counters, shared configuration, or factory methods.
Friend Functions
A friend function is a non-member function that is granted access to the private and protected members of a class. Declare it inside the class with the friend keyword.
#include <iostream>
using namespace std;
class Wallet {
private:
double balance;
public:
Wallet(double b) : balance(b) {}
// Grant friendship to a free function
friend void transfer(Wallet& from, Wallet& to, double amount);
friend void printBalance(const Wallet& w);
};
void transfer(Wallet& from, Wallet& to, double amount) {
if (from.balance >= amount) {
from.balance -= amount;
to.balance += amount;
} else {
cout << "Insufficient funds\n";
}
}
void printBalance(const Wallet& w) {
cout << "Balance: Rs. " << w.balance << "\n";
}
int main() {
Wallet alice(5000.0), bob(1000.0);
transfer(alice, bob, 2000.0);
printBalance(alice); // Balance: Rs. 3000
printBalance(bob); // Balance: Rs. 3000
return 0;
}
Use friend functions sparingly — they weaken encapsulation. They are justified when two classes need tightly coupled access, such as operator overloading.
Worked Example: BankAccount Class
Indian banks like SBI, HDFC, ICICI, and Kotak all implement accounts that share a common set of operations: deposit, withdrawal, and balance enquiry. Let us model a simple bank account with proper encapsulation.
#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
string ownerName;
string accountNumber;
double balance;
static double interestRate; // shared by all accounts
public:
// Constructor
BankAccount(string name, string accNo, double initialDeposit) {
ownerName = name;
accountNumber = accNo;
if (initialDeposit < 0) {
cout << "Invalid initial deposit. Setting to 0.\n";
balance = 0.0;
} else {
balance = initialDeposit;
}
cout << "Account created for " << ownerName << "\n";
}
// Deposit money
void deposit(double amount) {
if (amount <= 0) {
cout << "Deposit amount must be positive.\n";
return;
}
balance += amount;
cout << "Deposited Rs. " << amount
<< ". New balance: Rs. " << balance << "\n";
}
// Withdraw money
bool withdraw(double amount) {
if (amount <= 0) {
cout << "Withdrawal amount must be positive.\n";
return false;
}
if (amount > balance) {
cout << "Insufficient funds. Balance: Rs. " << balance << "\n";
return false;
}
balance -= amount;
cout << "Withdrawn Rs. " << amount
<< ". Remaining balance: Rs. " << balance << "\n";
return true;
}
// Apply annual interest
void applyInterest() {
double interest = balance * interestRate / 100.0;
balance += interest;
cout << "Interest of Rs. " << interest
<< " applied. New balance: Rs. " << balance << "\n";
}
// Getters
double getBalance() const { return balance; }
string getOwnerName() const { return ownerName; }
string getAccountNumber() const { return accountNumber; }
// Static getter/setter for interest rate
static double getInterestRate() { return interestRate; }
static void setInterestRate(double r) { interestRate = r; }
// Display account summary
void displaySummary() const {
cout << "\n--- Account Summary ---\n";
cout << "Owner: " << ownerName << "\n";
cout << "Acc No: " << accountNumber << "\n";
cout << "Balance: Rs. " << balance << "\n";
cout << "Rate: " << interestRate << "% p.a.\n";
cout << "-----------------------\n";
}
};
// Define static member
double BankAccount::interestRate = 6.5;
int main() {
BankAccount acc1("Meera Nair", "SBI00123456", 50000.0);
BankAccount acc2("Rahul Gupta", "SBI00789012", 25000.0);
acc1.deposit(15000.0);
acc1.withdraw(8000.0);
acc1.withdraw(100000.0); // should fail
acc2.deposit(5000.0);
// Change interest rate for all accounts
BankAccount::setInterestRate(7.0);
acc1.applyInterest();
acc2.applyInterest();
acc1.displaySummary();
acc2.displaySummary();
return 0;
}
Sample Output:
Account created for Meera Nair
Account created for Rahul Gupta
Deposited Rs. 15000. New balance: Rs. 65000
Withdrawn Rs. 8000. Remaining balance: Rs. 57000
Insufficient funds. Balance: Rs. 57000
Deposited Rs. 5000. New balance: Rs. 30000
Interest of Rs. 3990 applied. New balance: Rs. 60990
Interest of Rs. 2100 applied. New balance: Rs. 32100
--- Account Summary ---
Owner: Meera Nair
Acc No: SBI00123456
Balance: Rs. 60990
Rate: 7% p.a.
-----------------------
--- Account Summary ---
Owner: Rahul Gupta
Acc No: SBI00789012
Balance: Rs. 32100
Rate: 7% p.a.
-----------------------
Common Pitfalls
- Forgetting to initialise member variables: Uninitialised numeric members hold garbage values. Always initialise in the constructor (or with a default member initialiser).
- Making everything
public: This defeats encapsulation. If external code can freely modifybalance, your validation logic indepositandwithdrawis useless. - Forgetting to define static member variables: Declaring
static int count;inside the class is just a declaration. You must define it (int Employee::count = 0;) in exactly one.cppfile or you will get a linker error. - Using
thiswhen it is unnecessary: Overusingthis->for every member access makes code noisy. Reserve it for disambiguation. - Non-const methods on const objects: If you have a
const BankAccount, you can only call methods markedconst. Forgetting to mark read-only methods asconstbreaks this. - Friend functions breaking encapsulation: Grant friendship only when truly needed (e.g., operator overloading). Prefer public methods wherever possible.
Practice Exercises
- Create a
Studentclass with private membersname,rollNumber, and an array of 5marks. Add methods to set marks, compute the average, and print a report card. - Extend the
BankAccountclass to support a transaction history — store the last 10 transactions as astringarray and add aprintHistory()method. - Create a
Counterclass with a privatestatic int count. Every time aCounterobject is constructed, increment the count; every time one is destroyed, decrement it. Print the live count. - Write a
Matrixclass for 2×2 matrices withfriendfunctions for addition and multiplication of twoMatrixobjects. - Create a
Circleclass and aRectangleclass, both with agetArea()method. Write acompareBiggerfriend function that takes one of each and prints which has the larger area.
Summary
- A class is a user-defined blueprint; an object is a concrete instance of that blueprint.
- Access specifiers —
public,private,protected— control visibility. Data members should generally beprivate. - Member functions (methods) define the behaviour of a class. Mark read-only methods as
const. - The
thispointer refers to the current object inside a member function; use it to resolve name ambiguity or enable method chaining. - Getters and setters expose private data safely and allow validation logic to be centralised.
- Static members are shared across all objects of a class; static functions can be called without any object.
- Friend functions can access private members but should be used sparingly to preserve encapsulation.
- The
BankAccountexample tied all these concepts together in a realistic Indian banking scenario.