C++ Programming — Comprehensive Notes
1. Introduction to C++
C++ is a general-purpose, statically typed, compiled programming language created by
Bjarne Stroustrup at Bell Labs in 1979 as an extension of the C language. Originally called
“C with Classes,” it was renamed C++ in 1983. The ++ in the name is itself a C++ expression
— the post-increment operator applied to C — symbolizing an improvement upon C.
C++ supports multiple programming paradigms: procedural, object-oriented, generic, and
functional programming. It is widely used in system software, game development,
embedded systems, high-performance computing, and applications where fine-grained
control over hardware is required.
C++ programs are compiled into machine code, making them extremely fast and efficient.
The language gives programmers direct control over memory management, making it both
powerful and demanding of careful use.
2. Structure of a C++ Program
Every C++ program has a specific structure. Below is the classic “Hello, World!” example:
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
Key components:
• #include <iostream> — A preprocessor directive that includes the input/output
stream library.
• using namespace std; — Allows use of standard library names without the
std:: prefix.
• int main() — The entry point of every C++ program.
• cout — The standard output stream.
• return 0; — Indicates that the program ended successfully.
3. Data Types
C++ has a rich type system with both primitive and derived types.
Primitive Data Types
Type Size Description
int 4 bytes Integer values
float 4 bytes Single-precision
floating point
double 8 bytes Double-precision
floating point
char 1 byte Single character
bool 1 byte Boolean (true/false)
void — No value
long 8 bytes Extended integer
short 2 bytes Small integer
Type Modifiers
C++ provides modifiers: signed, unsigned, short, and long. For example: - unsigned
int — Only positive integers (0 to 4,294,967,295) - long long int — Large integers (up
to ±9.2 × 10¹⁸)
Variable Declaration
int age = 25;
float price = 19.99f;
char grade = 'A';
bool isActive = true;
4. Operators
Arithmetic Operators
Operator Meaning
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus
++ Increment
-- Decrement
Relational Operators
==, !=, >, <, >=, <= — these compare values and return true or false.
Logical Operators
• && — Logical AND
• || — Logical OR
• ! — Logical NOT
Bitwise Operators
&, |, ^, ~, <<, >> — operate on individual bits of integers.
Assignment Operators
=, +=, -=, *=, /=, %= — perform an operation and assign the result.
5. Control Flow
if-else
int x = 10;
if (x > 0) {
cout << "Positive";
} else if (x < 0) {
cout << "Negative";
} else {
cout << "Zero";
}
switch Statement
int day = 3;
switch (day) {
case 1: cout << "Monday"; break;
case 2: cout << "Tuesday"; break;
case 3: cout << "Wednesday"; break;
default: cout << "Other day";
}
Loops
for loop:
for (int i = 0; i < 5; i++) {
cout << i << " ";
}
while loop:
int i = 0;
while (i < 5) {
cout << i << " ";
i++;
}
do-while loop:
int i = 0;
do {
cout << i << " ";
i++;
} while (i < 5);
break and continue
• break — exits the loop immediately.
• continue — skips the current iteration and moves to the next.
6. Functions
Functions promote code reuse and modularity.
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 4);
cout << result; // Outputs: 7
return 0;
}
Function Overloading
C++ allows multiple functions with the same name but different parameters:
int multiply(int a, int b) { return a * b; }
double multiply(double a, double b) { return a * b; }
Default Arguments
void greet(string name = "User") {
cout << "Hello, " << name;
}
Inline Functions
Using inline suggests the compiler to expand the function at the call site for performance:
inline int square(int x) { return x * x; }
Recursion
A function calling itself:
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
7. Arrays and Strings
Arrays
An array stores a fixed-size sequence of elements of the same type:
int numbers[5] = {1, 2, 3, 4, 5};
cout << numbers[0]; // 1
Multi-dimensional Arrays
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
C-Style Strings
char name[] = "Alice";
std::string
The string class from the Standard Library is preferred:
#include <string>
string s = "Hello";
s += " World";
cout << [Link](); // 11
Common string methods: length(), size(), substr(), find(), replace(), append(),
compare().
8. Pointers and References
Pointers
A pointer stores the memory address of another variable:
int x = 10;
int* ptr = &x; // ptr holds the address of x
cout << *ptr; // dereference: prints 10
Pointer Arithmetic
int arr[] = {10, 20, 30};
int* p = arr;
cout << *(p + 1); // 20
References
A reference is an alias for an existing variable:
int a = 5;
int& ref = a;
ref = 10;
cout << a; // 10
References must be initialized at declaration and cannot be reseated. They are commonly
used in function parameters to avoid copying:
void increment(int& val) {
val++;
}
nullptr
Use nullptr (C++11 and later) instead of NULL:
int* ptr = nullptr;
9. Dynamic Memory Allocation
C++ allows manual memory management using new and delete:
int* ptr = new int(5); // allocate
cout << *ptr; // 5
delete ptr; // free
ptr = nullptr;
For arrays:
int* arr = new int[10];
delete[] arr;
Failing to delete allocated memory causes memory leaks. Modern C++ uses smart
pointers (unique_ptr, shared_ptr) to manage memory automatically.
10. Classes and Objects (OOP)
Object-Oriented Programming (OOP) is one of C++’s most powerful features. It organizes
code around objects — self-contained units that bundle data (attributes) and behavior
(methods). OOP is built on four pillars: Encapsulation, Inheritance, Polymorphism, and
Abstraction.
What is a Class?
A class is a user-defined data type that acts as a blueprint for creating objects. It defines
what data an object holds and what operations it can perform.
class Car {
public:
string brand;
int year;
float price;
void display() {
cout << brand << " (" << year << ") - $" << price << endl;
}
};
int main() {
Car c1;
[Link] = "Toyota";
[Link] = 2022;
[Link] = 25000;
[Link](); // Toyota (2022) - $25000
return 0;
}
Access Specifiers
Access specifiers control the visibility of class members:
Specifier Accessible From
public Anywhere — inside or outside the
class
private Only inside the class itself (default)
protected Inside the class and in derived
(child) classes
class BankAccount {
private:
double balance; // hidden from outside
protected:
string ownerName; // accessible to derived classes
public:
void deposit(double amount) { balance += amount; }
double getBalance() { return balance; }
};
Constructors
A constructor is a special member function that is automatically called when an object is
created. It has the same name as the class and no return type.
Default Constructor:
class Student {
public:
string name;
int roll;
Student() { // Default constructor
name = "Unknown";
roll = 0;
}
};
Parameterized Constructor:
class Student {
public:
string name;
int roll;
Student(string n, int r) {
name = n;
roll = r;
}
};
Student s1("Alice", 101);
Constructor Initializer List (preferred):
class Point {
public:
int x, y;
Point(int a, int b) : x(a), y(b) {} // Faster, initializes before
body runs
};
Copy Constructor:
class Box {
public:
int width;
Box(int w) : width(w) {}
Box(const Box& other) : width([Link]) {} // Copy constructor
};
Box b1(10);
Box b2 = b1; // Calls copy constructor
Destructor
A destructor is called automatically when an object goes out of scope or is deleted. It
cleans up resources.
class FileHandler {
private:
FILE* fp;
public:
FileHandler(const char* name) { fp = fopen(name, "r"); }
~FileHandler() {
if (fp) fclose(fp); // Automatically closes file
cout << "File closed." << endl;
}
};
Destructors have no return type, no parameters, and a ~ prefix. A class can have only one
destructor.
Encapsulation
Encapsulation means bundling data and methods together while restricting direct access to
the internal state. This is achieved through private data members and public
getter/setter methods.
class Employee {
private:
string name;
double salary;
public:
// Setter with validation
void setSalary(double s) {
if (s > 0) salary = s;
else cout << "Invalid salary!" << endl;
}
// Getter
double getSalary() { return salary; }
void setName(string n) { name = n; }
string getName() { return name; }
void display() {
cout << name << " earns $" << salary << endl;
}
};
int main() {
Employee e;
[Link]("Bob");
[Link](50000);
[Link]();
}
Benefits of encapsulation: data validation, hiding complexity, easier maintenance, and
controlled access.
Static Members
static data members are shared across all objects of a class. static member functions
can be called without creating an object.
class Counter {
private:
static int count; // shared by all instances
public:
Counter() { count++; }
~Counter() { count--; }
static int getCount() { return count; }
};
int Counter::count = 0; // must be defined outside class
int main() {
Counter c1, c2, c3;
cout << Counter::getCount(); // 3
}
Friend Functions and Classes
A friend function can access private and protected members of a class even though it is
not a member of that class.
class Rectangle {
private:
int length, width;
public:
Rectangle(int l, int w) : length(l), width(w) {}
friend int area(Rectangle r); // declare friend
};
int area(Rectangle r) {
return [Link] * [Link]; // can access private members
}
this Pointer
Every non-static member function has access to a special pointer called this, which points
to the calling object.
class Sample {
int value;
public:
Sample(int value) {
this->value = value; // distinguish parameter from member
}
Sample& setValue(int v) {
value = v;
return *this; // enables method chaining
}
};
Operator Overloading
C++ allows you to redefine the behavior of operators for user-defined types:
class Vector2D {
public:
float x, y;
Vector2D(float x, float y) : x(x), y(y) {}
Vector2D operator+(const Vector2D& v) {
return Vector2D(x + v.x, y + v.y);
}
void print() { cout << "(" << x << ", " << y << ")"; }
};
int main() {
Vector2D v1(1, 2), v2(3, 4);
Vector2D v3 = v1 + v2; // calls operator+
[Link](); // (4, 6)
}
11. Inheritance
Inheritance is a mechanism where a new class (derived/child class) acquires properties
and behaviors from an existing class (base/parent class). It promotes code reuse and
establishes an “is-a” relationship between classes.
Basic Syntax
class Animal {
public:
string name;
void eat() { cout << name << " is eating." << endl; }
void sleep() { cout << name << " is sleeping." << endl; }
};
class Dog : public Animal { // Dog inherits from Animal
public:
void bark() { cout << name << " says: Woof!" << endl; }
};
int main() {
Dog d;
[Link] = "Rex";
[Link](); // inherited from Animal
[Link](); // inherited from Animal
[Link](); // Dog's own method
}
Access Control in Inheritance
The inheritance mode affects how base class members are accessible in the derived class:
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 the most common and models the “is-a” relationship correctly.
Types of Inheritance
1. Single Inheritance — one base, one derived:
class Vehicle { /* ... */ };
class Car : public Vehicle { /* ... */ };
2. Multiple Inheritance — derived from more than one base:
class Flyable {
public:
void fly() { cout << "Flying!" << endl; }
};
class Swimmable {
public:
void swim() { cout << "Swimming!" << endl; }
};
class Duck : public Flyable, public Swimmable {
public:
void quack() { cout << "Quack!" << endl; }
};
Duck d;
[Link](); // from Flyable
[Link](); // from Swimmable
[Link](); // Duck's own
3. Multilevel Inheritance — chain of inheritance:
class LivingBeing {
public:
void breathe() { cout << "Breathing." << endl; }
};
class Animal : public LivingBeing {
public:
void move() { cout << "Moving." << endl; }
};
class Dog : public Animal {
public:
void bark() { cout << "Woof!" << endl; }
};
Dog d;
[Link](); // from LivingBeing
[Link](); // from Animal
[Link](); // from Dog
4. Hierarchical Inheritance — multiple derived from one base:
class Shape { /* ... */ };
class Circle : public Shape { /* ... */ };
class Rectangle : public Shape { /* ... */ };
class Triangle : public Shape { /* ... */ };
5. Hybrid Inheritance — combination of two or more types (often involves diamond
problem).
Constructors in Inheritance
When a derived object is created, the base class constructor is called first:
class Base {
public:
Base() { cout << "Base constructor" << endl; }
~Base() { cout << "Base destructor" << endl; }
};
class Derived : public Base {
public:
Derived() { cout << "Derived constructor" << endl; }
~Derived() { cout << "Derived destructor" << endl; }
};
Derived obj;
// Output:
// Base constructor
// Derived constructor
// (on destroy) Derived destructor
// (on destroy) Base destructor
To pass arguments to the base constructor:
class Animal {
string name;
public:
Animal(string n) : name(n) {}
string getName() { return name; }
};
class Dog : public Animal {
string breed;
public:
Dog(string n, string b) : Animal(n), breed(b) {} // calls Animal
constructor
void info() {
cout << getName() << " - " << breed << endl;
}
};
Method Overriding
A derived class can redefine a base class method:
class Animal {
public:
void sound() { cout << "Some sound" << endl; }
};
class Cat : public Animal {
public:
void sound() { cout << "Meow!" << endl; } // overrides base
};
Cat c;
[Link](); // "Meow!" — derived version called
Note: Without virtual, this is hiding (compile-time), not true polymorphic overriding
(runtime). See Polymorphism section for virtual.
The Diamond Problem
Multiple inheritance can create ambiguity when two base classes share a common
ancestor:
class A { public: void hello() { cout << "A"; } };
class B : public A {};
class C : public A {};
class D : public B, public C {}; // Diamond!
D obj;
[Link](); // ERROR: ambiguous — is it B::A::hello or C::A::hello?
Solution — Virtual Inheritance:
class A { public: void hello() { cout << "Hello from A"; } };
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {}; // Now only one copy of A
D obj;
[Link](); // OK
protected Members in Inheritance
class Person {
protected:
string name; // accessible to derived classes
int age;
public:
Person(string n, int a) : name(n), age(a) {}
};
class Student : public Person {
int rollNo;
public:
Student(string n, int a, int r) : Person(n, a), rollNo(r) {}
void display() {
// Can access protected members directly
cout << name << ", Age: " << age << ", Roll: " << rollNo;
}
};
12. Polymorphism
Polymorphism means “many forms.” It allows objects of different types to be treated
through a common interface. C++ supports two types of polymorphism: compile-time and
runtime.
12.1 Compile-time Polymorphism
Resolved at compile time. Achieved through function overloading and operator
overloading.
Function Overloading
Multiple functions with the same name but different parameter lists:
class Printer {
public:
void print(int x) {
cout << "Integer: " << x << endl;
}
void print(double x) {
cout << "Double: " << x << endl;
}
void print(string s) {
cout << "String: " << s << endl;
}
};
Printer p;
[Link](42); // Integer: 42
[Link](3.14); // Double: 3.14
[Link]("Hello"); // String: Hello
Operator Overloading
Customize operators for user-defined types:
class Complex {
public:
float real, imag;
Complex(float r, float i) : real(r), imag(i) {}
Complex operator+(const Complex& c) {
return Complex(real + [Link], imag + [Link]);
}
Complex operator*(const Complex& c) {
return Complex(real * [Link] - imag * [Link],
real * [Link] + imag * [Link]);
}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
Complex c1(2, 3), c2(1, 4);
Complex c3 = c1 + c2;
[Link](); // 3 + 7i
12.2 Runtime Polymorphism
Resolved at runtime. Achieved through virtual functions and pointers/references to
base class.
Virtual Functions
Without virtual, calling a method through a base pointer always calls the base version
even if the actual object is derived:
// Without virtual (WRONG behavior for polymorphism):
class Animal {
public:
void sound() { cout << "..."; }
};
class Dog : public Animal {
public:
void sound() { cout << "Woof"; }
};
Animal* a = new Dog();
a->sound(); // "..." — calls Animal's version! (Not what we want)
Adding virtual fixes this:
class Animal {
public:
virtual void sound() { cout << "Some sound" << endl; }
virtual void describe() { cout << "I am an animal." << endl; }
};
class Dog : public Animal {
public:
void sound() override { cout << "Woof!" << endl; }
void describe() override { cout << "I am a Dog." << endl; }
};
class Cat : public Animal {
public:
void sound() override { cout << "Meow!" << endl; }
void describe() override { cout << "I am a Cat." << endl; }
};
int main() {
Animal* animals[3];
animals[0] = new Animal();
animals[1] = new Dog();
animals[2] = new Cat();
for (int i = 0; i < 3; i++) {
animals[i]->sound(); // Correct version called for each!
}
// Output:
// Some sound
// Woof!
// Meow!
for (int i = 0; i < 3; i++) delete animals[i];
}
This is the essence of polymorphism — the same sound() call produces different results
depending on the actual object type at runtime.
The override Keyword (C++11)
override tells the compiler you intend to override a virtual function. It catches typos and
signature mismatches at compile time:
class Base {
public:
virtual void show(int x) {}
};
class Derived : public Base {
public:
void show(int x) override {} // OK
// void show(double x) override {} // ERROR: no matching virtual
in Base
};
Virtual Destructors
If you use polymorphism (base pointer to derived object), the base class destructor must
be virtual, otherwise only the base destructor runs when delete is called, causing a
resource leak:
class Base {
public:
virtual ~Base() { cout << "Base destroyed" << endl; }
};
class Derived : public Base {
int* data;
public:
Derived() { data = new int[100]; }
~Derived() {
delete[] data;
cout << "Derived destroyed" << endl;
}
};
Base* b = new Derived();
delete b;
// With virtual: "Derived destroyed" then "Base destroyed"
// Without virtual: only "Base destroyed" — memory leak!
12.3 Pure Virtual Functions and Abstract Classes
A pure virtual function has no implementation in the base class and is declared with = 0.
Any class with at least one pure virtual function becomes an abstract class — it cannot be
instantiated, only subclassed.
class Shape {
public:
virtual double area() = 0; // pure virtual
virtual double perimeter() = 0; // pure virtual
virtual void draw() = 0; // pure virtual
// Can still have regular methods
void describe() {
cout << "Area: " << area() << ", Perimeter: " << perimeter()
<< endl;
}
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
void draw() override { cout << "Drawing Circle" << endl; }
};
class Rectangle : public Shape {
double l, w;
public:
Rectangle(double length, double width) : l(length), w(width) {}
double area() override { return l * w; }
double perimeter() override { return 2 * (l + w); }
void draw() override { cout << "Drawing Rectangle" << endl; }
};
int main() {
// Shape s; // ERROR: cannot instantiate abstract class
Shape* shapes[2];
shapes[0] = new Circle(5);
shapes[1] = new Rectangle(4, 6);
for (auto s : shapes) {
s->draw();
s->describe();
}
delete shapes[0];
delete shapes[1];
}
12.4 Interfaces via Abstract Classes
C++ does not have a built-in interface keyword (unlike Java), but the same effect is
achieved with abstract classes where all methods are pure virtual:
class Printable {
public:
virtual void print() = 0;
virtual ~Printable() {}
};
class Serializable {
public:
virtual string serialize() = 0;
virtual ~Serializable() {}
};
class Document : public Printable, public Serializable {
string content;
public:
Document(string c) : content(c) {}
void print() override { cout << content << endl; }
string serialize() override { return "{content: " + content + "}";
}
};
12.5 The vtable (Virtual Table)
Internally, when a class has virtual functions, the compiler creates a vtable — a table of
function pointers, one per virtual function. Each object of such a class holds a hidden
pointer (vptr) to its class’s vtable. At runtime, the call goes through the vtable to dispatch
the correct function. This adds a small overhead (one extra pointer per object + one
indirection per virtual call) but enables powerful polymorphic designs.
13. Templates
Templates enable generic programming — writing code that works with any data type.
Function Templates
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
Class Templates
template <typename T>
class Stack {
private:
vector<T> elements;
public:
void push(T val) { elements.push_back(val); }
T pop() {
T top = [Link]();
elements.pop_back();
return top;
}
};
14. Standard Template Library (STL)
The STL is a powerful library of generic algorithms and data structures.
Containers
• vector — dynamic array
• list — doubly-linked list
• map — key-value pairs (sorted)
• unordered_map — hash map
• set — unique sorted elements
• stack, queue, deque
#include <vector>
vector<int> v = {1, 2, 3};
v.push_back(4);
for (int x : v) cout << x << " ";
Iterators
for (auto it = [Link](); it != [Link](); ++it) {
cout << *it;
}
Algorithms
#include <algorithm>
sort([Link](), [Link]());
int pos = find([Link](), [Link](), 3) - [Link]();
15. Exception Handling
C++ uses try, catch, and throw for error handling:
try {
int a = 10, b = 0;
if (b == 0) throw runtime_error("Division by zero");
cout << a / b;
} catch (runtime_error& e) {
cout << "Error: " << [Link]();
} catch (...) {
cout << "Unknown error";
}
Custom exception classes can be derived from std::exception.
16. File Handling
C++ uses fstream for file I/O:
#include <fstream>
// Write
ofstream outFile("[Link]");
outFile << "Hello File";
[Link]();
// Read
ifstream inFile("[Link]");
string line;
getline(inFile, line);
cout << line;
[Link]();
Modes: ios::in, ios::out, ios::app, ios::binary.
17. Modern C++ Features (C++11 and Beyond)
auto Keyword
auto x = 42; // int
auto y = 3.14; // double
auto s = "hello"; // const char*
Range-based for Loop
vector<int> v = {1, 2, 3};
for (auto& x : v) cout << x;
Lambda Expressions
auto add = [](int a, int b) { return a + b; };
cout << add(3, 4); // 7
Smart Pointers
#include <memory>
unique_ptr<int> p = make_unique<int>(10);
shared_ptr<int> sp = make_shared<int>(20);
unique_ptr — single ownership; shared_ptr — shared ownership with reference
counting; weak_ptr — non-owning reference.
nullptr
Replaces the old NULL macro with a type-safe null pointer constant.
Move Semantics and Rvalue References
Move semantics (C++11) allow transferring resources instead of copying, greatly
improving performance for large objects.
string s1 = "Hello";
string s2 = move(s1); // s1 is now empty, s2 has "Hello"
18. Namespaces
Namespaces prevent name collisions:
namespace MyApp {
int version = 1;
void show() { cout << "v" << version; }
}
int main() {
MyApp::show();
}
The using directive brings names into the current scope:
using namespace MyApp;
show(); // OK now
19. Preprocessor Directives
Directives are processed before compilation:
Directive Purpose
#include Include a header file
#define Define a macro
#ifdef / #endif Conditional compilation
#pragma Implementation-specific
instructions
#define PI 3.14159
#define MAX(a,b) ((a) > (b) ? (a) : (b))
Header guards prevent double inclusion:
#ifndef MYHEADER_H
#define MYHEADER_H
// header content
#endif
20. Best Practices
1. Use const wherever possible — signals intent and enables optimizations.
2. Prefer smart pointers over raw pointers — prevents memory leaks.
3. Follow the Rule of Three/Five — if you define a destructor, copy constructor, or
copy assignment, define all three (or five in C++11 with move semantics).
4. Use RAII (Resource Acquisition Is Initialization) — tie resource lifetimes to object
lifetimes.
5. Avoid global variables — they make code harder to test and maintain.
6. Use nullptr instead of NULL or 0 for null pointers.
7. Enable compiler warnings (-Wall -Wextra with GCC/Clang) and fix them.
8. Use std::string instead of C-style strings for safety and convenience.
9. Prefer the STL over hand-rolled data structures.
10. Write clear, self-documenting code and comment the “why,” not the “what.”
Summary
C++ is a powerful, flexible, and efficient language. It combines low-level memory control
with high-level abstractions through OOP and templates. Mastery of C++ involves
understanding not just the syntax, but also memory management, the object model, and
modern features introduced in C++11/14/17/20. These notes cover the foundational
concepts needed to build a strong base in C++ programming.