Road to C++ Programmer #2 - Pointers & References

Last Edited: 8/29/2024

The blog post introduces the pointers and references of C++.

C++ References

Pointers

The way pointers work is almost the same as in C, which is explained in detail in the articles "Road to C Programmer #5 - Pointers" and "Road to C Programmer #7 - Pointers Cont'd". If you are not familiar with pointers, I highly recommend checking them out. The notable difference in how C++ handles pointers lies in how void pointers are handled. While C is weakly typed in terms of void pointers and does not require type casting, C++ does require casting the type of the pointers.

void *ptr;
// The below are fine with C but not with C++
int *i = ptr; 
 
// In C++ you need to do the following instead
int *i = (int *) ptr;

As explicit type casting can be compiled by both C and C++, even though it might feel verbose, it could be the best practice.

References

In C, we have been using pointers a lot to achieve pass by reference, where we want to modify the input variables. For example, we build the swap function like the following:

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

Although it is straightforward to use pointers for pass by reference in a simple program like the above, it can easily get quite messy. To solve this, C++ introduced reference variables, which can work as an alias to a variable that can be passed by reference without creating a pointer.

void swap(int &x, int &y) {
    int temp = x;
    x = y;
    y = x;
}
 
int main () {
 
    int x = 10;
    int y = 20;
 
    swap(x, y);
 
    cout << "x: " << x << ", y: " << y << endl;
    // => x: 20, y: 10
 
    // Initalize reference variable
    int &rX = x;
    rX += 2; // it affects x as well
    cout << "x: " << x << ", rX: " << rX << endl;
    // => x: 22, rX: 22
 
    return 0;
}

A reference variable can be declared using &, and it needs to be initialized, unlike pointers. It does not require dereferencing as it is just an alias that can be passed by reference, which can simplify the definition of the swap function as shown above. Like pointers, reference variables can be returned by a function, which is useful in some scenarios like the max function below.

int &max(int array[], int length) {
    int max_idx = 0;
    for (int i = 1; i < length; i++) {
        if (array[i] > array[max_idx]) {
            max_idx = i;
        }
    }
    return array[max_idx];
}
 
int _max(int array[], int length) {
    int max_idx = 0;
    for (int i = 1; i < length; i++) {
        if (array[i] > array[max_idx]) {
            max_idx = i;
        }
    }
    return array[max_idx];
}
 
int main () {
    int array[3] = {3, 100, 3};
    
    int &max_val = max(array, 3); // reference var returned by max
    int _max_val = _max(array, 3); // value returned by _max
 
    _max_val++;
    cout << "array[1]: " << array[1] << "_max_val" << _max_val << endl;
    // => array[1]: 100, _max_val: 101
 
    max_val++;
    cout << "array[1]: " << array[1] << "max_val" << max_val << endl;
    // => array[1]: 101, _max_val: 101
 
    return 0;
}

The max function returns the reference variable to array[1], while the _max function returns the maximum value of the array. Hence, array[1] is only updated when max_val is incremented.

Dynamic Memory Allocation

Another way we have been using pointers is for dynamic memory allocation with malloc, calloc, realloc, and so on. C++ achieves a similar task but more simply by introducing the following new syntax:

// C/C++
int *j = (int *) malloc(5 * sizeof(int));
free(j);
 
// C++
int *j = new int[5];
delete[] j;

The new keyword is analogous to malloc, and delete is analogous to free, but there are many notable differences. While malloc allocates memory in the specified number of bytes and returns NULL when it fails, new makes the compiler calculate the necessary space, allocates memory in a region called the free store, and throws an error instead of returning NULL when it fails.

Additionally, the new and delete operators initialize and destroy objects using constructors and destructors, which will be introduced in the next article. You can also initialize a single int with these operators:

int *j = new int(5); // initalized as 5, not an array
delete j;

Exercises

From this article, there will be an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.

Resources