C++ Pointers Explained with Examples for Beginners

C++ Pointers Explained with Examples for Beginners


Pointers look scary at first, but they are just variables that store memory addresses. In this tutorial, you’ll find C++ pointers explained for beginners with clear examples you can run. We’ll cover what a pointer is, why it’s used, how to declare and initialize pointers in C++, how to use the & and * operators, pointers with arrays and functions, common mistakes, and modern, safer alternatives like smart pointers. If you’re following our C++ track, you can also explore more lessons on the CodDesire C++ section.

Key takeaways: C++ pointers explained for beginners

  • A pointer stores an address; use & to take an address and * to access the pointee.
  • Always initialize pointers; use nullptr when you don’t have a valid target.
  • Respect lifetimes and bounds to avoid dangling and out-of-bounds errors.
  • Prefer references for required, non-null access; use pointers for optional/reseatable access and array traversal.
  • In modern code, avoid manual new/delete; prefer containers and smart pointers.

What is a pointer in C++ and why is it used?

C++ pointer basics diagram: pointer stores an address and dereferencing reads the int variable's value
Pointer referencing and dereferencing shown with a simple memory layout

A pointer is a typed variable that holds the memory address of another object (or function). You use the address-of operator (&) to get an address, and the dereference operator (*) to access the object that a pointer points to.

Why pointers matter:

  • They let functions modify variables passed in by address.
  • They enable dynamic data structures and low-level control when necessary.
  • They interoperate with C APIs and system interfaces that expect raw pointers.
  • They provide efficient access to arrays and buffers.

Modern C++ tip: learn raw pointer basics, but prefer RAII (automatic resource management) and standard containers like std::vector or std::string in new code. Use smart pointers (std::unique_ptr, std::shared_ptr, std::weak_ptr) to express ownership safely.

C++ pointer basics: syntax, & and * operators

C++ pointer arithmetic graphic: pointer stepping through a contiguous int array
Pointer moves across a contiguous int array to show pointer arithmetic

How to declare and initialize pointers in C++

Declaration uses T* where T is the pointed-to type. Always initialize your pointers—use nullptr when you don’t have a target yet.

Code
#class="cd-package">include <iostream>

int main() {
    int value = 42;

    int* p = &value;        // p holds the address of value
    std::cout << "Before: value = " << value << "n";

    *p = 7;                 // dereference p to modify value
    std::cout << "After:  value = " << value << "n";

    int* q = nullptr;       // modern null pointer literal
    if (q == nullptr) {
        std::cout << "q is nulln";
    }
    return 0;
}
Output
Before: value = 42
After:  value = 7
q is null

Understanding the & and * operators

  • & (address-of) gives you the address of an existing object: int* p = &x;
  • * (dereference) lets you access the object a pointer points to: *p = 10; writes through the pointer.
  • Tip: The * symbol appears in both declaration (int* p;) and dereference (*p); they are different uses of the same symbol.
Pointer workflow: from variable to safe use
1
Have an object
int x = 42;

2
Take its address
&x

3
Store in a pointer
int* p = &x; // or int* p = nullptr;

4
Check before use
if (p) { /* safe to dereference */ }

5
Read/write via *
*p = 7; std::cout << *p;

6
Reseat or prefer RAII
p = &other; p = nullptr; // for ownership use std::unique_ptr

How to use pointers in C++ safely

  • Initialize every pointer. Use nullptr if not set yet.
  • Check for nullptr before dereferencing when a pointer may be null.
  • Keep lifetimes in mind: a pointer must not outlive the object it points to (avoid dangling pointers).
  • Avoid manual new/delete in beginner code. Prefer automatic storage (int x;), containers (std::vector, std::string), or smart pointers.

Pointers and const: pointer-to-const vs const pointer

You can make either the pointed-to value, or the pointer itself, const (or both):

  • const int* p means “pointer to const int” — you can’t modify the int via p, but you can reseat p.
  • int* const p means “const pointer to int” — the pointer can’t change where it points, but you can modify the int.
  • const int* const p makes both fixed.
Code
#class="cd-package">include <iostream>

