Bytebook Compiler

Bytebook · OOPS

Object-Oriented Programming with C++

Move from C to C++ and master object-oriented design: classes, constructors, inheritance, polymorphism, templates, exceptions and the STL.

Back to lessons

Chapter 1 · From C to C++

C++ Introduction

Where C++ came from, what it adds to C, your first program with iostream and namespaces, and how to compile it.

C++ was created by Bjarne Stroustrup at Bell Labs, starting in 1979 as "C with Classes". It keeps almost everything from C — the speed, the pointers, the control over memory — and adds tools for organising large programs: classes and objects, references, function overloading, templates, exceptions and a huge Standard Template Library (STL).

C++ powers game engines (Unreal), browsers (Chrome, Firefox), databases, trading systems, compilers and most performance-critical software you use.

Your first C++ program

C++
#include <iostream>

int main() {
    std::cout << "Hello, C++!" << std::endl;
    std::cout << "2 + 3 = " << 2 + 3 << "\n";
    return 0;
}
Output
Hello, C++!
2 + 3 = 5
Line Meaning
#include <iostream> the input/output stream library (no .h)
std::cout the standard character output stream — the screen
<< the insertion operator: sends the value on its right into the stream
std::endl ends the line and flushes the output; "\n" just ends the line
std:: everything in the standard library lives in the namespace std

Namespaces and using

A namespace groups names to avoid clashes — two libraries can both have a sort function if they live in different namespaces. Writing std:: everywhere is precise; using namespace std; lets you drop the prefix:

C++
#include <iostream>
using namespace std;

namespace physics { double g = 9.8; }
namespace games   { double g = 1.6; }     // same name, different namespace

int main() {
    cout << "Earth gravity: " << physics::g << endl;
    cout << "Moon gravity in our game: " << games::g << endl;
    return 0;
}
Output
Earth gravity: 9.8
Moon gravity in our game: 1.6

Compiling

Terminal
g++ hello.cpp -o hello      # g++ is the C++ compiler from GCC
./hello
g++ -std=c++17 -Wall hello.cpp -o hello   # pick a standard and turn on warnings

What C++ adds to C

Feature C C++
Programming style procedural procedural and object-oriented and generic
Input/output printf, scanf cout, cin (type-safe)
Data + functions together no classes
References no int &r = x;
Function overloading no yes
Default arguments no yes
Dynamic memory malloc / free new / delete
Strings char arrays std::string
Error handling return codes exceptions
Generic code macros, void * templates
Ready-made data structures none STL: vector, map, set, …

Almost any valid C program is also a valid C++ program, so everything from CSE 101 still applies.

Chapter 2 · From C to C++

Input/Output, Strings & References

Read and write with cin and cout, use the std::string class, format output with iomanip, and meet references — C++'s safer alias for a variable.

cin: reading input

cin (character input) reads values with the extraction operator >>. It works out the type from the variable — no format specifiers and no &:

C++
#include <iostream>
#include <string>
using namespace std;

int main() {
    string name;
    int age;
    double cgpa;
    cout << "Name, age and CGPA: ";
    cin >> name >> age >> cgpa;
    cout << name << " is " << age << " with a CGPA of " << cgpa << endl;
    return 0;
}
Output
Name, age and CGPA: Ravi 19 8.6
Ravi is 19 with a CGPA of 8.6
Sample input is includedTry it Yourself

Like scanf("%s"), cin >> name stops at whitespace. To read a whole line, use getline:

C++
#include <iostream>
#include <string>
using namespace std;

int main() {
    int roll;
    string fullName;
    cout << "Roll number: ";
    cin >> roll;
    cin.ignore();                       // skip the newline left after the number
    cout << "Full name: ";
    getline(cin, fullName);
    cout << "#" << roll << ": " << fullName << " (" << fullName.length() << " characters)" << endl;
    return 0;
}
Output
Roll number: 21
Full name: Ravi Kumar Sharma
#21: Ravi Kumar Sharma (17 characters)
Sample input is includedTry it Yourself

The string class

std::string grows automatically, knows its own length, and supports +, == and < directly:

C++
#include <iostream>
#include <string>
using namespace std;

int main() {
    string a = "Data", b = "Structures";
    string full = a + " " + b;                 // concatenation
    cout << full << " has " << full.size() << " characters" << endl;
    cout << "First letter: " << full[0] << ", last: " << full.back() << endl;
    cout << "Substring: " << full.substr(5, 6) << endl;         // from index 5, length 6
    cout << "\"Struct\" found at " << full.find("Struct") << endl;
    full.replace(0, 4, "Algo");
    cout << "After replace: " << full << endl;
    cout << boolalpha << ("apple" < string("banana")) << endl;   // dictionary order
    for (char c : string("abc")) cout << char(c - 32);           // range-based for
    cout << endl;
    return 0;
}
Output
Data Structures has 15 characters
First letter: D, last: s
Substring: Struct
"Struct" found at 5
After replace: Algo Structures
true
ABC

Formatting with iomanip

C++
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    double pi = 3.14159265;
    cout << fixed << setprecision(2) << pi << endl;          // 2 decimal places
    cout << setw(8) << 42 << "|" << endl;                      // width 8, right-aligned
    cout << left << setw(8) << 42 << "|" << endl;              // left-aligned
    cout << setfill('0') << right << setw(5) << 7 << endl;     // pad with zeros
    cout << hex << 255 << " " << oct << 8 << dec << endl;      // number bases
    return 0;
}
Output
3.14
      42|
42      |
00007
ff 10

References

A reference is another name (an alias) for an existing variable. It must be initialised when declared and can never be re-seated to refer to something else.

C++
#include <iostream>
using namespace std;

int main() {
    int marks = 70;
    int &r = marks;          // r is another name for marks
    r += 15;
    cout << "marks = " << marks << ", r = " << r << endl;
    cout << "same address? " << (&marks == &r ? "yes" : "no") << endl;
    return 0;
}
Output
marks = 85, r = 85
same address? yes

The real power of references is in function parameters — the subject of the next lesson.

Pointer Reference
can be nullptr always refers to a valid object
can be re-pointed later bound once, forever
needs * to read, & to take an address used like the variable itself
can do arithmetic no arithmetic
has its own memory usually just an alias

bool and auto

C++ has a built-in bool type with true and false. And auto lets the compiler deduce a variable's type from its initialiser:

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    bool passed = true;
    auto count = 42;            // int
    auto ratio = 0.75;          // double
    auto name = string("Asha"); // std::string
    cout << boolalpha << passed << " " << count << " " << ratio << " " << name << endl;
    vector<int> v = {3, 1, 4};
    for (auto x : v) cout << x * 10 << " ";
    cout << endl;
    return 0;
}
Output
true 42 0.75 Asha
30 10 40

Chapter 3 · From C to C++

Functions: Overloading, Defaults & Inline

What C++ adds to functions: pass by reference, const references, function overloading, default arguments and inline functions.

Pass by reference

A reference parameter lets a function change the caller's variable — without pointers:

C++
#include <iostream>
using namespace std;

void swapValues(int &a, int &b) {     // a and b are aliases for the caller's variables
    int t = a;
    a = b;
    b = t;
}

int main() {
    int x = 3, y = 8;
    swapValues(x, y);                 // no & needed at the call
    cout << "x = " << x << ", y = " << y << endl;
    return 0;
}
Output
x = 8, y = 3

Compare the three ways of passing an argument:

By value By pointer (C style) By reference
Declaration void f(int a) void f(int *a) void f(int &a)
Call f(x) f(&x) f(x)
Can change caller's variable no yes yes
Copies the argument yes no (copies an address) no

const references

Passing a large object (like a long string or vector) by value copies the whole thing. Pass it as a const reference instead: no copy, and the function promises not to change it.

C++
#include <iostream>
#include <string>
using namespace std;

int countVowels(const string &s) {      // fast and read-only
    int n = 0;
    for (char c : s)
        if (string("aeiouAEIOU").find(c) != string::npos) n++;
    return n;
}

int main() {
    string text = "Object Oriented Programming";
    cout << countVowels(text) << " vowels" << endl;
    return 0;
}
Output
9 vowels

Function overloading

Several functions can share a name if their parameter lists differ (in number or types). The compiler picks the right one from the arguments — this is compile-time polymorphism.

C++
#include <iostream>
using namespace std;

int area(int side)                 { return side * side; }               // square
int area(int length, int width)    { return length * width; }            // rectangle
double area(double radius)         { return 3.14159 * radius * radius; } // circle

int main() {
    cout << "square 4:      " << area(4) << endl;
    cout << "rectangle 4x6: " << area(4, 6) << endl;
    cout << "circle 2.0:    " << area(2.0) << endl;
    return 0;
}
Output
square 4:      16
rectangle 4x6: 24
circle 2.0:    12.5664

Default arguments

Parameters can have default values, used when the caller leaves them out. Defaults must come last in the parameter list:

C++
#include <iostream>
using namespace std;

double simpleInterest(double principal, double rate = 7.5, int years = 1) {
    return principal * rate * years / 100;
}

int main() {
    cout << simpleInterest(10000) << endl;          // rate 7.5, 1 year
    cout << simpleInterest(10000, 8) << endl;       // 1 year
    cout << simpleInterest(10000, 8, 3) << endl;
    return 0;
}
Output
750
800
2400

Inline functions

inline suggests that the compiler paste the function body at each call, avoiding call overhead for tiny functions. It is a safer replacement for function-like macros because arguments are evaluated once and type-checked:

