Arrays
An array is a group of elements of the same type sharing a common name. Array elements are written with the name of the array and an index.
To define an array, you have to specify, type of array, name, and size.
Syntax
data-type array-name[size];
e.g.
int arr[10];
arr[0]=19;
arr[1]=2;
float arr2[5];
In the first statement, we are defining an array called arr of 10 integers.
In the next lines, we are setting the 0th element of the array to 10 and in the next line, we are setting the first element to 2.
The array elements which are not assigned a value will have garbage, similar to other variables.
In the last line, we are defining an array arr2 of two floating-point numbers.
Array initialization
An array can be initialized during its definition by providing values in braces.
Remember that array initialization should be done along with the definition.
int arr[] = {10,22,3};//arr is initialized with 3 elements
char charr[4]={'a','2','<','['};
float flarr[10]={1.4};
Initialization assigns values provided to array elements.
In the first statement, arr[0] is 10, arr[1] is 22 and arr[2] is 3. Since the size of the array is not provided, it is taken as the total number of values provided which is 3.
In the second example charr[0] is character 'a', charr[1]='2' and so on.
In the third example, flarr has a size of 10, but only one value is provided, the rest of the values are initialized with 0.
A partially initialized array is filled with 0s for the remaining elements.
Let us look at some wrong initializers.
int arr[3]=[1,2,3];//have to use braces
float m[5];
m={1.1,2.2,3.3,4,5};//initialization should be with definition
The first example is wrong because we are using square brackets instead of braces.
The third line is wrong because we have already defined array m in the 2nd line and then trying to initialize in the 3rd line. The array can not be initialized after it has been defined.
Exercises
-
Write a program to read 10 numbers and find their sum using an array
-
Write a program to print the elements of an array in reverse order.
-
Write a program to find the largest element of an array.
-
Write a program to find the second largest element of an array.
Next, let us move on to pointers. If you are familiar with C, you have hated pointers. Unless you are a pro in C, of course.
In C++ we do have pointers. But we also have something better. Which is called a reference. Reference is a better pointer - that is it looks like an ordinary variable but behaves like a pointer. And you don't have to use &, * ->, etc. Great! Isn't it? We will learn about references a little bit later.
Pointers
A pointer is a variable that stores the address of another variable.
Pointers are useful for handling arrays, strings, etc. They also help in communicating with functions, data structures, and control tables.
If a is a variable and &a is the address of a.
If we store &a in a variable called ptr, ptr is a pointer to a.
A pointer must be defined like any other variable. But it is defined with a symbol * before its name.
The definition of a pointer uses its name, the data type of pointee, and the symbol *.
Syntax
data-type* variable;
e.g.
int *ptr;
float *ptr2;
The first-line defines a variable ptr, which is a pointer to an integer. The second line defines ptr2 which is a pointer to a float.
int * together is data type here which indicates ptr is integer pointer.
Pointers must be assigned to address of another variable using & (address of) operator.
Uninitialized pointers may crash the program.
Let us look at another example.
int *ptr;
int m=5;
ptr = &m;
cout<<*ptr;
Here we are defining ptr as integer pointer and assigning it to address of integer m. Then we are printing *ptr - value at address ptr. This will print variable m.
* used in lines 4 in the above example is not multiplication operator, but it is dereference or indirection operator. It says value at the address.
When we say cout<<*ptr , we say to display the value at the address stored in ptr.
Let us say that m is stored in address 8130, then ptr stores 8130.
Address Contents Variable
0x8130 0x00000005 m
0x8134 0x00008130 ptr
The content of ptr is the address of m.
Pointer Indirection (*)
* operator is called indirection operator or dereference operator. It gives us the value stored in the address given by the pointer.
float a = 1.7f;//if address of a is 1000
float *ptr = &a;//ptr will be 1000
cout<<*ptr;//print a value stored in address 1000
In the above example, cout prints *ptr. So instead of displaying the value of ptr, cout displays value at the address given by ptr - which is a.
If a is stored in location 1000, ptr = &a makes ptr to have the value of a's address which is 1000.
So *ptr will be a which is 1.7.
& (address of) and * (indirection) operators are complementary to each other.
Let us look at another example.
int a = 10;
int *ptr = &a;
*ptr = 33;/*now a is 33*/
Here we have changed the value of "a" indirectly using the pointer.
Note: If indirection (*) is used on an uninitialized pointer or a pointer with a NULL value, the program will crash.
Arrays and pointers
An array name can be treated as pointer to its first element. Because of this, array elements can be accessed using pointers easily.
int arr[3]={11,22,33};
*arr = 100;//arr[0] is set to 100
int m = *arr + 10;//m is 110
int *ptr = arr;//ptr is set to &arr[0]
In the above example, arr is addres of 0th element of array. So, when we set *arr to 100, we are setting arr[0] to 100.
The variable ptr points to 0th element of array arr. Now *ptr will be 0th element of array. *(ptr+1) will be first element of array and so on.
In fact, when an array is passed as a parameter to a function, the function treats this parameter as a pointer to 0th element of array.
Pointer arithmetic
Pointers are basically integers. Does that mean we can perform any arithmetic operations on pointers?
Add numbers to them, subtract, multiply? Yes and no.
The only operations permitted on pointers are:
-
Pointers can be incremented or decremented (++ and --).
-
Integers can be added to pointers and integers can be subtracted from pointers.
-
Two pointers can be subtracted to find the distance between them
No other operations are allowed on pointers.
int *ptr = &a;
ptr++;
In the above example, we are incrementing the pointer. If a is stored in location 1000 (in practice, the addresses are written in hex notation, and local variable addresses will be quite large numbers. But let us take1000 for the sake of simplicity) , ptr will have 1000 as its value. So after ptr++, ptr becomes 1001. Right?
Wrong.
Pointer arithmetic is always scaled up by the size of the pointee. If ptr points to integer and size of int is 8 then ptr++ will increment it by 8. So ptr will be 1008
The reason for this is, once a pointer is incremented it will have to point to the next variable of the same type - in this case, the next integer. As an integer needs 8 bytes, the next integer will be at 1008. So ptr++ makes ptr as 1008.
If a float pointer is incremented, it gets incremented by 4. If a character pointer is incremented, it gets added by 1 and so on.
The same thing happens if we use the decrement operator with pointers and . When we add an integer n to a pointer, the pointer gets added by n*size of pointee.
int ptr1 = &a;
ptr--;//ptr will be ptr-8
Subtracting two pointers
When we subtract two ponters, we get the distance between them.
int ptr1 = &a;
int ptr2 = &b;
cout<<ptr2-ptr1;
If a is stored at 1000 and b at 1500 , ptr1 is 1000, ptr2 is 1500. So ptr1-ptr2 will be 500. Correct?
Wrong.
It will be 500/sizeof(int)
References
As mentioned earlier, pointers can be dangerous. They are messy too. But they are quite useful in function calls, linked lists, etc.
In C++, the parameters to a function are sent using call by value mechanism. This means parameters are copies of actual arguments. If parameters are modified inside the function, the caller function will not see these modifications.
The only way of sending a value back from a function will be using a return statement. But a function can return only one value. What if we need to return two values or three?
If a function has to change more than one value, you will have to use pointer parameters. ( A typical example is a function to swap two variables. ) That is the reason many C functions make use of pointer parameters.
But C++ has something better. It has a reference type.
A reference variable is an alias for another variable.
A reference parameter is an alias for the actual argument. So if we modify a reference parameter to a function, its value is changed in the caller function. Just like a pointer.
Definition of a reference
A reference is defined with type, its name and & symbol. & indicates it is a reference.
int &m = b;
int & in the above example means a reference to an integer.
The second part of the statement initializes this reference variable with b. Now m is a reference to b or alias to b.
Any changes to m will change b and vice versa.
Remember that a reference should always be initialized with a variable it is referring to.
int &m = b;
cout<<m;
m=99;//both m and b are 99
Rules for a reference variable
-
A reference can not be pointed to another variable after initialization, unlike a pointer.
-
A reference variable should always be initialized.
-
Reference can not point to NULL, unlike pointer.
-
A function returning a reference can be assigned to a value.
Let us look at a complete program now.
Here, the variable b is a reference variable. b is a reference to a or it is an alias to a.
In the 3rd statement, cout will print 10 and 10 for b and a. a=300 will change both a and b to 300. Similarly, when we change b to 12, a also changes to 12.
References and functions
The discussion would be incomplete unless you see how are references useful as function parameters.
Let us first write an example with a function with 2 ordinary parameters, which tries to double both these parameters.
What will be the output of this program?
$./a.out
n1 in function20
n2 in function24
a in main10
b in main12
What went wrong? We expected a and b to be 20 and 24. That did not happen. Why?
Because C++ sends parameters to functions as copies. a and b are copied into n1 and n2. So any changes to n1 and n2 do not change the arguments.
But let us now use references as parameters for the same function.
What will be the output of the program after these changes?
$ ./a.out
n1 in function20
n2 in function24
a in main20
b in main24
Now both a and b are changed
So whenever we need a function to modify multiple values, we can use reference parameters.
There are also situations where we use reference parameters - but we don't want the function to modify the arguments. If we have functions that have large objects as parameters, we can make them constant reference parameters, to avoid the overhead of copying them, at the same time protecting them from accidental modification.
Function which returns reference
Even the return type of a function can be of reference type. In such a case, the return values are not copied.
When we have to use such a function
-
The return value must be an lvalue
-
Its will not go out of scope when the function returns.
Let us understand this with an example.
#include <iostream>
int& max(int& x, int& y)
{
return (x > y) ? x : y;
}
int main()
{
int a{ 5 };
int b{ 6 };
max(a, b) = 7; // sets the greater of a or b to 7
std::cout << a << b << '\n';
return 0;
}
In the example, the function max(int &x, int &y) takes two reference parameters and returns the reference to the larger of the two. And main() function assigns 7 to the return of the function. That is, the function returns reference to b in this case and b is assigned to 7.
You should be careful when using functions which return reference. This variable must exist when the function ends. Which means you can not return a non-static local variable.
Exercises:
-
Write a function to swap two variables using references.
-
Write a function to swap two variables using pointer parameters.
-
Write a function to find the largest of 3 numbers using reference parameters.