Functions

A function is a block of code which only runs when it is explicitly called.

In mathematics, a function is defined in the context of two sets usually called the domain and the range (or co-domain) and is a way of mapping each element of the domain to an uniquely specified element in the range.

The role of functions in programming langages is similar: you may think of a function as a black box that takes some input (possibly multiple inputs or even no input) and gives us some well-defined output (also possibly empty).

📖

Example: a function called sum2 may be designed to take to numbers as input and return their sum as the output.

Functions are important for reusing code: define the functionality once, and use it many times. Any changes/fixes also need be implemented only once.

Syntax:

<datatype of returned value> <function_name> ( <optional arguments> ) {

FUNCTION BLOCK

return <return value>;

}

Example:

#include <iostream>
using namespace std;

int es301(float y = 0.5){
	cout << y << endl;
	return y;
}

int main(){
	int x = es301(9.56);
	cout << x << endl;
}
⚠️

Remember to declare the function before the first call, or you might get a compilation error.

📘

In due course, you might want to organize your commonly used functions in your own libraries, but we will come to this later.

Function Arguments

On default arguments

  • Functions can have default values. When you specify a default value (as in the example above), it gets used when the argument is unspecified in an user's function call.
  • In general, if defaults are not specified, if the arguments supplied in a function call don't match the expectations of the function specification, then you will get an error at compilation.
  • In a function declaration, after a parameter with a default argument, all subsequent parameters must more or less have a default argument supplied (for more specifics see the documentation, but note that something like int sum(x,y = 0,z) will not work.

On Scope — stack versus heap memory

When you declare variables within a function (whether it's main or your own user-defined functions), these variables are allocated space in the stackframe, and they disappear once the return statement within the function is executed.

To have variables that persist beyond the function's lifetime, use the operator new to create variables in the heap memory. You can access variables in the heap memory via pointers:

int *ptr = new int;
⚠️

If this declaration happens within a function, the pointer ptr, which is still in the stackframe for that function, will be lost once you come out of the function; however, the integer itself will remain in the heap memory. To retain access beyond the lifetime of the function call, make sure to pass the pointer back to the calling function.

Remember to use delete ptr to clean up the heap memory after usage (unlike some other languages, C++ does not do this clean up on its own).

More on scope — passing by value v/s reference

Compare these two functions:

#include <iostream>
using namespace std;

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

int main(){
	int x = 10;
  int y = 20;
  swap(x,y);
	cout << "x is: " << x << endl << "y is: " << y << endl;
}
#include <iostream>
using namespace std;


int main(){
	int x = 10;
  int y = 20;
  int temp = x;
  x = y;
  y = temp;
	cout << "x is: " << x << endl << "y is: " << y << endl;
}

One of these will swap the values of x and y and the other will not.

image

Try this yourself in C++ Tutor.

⚠️

For all primitive data types (int, float, bool, etc.), when you pass variables of these types to a function, access to the original variables is not shared with the function; the function only works with a copy of these values and manipulates them locally within the scope of the function.

In contrast, when you pass by reference, the function works with the original variables. This is what happens, for example, when you pass an array to a function, for example:

#include <iostream>
using namespace std;

void swap(int arr[]){
    int temp = arr[0];
    arr[0] = arr[1];
    arr[1] = temp; 
}

int main(){
	int arr[] = {10,20};
    cout << arr[0] << endl << arr[1] << endl;
    swap(arr);
    cout << arr[0] << endl << arr[1] << endl;
}

Note that swapping variables is such a fundamental operation that it is built into C++, try the following:

#include <iostream>
using namespace std;

int main(){
    int x, y;
    cin >> x >> y;
    cout << x << endl << y << endl;
    swap(x,y);
    cout << x << endl << y << endl;
}

A more interesting example that demonstrates both the pass-by-referencing of arrays as well as the built-in swap function: sorting an array which has only 0's, 1's, and 2's as input.

#include <iostream>
using namespace std;

void sort2pointers(int arr[], int n){
    int L = 0, M = 0, R = n-1; 
    while(M <= R){
        if(arr[M] == 0){
            swap(arr[L],arr[M]);
            M++;
            L++;
        }
        else if(arr[M] == 2){
            swap(arr[R],arr[M]);
            R--;
        }
        else{
            M++;
        }
    }
}

int main(){
    int n;
    cin >> n;
    int arr[n];
    for(int i = 0; i < n; ++i){
        cin >> arr[i];
    }
    
    sort2pointers(arr,n);

    for(int i = 0; i < n; ++i){
        cout << arr[i] << endl;
    }
}

We will have more to say about passing by reference in the section on references.