C++
#include <iostream>
using namespace std;

inline int cube(int x) { return x * x * x; }
#define CUBE(x) x * x * x

int main() {
    cout << "inline cube(1 + 2) = " << cube(1 + 2) << endl;    // 27
    cout << "macro  CUBE(1 + 2) = " << CUBE(1 + 2) << endl;    // 1 + 2*1 + 2*1 + 2 = 7
    return 0;
}
Output
inline cube(1 + 2) = 27
macro  CUBE(1 + 2) = 7

Modern compilers decide on inlining by themselves, so inline is mostly a hint (and today it matters mainly for functions defined in headers).

Chapter 4 · Classes & Objects

OOP Concepts

Why object-oriented programming exists, the difference from procedural code, and the four pillars — encapsulation, abstraction, inheritance and polymorphism.

Procedural programming (what you did in C) organises a program around functions that act on separate data. As programs grow, any function can modify any data, and changes ripple everywhere.

Object-oriented programming (OOP) organises a program around objects — bundles of data (attributes) and the functions that operate on it (methods). A bank account object owns its balance; nothing else may change it except through the account's own deposit and withdraw methods.

Procedural Object-oriented
program = functions + separate data program = interacting objects
top-down design bottom-up design: build objects, then combine them
data is often global and exposed data is hidden inside objects
harder to reuse and extend reuse through inheritance and composition
C, Pascal, Fortran C++, Java, Python, C#

Class and object

  • A class is a blueprint: it describes what data and behaviour objects of that kind have.
  • An object is an instance of a class: a real thing built from the blueprint, with its own data.

Car is a class; your red Swift with registration KA-01-1234 is an object.

The four pillars

1. Encapsulation

Bundling data with the methods that use it, and hiding the data from outside code (making it private). Outside code must go through public methods, which can check every change.

A capsule hides the medicine inside; you just swallow it.

2. Abstraction

Showing what an object does while hiding how it does it. You call car.start() without knowing about fuel injection. In C++, abstraction comes from public interfaces, abstract classes and header files.

3. Inheritance

Creating a new class from an existing one, reusing its members and adding or changing some. A Student and a Teacher can both inherit from Person. It models an is-a relationship: a Student is a Person.

4. Polymorphism

"Many forms": the same call behaves differently depending on the object. shape->area() computes a circle's area for a Circle and a rectangle's for a Rectangle.

  • Compile-time polymorphism: function and operator overloading.
  • Run-time polymorphism: virtual functions.

A preview in code

You will understand every line by the end of this unit:

C++
#include <iostream>
#include <string>
using namespace std;

class Shape {                               // abstraction: what every shape can do
public:
    virtual double area() const = 0;
    virtual string name() const = 0;
    virtual ~Shape() {}
};

class Circle : public Shape {               // inheritance
    double r;                               // encapsulation: hidden data
public:
    Circle(double r) : r(r) {}
    double area() const override { return 3.14159 * r * r; }
    string name() const override { return "Circle"; }
};

class Rectangle : public Shape {
    double w, h;
public:
    Rectangle(double w, double h) : w(w), h(h) {}
    double area() const override { return w * h; }
    string name() const override { return "Rectangle"; }
};

int main() {
    Circle c(1.5);
    Rectangle r(4, 2.5);
    const Shape *shapes[] = {&c, &r};
    for (const Shape *s : shapes)           // polymorphism: one call, many behaviours
        cout << s->name() << " area = " << s->area() << endl;
    return 0;
}
Output
Circle area = 7.06858
Rectangle area = 10

Other OOP terms

Term Meaning
Message passing objects interact by calling each other's methods
Dynamic binding the method to run is chosen at run time (virtual functions)
Composition a class contains objects of other classes — a has-a relationship (a Car has an Engine)
Coupling how much classes depend on each other — keep it low
Cohesion how focused a class is on one job — keep it high

Chapter 5 · Classes & Objects

Classes & Objects

Define a class with data members and member functions, create objects, control access with public and private, and define methods outside the class with ::.

Defining a class

C++
#include <iostream>
#include <string>
using namespace std;

class Student {
public:                         // accessible from anywhere
    string name;
    int roll;
    double marks[3];

    double average() {          // member function (method)
        return (marks[0] + marks[1] + marks[2]) / 3;
    }
    void print() {
        cout << roll << " " << name << " avg " << average() << endl;
    }
};                              // note the semicolon!

int main() {
    Student s1;                 // create an object
    s1.name = "Asha";
    s1.roll = 7;
    s1.marks[0] = 88; s1.marks[1] = 92; s1.marks[2] = 79;
    s1.print();

    Student s2 = s1;            // copy: s2 has its own data
    s2.name = "Ravi";
    s2.roll = 8;
    s1.print();
    s2.print();
    return 0;
}
Output
7 Asha avg 86.3333
7 Asha avg 86.3333
8 Ravi avg 86.3333

Each object has its own copy of the data members; member functions are shared by all objects of the class.

Access specifiers

Specifier Accessible from
public anywhere
private only inside the class (and its friends)
protected inside the class and classes derived from it

In a class, members are private by default. In a struct, they are public by default — that is the only difference between the two in C++.

C++
#include <iostream>
using namespace std;

class BankAccount {
private:
    double balance = 0;               // hidden: no one outside can set it to -1000

public:
    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
    bool withdraw(double amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }
    double getBalance() { return balance; }
};

int main() {
    BankAccount acc;
    acc.deposit(5000);
    cout << "withdraw 7000: " << (acc.withdraw(7000) ? "ok" : "refused") << endl;
    cout << "withdraw 1200: " << (acc.withdraw(1200) ? "ok" : "refused") << endl;
    cout << "balance: " << acc.getBalance() << endl;
    // acc.balance = 1000000;   // error: 'balance' is a private member
    return 0;
}
Output
withdraw 7000: refused
withdraw 1200: ok
balance: 3800

Defining methods outside the class

For longer methods, declare them inside the class and define them outside using the scope resolution operator ::. This is how real projects split code between a header (.h) and a source file (.cpp).

C++
#include <iostream>
using namespace std;

class Rectangle {
    double length, width;
public:
    void set(double l, double w);
    double area();
    double perimeter();
};

void Rectangle::set(double l, double w) { length = l; width = w; }
double Rectangle::area()                { return length * width; }
double Rectangle::perimeter()           { return 2 * (length + width); }

int main() {
    Rectangle r;
    r.set(7, 3);
    cout << "area " << r.area() << ", perimeter " << r.perimeter() << endl;
    return 0;
}
Output
area 21, perimeter 20

Arrays of objects and objects as arguments

C++
#include <iostream>
#include <string>
using namespace std;

class Item {
public:
    string name;
    int qty;
    double price;
    double total() const { return qty * price; }
};

double billTotal(const Item items[], int n) {
    double sum = 0;
    for (int i = 0; i < n; i++) sum += items[i].total();
    return sum;
}

int main() {
    Item cart[3] = {{"Notebook", 4, 45.0}, {"Pen", 10, 12.5}, {"Geometry box", 1, 180.0}};
    for (const Item &it : cart)
        cout << it.name << " x" << it.qty << " = " << it.total() << endl;
    cout << "Bill: Rs " << billTotal(cart, 3) << endl;
    return 0;
}
Output
Notebook x4 = 180
Pen x10 = 125
Geometry box x1 = 180
Bill: Rs 485

Memory for objects

sizeof an object counts only its data members (plus padding). Member functions are stored once, in the code, not inside each object:

C++
#include <iostream>
using namespace std;

class Point {
    int x, y;
public:
    void move(int dx, int dy) { x += dx; y += dy; }
    int getX() { return x; }
};

int main() {
    cout << "sizeof(Point) = " << sizeof(Point) << " bytes (two ints)" << endl;
    return 0;
}
Output
sizeof(Point) = 8 bytes (two ints)

Chapter 6 · Classes & Objects

Constructors & Destructors

Initialise objects automatically: default, parameterised and copy constructors, initialiser lists, constructor overloading, and destructors with the order they run in.

A constructor is a special member function that runs automatically when an object is created. Its job is to put the object into a valid starting state.

  • Same name as the class.
  • No return type — not even void.
  • Can be overloaded.

Default and parameterised constructors

C++
#include <iostream>
#include <string>
using namespace std;

class Student {
    string name;
    int roll;
public:
    Student() {                                   // default constructor: no arguments
        name = "Unknown";
        roll = 0;
        cout << "default constructor" << endl;
    }
    Student(string n, int r) {                    // parameterised constructor
        name = n;
        roll = r;
        cout << "parameterised constructor for " << n << endl;
    }
    void show() { cout << roll << ": " << name << endl; }
};

int main() {
    Student a;                    // calls Student()
    Student b("Meera", 12);       // calls Student(string, int)
    Student c = {"Kiran", 13};    // also the parameterised one
    a.show(); b.show(); c.show();
    return 0;
}
Output
default constructor
parameterised constructor for Meera
parameterised constructor for Kiran
0: Unknown
12: Meera
13: Kiran

Initialiser lists

The preferred way to initialise members is a member initialiser list, after a colon. It initialises members directly instead of assigning them afterwards, and it is required for const members, reference members and base classes.

C++
#include <iostream>
using namespace std;

class Circle {
    const double PI;           // const members must use the initialiser list
    double radius;
public:
    Circle(double r = 1.0) : PI(3.14159), radius(r) {}    // also a default constructor
    double area() const { return PI * radius * radius; }
};

