C++ Programming Tutorial
Learn modern C++ step-by-step — from basics to advanced features like templates, STL, and smart pointers. Clear explanations with practical, runnable examples.
C++ Pointers and References
Pointers store memory addresses and allow indirect access to data. References are aliases for existing variables. Understanding both is essential for C++ — they enable dynamic memory, data structures, polymorphism, and efficient parameter passing.
References — Safe Aliases
references.cpp
#include <iostream>
int main() {
int x = 42;
int& ref = x; // ref is an alias for x
std::cout << ref << "
"; // 42
ref = 100; // modifies x through the reference
std::cout << x << "
"; // 100
// References MUST be initialized and cannot be reseated
// int& bad; // ERROR: uninitialized reference
// int& ref2 = ref; // ref2 now aliases x, not ref itself
// Common use: function parameters
return 0;
}
// Pass by reference — modifies original
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// Pass by const reference — read-only, no copy
void print(const std::string& msg) {
std::cout << msg << "
";
// msg = "x"; // ERROR: const reference
}Pointers — Memory Addresses
pointers.cpp
#include <iostream>
int main() {
int x = 42;
int* ptr = &x; // ptr stores the address of x
// Dereference: access value at the address
std::cout << "Address: " << ptr << "
"; // 0x7ffd...
std::cout << "Value: " << *ptr << "
"; // 42
*ptr = 100; // modify x through pointer
std::cout << x << "
"; // 100
// Null pointer — points to nothing
int* null_ptr = nullptr;
if (null_ptr == nullptr) {
std::cout << "Pointer is null
";
}
// Pointer arithmetic (arrays)
int arr[] = {10, 20, 30, 40, 50};
int* p = arr; // points to first element
std::cout << *p << "
"; // 10
std::cout << *(p + 2) << "
"; // 30
std::cout << p[3] << "
"; // 40 (same as *(p+3))
return 0;
}Pointers vs References
| Feature | Pointer | Reference |
|---|---|---|
| Syntax | int* p = &x; | int& r = x; |
| Can be null | Yes (nullptr) | No (must bind to object) |
| Can be reassigned | Yes (p = &y;) | No (bound at creation) |
| Needs dereference | Yes (*p) | No (used like the original) |
| Can be uninitialized | Yes (dangerous!) | No (must initialize) |
| Use case | Optional params, arrays, dynamic memory | Function params, aliases |
Dynamic Memory (new/delete)
dynamic.cpp
#include <iostream>
int main() {
// Allocate single object on the heap
int* p = new int(42);
std::cout << *p << "
"; // 42
delete p; // MUST free memory to avoid leaks
p = nullptr; // good practice after delete
// Allocate array on the heap
int* arr = new int[5]{1, 2, 3, 4, 5};
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
delete[] arr; // use delete[] for arrays
// PROBLEM: manual memory is error-prone
// - Forget delete → memory leak
// - Delete twice → undefined behavior
// - Use after delete → undefined behavior
// SOLUTION: use smart pointers (next section)
return 0;
}Smart Pointers (Modern C++)
smart_pointers.cpp
#include <iostream>
#include <memory>
#include <vector>
class Resource {
public:
Resource(int id) : id_(id) { std::cout << "Created " << id_ << "
"; }
~Resource() { std::cout << "Destroyed " << id_ << "
"; }
void use() { std::cout << "Using " << id_ << "
"; }
private:
int id_;
};
int main() {
// unique_ptr — exclusive ownership (most common)
auto p1 = std::make_unique<Resource>(1);
p1->use(); // Using 1
// auto p2 = p1; // ERROR: cannot copy unique_ptr
auto p2 = std::move(p1); // OK: transfer ownership
// p1 is now nullptr
// shared_ptr — shared ownership (reference counted)
auto s1 = std::make_shared<Resource>(2);
{
auto s2 = s1; // both own the resource
std::cout << "Count: " << s1.use_count() << "
"; // 2
} // s2 destroyed, count drops to 1
std::cout << "Count: " << s1.use_count() << "
"; // 1
// Resources automatically freed when smart pointers go out of scope
return 0;
} // Destroyed 2, Destroyed 1Best Practices
- Prefer references for function parameters — they cannot be null and do not need dereferencing.
- Use
const&for read-only parameters to avoid unnecessary copies. - Use
nullptrinstead ofNULLor0for null pointers (type-safe). - Never use raw
new/deletein modern C++ — usestd::unique_ptrorstd::shared_ptr. - Use
std::make_uniqueandstd::make_sharedto create smart pointers (exception-safe, cleaner). - Default to
unique_ptr— only useshared_ptrwhen multiple owners genuinely need the same object. - Always check pointers for null before dereferencing if they could be null.
Keep Practicing
Use the online compiler to run every example and experiment with modifications. The best way to learn C++ is by writing code — even small programs build strong foundations.