Skip to main content

Pointers

Using Pointers as Parameters

Imagine you're at home, and your mom asks you to go to your friend's house and give him a note. But instead of giving you the whole notebook, she just gives you the address of the notebook so your friend can go directly to your house and read it himself.

Sounds smart, right? You're not carrying the heavy notebook around. You're just passing an address, and that's enough.

Well, that's exactly what C++ does when we use pointers as function parameters. Instead of copying the whole variable (like the notebook), we just pass its address, and the function can directly access or change it from there!

Why Do We Even Need to Pass Pointers?

In C++, when you send a variable to a function, by default, it’s like sending a copy of it. So if the function changes that copy, your original variable stays the same — just like giving your friend a photocopy of your notebook instead of the real one.

But what if your friend needs to write something in your notebook? Then the photocopy won’t help!

This is where pointers come in. If we pass the address of the variable, the function can go straight to the original notebook and write in it.

Passing Pointers to Functions

Let’s say your friend Alex wants to borrow your locker at school for a day. You don’t give him the whole locker, right? That’s impossible! You just give him the key. With that key, he can open your locker and put things in or take them out.

That’s exactly what passing a pointer to a function means. You give the function the key (address) to the actual variable, and now the function can open it up and make changes directly.

Let’s First See a Regular Function (No Pointer)

#include <iostream>
using namespace std;

void square(int n) {
    n = n * n;
}

int main() {
    int number = 4;
    square(number);
    cout << "Number is: " << number << endl;  // Still prints 4
    return 0;
}

Here, we passed the number itself, not its address. So square() just works on a copy. The real value of number stays the same.

Now Let’s Pass a Pointer Instead

#include <iostream>
using namespace std;

void square(int *n) {
    *n = (*n) * (*n);
}

int main() {
    int number = 4;
    square(&number);  // We pass the address using &
    cout << "Number is: " << number << endl;  // Now prints 16
    return 0;
}

What Just Happened?

  • square(&number); → We’re giving the key (address) to the function.
  • void square(int *n) → This function knows it's getting a pointer to an int.
  • *n = (*n) * (*n); → We go to the address and square the real value.

So now the function can change the actual value because it has the key to it!

Why Do We Pass Pointers to Functions?

Here’s why this is super useful:

  1. To change actual values (like we did above).
  2. To work with multiple outputs. (More than one return value!)
  3. To avoid copying large data. (Saves memory and time.)
  4. To work with arrays and dynamic memory.

Example: Swap Using Pointers

Let’s say you and your friend want to swap your toys:

#include <iostream>
using namespace std;

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int toy1 = 10, toy2 = 20;
    swap(&toy1, &toy2);  // Passing addresses
    cout << "Toy1: " << toy1 << ", Toy2: " << toy2 << endl;  // Swapped!
    return 0;
}

If we didn’t use pointers here, we wouldn’t be able to actually swap the real values!

Modifying Values via Pointer Parameters

Imagine your classroom has a big whiteboard where students write answers during a quiz. You could say:

“Hey! Change the answer on that whiteboard to 42!”

You don’t give your friend a copy of the whiteboard. You just point at it, and they go make the change right there.

This is exactly what happens when you modify values through pointer parameters in C++. Instead of giving a copy of the variable, you give the actual location (address) — so the function can go directly there and change the value.

A Simple Example: Changing a Single Number

Step 1: Without Pointer (Change Doesn’t Work)

#include <iostream>
using namespace std;

void update(int n) {
    n = 100;
}

int main() {
    int score = 50;
    update(score);  // Passing value
    cout << "Score: " << score << endl;  // Still prints 50
    return 0;
}

Why didn’t it change?
Because the function got a copy of score, not the real thing.

Step 2: With Pointer (Change Works!)

#include <iostream>
using namespace std;

void update(int *n) {
    *n = 100;
}

int main() {
    int score = 50;
    update(&score);  // Passing address
    cout << "Score: " << score << endl;  // Now prints 100
    return 0;
}

What changed here?

  • We gave the address of score using &score.
  • The function received a pointer int *n, which is like saying: "I know where the real score lives!"
  • Inside the function, *n = 100; means: “go to that address and change the value to 100.”

Boom! The real score gets updated — like erasing and rewriting on the classroom whiteboard.

Returning Pointers from Functions

Imagine you ask your friend, “Hey, can you help me find my buried treasure?”

Your friend goes on a search, and instead of giving you the treasure directly (because it’s too heavy or not ready yet), he gives you a map that shows exactly where it is. Now, you can use that map to go there and grab the treasure yourself.

That’s what happens when a function returns a pointer in C++. The function may not return the actual value right away — instead, it gives you the address (a pointer) where the value can be found or used.

Why Return a Pointer?
Returning a pointer is useful when:

  1. You want to return multiple values over time.
  2. You want to return something that was dynamically created in memory.
  3. You want to avoid copying a large chunk of data.

Example: Returning Address of a Local Variable

#include <iostream>
using namespace std;

int* giveNumber() {
    int num = 10;
    return &num;  // Bad idea! Returning address of a local variable
}

int main() {
    int *ptr = giveNumber();
    cout << *ptr << endl;  // Might give garbage or crash
    return 0;
}

Why This is Dangerous?
Because num is a local variable — it disappears when the function ends. So the address &num is no longer safe to use. It’s like your friend drew a map on a tissue and then threw it away. You’re left with a useless scrap!