int main() {
    Circle unit, big(10);
    cout << unit.area() << " " << big.area() << endl;
    return 0;
}
Output
3.14159 314.159

Copy constructor

The copy constructor creates a new object as a copy of an existing one. It takes a const reference to an object of the same class. It runs when you:

  1. initialise one object from another: Point b = a; or Point b(a);
  2. pass an object by value to a function;
  3. return an object by value (often optimised away).
C++
#include <iostream>
using namespace std;

class Point {
    int x, y;
public:
    Point(int x, int y) : x(x), y(y) {}
    Point(const Point &other) : x(other.x), y(other.y) {     // copy constructor
        cout << "copying (" << x << ", " << y << ")" << endl;
    }
    void show() const { cout << "(" << x << ", " << y << ")" << endl; }
};

void display(Point p) { p.show(); }          // by value: copies

int main() {
    Point a(3, 4);
    Point b = a;                             // copy constructor
    display(a);                              // copy constructor again
    b.show();
    return 0;
}
Output
copying (3, 4)
copying (3, 4)
(3, 4)
(3, 4)

Destructors

A destructor runs automatically when an object is destroyed — when it goes out of scope, or when you delete it. Its name is the class name with a tilde: ~Student(). It takes no arguments, returns nothing, and cannot be overloaded. Use it to release resources: memory, files, network connections.

C++
#include <iostream>
#include <string>
using namespace std;

class Tracer {
    string name;
public:
    Tracer(string n) : name(n) { cout << "  construct " << name << endl; }
    ~Tracer()                  { cout << "  destroy   " << name << endl; }
};

Tracer global("global");

int main() {
    cout << "main starts" << endl;
    Tracer a("a");
    {
        Tracer b("b");
        Tracer c("c");
        cout << "  leaving inner block" << endl;
    }                                       // c then b destroyed here
    Tracer *d = new Tracer("d (heap)");
    delete d;                               // destroyed only when deleted
    cout << "main ends" << endl;
    return 0;
}                                           // a destroyed, then global
Output
  construct global
main starts
  construct a
  construct b
  construct c
  leaving inner block
  destroy   c
  destroy   b
  construct d (heap)
  destroy   d (heap)
main ends
  destroy   a
  destroy   global

Objects are destroyed in the reverse order of construction — like a stack.

Managing a resource: a dynamic array class

C++
#include <iostream>
using namespace std;

class IntArray {
    int *data;
    int size;
public:
    IntArray(int n) : data(new int[n]()), size(n) {}          // () zero-initialises
    IntArray(const IntArray &o) : data(new int[o.size]), size(o.size) {   // deep copy
        for (int i = 0; i < size; i++) data[i] = o.data[i];
    }
    ~IntArray() { delete[] data; }
    int &at(int i) { return data[i]; }
    int length() const { return size; }
};

int main() {
    IntArray a(5);
    for (int i = 0; i < a.length(); i++) a.at(i) = i * i;
    IntArray b = a;                  // deep copy: b gets its own memory
    b.at(0) = 100;
    cout << "a[0] = " << a.at(0) << ", b[0] = " << b.at(0) << endl;
    return 0;
}
Output
a[0] = 0, b[0] = 100

Without the copy constructor, b would share a's pointer (a shallow copy): changing b would change a, and both destructors would delete[] the same memory. More on this in Dynamic Memory & Smart Pointers.

Chapter 7 · Classes & Objects

Encapsulation, this & static Members

Getters and setters with validation, the this pointer, const member functions, and static data and functions shared by every object.

Getters and setters

Encapsulation means keeping data private and exposing controlled access. A setter can validate input; a getter can return a value without exposing the variable itself.

C++
#include <iostream>
#include <string>
using namespace std;

class Employee {
    string name;
    double salary = 0;
public:
    void setName(const string &n) { if (!n.empty()) name = n; }
    void setSalary(double s) {
        if (s < 0) { cout << "Rejected negative salary " << s << endl; return; }
        salary = s;
    }
    string getName() const  { return name; }
    double getSalary() const { return salary; }
};

int main() {
    Employee e;
    e.setName("Divya");
    e.setSalary(65000);
    e.setSalary(-500);                         // the class protects its own data
    cout << e.getName() << " earns " << e.getSalary() << endl;
    return 0;
}
Output
Rejected negative salary -500
Divya earns 65000

The this pointer

Inside a member function, this is a pointer to the object the function was called on. You use it to tell members apart from parameters with the same name, and to return the object itself (which allows method chaining):

C++
#include <iostream>
#include <string>
using namespace std;

class Pizza {
    string size = "medium";
    int toppings = 0;
public:
    Pizza &setSize(const string &size) { this->size = size; return *this; }   // this->size is the member
    Pizza &addTopping()                { toppings++; return *this; }
    void print() const { cout << size << " pizza with " << toppings << " toppings" << endl; }
};

int main() {
    Pizza p;
    p.setSize("large").addTopping().addTopping().addTopping();   // chaining
    p.print();
    return 0;
}
Output
large pizza with 3 toppings

const member functions

A member function marked const promises not to modify the object. Only const functions can be called on const objects and through const references — so mark every getter const.

C++
class Account {
    double balance;
public:
    double getBalance() const { return balance; }   // OK: only reads
    void reset() const { balance = 0; }             // error: modifies a member in a const function
};

static data members

A static data member belongs to the class, not to any object: there is exactly one copy shared by all objects. It must be defined once outside the class (or declared inline static in C++17).

static member functions

A static member function can be called without any object, as ClassName::function(). It has no this pointer, so it can only use static members.

C++
#include <iostream>
#include <string>
using namespace std;

class Student {
    string name;
    int id;
    static int count;                 // one shared counter
public:
    Student(const string &n) : name(n), id(++count) {}
    ~Student() { count--; }
    void show() const { cout << "#" << id << " " << name << endl; }
    static int total() { return count; }   // no object needed
};

int Student::count = 0;               // definition of the static member

int main() {
    cout << "students: " << Student::total() << endl;
    Student a("Asha"), b("Ravi");
    {
        Student c("Meera");
        c.show();
        cout << "students: " << Student::total() << endl;
    }
    a.show(); b.show();
    cout << "students: " << Student::total() << endl;
    return 0;
}
Output
students: 0
#3 Meera
students: 3
#1 Asha
#2 Ravi
students: 2
Regular member static member
Copies one per object one per class
Accessed via an object: s.name the class: Student::count
Function has this? yes no
Can use non-static members? yes no

Chapter 8 · Classes & Objects

Friend Functions & Classes

Grant selected functions or classes access to private members — when it is useful, how it works, and why to use it sparingly.

Private members are hidden from everything outside the class. Sometimes a function that is not a member genuinely needs that access — for example, a function that compares two objects of different classes, or an operator like << whose left side is cout. A class can declare such a function its friend.

Friend function

C++
#include <iostream>
using namespace std;

class Box {
    double length, width, height;
public:
    Box(double l, double w, double h) : length(l), width(w), height(h) {}
    friend double volume(const Box &b);        // grant access
};

double volume(const Box &b) {                  // NOT a member: no Box:: and no this
    return b.length * b.width * b.height;      // but it may read private members
}

int main() {
    Box b(2, 3, 4);
    cout << "Volume: " << volume(b) << endl;
    return 0;
}
Output
Volume: 24

Properties of a friend function:

  • It is declared inside the class with friend, but it is not a member — it is called like a normal function, volume(b), not b.volume().
  • It can access the class's private and protected members through an object.
  • The declaration can go in the public or private section; it makes no difference.

A friend of two classes

A classic use: one function needs the private data of two classes.

C++
#include <iostream>
using namespace std;

class Rupees;                                  // forward declaration

class Dollars {
    double amount;
public:
    Dollars(double a) : amount(a) {}
    friend bool richer(const Dollars &d, const Rupees &r);
};

class Rupees {
    double amount;
public:
    Rupees(double a) : amount(a) {}
    friend bool richer(const Dollars &d, const Rupees &r);
};

bool richer(const Dollars &d, const Rupees &r) {
    const double rate = 84.0;                  // rupees per dollar
    return d.amount * rate > r.amount;
}

int main() {
    Dollars savingsUS(150);
    Rupees savingsIN(11000);
    cout << (richer(savingsUS, savingsIN) ? "Dollar account is bigger" : "Rupee account is bigger") << endl;
    return 0;
}
Output
Dollar account is bigger

Friend class

A whole class can be a friend: all its member functions get access.

C++
#include <iostream>
using namespace std;

class Engine {
    int horsepower = 120;
    friend class Mechanic;                     // Mechanic may see inside
};

class Mechanic {
public:
    void tune(Engine &e) { e.horsepower += 15; }
    int inspect(const Engine &e) { return e.horsepower; }
};

int main() {
    Engine e;
    Mechanic m;
    cout << "before: " << m.inspect(e) << " hp" << endl;
    m.tune(e);
    cout << "after:  " << m.inspect(e) << " hp" << endl;
    return 0;
}
Output
before: 120 hp
after:  135 hp

Friendship rules

  1. Not mutual: if A declares B a friend, B can access A, but A cannot access B.
  2. Not inherited: a friend of the base class is not automatically a friend of derived classes.
  3. Not transitive: a friend of a friend is not a friend.

Chapter 9 · Inheritance & Polymorphism

Inheritance

Reuse and extend classes: base and derived classes, public/protected/private inheritance, the five types of inheritance, constructor order, and the diamond problem.

