Reference vs Raw Pointers in C++

What is reference? 

A reference is an alias for an already existing variable. Once a reference is initialised to a variable, it cannot be changed to refer to another variable. Hence, a reference is similar to a const pointer.

Const pointers

We can also make a pointer itself constant. A const pointer is a pointer whose address can not be changed after initialisation. Read here about Const Pointer.

Reference to variable

Click here to debug. 

#include <iostream>
using namespace std;
int
main ()
{
  int x = 1;
  int &y = x;
  int *z = &x;
  cout << "Initially x " << x << " y " << y << " z " << z <<  " z val " << *z << endl;
  x++;
  cout << "After x++ x " << x << " y " << y << " z " << z <<  " z val " << *z << endl;
  y++;
  cout << "After y++ x " << x << " y " << y << " z " << z <<  " z val " << *z << endl;
  z++; 
  cout << "After z++ x " << x << " y " << y << " z " << z <<  " z val " << *z << endl;
}

Output:

Initially x 1 y 1 z 0x7ffc764b6284 z val 1
After x++ x 2 y 2 z 0x7ffc764b6284 z val 2
After y++ x 3 y 3 z 0x7ffc764b6284 z val 3
After z++ x 3 y 3 z 0x7ffc764b6288 z val 1984651908

The first line depicts that y is a constant pointer to int and the second depicts y is a reference to variable x.

Both int& y = x; or int  &y = x; are same, but let's stick with int& y = x;

Reference to pointer

So, it is certainly possible to have references to arrays, even though the syntax is awkward. It may be easier to begin with a reference to a pointer, which basically just follows the normal reference syntax:

int* p = new int[10];

int* &q = p; // q is a reference to p, an int*

p[9] = 999;

cout << q[9]; // 999

Reference to an array

And if you really want a reference to an array, it is like this:

int a[10];

int (&b) [10] = a; // b is a reference to a, an int[10]

a[8] = 888;

cout << b[8]; // 888

The type of a here is int[10] (not just int[]); in C++ the size of an array is part of the type, i.e. int[10] is not the same type as int[5]. If raw arrays decay into pointers, size should be passed explicitly along with them. But, here b is a reference to an actual array instead of decaying into pointers.

If you are tempted to write just "int &b[10]", it will mean one dimensional array b of references to type int[10]. However, int (&b)[10] means a reference to one dimensional array of type int[10].

The bracket around &b is needed. Due to how C++ puts the square brackets after the variable name, this syntax is a bit weird.

Click here to learn more about reference to an array. 

Reference to an array element

You can also have reference to individual array entries, i.e.

int a[10];

int& b = a[1]; // b is a reference to a[1]

But you cannot have an array of references, i.e. an array where each entry is a reference to something different. 


What is pointer ?

A pointer in C++ is a variable that holds the memory address of another variable.

Reference

Pointer

A reference must be initialized when it is

declared.


int a = 5;

int &ref = a;


A pointer can be initialized to any value

anytime after it is declared.


int a = 5;

// some code

int *p = &a;


References cannot be NULL.



A pointer can be assigned to point to a

NULL value.



References can be used ,simply, by name.



Pointers need to be dereferenced with a *.




Once a reference is initialized to a variable,

it cannot be changed to refer to a variable

object.


A pointer can be changed to point to any

variable of the same type.


Example:


int a = 5;

int *p;

p = &a;

int b = 6;

p = &b;


Other Differences 

  • Reference to array can't be null whereas raw pointer can be null. 
  • If it is a raw pointer, it can be incremented. References can't be incremented to point to the next address.  

The last difference is the crucial difference.

Pass-by-value

/* Pass-by-value into function (TestPassByValue.cpp) */
#include <iostream>
using namespace std;
 
int square(int);
 
int main() {
   int number = 8;
   cout <<  "In main(): " << &number << endl;  // 0x22ff1c
   cout << number << endl;         // 8
   cout << square(number) << endl; // 64
   cout << number << endl;         // 8 - no change
}
 
int square(int n) {  // non-const
   cout <<  "In square(): " << &n << endl;  // 0x22ff00
   n *= n;           // clone modified inside the function
   return n;
}

Pass-by-reference with pointer

/* Pass-by-reference using pointer (TestPassByPointer.cpp) */
#include <iostream>
using namespace std;
 
void square(int *);
 
int main() {
   int number = 8;
   cout <<  "In main(): " << &number << endl;  // 0x22ff1c
   cout << number << endl;   // 8
   square(&number);          // Explicit referencing to pass an address
   cout << number << endl;   // 64
}
 
void square(int * pNumber) {  // Function takes an int pointer (non-const)
   cout <<  "In square(): " << pNumber << endl;  // 0x22ff1c
   *pNumber *= *pNumber;      // Explicit de-referencing to get the value pointed-to
}

Pass-by-reference with reference

/* Pass-by-reference using reference (TestPassByReference.cpp) */
#include <iostream>
using namespace std;
 
void square(int &);
 
int main() {
   int number = 8;
   cout <<  "In main(): " << &number << endl;  // 0x22ff1c
   cout << number << endl;  // 8
   square(number);          // Implicit referencing (without '&')
   cout << number << endl;  // 64
}
 
void square(int & rNumber) {  // Function takes an int reference (non-const)
   cout <<  "In square(): " << &rNumber << endl;  // 0x22ff1c
   rNumber *= rNumber;        // Implicit de-referencing (without '*')
}

Recall that references are to be initialised during declaration. In the case of function formal parameter, the references are initialised when the function is invoked, to the caller's arguments.

References are primarily used in passing reference in/out of functions to allow the called function accesses variables in the caller directly.

Lvalue reference

Can an rvalue be converted to lvalue? Nope. It's not a technical limitation, though: it's the programming language that has been designed that way.

In C++, when you do stuff like

int y = 10;

int& yref = y;

yref++;        // y is now 11

you are declaring yref as of type int&: a reference to y. It's called an lvalue reference. Now you can happily change the value of y through its reference yref.

We know that a reference must point to an existing object in a specific memory location, i.e. an lvalue. Here y indeed exists, so the code runs flawlessly.

Now, what if I shortcut the whole thing and try to assign 10 directly to my reference, without the object that holds it?

int& yref = 10;  // will it work?

On the right side we have a temporary thing, an rvalue that needs to be stored somewhere in an lvalue.

On the left side we have the reference (an lvalue) that should point to an existing object. But being 10 a numeric constant, i.e. without a specific memory address, i.e. an rvalue, the expression clashes with the very spirit of the reference.

Note: use reference over pointer, use pointers for non-owning parameters that may be null. 

Reference

learncpp.com

SO1 Link

University of Leicester's Master's Lecture 

Comments