int main() {
    int x = 10;

    const int* p_to_const = &x;   // cannot modify x through p_to_const
    std::cout << *p_to_const << "n";

    int* const const_ptr = &x;    // cannot reseat const_ptr
    *const_ptr = 20;              // OK: modifies x
    std::cout << x << "n";

    // p_to_const = nullptr;      // OK: can reseat pointer-to-const
    // *p_to_const = 5;           // ERROR: cannot modify through pointer-to-const
    // int y = 30;
    // const_ptr = &y;            // ERROR: cannot reseat const pointer
    return 0;
}
Output
10
20

Pointer arithmetic, arrays, and strings

Pointers and arrays are closely related. An array name often “decays” to a pointer to its first element when passed to a function. Pointer arithmetic (like p + i) is only valid within the same array (including one-past-the-end). Forming or using out-of-bounds pointers is undefined behavior—avoid it.

Iterate an array with a pointer

Code
#class="cd-package">include <iostream>

int main() {
    int numbers[5] = {1, 2, 3, 4, 5};
    int* p = numbers; // array decays to pointer to first element

    for (int i = 0; i < 5; ++i) {
        std::cout << *(p + i) << " ";
    }
    std::cout << "n";
    return 0;
}
Output
1 2 3 4 5

Pointers with C-style strings

A C-style string is a char array ending with a '' (null terminator). You can walk it with a pointer:

Code
#class="cd-package">include <iostream>

int main() {
    const char* s = "Hello";
    const char* p = s;

    while (*p != '') {
        std::cout << *p;
        ++p;
    }
    std::cout << "n";
    return 0;
}
Output
Hello

Passing arrays to functions as pointers

Because arrays decay to pointers, you’ll often see functions that accept a pointer plus a size. Use a const pointer for read-only access.

Code
#class="cd-package">include <iostream>
#class="cd-package">include <cstddef>

std::size_t sum(const int* data, std::size_t n) {
    std::size_t total = 0;
    for (std::size_t i = 0; i < n; ++i) {
        total += static_cast<std::size_t>(data[i]);
    }
    return total;
}

int main() {
    int a[4] = {3, 5, 7, 9};
    std::cout << sum(a, 4) << "n"; // array decays to const int*
    return 0;
}
Output
24

Modern alternative (C++20): use std::span<T> to pass array views with size information, which is safer and more expressive.

Code
#class="cd-package">include <iostream>
#class="cd-package">include <span>

int sum_span(std::span<const int> s) {
    int total = 0;
    for (int v : s) total += v;
    return total;
}

int main() {
    int a[3] = {2, 4, 6};
    std::cout << sum_span(a) << "n"; // implicit conversion to span
    return 0;
}
Output
12

Using pointers with functions (modify via address)

Passing a pointer lets the function modify the caller’s variable. This is a common beginner pattern:

Code
#class="cd-package">include <iostream>

void increment(int* p) {
    if (p) { *p += 1; }  // check for nullptr before dereferencing
}

int main() {
    int x = 10;
    increment(&x);       // pass address of x
    std::cout << x << "n";
    return 0;
}
Output
11

Pointers vs references in C++ for beginners

Both pointers and references provide indirect access, but they signal different intent:

Feature Pointer Reference
Can be null? Yes (use nullptr) No (must refer to a valid object)
Can be reseated? Yes (point to something else) No (binds once)
Syntax to access *p, p-> Use like a normal variable
Common use Optional or array-like parameters, interop Required, non-null aliasing

Guideline: prefer references when non-null, non-reseatable access is intended. Use pointers to signal optionality or when you need reseating.

Ownership, heap memory, and smart pointers (modern practice)

In modern C++, a raw T* is typically non-owning: it doesn’t manage the lifetime of the object it points to. Avoid writing new/delete in your own code. Prefer:

  • Automatic storage: int x = 5;
  • Standard containers: std::vector<T>, std::string
  • Smart pointers when dynamic lifetime is required:
    • std::unique_ptr<T> for sole ownership (std::make_unique)
    • std::shared_ptr<T> only when ownership must be shared (std::make_shared)
    • std::weak_ptr<T> to observe a shared_ptr without extending its lifetime

Example: managing a single object with std::unique_ptr—no manual delete needed.

Code
#class="cd-package">include <iostream>
#class="cd-package">include <memory>

