References and Pointers in C++

Diane Khambu
6 min readSep 24, 2023
Life cycle pointers © Diane Khambu

Few years ago, in some blog post I had read how to become a proficient programmer. Some of the advice were to learn different types of languages and to read at least 1 technical book/year. I still have to experiment the latter part; hope learning new frameworks make up for that. Certainly, want to dive back into technical books.

Following the former advice, I would like to revisit C++ . This blog post will go over references and pointers in C++!

References

It is marked by ampersand, & , sign. When it is used in a function declaration, it is a reference operator (alias). When it is not used in a function declaration, it is an address operator.

void swap(int &a, int &b); Here & is used as a reference operator.

int* ptr = &a; Here & is used as an address operator.

Let’s see examples:

#include <iostream>

void swap_num_t0(int a, int b){
int temp = a;
a = b;
b = temp;
}

void swap_num_t1(int &a, int &b){
int temp = a;
a = b;
b = temp;
}

double area_of_a_circle(double const &pi, double &r){
return pi*r*r;
}


int main(){
int a = 100;
int b = 200;

swap_num_t0(a, b);

// no change of values of `a` and `b` variables
std::cout << "a= " << a << ", b=" << b << "\n";

swap_num_t1(a, b);

// swap of values of `a` and `b` variables
std::cout << "a= " << a << ", b=" << b << "\n";

double const pi = 3.1415;
double r = 6.2830;

std::cout << "Area of a circle: " << area_of_a_circle(pi, r) << "\n";

std::cout << "r's address: " << &r << "\n";
}

On running the file using command g++ <filename>.cpp -o <filename>.o, ./<filename>.o , we get:

a=100, b=200
a=200, b=100
Area of a circle: 124.014
r's address: 0x16db1b238

When & is used in function declaration, it is done to save memory space by avoiding creating another variable.

We can also use & to represent a variable that is constant, as seen in area_of_a_circle function.

Pointers

Pointers stores variable address.

It is marked by <data_type>* <variable_name> = &<variable_name> . For example:

int year = 1999;
int* ptr = &year;

std::cout << *ptr << "\n"; // outputs 1999

Here ptr holds address of the variable year. To get the value from the pointer, we use * . This is also know as dereferencing.

#include <iostream>

int main(){
std::string fruit = "apple";

std::string* ptr1 = &fruit;
std::cout << fruit << "'s memory address is " << ptr1 << "\n";
std::cout << ptr1 << "'s value is " << *ptr1 << "\n"; // dereferencing

*ptr1 = "banana";
std::cout << "fruit's value is " << *ptr1 << " and memory address is ";
std::cout << ptr1 << "\n";

}

On running the file, the output is:

apple's memory address is 0x16ae3f228
0x16ae3f228's value is apple
fruit's value is banana and memory address is 0x16ae3f228

We can change the value the pointer holds using *ptr1=<new_value> . The address still remains the same.

Now let’s look into ways arguments can be passed to C++'s functions:

0. Pass-by-value

  1. Pass-by-reference with pointer arguments
  2. Pass-by-reference with reference arguments

Let’s illustrate them with an example:

#include <iostream>

// pass-by-value
int square1(int n1){
n1 *= n1;
std::cout << "address of n1 in square1(): " << &n1 << "\n";
return n1;
}

// pass-by-reference with pointer argument
int square2(int* n2){
std::cout << "address of n2 in square2() is: " << n2 << "\n";
return *n2 *= *n2;

}

// pass-by-reference with reference argument
int square3(int &n3){
std::cout << "address of n3 in square3() is: " << &n3 << "\n";
return n3 *= n3;
}

int main(){
int n1 = 7;
std::cout << "address of n1 in main(): " << &n1 << "\n";
square1(n1); // pass-by-value
std::cout << "called square1(). value is: " << n1 << "\n";
std::cout << "no change in n1 value: "<< n1 << "\n\n";

int n2 = 7;
std::cout << "address of n2 in main: " << &n2 << "\n";
square2(&n2); // pass-by-reference with pointer argument
std::cout << "called square2(). value is: " << n2 << "\n";
std::cout << "change is reflected: " << n2 << "\n\n";

int n3 = 7;
std::cout << "address of n3 in main: " << &n3 << "\n";
square3(n3); // pass-by-reference with reference argument
std::cout << "called square3(). value is: " << n3 << "\n";
std::cout << "change is reflected: " << n3 << "\n\n";

return 0;
}

On running the file, we get:

address of n1 in main(): 0x16f257248
address of n1 in square1(): 0x16f2571ec
called square1(). value is: 7
no change in n1 value: 7

address of n2 in main: 0x16f257244
address of n2 in square2() is: 0x16f257244
called square2(). value is: 49
change is reflected: 49

address of n3 in main: 0x16f257240
address of n3 in square3() is: 0x16f257240
called square3(). value is: 49
change is reflected: 49

Look how in pass-by-value, new variable is created in memory. Hence we have different memory address of the argument passed to the function square1() . This is the reason squaring the argument passed does not get reflected in main() .

For pass-by-reference with pointer argument and pass-by-reference with reference argument, the address of the passed argument is same as the one in main() . Hence, squaring of the argument is reflected in main() .

Arrays

Now let’s look at accessing arrays using pointers.

An array name contains the address of the first element of the array. This address is constant. So arr == &arr[0] . We can use this pointer to access successive values in the array. The next value is +1 address ahead then the current one. So that’s the reason we have indexes starting from 0!

Let’s see an example:

#include <iostream>

int main(){
int odds[5] = {1, 3, 5, 7, 9};
std::cout << "odds's starting memory address: " << odds << "\n";
std::cout << "odds's first element memory address: " << &odds[0] << "\n";
int* ptr = &odds[0];
std::cout << "odd's last element value: " << *(ptr+4);
std::cout << " and address: " << (ptr+4) << "\n\n";

int* ptr2 = odds;
for (int i=0; i <sizeof(odds)/sizeof(int); i++){
std::cout << "value from array index " << i << " " << odds[i] << "\n";
std::cout << "value from pointer " << i << " " << ptr2[i] << "\n";

}
return 0;
}

On running the file, we get:

odds's starting memory address: 0x16cedf230
odds's first element memory address: 0x16cedf230
odd's last element value: 9 and address: 0x16cedf240

value from array index 0 1
value from pointer index 0 1
value from array index 1 3
value from pointer index 1 3
value from array index 2 5
value from pointer index 2 5
value from array index 3 7
value from pointer index 3 7
value from array index 4 9
value from pointer index 4 9

Looks so efficient where we can directly access memory address and do what we wish to do!

source: xkcd

Hope you were able to learn something new!

Programming is so much easier, fun and boundless. A lot of respect to scientists in all fields.

See you in my next post! Until then ✨.

References:

You can support me in Patreon!

--

--