I l@ve RuBoard Previous Section Next Section

15.3 Pointers and Arrays

C++ allows pointer arithmetic. Addition and subtraction are allowed with pointers. Suppose you have the following:

char array[10]; 
char *array_ptr = &array[0]; 

This is represented graphically in Figure 15-5.

Figure 15-5. Pointers and an array
figs/C++2_1505.gif

In this example, *array_ptr is the same as array[0], *(array_ptr+1) is the same as array[1], and so on. Note the use of parentheses. (*array_ptr)+1 is not the same as array[1]. The +1 is outside the parentheses, so it is added after the dereference. Thus (*array_ptr)+1 is the same as array[0]+1.

At first glance this may seem like a complex way of representing simple array indices. You are starting with simple pointer arithmetic. In later chapters you will use more complex pointers to handle more difficult functions efficiently.

Pointers are merely variables that contain memory addresses. In an array each element is assigned to consecutive addresses. For example, array[0] may be placed at address 0xff000024. Then array[1] would be placed at address 0xff000025 and so on. Example 15-3 prints out the elements and addresses of a simple character array. (Note: The I/O manipulators hex and dec are described in Chapter 16. The reinterpret_cast is discussed later in this chapter.)

Example 15-2. array-p/array-p.cpp
#include <assert.h>
#include <iostream>
#include <iomanip>      

const int ARRAY_SIZE  = 10; // Number of characters in array 
// Array to print
char array[ARRAY_SIZE] = "012345678";   

int main(  )
{
    int index;  /* Index into the array */

    for (index = 0; index < ARRAY_SIZE; ++index) {
        std::cout << std::hex;  // Trick to print hex numbers
        assert(index >= 0);
        assert(index < sizeof(array)/sizeof(array[0]));
        std::cout << 
            "&array[index]=0x" <<  
                reinterpret_cast<int>(&array[index]) << 

            " (array+index)=0x" << 
                reinterpret_cast<int>(array+index) << 

            " array[index]=0x" <<  
                static_cast<int>(array[index]) << '\n',
        std::cout << std::dec;  // Another trick to go back to decimal
    }
    return (0);
}

When run, this program prints:

&array[index]=0x20090 (array+index)=0x20090 array[index]=0x30 
&array[index]=0x20091 (array+index)=0x20091 array[index]=0x31 
&array[index]=0x20092 (array+index)=0x20092 array[index]=0x32 
&array[index]=0x20093 (array+index)=0x20093 array[index]=0x33 
&array[index]=0x20094 (array+index)=0x20094 array[index]=0x34 
&array[index]=0x20095 (array+index)=0x20095 array[index]=0x35 
&array[index]=0x20096 (array+index)=0x20096 array[index]=0x36 
&array[index]=0x20097 (array+index)=0x20097 array[index]=0x37 
&array[index]=0x20098 (array+index)=0x20098 array[index]=0x38 
&array[index]=0x20099 (array+index)=0x20099 array[index]=0x0 

Characters usually take up one byte, so the elements in a character array will be assigned consecutive addresses. A short int takes up two bytes, so in an array of short ints, the addresses increase by two. Does this mean short_array+1 will not work for anything other than characters? No. C++ automatically scales pointer arithmetic so it works correctly. In this case short_array+1 will point to element number 1.

C++ provides a shorthand for dealing with arrays. Rather than write:

array_ptr = &array[0]; 

you can write:

array_ptr = array; 

C++ blurs the distinction between pointers and arrays by treating them the same in many cases. Here you used the variable array as a pointer, and C++ automatically did the necessary conversion.

Example 15-3 counts the number of elements that are nonzero and stops when a zero is found. No limit check is provided, so there must be at least one zero in the array.

Example 15-3. ptr2/ptr2a.cpp
#include <assert.h>
#include <iostream>

int array[10] = {4, 5, 8, 9, 8, 1, 0, 1, 9, 3};
int the_index;

int main(  )
{
    the_index = 0;
    while (true) {
        assert(the_index >= 0);
        assert(the_index < sizeof(array)/sizeof(array[0]));

        if (array[the_index] == 0)
            break;

        ++the_index;
    }

    std::cout << "Number of elements before zero " << the_index << '\n';
    return (0);
}

Rewriting this program to use pointers gives us Example 15-4.

Example 15-4. ptr2/ptr2.cpp
#include <iostream>

int array[10] = {4, 5, 8, 9, 8, 1, 0, 1, 9, 3};
int *array_ptr;

int main(  )
{
    array_ptr = array;

    while ((*array_ptr) != 0)
        ++array_ptr;

    std::cout << "Number of elements before zero " << 
        (array_ptr - array) << '\n';
    return (0);
}

In the second example, we lost the safety provided by the assert statement.

The first program uses the expression (array[index] != 0). This requires the compiler to generate an index operation, which takes longer than a simple pointer dereference: ((*array_ptr) != 0). The expression at the end of this program, array_ptr -array, computes how far array_ptr is into the array.

