Introduction
C++ is a general-purpose, high-performance, object-oriented programming language. It extends the C language with features like classes, templates, and the Standard Template Library (STL).
Memory Regions: Heap/Stack
- Code - region where the program instructions are stored
- Static memory - region where global and static local variables are allocated
- The stack - region where a function's local variables are allocated
- The heap - region where the
newoperator allocates memory, and wheredeleteoperator deallocates memory
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}Variables & Data Types
Basic Types
const int age = 25; // declaring constant
int age = 25; // Integer: maximum int value is roughly 2 billion (32-bit)
float price = 9.99; // Floating point
char grade = 'A'; // Character
bool isHappy = true;// Boolean
string name = "Bob"; // String (<string> header required)
enum ErrorCode { 1, 2, 3 }; // Enumeration; only consists of a set of defined constants
ErrorCode codeVariable = 1;Input & Output
#include <iostream>
using namespace std;
int main() {
string name;
cout << "Enter your name: ";
cin >> name;
cout << "Hello, " << name << "!" << endl;
return 0;
}Conditionals
int x = 10;
{condition} ? true : false;
if (x > 0) {
cout << "Positive" << endl;
} else if (x < 0) {
cout << "Negative" << endl;
} else {
cout << "Zero" << endl;
}
switch (a) {
case 0:
// Print "zero"
break;
case 1:
// Print "one"
break;
default:
// Print "unknown"
break;
}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);Functions
#include <iostream>
#include "filename.h"
using namespace std;
int add(int a, int b = 9) {
return a + b;
}
int main() {
cout << add(3, 4) << endl; // 7
return 0;
}Arrays in C++
1. Declaring Arrays
- Syntax:
type arrayName[size] - Example:
int numbers[5]; // Array of 5 integers char letters[10]; // Array of 10 characters double scores[3]; // Array of 3 doubles
2. Initializing Arrays
- Method 1: Inline initialization
int numbers[5] = {1, 2, 3, 4, 5}; char letters[] = {'A', 'B', 'C'}; // Size deduced automatically - Method 2: Partial initialization (rest will be 0)
int nums[5] = {10, 20}; // nums = {10, 20, 0, 0, 0}
3. Accessing Array Elements
- Syntax:
arrayName[index] - Index starts at
0and goes up tosize - 1. - Example:
int numbers[5] = {10, 20, 30, 40, 50}; cout << numbers[0]; // 10 cout << numbers[3]; // 40
4. Modifying Array Elements
- Syntax:
arrayName[index] = newValue; - Example:
int numbers[3] = {1, 2, 3}; numbers[1] = 99; // Change second element to 99 cout << numbers[1]; // Outputs 99
5. Looping Through Arrays
- Using for-loop:
int arr[5] = {10, 20, 30, 40, 50}; for (int i = 0; i < 5; i++) { cout << arr[i] << " "; } // Output: 10 20 30 40 50 - Using range-based for-loop (C++11+):
int arr[5] = {10, 20, 30, 40, 50}; for (int num : arr) { cout << num << " "; } // Output: 10 20 30 40 50
6. Getting Array Size
- Use
sizeofoperator:int arr[5] = {1, 2, 3, 4, 5}; int size = sizeof(arr) / sizeof(arr[0]); cout << "Size: " << size; // 5
Vectors
Vectors are safer to use than arrays. Arrays are fixed references arr1 = arr2 is not allowed
- Vector can be dynamically created, but arrays need a fixed size on build
vector<int> orderedList(5) = {1, 2, 3, 4, 5};
vector<int> dynamicArr[];
orderList.push_back(77); // appends 77
orderList.back(); // return the last element (w/o changing vector)
orderList.pop_back(); // removes the last element'
orderList[5]; // access/set value at index 6, return 77
orderList.at(5); // access/set value at index 6, return 77
orderList.size(); // return size of vector, return 6
orderList.reszie(10); // resize vector to have 10 elementsHeader File Guards
Prevent the preprocessor to include the file only once. Useful for making libraries or project headers/utils
#ifndef MY_HEADER_H // If not defined
#define MY_HEADER_H // Define it
// Your declarations here
void myFunction();
class MyClass { };
#endif // MY_HEADER_HModern Alternative: #pragma once
#pragma once
void myFunction();
class MyClass { };Pointers
Pointers store the memory address of a variable. You can use them to access and modify data directly.
newoperator allocate memory to create an object/ array and return a pointer to that objectdeleteoperator deallocate memory- Dynmamically allocated objects need to have
deletecall on it at the end to avoid memory leak - Use smart pointers to avoid having to manual delete
int x = 10;
int y;
int* ptr = &x; // Pointer stores the address of x
ptr = nullptr; // Pointer points to nothing now
ptr = &x;
cin >> y;
int* arrPtr = new int[y]; // dynamically allocate memory for new array
cout << "Value: " << x << endl; // 10
cout << "Address: " << ptr << endl; // Memory address
cout << "Dereferenced: " << *ptr << endl; // Access the data the ptr points to; 10
delete[] arrPtr; to deallocate the memory used by the array
arrPtr = new int[]; // now it's safe to have the pointer reference a new array
---------------------------------------
// Overview
int normal; // holds data value
int* ptr; // holds memory address
&normal; // return address of variable
*ptr; // * dereferences ptr and access data value of ptr
&ptr; // address of pointer
---------------------------------------
// Pointers and objects
int* arrPtr;
MyClass *objPtr, *objArrPtr;
arrPtr = new int[size]; // points to address of new int array
objPtr = new MyClass(); // points to MyClass object;
// objPtr is pointer to MyClass object;
objArrPtr = new MyClass[size]; // points to dynamically allocated array of MyClass objects;
// objArrPtr[0] is a MyClass object
objPtr->someMethod();
objArrPtr[0].someMethod();
// Risk of memory leak below
Object& obj = *( new Object() ); // obj is a reference, not a pointer, to an object
delete obj; // ❌ ERROR because obj is not a pointer
obj = *(new Object()); // Causes memory leakPointer Arithmetic
int arr[3] = {10, 20, 30};
int* p = arr;
cout << *p << endl; // 10
cout << *(p + 1) << endl; // 20
cout << *(p + 2) << endl; // 30Passing by reference
void ComputeChange(int totCents, int& numQuarters ) {
numQuarters = totCents / 25;
}
int main () {
int numQuarters;
int totCents = 99;
ComputeChange(totCents, numQuarters);
cout << "Number of Quarters: " << numQuarters
return 0;
}Smart Pointers
Smart pointers are C++ objects that manage dynamic memory automatically, reducing the risk of memory leaks. The main types areunique_ptr, shared_ptr, and weak_ptr.
Smart pointers are used the same way as normal pointer
obj->someMethod()(*obj).someMethod()
1. unique_ptr
- Owns a dynamically allocated object exclusively.
- Cannot be copied, only moved.
- Automatically deletes the object when it goes out of scope.
- obj1 = obj2 is not allowed
- obj2 = move(obj1) is allowed; it transfer ownership to obj2 so now obj1 points to nullptr
#include <memory>
#include <iostream>
using namespace std;
auto obj = make_unique<int>(42); // preferred way
cout << *obj << endl;
// Move ownership
auto obj2 = move(obj); // obj is now null
-------------------------------------
// Passing by const reference to avoid transfer of ownership
void useObject(const unique_ptr<MyClass>& obj) {
obj->method();
}
int main() {
auto obj = make_unique<MyClass>();
useObject(obj);
}2. shared_ptr
- Allows multiple pointers to share ownership of an object.
- Object is destroyed when the last shared_ptr goes out of scope.
- Keeps a reference count internally.
#include <memory>
#include <iostream>
using namespace std;
auto obj1 = make_shared<int>(10);
auto obj2 = obj1; // copy allowed
cout << obj1.use_count() << endl; // 2
cout << *obj2 << endl;3. weak_ptr
- Non-owning pointer to an object managed by shared_ptr.
- Does not increase reference count.
- Used to break cyclic references.
#include <memory>
#include <iostream>
using namespace std;
auto sp = make_shared<int>(50);
weak_ptr<int> wp = sp; // weak_ptr does not increase ref count
cout << sp.use_count() << endl; // 1
if (auto temp = wp.lock()) { // convert weak_ptr to shared_ptr temporarily
cout << *temp << endl; // 50
}4. make_unique & make_shared
- Preferred way to create smart pointers.
- Safer than manually using new.
- Ensures exception safety and cleaner code.
auto uPtr = make_unique<MyClass>();
auto sPtr = make_shared<MyClass>();5. When to Use Each
unique_ptr: Exclusive ownership, no copying needed.shared_ptr: Shared ownership among multiple objects.weak_ptr: Break cycles, observeshared_ptrwithout owning.- Always prefer
make_uniqueormake_sharedover rawnew.
Classes & Objects
Rule of Three : if any of the destructor, copy constructor, or copy assignment operator is defined, then all 3 should be explicitly define as well
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
OtherObject dataObject;
static int planet;
// constructor
Person(string name){
this->name = name;
}
// destructor (called when 'delete' operator is used on object)
~Person() {
// cleanup
}
// copy constructor (called when a new object is created)
Person(const Person& origObj) {
// copy all neccessary properties to make a deep copy
return copyObj;
}
// assignment operator (called when assigning an old object)
Person& operator=(Person other){
swap(dataObject, other.dataObject);
return *this;
}
void introduce() {
cout << "Hi, I'm " << name << " and I'm " << age << " years old." << endl;
}
private:
void helperFunc(int n);
}
void Person::helperFunc(int n){
// do something
}
int main() {
Person p1;
p1.name = "Bob";
p1.age = 21;
p1.introduce();
Person* p2 = new Person(); // make new Person obj
Person* p3 = new Person(p2); // copy p2 obj into p3, but needs to define a copy constructor (deep copy)
Person* p4 = p2; // also copy p2 to p4 using the copy constructor (deep copy)
p4 = p3; // does NOT call the copy constructor, it will use the assignment operator instead
return 0;
}Separate Files for One Class
Dev will keep a className.h file for class definitions and a className.cpp that contains the member functions definitions. The main.cpp file will #include "className.h" only
// StoreItem.h
#ifndef STOREITEM_H
#define STOREITEM_H
class StoreItem {
public:
StoreItem(); // constructor
StoreItem(int n);
~StoreItem(); // destructor
StoreItem(const StoreItem& origObj); // copy constructor
void SetWeightOunces(int ounces);
void Print() const;
private:
int weightOunces;
};
#endif
------------------------------------------------------------
// StoreItem.cpp
#include <iostream>
using namespace std;
#include "StoreItem.h"
StoreItem::StoreItem() {
}
StoreItem::StoreItem(int n){
}
void StoreItem::SetWeightOunces(int ounces) {
weightOunces = ounces;
}
void StoreItem::Print() const {
cout << "Weight (ounces): " << weightOunces << endl;
}
------------------------------------------------------------
// main.cpp
#include <iostream>
using namespace std;
#include "StoreItem.h"
int main() {
StoreItem item1;
item1.SetWeightOunces(16);
item1.Print();
return 0;
}Operator Overloading
Allows operations on classes like obj1 + obj2 or obj1 == obj2
#include <iostream>
using namespace std;
class TimeHrMn {
public:
TimeHrMn(int timeHours = 0, int timeMinutes = 0);
void Print() const;
TimeHrMn operator+(TimeHrMn rhs) ;
private:
int hours;
int minutes;
};
// Overload + operator for TimeHrMn
TimeHrMn TimeHrMn::operator+(TimeHrMn rhs)
TimeHrMn timeTotal;
timeTotal.hours = hours + rhs.hours;
timeTotal.minutes = minutes + rhs.minutes;
return timeTotal;
}
TimerHrMn timer1, timer2, timer3
timer3 = timer1 + timer2; // shortway to write below
timer3.hours = timer1.hours + timer2.hours;
timer3.minutes = timer1.minutes + timer2.minutes;Namespaces
Namespaces in C++ are used to organize code and prevent name collisions between variables, functions, or classes with the same name.
Basic Usage
// auditorium.h
namespace auditorium {
class Seat {
...
};
}
---------------------------------
// airplane.h
namespace airplane {
class Seat {
...
};
}
---------------------------------
// main.cpp
#include "auditorium.h"
#include "airplane.h"
int main() {
auditorium::Seat concertSeat;
airplane::Seat flightSeat;
// ...
return 0;
};Using Namespace
#include <iostream>
using namespace std; // Brings all std names into global scope
int main() {
cout << "Hello World" << endl; // No need for std:: prefix
return 0;
}Nested Namespaces (C++17+)
namespace A::B {
void sayHello() {
std::cout << "Hello from A::B" << std::endl;
}
}
int main() {
A::B::sayHello();
return 0;
}Best Practices
- Avoid
using namespace std;in header files. - Use namespaces to avoid naming collisions in large projects or libraries.
- Use nested namespaces for better code organization.