Correct Way: Return Pointer from Dynamic Memory

Let’s use new to allocate memory on the heap (which stays alive after the function ends):

#include <iostream>
using namespace std;

int* giveNumber() {
    int* num = new int;  // Create memory in heap
    *num = 42;           // Store value
    return num;          // Return address
}

int main() {
    int* ptr = giveNumber();
    cout << "Number is: " << *ptr << endl;  // Safe and works fine
    delete ptr;  // Clean up memory
    return 0;
}

Output:

Number is: 42

Here, the memory was created on the heap using new. It doesn’t disappear after the function ends — so the address (pointer) returned is safe to use. Later, we clean it up using delete to prevent memory leaks.

Think of the function as a toy factory:

  • If it creates a toy on a workbench (stack memory), and you take the address — someone else may toss it out right after.
  • But if it creates a toy in a permanent storage room (heap), then you can safely go there any time — until you decide to throw it away yourself (delete).

Important Note
If you're returning a pointer from a function, make sure:

  • It doesn’t point to a local variable.
  • You free the memory later using delete if you used new.

Function Pointers

Imagine you have a robot that can do many tasks — dance, clean, or sing. But instead of pressing buttons on the robot directly, you use a remote control. You can decide which action to trigger by setting the remote to different buttons.

This remote control is just like a function pointer in C++. It's a special pointer that doesn't point to data or numbers — instead, it points to a function!

You can then call the function using the pointer, just like pressing a button to make the robot move.

Why Use Function Pointers?
Function pointers are useful when:

  1. You want to choose between multiple functions at runtime.
  2. You want to pass a function to another function.
  3. You want to build callback systems (like in GUI, gaming, sorting functions, etc.).

Creating and Using a Function Pointer

#include <iostream>
using namespace std;

void sayHello() {
    cout << "Hello, world!" << endl;
}

void sayBye() {
    cout << "Goodbye!" << endl;
}

int main() {
    // Declare a pointer to a function that returns void and takes no parameters
    void (*messageFunc)();

    // Point to sayHello
    messageFunc = sayHello;
    messageFunc();  // Calls sayHello()

    // Change pointer to sayBye
    messageFunc = sayBye;
    messageFunc();  // Calls sayBye()

    return 0;
}

Let’s Break It Down:

  • void (*messageFunc)(); → This means: messageFunc is a pointer to a function that returns void and takes no parameters.
  • messageFunc = sayHello; → We assign the address of the function to the pointer.
  • messageFunc(); → We call the function using the pointer, just like pressing a remote button!

Let’s say you want to add or subtract numbers, depending on what the user chooses:

#include <iostream>
using namespace std;

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // Declare a function pointer for functions that take 2 ints and return int
    int (*operation)(int, int);

    // Choose add or subtract based on condition
    char choice;
    cout << "Enter '+' to add or '-' to subtract: ";
    cin >> choice;

    if (choice == '+')
        operation = add;
    else
        operation = subtract;

    int result = operation(10, 5);
    cout << "Result: " << result << endl;

    return 0;
}

Output Example:

If user enters +, output is:

Result: 15

If user enters -, output is:

Result: 5

Bonus Tip: Passing Function Pointers to Functions
Want to write a function that takes another function as input?

void runOperation(int x, int y, int (*func)(int, int)) {
    cout << "Result: " << func(x, y) << endl;
}

Then call like this:

runOperation(4, 2, add);      // prints 6
runOperation(4, 2, subtract); // prints 2

Callbacks Using Function Pointers

Imagine you’re baking a cake, and your friend says:

“Hey! I’ll call you when it’s done!”

You give your friend your phone number. You don’t know exactly when the cake will be ready, but you’ve told them how to reach you.

That’s a callback! Your friend can “call back” when something is ready.

In C++, we can do the same thing by giving a function another function’s address — so it can “call it back” when needed.

What is a Callback?
A callback is:

  • A function that is passed as an argument to another function.
  • It gets called later, usually after some task is done.
  • You can use it to make programs more flexible and reusable.

Let’s say you want to sort numbers, but you want to decide how to sort them — ascending or descending.

You can pass a callback function to the sorting logic!

#include <iostream>
#include <algorithm>
using namespace std;

bool ascending(int a, int b) {
    return a < b;
}

bool descending(int a, int b) {
    return a > b;
}

void sortArray(int arr[], int size, bool (*compare)(int, int)) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = i + 1; j < size; j++) {
            if (!compare(arr[i], arr[j])) {
                swap(arr[i], arr[j]);
            }
        }
    }
}

void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++)
        cout << arr[i] << " ";
    cout << endl;
}

int main() {
    int numbers[] = {5, 2, 8, 1, 9};
    int size = 5;

    cout << "Ascending: ";
    sortArray(numbers, size, ascending);  // callback for ascending sort
    printArray(numbers, size);

    cout << "Descending: ";
    sortArray(numbers, size, descending); // callback for descending sort
    printArray(numbers, size);

    return 0;
}

Output:

Ascending: 1 2 5 8 9 
Descending: 9 8 5 2 1

Here, we passed the sorting strategy as a function — so the main sort logic stays the same!

Why Callbacks are Cool?

  • They make your code flexible.
  • They allow for custom behavior without rewriting logic.
  • They are used in games, event handling, GUI programming, sorting, notifications, and more!