Inheritance lets a new class (the derived or child class) take on the members of an existing class (the base or parent class), then add its own or change some. It models an is-a relationship and avoids writing the same code twice.

C++
#include <iostream>
#include <string>
using namespace std;

class Person {                                   // base class
protected:
    string name;
    int age;
public:
    Person(const string &n, int a) : name(n), age(a) {}
    void introduce() const { cout << "I am " << name << ", " << age << " years old." << endl; }
};

class Student : public Person {                  // Student is-a Person
    string course;
public:
    Student(const string &n, int a, const string &c) : Person(n, a), course(c) {}
    void study() const { cout << name << " is studying " << course << "." << endl; }   // can use protected name
};

int main() {
    Student s("Asha", 19, "Data Structures");
    s.introduce();      // inherited from Person
    s.study();          // Student's own
    return 0;
}
Output
I am Asha, 19 years old.
Asha is studying Data Structures.

Access in derived classes

A derived class can use the base's public and protected members, but never its private ones. The mode of inheritance (public, protected, private) then decides how inherited members appear to the outside world:

Base member public inheritance protected inheritance private inheritance
public public protected private
protected protected protected private
private not accessible not accessible not accessible

public inheritance is by far the most common — it is the one that means "is-a".

Types of inheritance

Type Shape Example
Single one base → one derived Car : Vehicle
Multilevel chain: A → B → C Vehicle → Car → ElectricCar
Multiple one class, several bases SmartPhone : Phone, Camera
Hierarchical one base, several derived Shape → Circle, Square, Triangle
Hybrid a mix of the above multiple + multilevel together
ElectricCarCarBikeTruckVehicle
Multilevel (Vehicle → Car → ElectricCar) and hierarchical (Vehicle → Car, Bike, Truck) inheritance in one picture.

Multilevel inheritance and constructor order

Constructors run from the base down to the most derived class; destructors run in the reverse order.

C++
#include <iostream>
using namespace std;

class Vehicle {
public:
    Vehicle()  { cout << "Vehicle constructor" << endl; }
    ~Vehicle() { cout << "Vehicle destructor" << endl; }
};
class Car : public Vehicle {
public:
    Car()  { cout << "  Car constructor" << endl; }
    ~Car() { cout << "  Car destructor" << endl; }
};
class ElectricCar : public Car {
public:
    ElectricCar()  { cout << "    ElectricCar constructor" << endl; }
    ~ElectricCar() { cout << "    ElectricCar destructor" << endl; }
};

int main() {
    ElectricCar tesla;
    cout << "--- object in use ---" << endl;
    return 0;
}
Output
Vehicle constructor
  Car constructor
    ElectricCar constructor
--- object in use ---
    ElectricCar destructor
  Car destructor
Vehicle destructor

Multiple inheritance

C++
#include <iostream>
using namespace std;

class Camera {
public:
    void takePhoto() const { cout << "Click! Photo saved." << endl; }
};
class Phone {
public:
    void call(const char *who) const { cout << "Calling " << who << "..." << endl; }
};
class SmartPhone : public Phone, public Camera {      // two bases
public:
    void browse() const { cout << "Opening browser." << endl; }
};

int main() {
    SmartPhone p;
    p.call("Mom");
    p.takePhoto();
    p.browse();
    return 0;
}
Output
Calling Mom...
Click! Photo saved.
Opening browser.

The diamond problem and virtual base classes

In hybrid inheritance, a class can inherit the same base twice:

PersonStudentEmployeeTA
The diamond: a teaching assistant is both a Student and an Employee, and both are Persons. Arrows point from derived to base.

Without help, TA would contain two copies of Person, and ta.name would be ambiguous. Declaring the shared base virtual makes the derived classes share one copy:

C++
#include <iostream>
#include <string>
using namespace std;

class Person {
public:
    string name;
    Person(const string &n = "") : name(n) { cout << "Person(" << n << ")" << endl; }
};
class Student  : virtual public Person { public: Student()  { cout << "Student" << endl; } };
class Employee : virtual public Person { public: Employee() { cout << "Employee" << endl; } };

class TA : public Student, public Employee {
public:
    TA(const string &n) : Person(n) { cout << "TA" << endl; }   // the most derived class builds the shared base
};

int main() {
    TA ta("Nikhil");
    cout << "name = " << ta.name << " (only one Person inside)" << endl;
    return 0;
}
Output
Person(Nikhil)
Student
Employee
TA
name = Nikhil (only one Person inside)

Overriding (redefining) base functions

A derived class can define a function with the same signature as the base, replacing its behaviour. The base version is still reachable with Base::function():

C++
#include <iostream>
using namespace std;

class Account {
public:
    double interestRate() const { return 3.0; }
};
class SeniorCitizenAccount : public Account {
public:
    double interestRate() const { return Account::interestRate() + 0.5; }   // reuse, then extend
};

int main() {
    Account a;
    SeniorCitizenAccount s;
    cout << "regular: " << a.interestRate() << "%, senior: " << s.interestRate() << "%" << endl;
    return 0;
}
Output
regular: 3%, senior: 3.5%

Without virtual, which version runs depends on the declared type of the pointer or reference — the problem that virtual functions solve.

Chapter 10 · Inheritance & Polymorphism

Operator Overloading

Make your own types work with +, ==, <<, [] and more: member vs friend operators, unary and binary operators, and the rules you must follow.

Operator overloading lets you define what operators mean for your own classes, so code reads naturally: c1 + c2 instead of add(c1, c2). It is a form of compile-time polymorphism.

An overloaded operator is just a function named operator followed by the symbol: operator+, operator==, operator<<.

Binary operators as members

For a + b written as a member function, a is the object the function is called on and b is the parameter.

C++
#include <iostream>
using namespace std;

class Complex {
    double re, im;
public:
    Complex(double r = 0, double i = 0) : re(r), im(i) {}
    Complex operator+(const Complex &o) const { return Complex(re + o.re, im + o.im); }
    Complex operator-(const Complex &o) const { return Complex(re - o.re, im - o.im); }
    Complex operator*(const Complex &o) const {
        return Complex(re * o.re - im * o.im, re * o.im + im * o.re);   // (a+bi)(c+di)
    }
    bool operator==(const Complex &o) const { return re == o.re && im == o.im; }
    void print() const { cout << re << (im < 0 ? " - " : " + ") << (im < 0 ? -im : im) << "i" << endl; }
};

int main() {
    Complex a(3, 2), b(1, -4);
    Complex sum = a + b;           // calls a.operator+(b)
    sum.print();
    (a - b).print();
    (a * b).print();
    cout << boolalpha << (a + b == Complex(4, -2)) << endl;
    return 0;
}
Output
4 - 2i
2 + 6i
11 - 10i
true

Unary operators

Prefix and postfix ++ are told apart by a dummy int parameter on the postfix version:

C++
#include <iostream>
using namespace std;

class Counter {
    int value;
public:
    Counter(int v = 0) : value(v) {}
    Counter &operator++()   { ++value; return *this; }                 // prefix: ++c
    Counter operator++(int) { Counter old = *this; ++value; return old; }  // postfix: c++
    Counter operator-() const { return Counter(-value); }              // unary minus
    int get() const { return value; }
};

int main() {
    Counter c(5);
    Counter a = c++;     // a gets 5, c becomes 6
    Counter b = ++c;     // c becomes 7, b gets 7
    cout << a.get() << " " << b.get() << " " << c.get() << " " << (-c).get() << endl;
    return 0;
}
Output
5 7 7 -7

Overloading << and >> with friends

cout << obj has cout (an ostream) on the left, so it cannot be a member of your class. Write it as a non-member — usually a friend so it can read private data — and return the stream to allow chaining:

C++
#include <iostream>
using namespace std;

class Fraction {
    int num, den;
    static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
    void reduce() { int g = gcd(num < 0 ? -num : num, den); if (g) { num /= g; den /= g; } }
public:
    Fraction(int n = 0, int d = 1) : num(n), den(d) { reduce(); }
    Fraction operator+(const Fraction &o) const { return Fraction(num * o.den + o.num * den, den * o.den); }
    friend ostream &operator<<(ostream &out, const Fraction &f) { return out << f.num << "/" << f.den; }
    friend istream &operator>>(istream &in, Fraction &f) { in >> f.num >> f.den; f.reduce(); return in; }
};

int main() {
    Fraction a(1, 2), b;
    cout << "Enter a fraction (numerator denominator): ";
    cin >> b;
    cout << a << " + " << b << " = " << a + b << endl;
    cout << "6/8 simplifies to " << Fraction(6, 8) << endl;
    return 0;
}
Output
Enter a fraction (numerator denominator): 7 3
1/2 + 7/3 = 17/6
6/8 simplifies to 3/4
Sample input is includedTry it Yourself

Subscript and comparison operators

C++
#include <iostream>
#include <stdexcept>
using namespace std;

class SafeArray {
    int data[5] = {10, 20, 30, 40, 50};
public:
    int &operator[](int i) {                     // returns a reference so a[i] = x works
        if (i < 0 || i >= 5) throw out_of_range("index " + to_string(i));
        return data[i];
    }
};

int main() {
    SafeArray a;
    a[2] = 99;
    cout << a[2] << endl;
    try { cout << a[7] << endl; }
    catch (const out_of_range &e) { cout << "caught bad " << e.what() << endl; }
    return 0;
}
Output
99
caught bad index 7