int main() {
    std::unique_ptr<int> up = std::make_unique<int>(99);
    std::cout << *up << "n";
    // up automatically deletes the int when it goes out of scope
    return 0;
}
Output
99

If you need a raw pointer for interop, use up.get() to access it while keeping the unique_ptr alive at the call site. For pointer-like types and iterators, std::to_address (C++20) provides a well-defined way to obtain a raw pointer.

Note: std::shared_ptr shares ownership; operations on its control block are thread-safe, but the pointed-to object itself is not automatically thread-safe—protect shared data with proper synchronization.

Common pointer mistakes for beginners in C++ (and how to avoid them)

  • Uninitialized pointers: always initialize (use nullptr or a valid address).
  • Dereferencing nullptr: check for null before *p or p-> when the pointer may be empty.
  • Dangling pointers: don’t keep pointers to objects that have been destroyed or gone out of scope.
  • Out-of-bounds access: pointer arithmetic is only valid within the same array; don’t form or use pointers outside array bounds.
  • Mismatched new/delete[]: avoid manual memory management; if used, match new with delete and new[] with delete[] (but prefer RAII).
  • Returning addresses of local variables: locals disappear when the function returns—never return a pointer to a local variable.
  • Assuming arrays keep their size when decayed to pointers: pass sizes explicitly or use std::span.

Tooling tip: compile and run tests with AddressSanitizer (e.g., -fsanitize=address -fno-omit-frame-pointer -O1 -g on many compilers) to catch use-after-free and buffer overflows early.

Pointer safety and debugging checklist

  • Initialize: int* p = nullptr; or int* p = &x; (never leave it uninitialized).
  • Preconditions: assert(p) or guard with if (p) { ... } before dereference.
  • Lifetime: ensure the pointee outlives the pointer; never return a pointer to a local variable.
  • Bounds: when doing pointer arithmetic, stay within the same array; pass lengths or use std::span.
  • RAII first: use std::vector, std::string, std::unique_ptr instead of manual new/delete.
  • Sanitizers: build with -fsanitize=address -fno-omit-frame-pointer -O1 -g during development.
  • Interop: when an API needs a raw pointer from a smart pointer, pass up.get() and keep the owner alive.

Quick recap: C++ pointer basics

  • Pointers store addresses; use & to take an address and * to dereference.
  • Prefer nullptr to represent “no object.”
  • Respect lifetimes and bounds; never form or use out-of-bounds pointers.
  • Favor references for non-null aliasing, pointers for optionality or array-like access.
  • Prefer RAII: containers, std::unique_ptr, std::shared_ptr when needed, and std::span for array views.

FAQ: C++ pointers explained for beginners

What is a pointer in C++ and why is it used?

A pointer is a variable holding the memory address of another object. It’s used for efficient parameter passing, interacting with arrays and C APIs, and for dynamic data structures. Modern code uses pointers carefully, usually combined with RAII and smart pointers for safety.

How do I declare and initialize a pointer in C++?

Use T* p = &obj; to point to an existing object, or T* p = nullptr; if you don’t have one yet. Example: int x = 10; int* p = &x;. Always initialize your pointers.

What is the difference between a pointer and a reference in C++?

Pointers can be null and reseated; references must bind to a valid object and cannot be reseated. Use references for required, non-null access; use pointers for optional or reseatable access and for array-like traversal.

How do pointers work with arrays and strings in C++?

An array can decay to a pointer to its first element, so functions often take T* plus a size. C-style strings are char arrays ending with ''; you can move a const char* pointer along the characters until the terminator. Prefer std::string and std::string_view for safety in modern code.

What are common pointer errors in C++ and how can I avoid them?

Common errors include uninitialized or null dereference, dangling pointers, and out-of-bounds arithmetic. Initialize pointers, check for nullptr where appropriate, respect lifetimes and array bounds, and use AddressSanitizer during testing. Prefer RAII and avoid manual new/delete in most cases.

Practice next

  • Write a function that swaps two integers using pointers (void swap_ints(int* a, int* b)).
  • Implement sum using std::span<const int> (C++20) and compare it with the pointer-plus-length version.
  • Refactor a small program that uses new/delete to use std::unique_ptr.

Keep building your skills in our C++ tutorials.

Sources / Further reading

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted