Topic : Poiners and Arrays in C++
Author : GHA
Page : 1 Next >>
Go to page :


Poiners and Arrays in C++

There is a close relationship between arrays nad pointers in C++. Suppose we have declared an array of 100 elements of the data type double:

   double a[100];
The elements of the array can be referred to in the program as a[0] . . . a[99]. When the program is compiled, the compiler does not save the addresses of all the elements, but only the address of the first element, a[0]. whgen the program needs to access any element, a[i], it calculates its address by adding i units to the address of a[0]. The number of bytes in each "unit" is, in our example, equale tothe sizeof(double) (e.g.: 8). In general, it is equal to the number of bytes required to store an element of the array.

The address of a[0] can be explicitly obtained using the & ("address of") operator:

   &a[0];
Since the data type of a[0] is double, the data type of &a[0] is, as usual, double* (pointer to double).

C++ allows us to use the name of the array a, without any subscript, as another name for &a[0].

The name can be used as an rvalue of the type double*. It cannot be used as an lvalue becasue it cannot be changed. We can assign this value to any double* variable. For example:

   double a[100];
   double *p;

   p = a;   // Same as p = &a[0];


As long as p points to the first element of the array, *p becomes an alies for a[0]. In general, we can assign p = &a[i]; Then *p becomes a temporary alias for a[i].
C++ supports arithmetic operations on pointers that mimic calculations of addresses of array elements. In this pointer arithmentic, we can add an integer to a pointer or subtract an integer from a pointer. for convenience, the integer operand signifies "units" corresponding to the pointer's data type, not bytes. For example, we have

   double a[100];
   double *p;

   p = a;   // Same as p = &a[0];


then p+1 points to a[1], p+2 points to a[2] and so on. The actual difference in bytes between p+1 and p is sizeof(double) (e.g.: 8).

If p is equla to a, then we can refer to a[1] as *(p+1) and, in general, to a[i] as *(p+i).

We can also increment and decrement a pointer using the ++ and -- operators.

In the expression *p++, the increment operator applies to the pointer, and not to the value to which it points. It meansL take the value of *p, then increment p, (not the dereferenced value *p).

The statment:

   x = *p++;
Is the same as:
   x = *p;
   p++;


The relational operators >, <, <=, and >= can be applied to pointers that point to elements of the same array. For example:

   ...
   char s[20], *p1, *p2;

   p1 = &s[0];
   p2 = &s[19];

   while (p1 < p2) {
     ...
     p1++;
   }


All of the above allows us to scan through an array using a pointer variable rather than subscripts. Instead of

   for (i=0; i<100; i++)
     cout << a[i] << ' ';


we can write:
   p = a;

   for (i=0; i<100; i++) {
     cout << *p << ' ';
     p++;
   }


Or, even more economically, utilizing all the shortcuts that C++ provides
   for (p=a, i=0; i<100; i++)
     cout << *p++ << ' ';


You may encounter this idiom in the code that deals with null-terminated strings:

   char str[60], *s = str;
   ...
   // Find the first '@':
   while (*s && *s != '@')
     s++;


This relationship between arrays and pointers is reciprocal. And pointer may be construed as point to the first element of some logical array, albeit undeclared. If p is a pointer, C++ allows us to write p[0] instead of *p, and in general, p[i] instead of *(p+i).

Consider the following example of a function that shifts the elements of an array to the left by 3 (starting at a[3]):

   void shiftLeftBy3(double a[], int size) {
     //Shifts elements:
     // a[0] = a[3];
     // a[1] = a[4];
     // ...
     // a[size-4] = a[size-1];

     double *p = a + 3;

     for (int i=0; i<size-3; i++)
       a[i] = p[i];
   }


In view of the reciprocity between pointers and arrays, we have to conclude that the most appropriate way of looking at the expression p[i] is to think of [] as the "subscript" operator: we are applying the operator [] to the operands p (of a particular pointer data type) and i (an integer).

We have to be a little careful, though. When we declare a pointer p, this by itself does not declare any array. Before we start using p[i], we have to make sure that p points to some element in an array declared elsewhere, and that p[i] is within range of that array.

In a nutshell, whether s is declared as an array or as a pointer, the following expressions are equivalent:

Expression      Equivalent
    s                   &s[0]
   s + i               &s[i]
    *s                  s[0]
  *(s+i)               s[i];

If a is declared as an array, enough memory is reserved to hold the specified number of elements, and a cannot be used as an lvalue (i.e.: you cannot set a equal to a new address). If p is declared as a pointer, the declaration by itself does not reserve any memory to which p points; p can be used as an lvalue, and, in fact, before p is used as a pointer, it must be set to some valid address (the address of some variable or constant, or an array element).

The difference between an array and a pointer disappears completely within a function to which an array is passed as an argument. An array argument, a, is passed to a fuction as a pointer equal to the address of the first element of the array. the function declarations

   ... myFunction(double a[], ...)
and
   ... myFunction(double *a, ...)
are identical. The former simply emphasizes the fact that a points to the whole array, not just one variable.

Within the function, this array (or pointer) argument is a copy of the pointer passed from the calling code. That is why this pointer can be used as both an lvalue and a rvalue. for example, the following function copies an array into another:

   void copy(double a[], double b[], int size) {
     // Copies a[0] into b[0], ..., a[size-1] into b[size-1]

     for (int i=0; i<size; i++)
       b[i] = a[i];
   }


The same function can be written with pointers:

   void copy(double a[], double b[], int size) {
     // More obscure implementation with pointers:

     while (size-- > 0)      // compare size with 0, then decrement
       *b++ = *a++;
   }


But there is no legitimate reason for using the later version, and it may even seem obscure to some programmers.

In another example, consider the function addElements(...), which returns the sum of an array's elements:

   double addElement(double a[], int size) {
     // Returns a[0] + ... + a[size-1]

     double sum = 0.;

     for (int i=0; i<size; i++)
       sum += a[i];

     return sum;
   }


Now, suppose we have an array of 100 elements:

   ...
   double a[100];
   ...

We can call addElements(...) to calculate, for instance, the sum of the first 80 and the sum of the last 20 elements of this array, as follows:

   ...
   double sum1, sum2;

Page : 1 Next >>