Rules

  • You cannot create new operators or change precedence, associativity or the number of operands.
  • At least one operand must be a user-defined type (you cannot redefine int + int).
  • These cannot be overloaded: :: . .* ?: sizeof typeid.
  • =, (), [] and -> must be member functions.
  • << and >> for streams must be non-members (usually friends).
Member operator Friend / non-member operator
left operand is *this both operands are parameters
one parameter for binary operators two parameters for binary operators
left operand must be your class left operand can be any type (cout << x, 5 + x)

Chapter 11 · Inheritance & Polymorphism

Virtual Functions & Runtime Polymorphism

Base-class pointers to derived objects, why virtual changes which function runs, override, virtual destructors, and how the vtable makes it work.

The problem: static binding

A base-class pointer can point to a derived object. But by default, calling a function through it runs the base version — the compiler decides at compile time from the pointer's type. This is static (early) binding.

C++
#include <iostream>
using namespace std;

class Animal {
public:
    void speak() const { cout << "..." << endl; }          // NOT virtual
};
class Dog : public Animal {
public:
    void speak() const { cout << "Woof!" << endl; }
};

int main() {
    Dog d;
    Animal *p = &d;      // base pointer, derived object
    d.speak();           // Woof!
    p->speak();          // "..." — chosen by the pointer type
    return 0;
}
Output
Woof!
...

The fix: virtual functions

Mark the base function virtual. Now the function is chosen at run time from the actual object — dynamic (late) binding. This is runtime polymorphism.

C++
#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Employee {
protected:
    string name;
    double base;
public:
    Employee(const string &n, double b) : name(n), base(b) {}
    virtual double pay() const { return base; }
    virtual string role() const { return "Employee"; }
    virtual ~Employee() {}
    void payslip() const { cout << name << " (" << role() << "): Rs " << pay() << endl; }
};

class Manager : public Employee {
    double bonus;
public:
    Manager(const string &n, double b, double bo) : Employee(n, b), bonus(bo) {}
    double pay() const override { return base + bonus; }
    string role() const override { return "Manager"; }
};

class Intern : public Employee {
public:
    Intern(const string &n) : Employee(n, 15000) {}
    string role() const override { return "Intern"; }       // keeps Employee::pay
};

int main() {
    Manager m("Priya", 90000, 25000);
    Intern i("Rahul");
    Employee e("Sanjay", 60000);
    vector<const Employee *> staff = {&e, &m, &i};
    for (const Employee *s : staff) s->payslip();          // one loop, three behaviours
    return 0;
}
Output
Sanjay (Employee): Rs 60000
Priya (Manager): Rs 115000
Rahul (Intern): Rs 15000

payslip() is written once in the base class, yet it calls the right pay() and role() for each object. Adding a new kind of employee requires no change to payslip — that is the real power of polymorphism.

Virtual destructors

If you delete a derived object through a base pointer and the base destructor is not virtual, only the base destructor runs — the derived part leaks.

C++
#include <iostream>
using namespace std;

class Base {
public:
    virtual ~Base() { cout << "~Base" << endl; }     // remove 'virtual' and ~Derived is skipped
};
class Derived : public Base {
    int *buffer = new int[1000];
public:
    ~Derived() { delete[] buffer; cout << "~Derived (freed buffer)" << endl; }
};

int main() {
    Base *p = new Derived;
    delete p;            // runs ~Derived, then ~Base
    return 0;
}
Output
~Derived (freed buffer)
~Base

How it works: the vtable

For each class with virtual functions, the compiler builds a virtual table (vtable): an array of pointers to that class's versions of the virtual functions. Each object gets a hidden pointer (the vptr) to its class's vtable. A virtual call follows object → vptr → vtable → function. That is one extra pointer lookup per call, and one extra pointer per object:

C++
#include <iostream>
using namespace std;

class Plain   { int x = 0; public: int f() { return x; } };
class Virtual { int x = 0; public: virtual int f() { return x; } };

int main() {
    cout << "sizeof(Plain)   = " << sizeof(Plain) << endl;
    cout << "sizeof(Virtual) = " << sizeof(Virtual) << " (int + hidden vptr, padded)" << endl;
    return 0;
}
Output
sizeof(Plain)   = 4
sizeof(Virtual) = 16 (int + hidden vptr, padded)
Static binding Dynamic binding
decided at compile time decided at run time
normal and overloaded functions virtual functions called through pointers/references
faster tiny overhead (vtable lookup)
also called early binding also called late binding

Chapter 12 · Inheritance & Polymorphism

Abstract Classes & Interfaces

Pure virtual functions, abstract base classes that cannot be instantiated, interfaces, and the final keyword — designing a clean class hierarchy.

Pure virtual functions

Sometimes a base class cannot sensibly implement a function. What is the area of a generic "Shape"? Declare it pure virtual with = 0: it has no body in the base, and every concrete derived class must override it.

C++
virtual double area() const = 0;   // pure virtual

Abstract classes

A class with at least one pure virtual function is an abstract class:

  • You cannot create objects of it: Shape s; is a compile error.
  • You can have pointers and references to it.
  • A derived class that does not override every pure virtual function is abstract too.
C++
#include <iostream>
#include <cmath>
using namespace std;

class Shape {                                    // abstract base class
public:
    virtual double area() const = 0;
    virtual double perimeter() const = 0;
    virtual const char *name() const = 0;
    void describe() const {                      // concrete function using the abstract ones
        cout << name() << ": area " << area() << ", perimeter " << perimeter() << endl;
    }
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double r;
public:
    explicit Circle(double r) : r(r) {}
    double area() const override { return M_PI * r * r; }
    double perimeter() const override { return 2 * M_PI * r; }
    const char *name() const override { return "Circle"; }
};

class Square : public Shape {
    double s;
public:
    explicit Square(double s) : s(s) {}
    double area() const override { return s * s; }
    double perimeter() const override { return 4 * s; }
    const char *name() const override { return "Square"; }
};

int main() {
    // Shape s;              // error: cannot declare variable 's' to be of abstract type
    Circle c(1);
    Square q(2.5);
    const Shape &a = c, &b = q;
    a.describe();
    b.describe();
    return 0;
}
Output
Circle: area 3.14159, perimeter 6.28319
Square: area 6.25, perimeter 10

Interfaces

C++ has no interface keyword (Java does). An interface in C++ is simply an abstract class with only pure virtual functions and a virtual destructor. A class can implement several interfaces through multiple inheritance:

C++
#include <iostream>
#include <string>
using namespace std;

class Printable { public: virtual void print() const = 0; virtual ~Printable() = default; };
class Payable   { public: virtual double amountDue() const = 0; virtual ~Payable() = default; };

class Invoice : public Printable, public Payable {
    string client;
    double hours, rate;
public:
    Invoice(string c, double h, double r) : client(c), hours(h), rate(r) {}
    double amountDue() const override { return hours * rate; }
    void print() const override { cout << "Invoice for " << client << ": Rs " << amountDue() << endl; }
};

void printAll(const Printable *items[], int n) { for (int i = 0; i < n; i++) items[i]->print(); }

int main() {
    Invoice a("Acme Labs", 12, 1500), b("Nova Foods", 4.5, 2000);
    const Printable *docs[] = {&a, &b};
    printAll(docs, 2);
    const Payable &p = a;
    cout << "Total due from Acme: " << p.amountDue() << endl;
    return 0;
}
Output
Invoice for Acme Labs: Rs 18000
Invoice for Nova Foods: Rs 9000
Total due from Acme: 18000

final

final stops further overriding or inheritance:

C++
class Base { virtual void f(); };
class Mid : public Base { void f() override final; };   // no class may override f again
class Leaf final : public Mid { };                       // no class may inherit from Leaf

Abstract class vs concrete class

Abstract class Concrete class
has at least one pure virtual function all functions have bodies
cannot be instantiated can create objects
defines a contract for derived classes provides a full implementation
used through pointers/references used directly or through pointers

Chapter 13 · Beyond the Basics

Templates

Write code once for any type: function templates, class templates, multiple type parameters, non-type parameters and specialisation — the foundation of the STL.

Suppose you need maximum for int, double and string. Overloading means three copies of identical code. A template lets you write the code once with a placeholder type; the compiler generates each version you use. This is generic programming.

Function templates

C++
#include <iostream>
#include <string>
using namespace std;

template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

template <typename T>
void swapAny(T &a, T &b) { T t = a; a = b; b = t; }

int main() {
    cout << maximum(3, 9) << endl;                          // T = int
    cout << maximum(2.5, 1.75) << endl;                     // T = double
    cout << maximum(string("apple"), string("mango")) << endl;
    cout << maximum<double>(4, 7.5) << endl;                // T given explicitly

    string a = "left", b = "right";
    swapAny(a, b);
    cout << a << " " << b << endl;
    return 0;
}
Output
9
2.5
mango
7.5
right left

typename and class mean the same thing in a template parameter list: template <class T> is equally valid.

Templates with arrays

C++
#include <iostream>
using namespace std;

template <typename T>
T sum(const T arr[], int n) {
    T total = T();                 // zero for numbers
    for (int i = 0; i < n; i++) total += arr[i];
    return total;
}

template <typename T>
int linearSearch(const T arr[], int n, const T &key) {
    for (int i = 0; i < n; i++) if (arr[i] == key) return i;
    return -1;
}

int main() {
    int marks[] = {78, 85, 92};
    double prices[] = {9.99, 24.5, 3.25};
    char grades[] = {'A', 'B', 'O', 'C'};
    cout << sum(marks, 3) << " " << sum(prices, 3) << endl;
    cout << "'O' at index " << linearSearch(grades, 4, 'O') << endl;
    return 0;
}
Output
255 37.74
'O' at index 2

