What are reference variables?

So far, we have seen functions returning a single value only. However, there might be times when we want a function to return more than one value.

For example, say we want our function to find the maximum and the second maximum values and then return both values. However, the function can’t return two variables simultaneously.

There are some programming languages that support multi-value returns. For instance, programming languages like Python and Go allow us to return multiple values from a function simultaneously.

Look at the code below.

Press + to interact
#include <iostream>
using namespace std;
// first version
int threeConsecutiveNums(int i)
{
return i, i+1,i+2;
}
// second version
/* // To test this please comment above function and uncomment this region.
int,int,int threeConsecutiveNums(int i) // This will generate a syntax error
{
return i, i+1,i+2;
}
*/
int main()
{
cout << threeConsecutiveNums(5); // It will only print 7.
return 0;
}

When we execute the first version of the code above, we see that only the last value, 7, gets returned. Whenever we try to return multiple values from a function in C++, all the other returning values will be ignored except for the last value.

As for the second version of the code above, we get a syntax error because, after the function’s first return data type (the first int), the compiler does not expect a comma or anything other than the name of a function or a variable. This is clearly a syntax error because we cannot write multiple return types for a function, as shown above.

So what do we do if we want to have multiple values in the returning sense? No need to worry, we’ll look at the solution very soon.

In C++, there is a concept called referencing or aliasingcreating an alternative name, e.g., Jonathan is called Jon as a nickname a variable. We already know that a variable is a placeholder (a memory) that stores some data and has a unique name. What if we created a second name for that variable or memory and, instead of passing the values as parameters, we passed these memory locations as references inside the functions? This way, we can directly access the data inside the variables (or memory) and modify it.

Don’t worry, we’ll explain what we mean above in just a bit.

For now, we only need to understand that we can make an alternative name (something like a nickname) of any existing variable (memory) and use that nickname to make whatever changes we want. In C++, we call these alternative names references.

How to create a reference

Here is the syntax for creating a reference.

Press + to interact
// creating a variable
dataType actualVariableName;
// creating a reference variable of the above variable
dataType & refName = actualVariableName; // refName could be any identifier

We first write the data type, followed by the ampersand (&), then the reference name, and equate it to the actual variable.

Let’s look at an example.

Example: Using references/aliasing

Suppose we created an alias called aliasNum for the variable num.

Press + to interact
// variable num
int num;
num = 16;
// reference variable of variable num
int & aliasNum = num;

The pictorial representation of the code above is depicted in the illustration below:

Now, if we were to assign a different value to aliasNum, and print the value of num instead, the newly assigned value would be printed. The reason is that the change would be reflected in the same memory location, which holds the two names of the same variable.

Let’s look at the complete example. Change the code in the playground to get a better understanding of what we just learned.

Press + to interact
#include <iostream>
using namespace std;
int main() {
// variable num
int num;
num = 16;
// reference variable of variable num
int & aliasNum = num;
cout << "Before updating: " << endl;
cout << "num = " << num << endl;
cout << "aliasNum = " << aliasNum << endl;
aliasNum = 20;
cout << "After updating the reference variable (aliasNum): " << endl;
cout << "num = " << num << endl;
cout << "aliasNum = " << aliasNum << endl;
return 0;
}

We’ll use the word reference instead of the alias in the rest of the lessons.

The operator &: As an address vs. reference operator

If we want to look at the address of some variable, we can use an ampersand & as an address operator. Look at the following code:

Press + to interact
#include <iostream>
using namespace std;
int main()
{
int a = 10, b = 20;
cout << "Address of a: "<<&a<<endl
<< "Address of b: "<<&b<<endl;
return 0;
}

If we want to make a reference variable of some memory, we can use & as a reference operator. Look at the following code:

Press + to interact
#include <iostream>
using namespace std;
int main() {
int num = 16;
int & aliasNum = num;
// accessing the address of the alias, returns the address of the variable
// since the memory address is the same
cout << "Address of num: \t" << &num << endl;
cout << "Address of aliasNum: \t" << &aliasNum << endl;
return 0;
}

