Skip to content

Pointers With Functions

Using a pointer as a function parameter offers the same advantages as using call-by-reference parameters. By passing the address of a variable, you can modify its value directly within the function, saving space and allowing the caller’s data to be changed without returning a new value. For example:

int Square(int *x) {
*x *= *x; // Modify the value at the pointer's address
return *x;
}
// Square(&variable) <- remember reference operator

Here, x is a pointer to an integer. Calling Square(&num) will modify num in place.

While call-by-reference (using references) and call-by-pointer (using pointers) both allow in-place modifications, pointers offer more flexibility. For instance, you can:

  • Reassign Pointers: Change what a pointer points to at runtime, allowing dynamic behavior not possible with a simple reference.

  • Work With Dynamically Allocated Memory: Allocate, deallocate, and manage memory on the fly.

As mentioned in the Algorithms Page, lets refactor the MergeSort algorithm using what we’ve learned about pointers. We’ll also create a new Print function with a pointer to test the function.

  1. Create Driver File

    Create a file called driver.cc. We’ll be making everything in one file since we’re only implementing a couple of functions:

    driver.cc
    // Copyright 2024 CSCE240
    #include <iostream>
    using std::cout;
    using std::endl;
    void MergeSort(int*, int);
    void Merge(int*, int*, int, int*, int);
    void Print(const int*, int);
    int main() {
    const int kSize = 5;
    int array[kSize] = {10, 8, 6, 4, 2};
    MergeSort(array, kSize);
    Print(array, kSize);
    return 0;
    }
  2. Add Function Implementation

    Under the main function, lets first add the implementation for the primary function that will be called, MergeSort. Highlighted are the newly added or changed lines:

    driver.cc
    void MergeSort(int* array, int size) {
    // check if array has one element
    if (size < 2) return;
    // calculate mid index
    int mid = size / 2;
    // determine sizes of left and right subarrays
    const int kLeftSize = mid;
    const int kRightSize = size - mid;
    // create left and right subarrays
    int* left = new int[kLeftSize];
    int* right = new int[kRightSize];
    // populate left subarray
    for (int i = 0; i < mid; ++i) {
    left[i] = array[i];
    }
    // populate right subarray
    for (int i = mid; i < size; ++i) {
    right[i - mid] = array[i];
    }
    // recursively create smaller subarrays
    MergeSort(left, kLeftSize);
    MergeSort(right, kRightSize);
    // merge subarrays
    Merge(array, left, kLeftSize, right, kRightSize);
    // free dynamically allocated memory
    delete[] left;
    delete[] right;
    }

    Next we’ll add the implementation for Merge, the helper function:

    driver.cc
    void Merge(int* array, int* left_array, int left_size, int* right_array,
    int right_size) {
    int i = 0; // left array index
    int j = 0; // right array index
    int k = 0; // merged array's index
    // merge elements into the array in sorted order
    while (i < left_size && j < right_size) {
    // compare elements from left and right subarrays
    if (left_array[i] <= right_array[j]) {
    array[k] = left_array[i];
    ++i;
    ++k;
    } else {
    array[k] = right_array[j];
    ++j;
    ++k;
    }
    }
    // copy remaining elements from left subarray
    while (i < left_size) {
    array[k] = left_array[i];
    ++i;
    ++k;
    }
    // copy remaining elements from right subarray
    while (j < right_size) {
    array[k] = right_array[j];
    ++j;
    ++k;
    }
    }

    Finally, let’s add the implementation for the Print function:

    driver.cc
    void Print(const int* array, int size) {
    for (int i = 0; i < size; ++i) {
    cout << array[i] << " ";
    }
    cout << endl;
    }
  3. Test Functions

    The last step is to test all the functions we’ve created:

    driver.cc
    g++ -Wall -std=c++17 driver.cc
    ./a.out
    2 4 6 8 10