Class templates

A class template describes a family of classes. The STL's vector<int> and stack<string> are class templates.

C++
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;

template <typename T, int CAPACITY = 100>       // a type parameter and a non-type parameter
class Stack {
    T items[CAPACITY];
    int top = -1;
public:
    void push(const T &x) {
        if (top == CAPACITY - 1) throw overflow_error("stack full");
        items[++top] = x;
    }
    T pop() {
        if (top == -1) throw underflow_error("stack empty");
        return items[top--];
    }
    bool empty() const { return top == -1; }
    int size() const { return top + 1; }
};

int main() {
    Stack<int> numbers;
    for (int i = 1; i <= 4; i++) numbers.push(i * i);
    cout << "popping ints:";
    while (!numbers.empty()) cout << " " << numbers.pop();
    cout << endl;

    Stack<string, 3> words;                      // capacity 3
    words.push("templates"); words.push("are"); words.push("neat");
    try { words.push("overflow!"); }
    catch (const overflow_error &e) { cout << "error: " << e.what() << endl; }
    cout << "words on stack: " << words.size() << endl;
    return 0;
}
Output
popping ints: 16 9 4 1
error: stack full
words on stack: 3

Two type parameters

C++
#include <iostream>
#include <string>
using namespace std;

template <typename K, typename V>
class Pair {
public:
    K key;
    V value;
    Pair(K k, V v) : key(k), value(v) {}
    void show() const { cout << key << " -> " << value << endl; }
};

int main() {
    Pair<string, int> p1("Asha", 92);
    Pair<int, double> p2(7, 8.75);
    p1.show();
    p2.show();
    return 0;
}
Output
Asha -> 92
7 -> 8.75

Template specialisation

Give a type its own special version when the generic code does not fit:

C++
#include <iostream>
#include <cstring>
using namespace std;

template <typename T>
bool isEqual(T a, T b) { return a == b; }

template <>                                       // full specialisation for C strings
bool isEqual<const char *>(const char *a, const char *b) { return strcmp(a, b) == 0; }

int main() {
    char x[] = "hello", y[] = "hello";
    cout << boolalpha << isEqual(4, 4) << " " << isEqual<const char *>(x, y) << endl;
    return 0;
}
Output
true true

Without the specialisation, comparing two const char * would compare addresses, not text.

Templates Overloading Macros
one definition, many types one definition per type text substitution
type-checked type-checked no type checking
code generated per type used written by hand expanded inline

Chapter 14 · Beyond the Basics

Exception Handling

Handle errors cleanly with try, throw and catch: standard exception classes, multiple and catch-all handlers, custom exceptions, and stack unwinding.

Programs meet errors at run time: division by zero, a missing file, bad input, memory exhaustion. Checking return codes everywhere clutters code and errors are easy to ignore. Exceptions separate error detection from error handling.

  • throw — signal an error.
  • try — wrap code that might throw.
  • catch — handle a specific kind of error.
C++
#include <iostream>
using namespace std;

double divide(double a, double b) {
    if (b == 0) throw "Division by zero!";        // throw a value
    return a / b;
}

int main() {
    try {
        cout << divide(10, 4) << endl;
        cout << divide(1, 0) << endl;              // throws: the rest of try is skipped
        cout << "never printed" << endl;
    } catch (const char *msg) {
        cout << "Error: " << msg << endl;
    }
    cout << "Program continues normally." << endl;
    return 0;
}
Output
2.5
Error: Division by zero!
Program continues normally.

Standard exception classes

The standard library throws objects derived from std::exception. Each has a what() method returning a message.

Class (header <stdexcept>) Typical cause
invalid_argument a bad argument, e.g. stoi("abc")
out_of_range index past the end, e.g. vector::at(100)
length_error a container grown beyond its maximum
overflow_error, underflow_error arithmetic range errors
runtime_error general errors detectable only at run time
bad_alloc (<new>) new failed to get memory
C++
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};
    try {
        cout << v.at(1) << endl;
        cout << v.at(10) << endl;                   // .at() checks bounds and throws
    } catch (const out_of_range &e) {
        cout << "out_of_range caught" << endl;
    }

    string inputs[] = {"42", "abc", "99999999999"};
    for (const string &s : inputs) {
        try {
            int n = stoi(s);                            // may throw
            cout << "stoi(\"" << s << "\") = " << n << endl;
        } catch (const invalid_argument &) {
            cout << "\"" << s << "\" is not a number" << endl;
        } catch (const out_of_range &) {
            cout << "\"" << s << "\" is too big for an int" << endl;
        }
    }
    return 0;
}
Output
2
out_of_range caught
stoi("42") = 42
"abc" is not a number
"99999999999" is too big for an int

Multiple catch blocks and catch-all

Handlers are tried in order; the first matching one runs. Put specific types before general ones, and catch (...) (catch anything) last:

C++
#include <iostream>
#include <stdexcept>
using namespace std;

void risky(int option) {
    if (option == 1) throw 404;
    if (option == 2) throw runtime_error("disk not ready");
    if (option == 3) throw 3.14;
    cout << "option " << option << " ran fine" << endl;
}

int main() {
    for (int option = 0; option <= 3; option++) {
        try {
            risky(option);
        } catch (int code) {
            cout << "int error code " << code << endl;
        } catch (const exception &e) {                 // catches every std exception
            cout << "std::exception: " << e.what() << endl;
        } catch (...) {
            cout << "unknown error" << endl;
        }
    }
    return 0;
}
Output
option 0 ran fine
int error code 404
std::exception: disk not ready
unknown error

Custom exceptions

Derive from std::exception (or runtime_error) to describe your own errors:

C++
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;

class InsufficientFunds : public runtime_error {
public:
    double shortBy;
    InsufficientFunds(double s) : runtime_error("insufficient funds"), shortBy(s) {}
};

class Account {
    double balance = 1000;
public:
    void withdraw(double amt) {
        if (amt <= 0) throw invalid_argument("amount must be positive");
        if (amt > balance) throw InsufficientFunds(amt - balance);
        balance -= amt;
    }
    double getBalance() const { return balance; }
};

int main() {
    Account acc;
    double amt;
    cout << "Withdraw how much? ";
    cin >> amt;
    try {
        acc.withdraw(amt);
        cout << "Done. Balance: " << acc.getBalance() << endl;
    } catch (const InsufficientFunds &e) {
        cout << "Failed: " << e.what() << " (short by Rs " << e.shortBy << ")" << endl;
    } catch (const invalid_argument &e) {
        cout << "Failed: " << e.what() << endl;
    }
    return 0;
}
Output
Withdraw how much? 2500
Failed: insufficient funds (short by Rs 1500)
Sample input is includedTry it Yourself

Stack unwinding

When an exception propagates out of functions, every local object on the way is destroyed properly — its destructor runs. This is why C++ resources should be owned by objects (RAII): cleanup happens automatically even on errors.

C++
#include <iostream>
#include <stdexcept>
using namespace std;

struct Guard {
    const char *name;
    Guard(const char *n) : name(n) { cout << "acquire " << name << endl; }
    ~Guard() { cout << "release " << name << endl; }
};

void level2() { Guard g("file"); throw runtime_error("parse failed"); }
void level1() { Guard g("lock"); level2(); cout << "not reached" << endl; }

int main() {
    try { level1(); }
    catch (const exception &e) { cout << "caught: " << e.what() << endl; }
    return 0;
}
Output
acquire lock
acquire file
release file
release lock
caught: parse failed

Chapter 15 · Beyond the Basics

Dynamic Memory & Smart Pointers

new and delete, arrays on the heap, shallow vs deep copy, the rule of three, and modern smart pointers — unique_ptr and shared_ptr — that free memory for you.

new and delete

C++ replaces malloc/free with new and delete. new allocates memory and calls the constructor; delete calls the destructor and frees the memory.

C++
#include <iostream>
using namespace std;

class Book {
public:
    string title;
    Book(const string &t) : title(t) { cout << "construct " << title << endl; }
    ~Book() { cout << "destroy " << title << endl; }
};

int main() {
    int *n = new int(42);                 // one int, initialised to 42
    cout << *n << endl;
    delete n;

    int *arr = new int[5]{1, 2, 3, 4, 5}; // an array
    for (int i = 0; i < 5; i++) cout << arr[i] << " ";
    cout << endl;
    delete[] arr;                         // arrays need delete[]

    Book *b = new Book("C++ Primer");     // constructor runs
    cout << "reading " << b->title << endl;
    delete b;                             // destructor runs
    return 0;
}
Output
42
1 2 3 4 5 
construct C++ Primer
reading C++ Primer
destroy C++ Primer
malloc / free new / delete
function from <cstdlib> operator built into the language
returns void *, needs a cast returns the correct pointer type
size given in bytes size computed from the type
does not call constructors/destructors calls constructors and destructors
returns NULL on failure throws bad_alloc on failure

Shallow copy vs deep copy

If a class owns heap memory through a pointer, the compiler's default copy copies the pointer, not the data — a shallow copy. Two objects then share one buffer, and both destructors free it: a crash.

A deep copy allocates new memory and copies the contents.

The rule of three

If a class needs a custom destructor, it almost certainly also needs a custom copy constructor and copy assignment operator:

C++
#include <iostream>
#include <cstring>
using namespace std;

class Name {
    char *text;
public:
    Name(const char *s) : text(new char[strlen(s) + 1]) { strcpy(text, s); }
    ~Name() { delete[] text; }                                     // 1. destructor
    Name(const Name &o) : text(new char[strlen(o.text) + 1]) {     // 2. copy constructor (deep)
        strcpy(text, o.text);
    }
    Name &operator=(const Name &o) {                               // 3. copy assignment (deep)
        if (this != &o) {                                          // guard against a = a
            char *fresh = new char[strlen(o.text) + 1];
            strcpy(fresh, o.text);
            delete[] text;
            text = fresh;
        }
        return *this;
    }
    void setFirst(char c) { text[0] = c; }
    const char *get() const { return text; }
};

int main() {
    Name a("asha");
    Name b = a;          // copy constructor
    Name c("x");
    c = a;               // copy assignment
    b.setFirst('U');
    c.setFirst('N');
    cout << a.get() << " " << b.get() << " " << c.get() << endl;   // each has its own buffer
    return 0;
}
Output
asha Usha Nsha

(C++11 extends this to the rule of five by adding a move constructor and move assignment. Even better is the rule of zero: let standard types like string, vector and smart pointers own the memory, and write none of these functions.)

Smart pointers

Manual delete is easy to forget, especially when exceptions fly. Smart pointers in <memory> own heap objects and free them automatically — the RAII idea applied to memory.

unique_ptr: sole ownership

C++
#include <iostream>
#include <memory>
using namespace std;

struct Sensor {
    int id;
    Sensor(int i) : id(i) { cout << "sensor " << id << " on" << endl; }
    ~Sensor() { cout << "sensor " << id << " off" << endl; }
};

int main() {
    unique_ptr<Sensor> a = make_unique<Sensor>(1);
    cout << "reading sensor " << a->id << endl;
    // unique_ptr<Sensor> b = a;          // error: cannot copy a unique_ptr
    unique_ptr<Sensor> b = std::move(a);  // ownership moves to b
    cout << "a is " << (a ? "set" : "empty") << ", b owns sensor " << b->id << endl;
    {
        auto c = make_unique<Sensor>(2);
    }                                     // c's sensor freed here automatically
    cout << "end of main" << endl;
    return 0;
}                                         // b's sensor freed here
Output
sensor 1 on
reading sensor 1
a is empty, b owns sensor 1
sensor 2 on
sensor 2 off
end of main
sensor 1 off

shared_ptr: shared ownership

A shared_ptr keeps a reference count; the object is deleted when the last shared_ptr to it is destroyed.

C++
#include <iostream>
#include <memory>
#include <string>
using namespace std;

struct Playlist {
    string name;
    Playlist(string n) : name(n) {}
    ~Playlist() { cout << "deleting playlist " << name << endl; }
};

int main() {
    shared_ptr<Playlist> mine = make_shared<Playlist>("Coding Lo-fi");
    cout << "owners: " << mine.use_count() << endl;
    {
        shared_ptr<Playlist> friendCopy = mine;
        cout << "owners: " << mine.use_count() << endl;
    }
    cout << "owners: " << mine.use_count() << endl;
    mine.reset();                          // last owner gone: object deleted
    cout << "done" << endl;
    return 0;
}
Output
owners: 1
owners: 2
owners: 1
deleting playlist Coding Lo-fi
done
Smart pointer Ownership Copyable? Use for
unique_ptr exactly one owner no (move only) the default choice
shared_ptr many owners, reference counted yes genuinely shared objects
weak_ptr observes a shared_ptr without owning yes breaking reference cycles

Chapter 16 · Beyond the Basics

STL Containers

The Standard Template Library's ready-made data structures: vector, list, deque, stack, queue, priority_queue, set, map and their unordered versions — with costs.

The Standard Template Library (STL) gives you tested, fast implementations of every structure from the Data Structures course. It has three parts: containers (data structures), iterators (a uniform way to walk through containers) and algorithms (sort, search and more — next lesson).

vector: the default container

A vector is a dynamic array: contiguous memory that grows as needed (the realloc doubling trick from C, done for you).

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {5, 3, 8};
    v.push_back(1);                       // add at the end: amortised O(1)
    v.push_back(9);
    cout << "size " << v.size() << ", front " << v.front() << ", back " << v.back() << endl;
    v.insert(v.begin() + 1, 42);          // insert at index 1: O(n)
    v.pop_back();                         // remove the last element
    v.erase(v.begin());                   // remove the first element: O(n)
    for (int x : v) cout << x << " ";
    cout << endl;

    vector<vector<int>> grid(3, vector<int>(4, 0));   // 3x4 matrix of zeros
    grid[1][2] = 7;
    cout << "grid[1][2] = " << grid[1][2] << ", rows " << grid.size() << endl;
    return 0;
}
Output
size 5, front 5, back 9
42 3 8 1 
grid[1][2] = 7, rows 3

Stack, queue and priority_queue

These are container adapters with a restricted interface — exactly the ADTs from Data Structures:

C++
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <functional>
using namespace std;

int main() {
    stack<int> s;
    for (int i = 1; i <= 3; i++) s.push(i);
    cout << "stack pops:";
    while (!s.empty()) { cout << " " << s.top(); s.pop(); }

    queue<string> q;
    q.push("first"); q.push("second"); q.push("third");
    cout << "\nqueue pops:";
    while (!q.empty()) { cout << " " << q.front(); q.pop(); }

    priority_queue<int> maxHeap;                                  // largest on top
    priority_queue<int, vector<int>, greater<int>> minHeap;       // smallest on top
    for (int x : {40, 10, 70, 30}) { maxHeap.push(x); minHeap.push(x); }
    cout << "\nmax-heap top: " << maxHeap.top() << ", min-heap top: " << minHeap.top() << endl;
    return 0;
}
Output
stack pops: 3 2 1
queue pops: first second third
max-heap top: 70, min-heap top: 10

set and map

set stores unique keys in sorted order; map stores sorted key → value pairs. Both are balanced binary search trees (red-black trees): O(log n) insert, find and erase.

C++
#include <iostream>
#include <set>
#include <map>
#include <string>
using namespace std;

int main() {
    set<int> s = {5, 1, 5, 3, 1, 9};          // duplicates ignored
    s.insert(4);
    cout << "set:";
    for (int x : s) cout << " " << x;          // always sorted
    cout << "\ncontains 3? " << (s.count(3) ? "yes" : "no") << endl;

    map<string, int> marks;
    marks["Asha"] = 91;
    marks["Ravi"] = 78;
    marks["Meera"] = 85;
    marks["Ravi"] += 5;                        // update
    for (const auto &[name, m] : marks)        // sorted by key
        cout << name << ": " << m << endl;
    if (marks.find("Zoya") == marks.end()) cout << "Zoya not found" << endl;
    return 0;
}
Output
set: 1 3 4 5 9
contains 3? yes
Asha: 91
Meera: 85
Ravi: 83
Zoya not found

unordered_map: hash tables

unordered_map and unordered_set are hash tables: O(1) average lookup, but no ordering. Perfect for counting:

C++
#include <iostream>
#include <unordered_map>
#include <sstream>
#include <string>
using namespace std;

int main() {
    string text = "the cat and the dog and the bird";
    unordered_map<string, int> freq;
    stringstream ss(text);
    string word;
    while (ss >> word) freq[word]++;           // missing keys start at 0
    for (string w : {"the", "and", "cat", "fish"})
        cout << w << ": " << freq[w] << endl;
    return 0;
}
Output
the: 3
and: 2
cat: 1
fish: 0

Choosing a container

Container Underlying structure Access Insert/erase Find
vector dynamic array O(1) by index O(1) at end, O(n) elsewhere O(n)
deque blocks of arrays O(1) by index O(1) at both ends O(n)
list doubly linked list O(n) O(1) with an iterator O(n)
stack / queue adapter (deque by default) top / front only O(1) —
priority_queue binary heap top only O(log n) —
set / map red-black tree — O(log n) O(log n), sorted
unordered_set / unordered_map hash table — O(1) average O(1) average

Chapter 17 · Beyond the Basics

Iterators, Algorithms & Lambdas

Walk any container with iterators, then use the STL algorithms — sort, find, count, binary_search, accumulate, transform — with lambda functions for custom behaviour.

Iterators

An iterator is an object that points to an element of a container and can move to the next one — a generalised pointer. begin() points to the first element; end() points one past the last.

C++
#include <iostream>
#include <vector>
#include <list>
using namespace std;

int main() {
    vector<int> v = {10, 20, 30, 40};
    for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
        cout << *it << " ";                    // dereference like a pointer
    cout << endl;

    list<string> names = {"Asha", "Ravi", "Meera"};
    for (auto it = names.rbegin(); it != names.rend(); ++it)   // reverse iterators
        cout << *it << " ";
    cout << endl;
    return 0;
}
Output
10 20 30 40 
Meera Ravi Asha

Because every container offers iterators, one algorithm works on all of them.

Lambda expressions

A lambda is an unnamed function written inline: [capture](parameters) { body }. It is the easiest way to give an algorithm custom behaviour.

C++
#include <iostream>
using namespace std;

int main() {
    auto square = [](int x) { return x * x; };
    int bonus = 5;
    auto addBonus = [bonus](int m) { return m + bonus; };     // captures bonus by value
    int calls = 0;
    auto counter = [&calls]() { calls++; };                    // captures calls by reference
    counter(); counter(); counter();
    cout << square(7) << " " << addBonus(80) << " " << calls << endl;
    return 0;
}
Output
49 85 3