If we were to look at the addresses of both the variable num and its reference variable aliasNum using the symbol & (address operator), we will see the same address for both variables because they are the different names of the same memory location.

Instruction: Execute the above code to see it for yourself. Also, create some other references of different data types. Keep in mind that the reference and variable types should match each other.

Remember: The ampersand & is an address operator in C++ that can be used to access the memory address of a variable. However, at the time of declaration, the & denotes a reference (an identifier that is an alias for some other variable).


Applications of references

The aliases make it possible to modify multiple variables directly within a function without having to return multiple values, giving the sense of returning multiple values.

Function receiving parameters as aliases/references

Look at the following simple program:

#include <iostream>
using namespace std;

void fbyRef(int &A, int &B)   // Function receiving two memory locations 
{                             // which we are referring as A and B
    int T = A;                // We can call them using any name (not necessarily 
    A = B;                    // the names AS PASSED in arguments).
    B = T;
}

void fbyVal(int A, int B) // In this function we will receive two 
{                         // values which will be received by A,B
    int T = A;            // Any change here in A and B will not be 
    A = B;                // reflected in the variables which is passed
    B = T;                // as argument in this function.
}


int main() 
{
    int A  = 10;
    int B  = 20;
    int C  = 30;
    int D  = 40;
    cout << "Initial values: \n"
         << "A: "<<A<<endl
         << "B: "<<B<<endl
         << "C: "<<C<<endl
         << "D: "<<D<<endl;

    fbyVal(A, B);
    cout << "\nAfter calling fbyVal: \n"
         << "A: "<<A<<endl
         << "B: "<<B<<endl;

    fbyRef(A, B);
    cout << "\nAfter calling fbyRef: \n"
         << "A: "<<A<<endl
         << "B: "<<B<<endl;

    fbyRef(C, D);  // Now inside fbyRef C will be referred by A and D by B
    cout << "\nAfter calling fbyRef: \n"
         << "C: "<<C<<endl
         << "D: "<<D<<endl;
    return 0;
}










Function receiving parameters as aliases/references

When passing a variable to a function in C++, the difference between pass-by-value and pass-by-reference is that pass-by-value creates a copy of the variable. Any changes made to it within the function do not affect the original, while pass-by-reference passes the memory address of the variable, so any changes made to it within the function do affect the original.

Instruction: Execute the above program step by step and see how the two functions fbyRef() and fbyVal() are working.

Let’s look at the pictorial representation of the function fbyRef() below to understand how the original values are changed too:

Look at the “Watches” (inside main() and fbyRef()) closely in the above animation. We can see that when the fbyRef() function is being executed, the arguments received by fbyRef() are being automatically updated inside the main() function as well.


Let’s now look at the pictorial representation of the function fbyVal() below to understand how the original values remain unchanged:

Look at the “Watches” (inside main() and fbyVal()) closely in the above animation. We can see that when the fbyVal() function is being executed, the arguments received by fbyVal() are not changed at all inside the main() function. This is because the fbyVal() function operates on a copy of the arguments passed to it rather than the original values. Any changes made to the parameters within the fbyVal() function do not affect the original arguments passed to the function in the main() function.

Quiz: Using references

Question

What is the above function fbyRef(int &A, int &B) doing?

Show Answer

We can access a memory location from another scope. The references act as the function’s formal parameters to support pass-by-reference. Instead of making a copy and working on the cloned copy (like in pass-by-value), the function works on the original memory when a reference is received in a function (pass-by-reference).


Summary

To sum up, we have now learned the first way to return more than one value from a function: we can pass variables by references and update those references. This is one way to return multiple values by the function. We will shortly enhance this idea and even pass a block of contiguous memory to functions and see how this powerful idea can be utilized to equip functions to work on large data.