References and Pointers in C++
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
- Pass-by-reference with pointer arguments
- 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!
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:
- Code Academy
- Geeks for Geeks
You can support me in Patreon!