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 new operator allocates memory, and where delete operator 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 0 and goes up to size - 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 sizeof operator:
    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 elements

Header 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_H

Modern 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.

  • new operator allocate memory to create an object/ array and return a pointer to that object
  • delete operator deallocate memory
  • Dynmamically allocated objects need to have delete call 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 leak

Pointer Arithmetic

int arr[3] = {10, 20, 30};
int* p = arr;

cout << *p << endl;     // 10
cout << *(p + 1) << endl; // 20
cout << *(p + 2) << endl; // 30

Passing 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, observe shared_ptr without owning.
  • Always prefer make_unique or make_shared over raw new.

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.