References. References as arguments of functions

1. Introduction to references

We have learned before that the computer allows us to associate names such as \(x\), \(y\) to memory locations. The command int x is a request issued to the computer to give us a piece of RAM memory sufficient to hold one integer. We also inform the computer that we will use the name \(x\) for the content of this memory.

If after the above command we issue the command int &y=x we are asking the computer to allow us to use name \(y\) as well as name \(x\) for the content of the memory location allocated by the command int x. At the moment this looks extremely useless, but we will see later that we are able to achieve amazing control over the computer if we are allowed to use different names for memory locations. For now, let us start by analyzing the following code

// refs_01.cpp
// compile with
// c++ refs_01.cpp -o b_refs_01 
// execute with
// ./b_refs_01
#include <iostream>
int main(){
   int x;
   int& y=x;
   x=27;
   std::cout<< "x="<< x<< ". y="<< y<< ". &x=";
   std::cout<< (long int)(&x)<< ". &y=";
   std::cout<< (long int)(&y)<< "."<< std::endl;
   std::cout<< "Modification of y"<< std::endl;
   y=37;
   std::cout<< "x="<< x<< ". y="<< y<< ". &x=";
   std::cout<< (long int)(&x)<< ". &y=";
   std::cout<< (long int)(&y)<< "."<< std::endl;  
   return 0;
}

The output of the program indicates that \(x\) and \(y\) have the same value and that their addresses in the memory are the same. The command x=27 updates the memory location whose name is \(x\). The memory location with the name \(y\) is the very same as the one with the name \(x\). Thus when we print \(y\) we see the same number \(27\). Later on we change \(y=37\) and we observe that \(x\) is changed as well.

Let us point out that reference must be initialized upon declaration. The command int myNumber=37; can be replaced by two commands

int myNumber; 
myNumber=37;
However, the same cannot be done with int& y=x;. Also we cannot initialize the reference using numbers int& y=37. During the initialization we must provide a variable that corresponds to the physical location in the memory (also known as L-value because it can be on the left side of the assignment operator).

Problem 1. What happens when the following program is executed? Provide a rigorous justification for your answer.
// myCode.cpp
// compile with 
// c++ myCode.cpp -o myProgam -std=c++11
// execute with 
// ./myProgram
#include<iostream>
int main(){
      int x= 50;
      int& y=x;
      int* z=&y;
      y = x+y;
      *z = x+y;
      std::cout << x;
      std::cout << std::endl;
      return 0;
}

2. Arguments of functions

We have learned how to use pointers to create mutator functions. Those are the functions capable of changing their arguments. References also offer a way to accomplish this task.

Before we can do that, we need to fully clarify the mechanism of function calls.

Let us first identify the following four categories of functions:

  • Category 1: Functions with no arguments and no return value
  • Category 2: Functions with arguments but no return value
  • Category 3: Functions with no arguments but with return value
  • Category 4: Functions with arguments and with return value

In this document we will analyze in great details the functions in categories 1 and 2. We have used the functions in categories 3 and 4, but we will fully understand the mechanics of return statement once we learn about classes and move semantics.

2.1. Functions without arguments and without return values

We will analyze a code in which a fairly simple function is called from the main.

#include<iostream>
void simplestFunction(){
   std::cout<< "Executing easy function"<< std::endl;
}
int main(){
   std::cout<< "Printing before the function."<< std::endl;
   simplestFunction();
   std::cout<< "Printing after the function."<< std::endl;
   return 0;
}

The previous code is equivalent to the one in which the command simpleFunction(); is replaced by the block of code

{
   std::cout<< "Executing easy function"<< std::endl;
}

Any local variable defined inside the function would behave as a local variable defined inside a block of code surrounded by { and }: it would cease to exist after the block.

2.2. Function with argument(s) and no return value

Our next example is the function that has one argument \(x\) of type int.

#include<iostream>
void lessSimpleFunction(int x){
   std::cout<< "Executing function with x="<< x<< std::endl;
}
int main(){
   std::cout<< "Printing before the function."<< std::endl;
   lessSimpleFunction(7);
   std::cout<< "Printing after the function."<< std::endl;
   return 0;
}

The line lessSimpleFunction(7) is replaced by the code from the declaration of the function (i.e. the code between { and }). However, one line is added at the beginning of the code: int x=7;. The line is obtained by copying the statement between ( and ) of the declaration and adding =7 to it. Thus the above code is equivalent to

#include<iostream>
int main(){
   std::cout<< "Printing before the function."<< std::endl;
   {
      int x=7;
      std::cout<< "Executing function with x="<< x<< std::endl;
   }
   std::cout<< "Printing after the function."<< std::endl;
   return 0;
}

Therefore the line with arguments of functions is copied to the very beginning of the block of the code and the arguments are initialized with the values provided in the function call.

2.3. Function with references in argument line

We could have placed int& x in the argument line of the function. The same rule of replacement from previous section would apply in this situation.

#include<iostream>
void lessSimpleFunction(int& x){
   std::cout<< "Executing function with x="<< x<< std::endl;
   x=20;
   std::cout<< "I changed x to x="<< x<< std::endl;
}
int main(){
   int z=7;
   std::cout<< "Printing before the function."<< std::endl;
   lessSimpleFunction(z);
   std::cout<< "Printing after the function. ";
   std::cout<<"The variable z became z="<< z<< std::endl;
   return 0;
}

The previous code is equivalent to the following one:

#include<iostream>
int main(){
   int z=7;
   std::cout<< "Printing before the function."<< std::endl;
   {
      int& x=z; //This line is due to the argument line lessSimpleFunction(int& x)
      std::cout<< "Executing function with x="<< x<< std::endl;
      x=20;
      std::cout<< "I changed x to x="<< x<< std::endl;
   }
   std::cout<< "Printing after the function. ";
   std::cout<<"The variable z became z="<< z<< std::endl;
   return 0;
}

The function lessSimpleFunction is a mutator. It changes the argument x.

Problem 2. What happens when the following program is executed? Provide a rigorous justification for your answer.
// myCode.cpp
// compile with 
// c++ myCode.cpp -o myProgam -std=c++11
// execute with 
// ./myProgram
#include<iostream>
int main(){
   int orange = 7;
   int& apple = orange;
   int* strawberry = &orange;
   *strawberry = 9;
   strawberry = new int;
   *strawberry = 11;
   std::cout<< *strawberry + orange + apple << std::endl;
   delete strawberry;
   return 0;
}

Problem 3. What happens when the following program is executed? Provide a rigorous justification for your answer.
// myCode.cpp
// compile with 
// c++ myCode.cpp -o myProgam -std=c++11
// execute with 
// ./myProgram
#include<iostream>int simpleAdd(int inValue){
   inValue = inValue + 15;
   return inValue;
}

int pointerAdd(int * inValue){
   *inValue = *inValue + 15;
   return *inValue;
}

int referenceAdd(int & inValue){
   inValue = inValue + 15;
   return inValue;
}

int main(){
   int inValue, outValue;
   inValue= 20;
   outValue=simpleAdd(inValue);
   std::cout<< "inValue="<< inValue<<" and outValue="<< outValue<< std::endl;
   inValue=20;
   outValue=pointerAdd(&inValue);
   std::cout<< "inValue="<< inValue<<" and outValue="<< outValue<< std::endl;
   inValue=20;
   outValue=referenceAdd(inValue);
   std::cout<< "inValue="<< inValue<<" and outValue="<< outValue<< std::endl;
   return 0;
}