When passing an array to a procedure, C++ will automatically change the array into a pointer. In fact, if you put an & before the array, C++ will issue a warning. Example 15-5 illustrates array passing.

Example 15-5. init-a/init-a.cpp
#include <assert.h>

const int MAX = 10;

/********************************************************
 * init_array_1 -- Zero out an array                    *
 *                                                      *
 * Parameters                                           *
 *      data -- the array to zero                       *
 ********************************************************/
void init_array_1(int data[])
{
    int  index;

    for (index = 0; index < MAX; ++index) {
        assert(index >= 0);
        assert(index < MAX);
        data[index] = 0;
}

/********************************************************
 * init_array_2 -- Zero out an array                    *
 *                                                      *
 * Parameters                                           *
 *      data_ptr -- pointer to array to zero            *
 ********************************************************/
void init_array_2(int *data_ptr)
{
    int index;

    for (index = 0; index < MAX; ++index)
        *(data_ptr + index) = 0;
}
int main(  )
{
    int  array[MAX];

    // one way of initializing the array
    init_array_1(array);

    // another way of initializing the array
    init_array_1(&array[0]);

    // Similar to the first method but
    //    function is different
    init_array_2(array);

    return (0);
}

15.3.1 Splitting a C-Style String

Suppose you are given a C-style string (character array) of the form "Last/First". You want to split this into two strings, one containing the first name and one containing the last name.

Example 15-6 reads in a single line, stripping the newline character from it. The function strchr is called to find the location of the slash (/). (The function strchr is actually a standard function. I have duplicated it for this example so you can see how it works.)

At this point last_ptr points to the beginning character of the last name (with the first tacked on) and first_ptr points to a slash. You then split the string by replacing the slash (/) with an end-of-string (NUL or "\0"). Now last_ptr points to just the last name and first_ptr points to a null string. Moving first_ptr to the next character makes first_ptr point to the beginning of the first name.

Graphically what you are doing is illustrated in Figure 15-6. Example 15-6 contains the full program.

Figure 15-6. Splitting a string
figs/C++2_1506.gif
Example 15-6. split/split.cpp
/********************************************************
 * split -- split a entry of the form Last/First        *
 *      into two parts.                                 *
 ********************************************************/
#include <iostream>
#include <cstring>
#include <cstdlib>

int main(  )
{
    char line[80];      // The input line
    char *first_ptr;    // pointer we set to point to the first name
    char *last_ptr;     // pointer we set to point to the last name 

    std::cin.getline(line, sizeof(line));

    last_ptr = line;    // last name is at beginning of line 

    first_ptr = strchr(line, '/');      // Find slash 

    // Check for an error 
    if (first_ptr == NULL) {
        std::cerr << "Error: Unable to find slash in " << line << '\n';
        exit (8);
    }

    *first_ptr = '\0';  // Zero out the slash 

    ++first_ptr;        // Move to first character of name 

    std::cout << "First:" << first_ptr << " Last:" << last_ptr << '\n';
    return (0);
}
/********************************************************
 * strchr -- find a character in a string               *
 *      Duplicate of a standard library function,       *
 *      put here for illustrative purposes.             *
 *                                                      *
 * Parameters                                           *
 *      string_ptr -- string to look through            *
 *      find -- character to find                       *
 *                                                      *
 * Returns                                              *
 *      pointer to 1st occurrence of character in string*
 *      or NULL for error                               *
 ********************************************************/
char *strchr(char * string_ptr, char find)
{
    while (*string_ptr != find) {

       // Check for end 

       if (*string_ptr == '\0')
           return (NULL);       // not found 

        ++string_ptr;
    }
    return (string_ptr);        // Found 
}

This program illustrates how pointers and character arrays may be used for simple string processing.

Question 15-1: Example 15-7 is supposed to print out:

Name: tmp1 

but instead you get:

Name: !_@$#ds80 

(Your results may vary.) Why does this happen? Would this happen if we used the C++ string class instead of the old C-style strings?

Example 15-7. tmp-name/tmp-name.cpp
#include <iostream>
#include <cstring>

/********************************************************
 * tmp_name -- return a temporary file name             *
 *                                                      *
 * Each time this function is called, a new name will   *
 * be returned.                                         *
 *                                                      *
 * Returns                                              *
 *      Pointer to the new file name.                   *
 ********************************************************/
char *tmp_name(  )
{
    char name[30];              // The name we are generating 
    static int sequence = 0;    // Sequence number for last digit 

    ++sequence; // Move to the next file name 

    strcpy(name, "tmp");

    // Put in the sequence digit 
    name[3] = static_cast<char>(sequence + '0');

    // End the string 
    name[4] = '\0';

    return(name);
}

int main(  )
{
    std::cout << "Name: " << tmp_name(  ) << '\n';
    return(0);
}
    I l@ve RuBoard Previous Section Next Section