Categories
Computer Science / Information Technology Language: C++

References

A reference is an alias for a variable and must be initialised to a variable at declaration. A reference cannot be null and once initialised, it cannot be made to refer to a different variable. A reference can be thought of as a constant pointer that is automatically dereferenced once out of scope.

Declaration and initialisation

Syntax:

data_type &ref_var = var;

Where,

  • data_type is any data type; user-defined or primitive.
  • var is a variable of data type data_type.

Note that the ampersand (&) before the reference variable is compulsory during the declaration of a reference variable.

Use of references in Range-based for loop

When one needs to modify the variable in a loop, using references in a range-based for loop is the best combination. The following program is an example of how to do it:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector <int> int_num {1, 2, 3, 4, 5};
    for (int &var: int_num)
        var = var * var;
    for (int var: int_num)
        cout << var << " ";
    return 0;
}
Output1 4 9 14 25
ConclusionReferences can be used to modify the contents of a collection in a range-based for loop

Using references is very efficient as the extra cost of copying each collection element is not incurred. Sometimes this approach can be deadly as this is prone to accidental modifications of the collection elements inside of the loop. To solve this, one can use a const modifier which prohibits modifying the collection elements which are being iterated upon.

#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector <int> int_num {1, 2, 3, 4, 5};
    for (const int &var: int_num)
        var = var * var;
    return 0;
}
OutputCompilation error
ConclusionThe values in const references cannot be modified. It’s a safer way to use references when no modification is intended.

Using references in function calls

Similar to the use of references in range-based for loop, using references in function parameters is very efficient. The use of references in this context can be done in 2 scenarios described as follows:

  1. When the data of the parameters have to be accessed and not modified
    1. In this scenario, the parameters in the function should be made constant references to the argument passed by the calling function. This prevents accidental modification of the values and reduces the overhead of copying contents to the called function scope as the reference variables are used.
    2. This is illustrated in the following set of examples:
#include <iostream>
#include <vector>
using namespace std;
void print_num(int &num) {
    cout << "Received num: " << num;
}
int main() {
    int num {5};
    print_num(num);
    return 0;
}
OutputReceived num: 5
ConclusionThe syntax of passing the arguments remains the same but the parameter list has to be changed to references. 
#include <iostream>
#include <vector>
using namespace std;
void print_num(const int &num) {
    cout << "Received num: " << num;
}
int main() {
    int num {5};
    print_num(num);
    return 0;
}
OutputReceived num: 5
ConclusionThe value num is not being modified in print_num() function, the best practice is to make the reference constant.
#include <iostream>
#include <vector>
using namespace std;
void print_num(const int &num) {
    cout << "Received num: " << num;
    num++;
}
int main() {
    int num {5};
    print_num(num);
    return 0;
}
OutputCompilation error
ConclusionThe values in constant references cannot be modified. This is a safer way to use references when no modification is intended.
  1. When the data of the parameters have to be modified
    1. Previously, pointers were an optimal way to modify the values in the functions to reduce the overhead of copying. References provide a cleaner way to achieve the same goal. This is illustrated in the following set of examples:
#include <iostream>
#include <vector>
using namespace std;
void square(int &num) {
    num *= num;
}
int main() {
    int num {5};
    square(num);
    cout << "Square of num: " << num;
    return 0;
}
OutputSquare of num: 25
ConclusionThe syntax of passing the arguments remains the same but the parameter list has to be changed to references. 
#include <iostream>
#include <vector>
using namespace std;
void square(vector<int> &num) {
    for (int &val : num)
        val *= val;
}
int main() {
    vector <int> num {1, 2, 3, 4, 5};
    square(num);
    for (int &val : num)
        cout << val << " ";
    return 0;
}
Output1 4 9 16 25
ConclusionNot only primitive data types but references of all data types can be used in function calls. This is far more efficient than making a separate local copy of values in the called function.

Difference between a reference and a pointer

It is common for beginners to get confused between a reference and a pointer. The following table lists the differences between a reference and a pointer.

ReferencePointer
Initialisation:data_type &ref_var = var;Initialisation:data_type *ptr_var = &var;
References only available in C++Pointers are available both in C and C++
Cannot be reassigned to different variableCan be reassigned to point to different variables at a different point in time.
Cannot hold NULLCan hold NULL as a value
Does not need dereferencing operator to access the valueNeeds dereferencing operator (asterisk – ‘*’) to access the value pointed by the pointer
After the initialisation, the reference variable and the original variable are effectively the same but with different names. A reference variable is an alias to the original variableA variable and the pointer to the variable are two different entities.
A reference to a reference (multilevel referencing/nested referencing) is illegal in C++A pointer to pointer (double pointer/multilevel pointer/nested pointers) is valid
To access the address of the variable, ampersand (&) can be suffixed to the variable or the referenceJust the pointer without the ampersand or the dereferencing symbol returns the address of the variable it is pointing to
References cannot be used during dynamic memory allocationPointers must be used during dynamic memory allocation

Reference of pointers

It is possible to have references of pointers. The following example shows the same:

#include <iostream>
using namespace std;
void swap(int * &ptr_var1, int * &ptr_var2){
    int temp = *ptr_var1;
    *ptr_var1 = *ptr_var2;
    *ptr_var2 = temp;
}
int main(){
    int var1 = 5, var2 = 6;
    int *ptr_var1 = &var1, *ptr_var2 = &var2;
    swap(ptr_var1, ptr_var2);
    cout << "var1 is " << var1 << endl;
    cout << "var2 is " << var2;
    return 0;
}
Outputvar1 is 6
var2 is 5
Explanationptr_var1 and ptr_var2 are pointers containing addresses of var1 and var2 respectively.
The two pointers are passed to swap() function as arguments.
The swap function receives the pointer arguments as a reference of pointer parameters and swaps the contents pointed by the pointers.

Exercises

  1. Write a function which has a reference int variable as a parameter and returns void. The function name should be ‘cube’ and it should save the cube of the parameter to itself.
  2. Write a function which has a reference vector variable and returns void. This function should square all the elements of the vector.

L-values and R-values

L-values

  • It is an object that occupies a location in memory and is addressable.
  • These are modifiable if they are not constants.
  • All variables with valid memory allocation are l-values.
  • The name l-value comes from the fact that these entities can be kept in the left hand side of an equality operator: x = 100;

R-values

  • The constant literals with no memory are r-values. These essentially cannot be placed in the left hand side of the equality operator, hence the name r-value.
  • Example “String Literal”, 1, (10 + 20).
  • Assigning values to r-values is illegal:
    • "String literal" = str;
  • Hence we cannot create a reference to an r-value. This is especially important when we are passing values to a function. Consider the following program:
#include <iostream>
#include <vector>

using std::cout;

int double_it(int &a) {
    return a * 2;
}

int main() {
    int n = 5;
    cout << double_it(n); // Legal
    cout << double_it(5); // Illegal
    return 0;
}

Leave a Reply

Your email address will not be published. Required fields are marked *

You cannot copy content of this page