More Pointers - VI
The following topics are covered in this section:
Check out the following code:
int main( )
{
char *name = "Tintin";
cout<<name;
return 0;
}
The output will be Tintin.
char *name = "Tintin";
will create a null terminated string (i.e. an array of constant characters) and the address of the 1st character will be stored in the pointer.
cout<<name;
This will print all the characters starting from the address held by pointer ‘name’ till it encounters the null character. A pointer to a character is treated as a null terminated string. The following will also work:
char name[]="Tintin";
cout<<name;
In this case also ‘name’ will contain the address of the first character and when we display it, the entire string will be displayed.
Note: When you print a character array it will print till a null character is encountered.
The following will create problems:
char name[]={'T','i','n','t','i','n'};
cout<<name;
When individual characters are assigned to a character array, it is the responsibility of the programmer to include the null character. In the above example we haven’t assigned a null character and thus the resultant output will contain garbage values:
Tintin¦¦8_e
Strings are constants and you cannot modify them after initializing them. Thus the following code will produce an error:
char *name = "Tintin";
*name="h"; //COMPILER error
The reason is because the right hand side is a char[2] (the letter h and the null character). In other words you cannot assign a string to a character (*name can only hold a character since it is a pointer to a character data type).
So, we could try the following:
char *name = "Tintin";
*name=’h’; //Run Time error
Now the compiler is satisfied with what you’ve done. Why? Because ‘name’ points to a character and you’ve asked the compiler to store a character at that memory location. The two types match and the compiler gives the green flag.
But when you execute the program, it will crash because of a run-time error. Why? Because a string in memory is a constant and you are not supposed to change the value. To prevent such bugs from creeping into your code, it is a good idea to use the keyword const:
const char *name = "Tintin";
*name='x'; //Compiler error
Now you’ll get a compiler error because the compiler has been informed that ‘name’ is pointing to a constant and thus it shouldn’t be able to modify the data it holds.
Before getting into pointers lets refresh our memory on a few important concepts:
· Variables are actually named memory locations.
· When we ask for a variable value, the program has to access the memory address and retrieve the value stored there.
· The same concept applies to arrays as well.
· Arrays are stored contiguously (i.e. in consecutive memory locations).
Now, let’s go a bit deeper into pointers now that we are familiar with the basic concepts. We’ll again take up our discussion on pointers and arrays. Consider the following code snippet:
short int marks[5];
//some assignments
cout<<marks[2];
The computer always works in terms of memory addresses. So when we say marks[2], the program has to calculate the actual address of marks[2]. This can be done by using a simple formula. The program does the following:
Address of marks[2] = Base address of the array + offset
where offset will be the number of bytes to be added depending on the size of the data type and the element to be accessed. In a equation format it would be:
offset = 2*sizeof(short int)
Base address of the array = address of marks[0]
Instead of using
marks[2]
we can also use
*(marks + 2)
since marks actually contains the address of the first element and adding 2 will take us to marks[2] (pointer addition).
Let’s consider two cases.
Case I
Sometimes, when you are dealing with
arrays it might be a good idea to use the pointers to access elements rather
than using array indexing. The pointer method might improve the performance.
Let’s take a simple example:
int main(
)
{
short int marks[5]={80,70,60,75,90};
int i;
short int *ptr;
ptr=marks;
cout<<endl<<"Using indexing: ";
for (i=0;i<5;i++)
{
cout<<"\t"<<marks[i];
}
cout<<endl<<"Using pointers: ";
for (i=0;i<5;i++)
{
cout<<"\t"<<*(ptr++);
}
return 0;
}
The output will be:
Using indexing: 80 70 60 75 90
Using pointers: 80 70 60 75 90
Rather than the output we need to focus on the difference between the 2 methods used. In the first method we used array indexes to display the value of each element:
for (i=0;i<5;i++)
{
cout<<"\t"<<marks[i];
}
Our program, for each value of ‘i’, is forced to calculate the offset of the element from the base address. In other words, each time the program has to calculate:
&marks[0] + i*sizeof(short int)
In this way the program calculates the address and retrieves the value stored at that location. You might wonder what’s the problem in this? Let’s take a look at the second method, using pointers:
short int *ptr;
ptr=marks;
for (i=0;i<5;i++)
{
cout<<"\t"<<*(ptr++);
}
‘ptr’ is a pointer which initially holds the address of the first element of the array marks. The first time the program enters the for loop, it will display the value of the first element of the array. Then ‘ptr’ is incremented. Incrementing ‘ptr’ is equivalent to:
ptr = ptr + sizeof(short int)
Each time the program executes the loop it has to move the pointer to the next element using the above equation. The difference in array indexing and pointer referencing lies in the 2 equations. In the array indexing method the program has to perform a multiplication whereas in the case of pointers this is not required. By using the second method we can improve performance (speed of execution) because multiplication needn’t be performed (and multiplication is generally a time consuming operation for computers). In small programs you may not notice much of a difference but when you are dealing with larger data types this could cause a significant improvement.
Case II
But this doesn’t mean that using pointers instead of array indexing will always improve performance. It all depends on the situation (just try to think of the problem from the compiler’s point of view). For example, let’s say we have an array:
int salary[10];
int id;
Later in some part of the code we have the statement:
cout<<”Enter the employee ID:”;
cin>>id;
cout<<”Salary of that employee is:”<<salary[id];
In this case even if you try to use a pointer to access this element you won’t be able to prevent the compiler from multiplication. In the first case we were able to bypass the multiplication step because it was a loop and each time we didn’t need to perform multiplication. In the second case you wouldn’t be able to do so.
The following:
marks[2];
and
*(marks+2);
are actually the same. When we say marks[2] the compiler would internally convert it into *(marks + 2). The following code snippet should clarify your doubts:
int weights[4]={10,20,30,40};
int *ptr=&weights[0];
cout<<endl<<weights[2];
cout<<endl<<*(weights+2);
cout<<endl<<*(ptr+2);
cout<<endl<<ptr[2];
cout<<endl<<*(2+ptr);
cout<<endl<<2[ptr];
All the statements above will yield the same result: 30. The notation:
2[ptr]
might seem absurd but it proves the point that the compiler doesn’t differentiate between ptr[2] and *(ptr + 2) or *(2 + ptr).
Two-dimensional arrays and pointers:
Let us say that we’ve declared a 2-D array:
int marks[4][2];
Though we feel that this array is similar to a tabular structure, we don’t have tables in memory. Array elements are stored contiguously in memory (irrespective of whether it is a one dimension or multi-dimensional array). We refer to the 2 dimensions as rows and columns (the first square bracket denotes the row and the second denotes the column number) but as far as the computer is concerned, all the elements are just stored continuously in memory. So, how would the array marks[][] be stored in memory?
Thus, the first row elements are stored first, followed by the second row elements and so on. When we refer to an element as:
marks[3][1]
the program has to calculate the address of the element to retrieve the value.
The address of marks[3][1] = base address of the 2-D array + offset
This is similar to what we saw for 1-D arrays.
Base address of the 2-D array = address of marks[0][0]
and
offset = (number of columns * element’s row number) + element’s col. number
In our case:
offset (in terms of the number of elements) = (2 * 3) + 1
To obtain the offset in terms of bytes, just multiply the above value by sizeof(short int).
The concept might seem confusing at first but once you substitute some values you should be able to grasp the idea.
So, how do we refer to 2-D array elements using pointers?
A 2-D array is a pointer to an array of 1-D arrays. If we declared an array as:
marks [4][5]
then when we say mark[0] we are referring to the first row.
mark[1] will point to the second row and so on. Let’s take this one at a time. A 2-D array is a pointer to an array of 1-D arrays. In our example, each row of the array contains 5 elements. These 5 elements form the set of 1-D arrays. Thus each 1-D array will make up a row of our original array. If we use:
marks[0]
it is equivalent to
marks
and it contains a set of elements (the elements are the individual rows: marks[0], marks[1] etc.). marks[0] is an array with elements marks[0][0], marks[0][1], marks[0][2]…marks[0][4].
We finally arrive to the conclusion that a 2-D array is a pointer to an array of 1-D arrays.
For a normal 1-D array,
marks [2] = *(marks + 2)
To refer to an element in a 2-D array, say:
marks [2][3]
we can use the notation:
*(marks[2] + 3)
This tells the program to take the address of row2 and add 3 elements to it. But we’ve seen that marks[2] = *(marks +2). Thus:
marks[2][3] = *( *(marks + 2) + 3 )
The double asterisk confirms what we stated at the beginning. It denotes that our 2-D array is in fact a pointer to a pointer.
Try out the following code snippet:
int marks[4][2]={60,75,
80,65,
90,95,
87,76};
cout<<endl<<marks[3][1];
cout<<endl<<*(marks[3] + 1);
cout<<endl<<*(*(marks + 3) + 1);
cout<<endl<<*(*(marks) + (2*3) + 1);
All statements will yield the same result: 76.
Copyright © 2004 Sethu Subramanian All rights reserved.