The essential algorithms

C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <string>
using namespace std;

void show(const vector<int> &v) { for (int x : v) cout << x << " "; cout << endl; }

int main() {
    vector<int> v = {42, 7, 19, 7, 88, 3, 25};

    sort(v.begin(), v.end());                                   // ascending
    cout << "sorted:        "; show(v);
    sort(v.begin(), v.end(), [](int a, int b) { return a > b; });   // descending
    cout << "descending:    "; show(v);

    cout << "max element:   " << *max_element(v.begin(), v.end()) << endl;
    cout << "sum:           " << accumulate(v.begin(), v.end(), 0) << endl;
    cout << "count of 7:    " << count(v.begin(), v.end(), 7) << endl;
    cout << "count > 20:    " << count_if(v.begin(), v.end(), [](int x) { return x > 20; }) << endl;

    auto it = find(v.begin(), v.end(), 19);
    cout << "19 found at index " << (it - v.begin()) << endl;

    sort(v.begin(), v.end());
    cout << "binary_search 25: " << boolalpha << binary_search(v.begin(), v.end(), 25) << endl;
    v.erase(unique(v.begin(), v.end()), v.end());              // remove adjacent duplicates
    cout << "unique:        "; show(v);
    reverse(v.begin(), v.end());
    cout << "reversed:      "; show(v);

    vector<int> doubled(v.size());
    transform(v.begin(), v.end(), doubled.begin(), [](int x) { return 2 * x; });
    cout << "doubled:       "; show(doubled);
    return 0;
}
Output
sorted:        3 7 7 19 25 42 88 
descending:    88 42 25 19 7 7 3 
max element:   88
sum:           191
count of 7:    2
count > 20:    3
19 found at index 3
binary_search 25: true
unique:        3 7 19 25 42 88 
reversed:      88 42 25 19 7 3 
doubled:       176 84 50 38 14 6

Sorting objects

Pass a comparison lambda to sort by any field:

C++
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

struct Student { string name; int marks; };

int main() {
    vector<Student> cls = {{"Asha", 88}, {"Ravi", 95}, {"Meera", 88}, {"Kiran", 72}};
    stable_sort(cls.begin(), cls.end(), [](const Student &a, const Student &b) {
        return a.marks > b.marks;               // highest marks first; ties keep their order
    });
    int rank = 1;
    for (const auto &s : cls) cout << rank++ << ". " << s.name << " " << s.marks << endl;
    return 0;
}
Output
1. Ravi 95
2. Asha 88
3. Meera 88
4. Kiran 72
Algorithm Does Cost
sort sort a range (introsort) O(n log n)
stable_sort sort, keeping equal elements in order O(n log n)
find, find_if first match O(n)
count, count_if number of matches O(n)
binary_search, lower_bound search a sorted range O(log n)
accumulate (<numeric>) sum or fold O(n)
min_element, max_element position of the min / max O(n)
reverse, rotate, unique rearrange O(n)
transform apply a function to every element O(n)

Chapter 18 · Beyond the Basics

File I/O with fstream

Read and write files the C++ way with ifstream, ofstream and fstream: modes, line-by-line reading, parsing records with stringstream, and binary files.

C++ file handling uses streams, exactly like cin and cout. Include <fstream>:

Class Purpose
ofstream write to a file (output)
ifstream read from a file (input)
fstream both

Writing and reading text

C++
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    ofstream out("notes.txt");                    // creates or truncates the file
    if (!out) { cerr << "cannot open file" << endl; return 1; }
    out << "Templates make code generic." << endl;
    out << "Exceptions separate error handling." << endl;
    out << "RAII frees resources automatically." << endl;
    out.close();

    ifstream in("notes.txt");
    string line;
    int n = 0;
    while (getline(in, line))                      // read until end of file
        cout << ++n << ": " << line << endl;
    return 0;                                     // in closes automatically (RAII)
}
Output
1: Templates make code generic.
2: Exceptions separate error handling.
3: RAII frees resources automatically.

File modes

Flag Meaning
ios::in open for reading
ios::out open for writing (truncates by default)
ios::app append: every write goes to the end
ios::ate start at the end, but allow moving around
ios::trunc erase existing contents
ios::binary binary mode (no newline translation)

Combine flags with |: fstream f("data.txt", ios::in | ios::out);

Reading structured records

Write records as text, then read them back with >> or parse each line with a stringstream:

C++
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <iomanip>
using namespace std;

int main() {
    ofstream out("students.csv");
    out << "roll,name,marks\n101,Asha,88\n102,Ravi,93\n103,Meera,79\n";
    out.close();

    ifstream in("students.csv");
    string line;
    getline(in, line);                                  // skip the header
    double total = 0;
    int count = 0;
    cout << left << setw(6) << "Roll" << setw(8) << "Name" << "Marks" << endl;
    while (getline(in, line)) {
        stringstream ss(line);
        string roll, name, marksText;
        getline(ss, roll, ',');                         // split on commas
        getline(ss, name, ',');
        getline(ss, marksText, ',');
        int marks = stoi(marksText);
        cout << setw(6) << roll << setw(8) << name << marks << endl;
        total += marks;
        count++;
    }
    cout << "Average: " << fixed << setprecision(2) << total / count << endl;

    ofstream log("students.csv", ios::app);              // append a new record
    log << "104,Kiran,85\n";
    return 0;
}
Output
Roll  Name    Marks
101   Asha    88
102   Ravi    93
103   Meera   79
Average: 86.67

Binary files

Binary I/O writes raw bytes — compact and fast, but not human-readable:

C++
#include <iostream>
#include <fstream>
using namespace std;

struct Reading { int sensor; double value; };

int main() {
    Reading data[3] = {{1, 21.5}, {2, 22.25}, {3, 19.75}};
    ofstream out("readings.bin", ios::binary);
    out.write(reinterpret_cast<const char *>(data), sizeof(data));
    out.close();

    ifstream in("readings.bin", ios::binary);
    in.seekg(sizeof(Reading) * 2);                   // jump to the third record
    Reading r;
    in.read(reinterpret_cast<char *>(&r), sizeof(r));
    cout << "sensor " << r.sensor << " read " << r.value << endl;
    cout << "file size: " << sizeof(data) << " bytes" << endl;
    return 0;
}
Output
sensor 3 read 19.75
file size: 48 bytes

seekg / tellg move and report the get (read) position; seekp / tellp do the same for the put (write) position.

Chapter 19 · Beyond the Basics

C++ OOP Cheat Sheet & Exam Prep

The whole OOPS course on one page: syntax for classes, constructors, inheritance and polymorphism, the comparisons examiners love, and programs to practise.

Class skeleton

C++
class Name : public Base {             // inheritance
private:                               // hidden data
    int value;
    static int count;                  // shared by all objects
public:
    Name(int v = 0) : Base(), value(v) { count++; }      // constructor + initialiser list
    Name(const Name &o) : Base(o), value(o.value) {}     // copy constructor
    Name &operator=(const Name &o);                      // copy assignment
    virtual ~Name() {}                                   // virtual destructor

    int get() const { return value; }                    // const getter
    void set(int v) { if (v >= 0) value = v; }           // validating setter
    virtual void show() const override;                  // runtime polymorphism
    static int total() { return count; }                 // class-level function
    friend ostream &operator<<(ostream &, const Name &); // friend operator
};
int Name::count = 0;

OOP pillars in one table

Pillar Idea C++ tool
Encapsulation bundle data and methods, hide data private, getters/setters
Abstraction expose what, hide how public interface, abstract classes
Inheritance reuse and extend (is-a) class D : public B
Polymorphism one interface, many forms overloading (compile time), virtual (run time)

Comparisons examiners ask

Topic Key difference
struct vs class default access: public vs private
Constructor vs destructor creates/initialises vs cleans up; destructor has ~, no parameters, no overloading
Copy constructor vs assignment initialise a new object vs overwrite an existing one
Shallow vs deep copy copies the pointer vs copies the pointed-to data
Overloading vs overriding same name, different parameters (compile time) vs same signature in derived class (run time, virtual)
Static vs dynamic binding decided at compile time vs at run time through vtable
Abstract class vs interface has some pure virtual functions vs only pure virtual functions
Pointer vs reference nullable, re-seatable vs always bound, alias
new/delete vs malloc/free calls constructors/destructors, type-safe, throws bad_alloc
Function template vs overloading one generic definition vs one definition per type

Inheritance access table

Base member public inheritance protected inheritance private inheritance
public public protected private
protected protected protected private
private not accessible not accessible not accessible

Operators you cannot overload

::   .   .*   ?:   sizeof   typeid

STL quick picks

Need Use
growable array vector<T>
LIFO / FIFO stack<T> / queue<T>
always the largest (smallest) priority_queue<T> (with greater<T>)
sorted unique keys set<T>
sorted key → value map<K, V>
fastest lookup by key unordered_map<K, V>
sort / search sort, binary_search, lower_bound

Programs to practise

  1. Class Student/Employee/BankAccount with constructors, getters, setters and validation.
  2. Count objects with a static member.
  3. Copy constructor demonstrating deep copy.
  4. Complex or Fraction with +, -, *, ==, <<, >> overloaded.
  5. Multilevel inheritance showing constructor/destructor order.
  6. Shape hierarchy with an abstract base class and virtual functions.
  7. Function template maximum, class template Stack<T>.
  8. Custom exception class with try/catch.
  9. Word frequency with map or unordered_map.
  10. Read and write student records with fstream.