No history yet

Pointers and References

Direct Memory Control

Variables in your program live somewhere in your computer's memory. Each location has a unique address, like a house number on a street. A pointer is a special kind of variable that doesn't hold data itself, but instead holds the memory address of another variable. This gives you a way to interact with data indirectly, or 'by pointing' to it.

To declare a pointer, you use an asterisk (*). To get the memory address of a variable, you use the ampersand (&), which is the 'address-of' operator.

int score = 100;
int* scorePtr;      // Declares a pointer to an integer

scorePtr = &score;  // Assigns the address of 'score' to 'scorePtr'

Now scorePtr holds the address where the value 100 is stored. To get the value back from the address, you dereference the pointer, again using the asterisk (*). This time, it means 'the value at this address'.

#include <iostream>

int main() {
    int score = 100;
    int* scorePtr = &score;

    std::cout << "Value of score: " << score << std::endl;
    std::cout << "Address of score: " << scorePtr << std::endl;
    std::cout << "Value at scorePtr: " << *scorePtr << std::endl;

    // You can also change the original value through the pointer
    *scorePtr = 250;
    std::cout << "New value of score: " << score << std::endl; // Prints 250

    return 0;
}

Aliases with References

A reference is an alias for an existing variable. Think of it as a nickname. Once you create a reference, it refers to the original variable for its entire life. Any changes made through the reference are made to the original variable.

Unlike pointers, a reference must be initialized when it's declared, and it can't be made to refer to a different variable later. They also use the ampersand (&) in their declaration, which can sometimes be confusing. The key difference is context: when & is used in a declaration, it creates a reference. When used on an existing variable, it gets its address.

int original = 50;
int& ref = original; // 'ref' is now an alias for 'original'

ref = 75; // This changes the value of 'original' to 75

References provide similar capabilities to pointers but with a simpler, safer syntax. You don't need to worry about dereferencing, and because they must be initialized, you avoid issues with null or uninitialized pointers.

Efficient Function Calls

When you pass an argument to a function, C++ defaults to 'pass-by-value'. This means the function receives a copy of the argument. For simple types like int or char, this is fast and safe. But for large objects or structs, creating a copy can be slow and consume a lot of memory.

This is where passing by pointer or reference shines. Instead of copying the whole object, you just pass its address (with a pointer) or an alias to it (with a reference). The function can then work with the original data directly, which is much more efficient. Passing by reference is often preferred because the syntax is cleaner and it prevents the possibility of a null pointer being passed.

// Pass by reference is often the most readable and efficient
void doubleValue(int& num) {
    num *= 2;
}

// Pass by pointer achieves the same result
void doubleValuePtr(int* numPtr) {
    if (numPtr) { // Always check for null pointers!
        *numPtr *= 2;
    }
}

int main() {
    int myValue = 10;
    doubleValue(myValue); // myValue is now 20

    int anotherValue = 15;
    doubleValuePtr(&anotherValue); // anotherValue is now 30
    return 0;
}

To get the efficiency of passing by reference without allowing the function to change your data, you can use const references. This is extremely common in C++ and is considered a best practice. It signals that the function will only read from the variable, not write to it.

void printData(const BigObject& data); This function signature promises to not modify data, while avoiding a slow and costly copy.

Navigating Memory

Pointers have another trick up their sleeve: pointer arithmetic. When you add or subtract from a pointer, you aren't just changing the memory address by a few bytes. Instead, you move it by a number of whole elements of the type it points to. For example, if intPtr points to an integer, intPtr + 1 gives you the address of the next integer in memory, which is typically 4 bytes away.

This feature makes pointers perfect for walking through arrays. The name of an array in C++ can actually act like a pointer to its first element.

#include <iostream>

int main() {
    int numbers[] = {10, 20, 30, 40, 50};
    int* ptr = numbers; // ptr points to the first element (10)

    std::cout << *ptr << std::endl; // Prints 10

    ptr++; // Move to the next integer in memory
    std::cout << *ptr << std::endl; // Prints 20

    ptr += 3; // Move forward 3 integers
    std::cout << *ptr << std::endl; // Prints 50

    return 0;
}

You can also protect data using const with pointers in two ways. A pointer to a constant value prevents you from changing the data it points to. A constant pointer, on the other hand, is a pointer that cannot be changed to point to something else after it's initialized.

int x = 5, y = 10;

// Pointer to a constant value. The data can't be changed via the pointer.
const int* ptrToConst = &x;
// *ptrToConst = 7; // ERROR! Can't change the value.
ptrToConst = &y;     // OK. The pointer itself can be changed.

// Constant pointer. The pointer's address can't be changed.
int* const constPtr = &x;
*constPtr = 7;     // OK. The value can be changed.
// constPtr = &y;  // ERROR! Can't change where the pointer points.

// Constant pointer to a constant value.
const int* const superConstPtr = &x;
// *superConstPtr = 7; // ERROR!
// superConstPtr = &y; // ERROR!
Quiz Questions 1/7

What is the primary purpose of a pointer variable in C++?

Quiz Questions 2/7

Consider the following C++ code. What will be printed to the console?

#include <iostream>

int main() {
    int score = 90;
    int& scoreRef = score;
    scoreRef = 100;
    std::cout << score;
    return 0;
}