Chapter 4 Arrays, Strings, and Pointers, Section 1

Applies to: Visual Studio 2010

Published: April, 2010

Provided by: Ivor Horton

Book Cover

Buy This Book from Publisher

This topic contains the following sections.

  • How to use arrays

  • How to declare and initialize arrays of different types

  • How to declare and use multidimensional arrays

  • How to use pointers

  • How to declare and initialize pointers of different types

  • The relationship between arrays and pointers

  • How to declare references and some initial ideas on their uses

  • How to allocate memory for variables dynamically in a native C++ program

  • How dynamic memory allocation works in a Common Language Runtime (CLR) program

  • Tracking handles and tracking references and why you need them in a CLR program

  • How to work with strings and arrays in C++/CLI programs

  • How to create and use interior pointers

So far, we have covered all the fundamental data types of consequence, and you have a basic knowledge of how to perform calculations and make decisions in a program. This chapter is about broadening the application of the basic programming techniques that you have learned so far, from using single items of data to working with whole collections of data items.

In this chapter, you’ll be using objects more extensively. Although you have not yet explored the details of how they are created, don’t worry if everything is not completely clear. You’ll learn about classes and objects in detail starting in Chapter 7.

You already know how to declare and initialize variables of various types that each holds a single item of information; I’ll refer to single items of data as data elements. The most obvious extension to the idea of a variable is to be able to reference several data elements of a particular type with a single variable name. This would enable you to handle applications of a much broader scope.

Let’s consider an example of where you might need this. Suppose that you needed to write a payroll program. Using a separately named variable for each individual’s pay, their tax liability, and so on, would be an uphill task to say the least. A much more convenient way to handle such a problem would be to reference an employee by some kind of generic name — employeeName to take an imaginative example — and to have other generic names for the kinds of data related to each employee, such as pay, tax, and so on. Of course, you would also need some means of picking out a particular employee from the whole bunch, together with the data from the generic variables associated with them. This kind of requirement arises with any collection of like entities that you want to handle in your program, whether they’re baseball players or battleships. Naturally, C++ provides you with a way to deal with this.

Arrays

The basis for the solution to all of these problems is provided by the array in ISO/IEC C++. An array is simply a number of memory locations called array elements or simply elements, each of which can store an item of data of the same given data type, and which are all referenced through the same variable name. The employee names in a payroll program could be stored in one array, the pay for each employee in another, and the tax due for each employee could be stored in a third array.

Individual items in an array are specified by an index value which is simply an integer representing the sequence number of the elements in the array, the first having the sequence number 0, the second 1, and so on. You can also envisage the index value of an array element as being an offset from the first element in an array. The first element has an offset of 0 and therefore an index of 0, and an index value of 3 will refer to the fourth element of an array. For the payroll, you could arrange the arrays so that if an employee’s name was stored in the employeeName array at a given index value, then the arrays pay and tax would store the associated data on pay and tax for the same employee in the array positions referenced by the same index value.

The basic structure of an array is illustrated in Figure 4-1.

Figure 4-1

Referenced Screen

Figure 4-1 shows an array with the name height that has six elements, each storing a different value. These might be the heights of the members of a family, for instance, recorded to the nearest inch. Because there are six elements, the index values run from 0 through 5. To refer to a particular element, you write the array name, followed by the index value of the particular element between square brackets. The third element is referred to as height[2], for example. If you think of the index as being the offset from the first element, it’s easy to see that the index value for the fourth element will be 3.

The amount of memory required to store each element is determined by its type, and all the elements of an array are stored in a contiguous block of memory.

Declaring Arrays

You declare an array in essentially the same way as you declared the variables that you have seen up to now, the only difference being that the number of elements in the array is specified between square brackets immediately following the array name. For example, you could declare the integer array height, shown in the previous figure, with the following declaration statement:

long height[6];

Because each long value occupies 4 bytes in memory, the whole array requires 24 bytes. Arrays can be of any size, subject to the constraints imposed by the amount of memory in the computer on which your program is running.

You can declare arrays to be of any type. For example, to declare arrays intended to store the capacity and power output of a series of engines, you could write the following:

double cubic_inches[10];     // Engine size
double horsepower[10];       // Engine power output

If auto mechanics is your thing, this would enable you to store the cubic capacity and power output of up to 10 engines, referenced by index values from 0 to 9. As you have seen before with other variables, you can declare multiple arrays of a given type in a single statement, but in practice it is almost always better to declare variables in separate statements.

Try it Out: Using Arrays

As a basis for an exercise in using arrays, imagine that you have kept a record of both the amount of gasoline you have bought for the car and the odometer reading on each occasion. You can write a program to analyze this data to see how the gas consumption looks on each occasion that you bought gas:

// Ex4_01.cpp
// Calculating gas mileage
#include <iostream>
#include <iomanip>
        
using std::cin;
using std::cout;
using std::endl;

using std::setw;
        
int main()
{
   const int MAX(20);                      // Maximum number of values
   double gas[ MAX ];                      // Gas quantity in gallons
   long miles[ MAX ];                      // Odometer readings
   int count(0);                           // Loop counter
   char indicator('y');                    // Input indicator
        
   while( ('y' == indicator || 'Y' == indicator) && count < MAX )
   {
      cout << endl << "Enter gas quantity: ";
      cin >> gas[count];                   // Read gas quantity
      cout << "Enter odometer reading: ";
      cin >> miles[count];                 // Read odometer value
        
      ++count;
      cout << "Do you want to enter another(y or n)? ";
      cin >> indicator;
   }
        
   if(count <= 1)                     // count = 1 after 1 entry completed
   {                                  // ... we need at least 2
      cout << endl << "Sorry - at least two readings are necessary.";
      return 0;
   }
        
   // Output results from 2nd entry to last entry
   for(int i = 1; i < count; i++)
  {
     cout << endl
          << setw(2) << i << "."             // Output sequence number
          << "Gas purchased = " << gas[i] << " gallons" // Output gas
          << " resulted in "                 // Output miles per gallon
          << (miles[i] - miles[i - 1])/gas[i] << " miles per gallon.";
  }
   cout << endl;
   return 0;
}

The program assumes that you fill the tank each time so the gas bought was the amount consumed by driving the distance recorded. Here’s an example of the output produced by this example:

Enter gas quantity: 12.8
Enter odometer reading: 25832
Do you want to enter another(y or n)? y
        
Enter gas quantity: 14.9
Enter odometer reading: 26337
Do you want to enter another(y or n)? y
        
Enter gas quantity: 11.8

Enter odometer reading: 26598
Do you want to enter another(y or n)? n
        
 1.Gas purchased = 14.9 gallons resulted in 33.8926 miles per gallon.
 2.Gas purchased = 11.8 gallons resulted in 22.1186 miles per gallon.
TipTip

Because you need to take the difference between two odometer readings to calculate the miles covered for the gas used, you use only the odometer reading from the first pair of input values — you ignore the gas bought in the first instance as that would have been consumed during miles driven earlier.

During the second period shown in the output, the traffic must have been really bad — or maybe the parking brake was left on.

The dimensions of the two arrays gas and miles used to store the input data are determined by the value of the constant with the name MAX. By changing the value of MAX, you can change the program to accommodate a different maximum number of input values. This technique is commonly used to make a program flexible in the amount of information that it can handle. Of course, all the program code must be written to take account of the array dimensions, or of any other parameters being specified by const variables. This presents little difficulty in practice, however, so there’s no reason why you should not adopt this approach. You’ll also see later how to allocate memory for storing data as the program executes, so that you don’t need to fix the amount of memory allocated for data storage in advance.

Entering the Data

The data values are read in the while loop. Because the loop variable count can run from 0 to MAX - 1, we haven’t allowed the user of our program to enter more values than the array can handle. You initialize the variables count and indicator to 0 and 'y' respectively, so that the while loop is entered at least once. There’s a prompt for each input value required and the value is read into the appropriate array element. The element used to store a particular value is determined by the variable count, which is 0 for the first input. The array element is specified in the cin statement by using count as an index, and count is then incremented, ready for the next value.

After you enter each value, the program prompts for confirmation that another value is to be entered. The character entered is read into the variable indicator and then tested in the loop condition. The loop will terminate unless 'y' or 'Y' is entered and the variable count is less than the specified maximum value, MAX.

After the input loop ends (by whatever means), the value of count contains one more than the index value of the last element entered in each array. (Remember, you increment it after you enter each new element). This is checked in order to verify that at least two pairs of values were entered. If this wasn’t the case, the program ends with a suitable message because two odometer values are necessary to calculate a mileage value.

Producing the Results

The output is generated in the for loop. The control variable i runs from 1 to count-1, allowing mileage to be calculated as the difference between the current element, miles[i] and the previous element, miles[i - 1]. Note that an index value can be any expression evaluating to an integer that represents a legal index for the array in question, which is an index value from 0 to one less than the number of elements in the array.

If the value of an index expression lies outside of the range corresponding to legitimate array elements, you will reference a spurious data location that may contain other data, garbage, or even program code. If the reference to such an element appears in an expression, you will use some arbitrary data value in the calculation, which certainly produces a result that you did not intend. If you are storing a result in an array element using an illegal index value, you will overwrite whatever happens to be in that location. When this is part of your program code, the results are catastrophic. If you use illegal index values, there are no warnings produced either by the compiler or at runtime. The only way to guard against this is to code your program to prevent it happening.

The output is generated by a single cout statement for all values entered, except for the first. A line number is also generated for each line of output using the loop control variable i. Miles per gallon is calculated directly in the output statement. You can use array elements in exactly the same way as any other variables in an expression.

Initializing Arrays

To initialize an array in its declaration, you put the initializing values, separated by commas, between braces, and you place the set of initial values following an equals sign after the array name. Here’s an example of how you can declare and initialize an array:

int cubic_inches[5] = { 200, 250, 300, 350, 400 };

The array has the name cubic_inches and has five elements that each store a value of type int. The values in the initializing list between the braces correspond to successive index values of the array, so in this case cubic_inches[0] has the value 200, cubic_inches[1] the value 250, cubic_inches[2] the value 300, and so on.

You must not specify more initializing values than there are elements in the array, but you can include fewer. If there are fewer, the values are assigned to successive elements, starting with the first element — which is the one corresponding to the index value 0. The array elements for which you didn’t provide an initial value are initialized with zero. This isn’t the same as supplying no initializing list. Without an initializing list, the array elements contain junk values. Also, if you include an initializing list, there must be at least one initializing value in it; otherwise the compiler generates an error message. I can illustrate this with the following rather limited example.

Try it Out: Initializing an Array

// Ex4_02.cpp
// Demonstrating array initialization
#include <iostream>
#include <iomanip>
        
using std::cout;
using std::endl;

using std::setw;
        
int main()
{
   int value[5] = { 1, 2, 3 };
   int junk [5];
        
   cout << endl;
   for(int i = 0; i < 5; i++)
      cout << setw(12) << value[i];
        
   cout << endl;
   for(int i = 0; i < 5; i++)
      cout << setw(12) << junk[i];
        
   cout << endl;
   return 0;
}

In this example, you declare two arrays, the first of which, value, you initialize in part, and the second, junk, you don’t initialize at all. The program generates two lines of output, which on my computer look like this:

           1           2           3           0         0
  -858993460  -858993460  -858993460  -858993460  -858993460

The second line (corresponding to values of junk[0] to junk[4]) may well be different on your computer.

TipTip

The first three values of the array value are the initializing values, and the last two have the default value of 0. In the case of junk, all the values are spurious because you didn’t provide any initial values at all. The array elements contain whatever values were left there by the program that last used these memory locations.

A convenient way to initialize a whole array to zero is simply to specify a single initializing value as 0. For example:

long data[100] = {0}; // Initialize all elements to zero

This statement declares the array data, with all one hundred elements initialized with 0. The first element is initialized by the value you have between the braces, and the remaining elements are initialized to zero because you omitted values for these.

You can also omit the dimension of an array of numeric type, provided you supply initializing values. The number of elements in the array is determined by the number of initializing values you specify. For example, the array declaration

int value[] = { 2, 3, 4 };

defines an array with three elements that have the initial values 2, 3, and 4.

Character Arrays and String Handling

An array of type char is called a character array and is generally used to store a character string. A character string is a sequence of characters with a special character appended to indicate the end of the string. The string-terminating character indicates the end of the string; this character is defined by the escape sequence ‘\0’, and is sometimes referred to as a null character, since it’s a byte with all bits as zero. A string of this form is often referred to as a C-style string because defining a string in this way was introduced in the C language from which C++ was developed by Bjarne Stroustrup (you can find his home page at http://www.research.att.com/~bs/).

This is not the only representation of a string that you can use — you’ll meet others later in the book. In particular, C++/CLI programs use a different representation of a string, and the MFC defines a CString class to represent strings.

The representation of a C-style string in memory is shown in Figure 4-2.

Figure 4-2

Referenced Screen

Figure 4-2 illustrates how a string looks in memory and shows a form of declaration for a string that I’ll get to in a moment.

NoteNote

Each character in the string occupies one byte, so together with the terminating null character, a string requires a number of bytes that is one greater than the number of characters contained in the string.

You can declare a character array and initialize it with a string literal. For example:

char movie_star[15] = "Marilyn Monroe";

Note that the terminating '\0' is supplied automatically by the compiler. If you include one explicitly in the string literal, you end up with two of them. You must, however, allow for the terminating null in the number of elements that you allot to the array.

You can let the compiler work out the length of an initialized array for you, as you saw in Figure 4-1. Here’s another example:

char president[] = "Ulysses Grant";

Because the dimension is unspecified, the compiler allocates space for enough elements to hold the initializing string, plus the terminating null character. In this case it allocates 14 elements for the array president. Of course, if you want to use this array later for storing a different string, its length (including the terminating null character) must not exceed 14 bytes. In general, it is your responsibility to ensure that the array is large enough for any string you might subsequently want to store.

You can also create strings that comprise Unicode characters, the characters in the string being of type wchar_t. Here’s a statement that creates a Unicode string:

wchar_t president[] = L"Ulysses Grant";

The L prefix indicates that the string literal is a wide character string, so each character in the string, including the terminating null character, will occupy two bytes. Of course, indexing the string references characters, not bytes, so president[2] corresponds to the character L'y'.

String Input

The iostream header file contains definitions of a number of functions for reading characters from the keyboard. The one that you’ll look at here is the function getline(), which reads a sequence of characters entered through the keyboard and stores it in a character array as a string terminated by '\0'. You typically use the getline() function statements like this:

const int MAX(80);                // Maximum string length including \0
char name[MAX];                   // Array to store a string
cin.getline(name, MAX, '\n');     // Read input line as a string

These statements first declare a char array name with MAX elements and then read characters from cin using the function getline(). The source of the data, cin, is written as shown, with a period separating it from the function name. The period indicates that the getline() function you are calling is the one belonging to the cin object. The significance of the arguments to the getline() function is shown in Figure 4-3.

Figure 4-3

Referenced Screen

Because the last argument to the getline() function is '\n'(newline or end line character) and the second argument is MAX, characters are read from cin until the ‘\n’ character is read, or when MAX - 1 characters have been read, whichever occurs first. The maximum number of characters read is MAX - 1 rather than MAX to allow for the ‘\0’ character to be appended to the sequence of characters stored in the array. The ‘\n’ character is generated when you press the Return key on your keyboard and is therefore usually the most convenient character to end input. You can, however, specify something else by changing the last argument. The ‘\n’ isn’t stored in the input array name, but as I said, a ‘\0’ is added at the end of the input string in the array.

You will learn more about this form of syntax when classes are discussed later on. Meanwhile, just take it for granted as you use it in an example.

Try it Out: Programming with Strings

You now have enough knowledge to write a simple program to read a string and then count how many characters it contains.

// Ex4_03.cpp
// Counting string characters
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
        
int main()
{
   const int MAX(80);                 // Maximum array dimension
   char buffer[MAX];                  // Input buffer
   int count(0);                      // Character count
        
   cout << "Enter a string of less than "
        << MAX << " characters:\n";
   cin.getline(buffer, MAX, '\n');    // Read a string until \n
        
   while(buffer[count] != '\0')       // Increment count as long as
      count++;                        // the current character is not null
        
   cout << endl
        << "The string \"" << buffer
        << "\" has " << count << " characters.";
   cout << endl;
   return 0;
}

Typical output from this program is as follows:

Enter a string of less than 80 characters:
Radiation fades your genes
The string "Radiation fades your genes" has 26 characters.
TipTip

This program declares a character array buffer and reads a character string into the array from the keyboard after displaying a prompt for the input. Reading from the keyboard ends when the user presses Return, or when MAX-1 characters have been read.

A while loop is used to count the number of characters read. The loop continues as long as the current character referenced with buffer[count] is not ‘\0’. This sort of checking on the current character while stepping through an array is a common technique in native C++. The only action in the loop is to increment count for each non-null character.

There is a library function, strlen(), that will do what this loop does; you’ll learn about it later in this chapter.

Finally, in the example, the string and the character count is displayed with a single output statement. Note the use of the escape sequence '\“' to output a double quote.

Multidimensional Arrays

The arrays that you have defined so far with one index are referred to as one-dimensional arrays. An array can also have more than one index value, in which case it is called a multidimensional array. Suppose you have a field in which you are growing bean plants in rows of 10, and the field contains 12 such rows (so there are 120 plants in all). You could declare an array to record the weight of beans produced by each plant using the following statement:

double beans[12][10];

This declares the two-dimensional array beans, the first index being the row number, and the second index the number within the row. To refer to any particular element requires two index values. For example, you could set the value of the element reflecting the fifth plant in the third row with the following statement:

beans[2][4] = 10.7;

Remember that the index values start from zero, so the row index value is 2 and the index for the fifth plant within the row is 4.

Being a successful bean farmer, you might have several identical fields planted with beans in the same pattern. Assuming that you have eight fields, you could use a three-dimensional array to record data about these, declared thus:

double beans[8][12][10];

This records production for all of the plants in each of the fields, the leftmost index referencing a particular field. If you ever get to bean farming on an international scale, you are able to use a four-dimensional array, with the extra dimension designating the country. Assuming that you’re as good a salesman as you are a farmer, growing this quantity of beans to keep up with the demand may well start to affect the ozone layer.

Arrays are stored in memory such that the rightmost index value varies most rapidly. Thus, the array data[3][4] is three one-dimensional arrays of four elements each. The arrangement of this array is illustrated in Figure 4-4.

Figure 4-4

Referenced Screen

The elements of the array are stored in a contiguous block of memory, as indicated by the arrows in Figure 4-4. The first index selects a particular row within the array, and the second index selects an element within the row.

Note that a two-dimensional array in native C++ is really a one-dimensional array of one-dimensional arrays. A native C++ array with three dimensions is actually a one-dimensional array of elements where each element is a one-dimensional array of one-dimensional arrays. This is not something you need to worry about most of the time, but as you will see later, C++/CLI arrays are not the same as this. It also implies that for the array in Figure 4-4, the expressions data[0], data[1], and data[2], represent one-dimensional arrays.

Initializing Multidimensional Arrays

To initialize a multidimensional array, you use an extension of the method used for a one-dimensional array. For example, you can initialize a two-dimensional array, data, with the following declaration:

long data[2][4] = {
                     { 1,  2,  3,  5 },
                     { 7, 11, 13, 17 }
                  };

Thus, the initializing values for each row of the array are contained within their own pair of braces. Because there are four elements in each row, there are four initializing values in each group, and because there are two rows, there are two groups between braces, each group of initializing values being separated from the next by a comma.

You can omit initializing values in any row, in which case the remaining array elements in the row are zero. For example:

long data[2][4] = {
                     { 1,  2,  3       },
                     { 7, 11           }
                  };

I have spaced out the initializing values to show where values have been omitted. The elements data[0][3], data[1][2], and data[1][3] have no initializing values and are therefore zero.

If you wanted to initialize the whole array with zeros you could simply write:

long data[2][4] = {0};

If you are initializing arrays with even more dimensions, remember that you need as many nested braces for groups of initializing values as there are dimensions in the array — unless you’re initializing the array with zeros

Try it Out: Storing Multiple Strings

You can use a single two-dimensional array to store several C-style strings. You can see how this works with an example:

// Ex4_04.cpp
// Storing strings in an array
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
        
int main()
{
   char stars[6][80] = { "Robert Redford",
                         "Hopalong Cassidy",
                         "Lassie",
                         "Slim Pickens",
                         "Boris Karloff",
                         "Oliver Hardy"
                       };
   int dice(0);
        
   cout << endl
        << "Pick a lucky star!"
        << "Enter a number between 1 and 6: ";
   cin >> dice;
        
   if(dice >= 1 && dice <= 6)          // Check input validity
      cout << endl                     // Output star name
           << "Your lucky star is " << stars[dice - 1];
   else
      cout << endl                     // Invalid input
           << "Sorry, you haven't got a lucky star.";
        
   cout << endl;
   return 0;
}
TipTip

Apart from its incredible inherent entertainment value, the main point of interest in this example is the declaration of the array stars. It is a two-dimensional array of elements of type char that can hold up to six strings, each of which can be up to 80 characters long (including the terminating null character that is automatically added by the compiler). The initializing strings for the array are enclosed between braces and separated by commas.

One disadvantage of using arrays in this way is the memory that is almost invariably left unused. All of the strings are fewer than 80 characters, and the surplus elements in each row of the array are wasted.

You can also let the compiler work out how many strings you have by omitting the first array dimension and declaring it as follows:

   char stars[][80] = { "Robert Redford",
                        "Hopalong Cassidy",
                        "Lassie",
                        "Slim Pickens",
                        "Boris Karloff",
                        "Oliver Hardy"
                      };

This causes the compiler to define the first dimension to accommodate the number of initializing strings that you have specified. Because you have six, the result is exactly the same, but it avoids the possibility of an error. Here, you can’t omit both array dimensions. With an array of two or more dimensions, the rightmost dimension must always be defined.

NoteNote

Note the semicolon at the end of the declaration. It’s easy to forget it when there are initializing values for an array.

Where you need to reference a string for output in the following statement, you need only specify the first index value:

      cout << endl                             // Output star name
           << "Your lucky star is " << stars[dice - 1];

A single index value selects a particular 80-element sub-array, and the output operation displays the contents up to the terminating null character. The index is specified as dice - 1 as the dice values are from 1 to 6, whereas the index values clearly need to be from 0 to 5.

The variables that you have dealt with so far provide you with the ability to name a memory location in which you can store data of a particular type. The contents of a variable are either entered from an external source, such as the keyboard, or calculated from other values that are entered. There is another kind of variable in C++ that does not store data that you normally enter or calculate, but greatly extends the power and flexibility of your programs. This kind of variable is called a pointer.

What Is a Pointer?

Each memory location that you use to store a data value has an address. The address provides the means for your PC hardware to reference a particular data item. A pointer is a variable that stores the address of another variable of a particular type. A pointer has a variable name just like any other variable and also has a type that designates what kind of variables its contents refer to. Note that the type of a pointer variable includes the fact that it’s a pointer. A variable that is a pointer, that can hold addresses of locations in memory containing values of type int, is of type ‘pointer to int’.

Declaring Pointers

The declaration for a pointer is similar to that of an ordinary variable, except that the pointer name has an asterisk in front of it to indicate that it’s a variable that is a pointer. For example, to declare a pointer pnumber of type long, you could use the following statement:

long* pnumber;

This declaration has been written with the asterisk close to the type name. If you want, you can also write it as:

long *pnumber;

The compiler won’t mind at all; however, the type of the variable pnumber is ‘pointer to long’, which is often indicated by placing the asterisk close to the type name. Whichever way you choose to write a pointer type, be consistent.

You can mix declarations of ordinary variables and pointers in the same statement. For example:

long* pnumber, number (99);

This declares the pointer pnumber of type ‘pointer to long’ as before, and also declares the variable number, of type long. On balance, it’s probably better to declare pointers separately from other variables; otherwise, the statement can appear misleading as to the type of the variables declared, particularly if you prefer to place the * adjacent to the type name. The following statements certainly look clearer, and putting declarations on separate lines enables you to add comments for them individually, making for a program that is easier to read.

long number(99);     // Declaration and initialization of long variable
long* pnumber;       // Declaration of variable of type pointer to long

It’s a common convention in C++ to use variable names beginning with p to denote pointers. This makes it easier to see which variables in a program are pointers, which in turn can make a program easier to follow.

Let’s take an example to see how this works, without worrying about what it’s for. I will get to how you use pointers very shortly. Suppose you have the long integer variable number containing the value 99 because you declared it above. You also have the pointer pnumber of type pointer to long, which you could use to store the address of the variable number. But how do you obtain the address of a variable?

The Address-Of Operator

What you need is the address-of operator, &. This is a unary operator that obtains the address of a variable. It’s also called the reference operator, for reasons I will discuss later in this chapter. To set up the pointer that I have just discussed, you could write this assignment statement:

pnumber = &number;            // Store address of number in pnumber

The result of this operation is illustrated in Figure 4-5.

Figure 4-5

Referenced Screen

You can use the operator & to obtain the address of any variable, but you need a pointer of the appropriate type to store it. If you want to store the address of a double variable, for example, the pointer must have been declared as type double*, which is type ‘pointer to double’.

Using Pointers

Taking the address of a variable and storing it in a pointer is all very well, but the really interesting aspect is how you can use it. Fundamental to using a pointer is accessing the data value in the variable to which a pointer points. This is done using the indirection operator *.

The Indirection Operator

You use the indirection operator, *, with a pointer to access the contents of the variable that it points to. The name ‘indirection operator’ stems from the fact that the data is accessed indirectly. It is also called the dereference operator, and the process of accessing the data in the variable pointed to by a pointer is termed de-referencing the pointer.

One aspect of this operator that can seem confusing is the fact that you now have several different uses for the same symbol, *. It is the multiply operator, it serves as the indirection operator, and it is used in the declaration of a pointer. Each time you use *, the compiler is able to distinguish its meaning by the context. When you multiply two variables, A*B for instance, there’s no meaningful interpretation of this expression for anything other than a multiply operation.

Why Use Pointers?

A question that usually springs to mind at this point is, “Why use pointers at all?” After all, taking the address of a variable you already know and sticking it in a pointer so that you can dereference it seems like overhead you can do without. There are several reasons why pointers are important.

As you will see shortly, you can use pointer notation to operate on data stored in an array, which often executes faster than if you use array notation. Also, when you get to define your own functions later in the book, you will see that pointers are used extensively for enabling access within a function to large blocks of data, such as arrays, that are defined outside the function. Most importantly, however, you will also see that you can allocate space for variables dynamically — that is, during program execution. This sort of capability allows your program to adjust its use of memory depending on the input to the program. Because you don’t know in advance how many variables you are going to create dynamically, a primary way you have for doing this is using pointers — so make sure you get the hang of this bit.

Initializing Pointers

Using pointers that aren’t initialized is extremely hazardous. You can easily overwrite random areas of memory through an uninitialized pointer. The resulting damage depends on how unlucky you are, so it’s more than just a good idea to initialize your pointers. It’s very easy to initialize a pointer to the address of a variable that has already been defined. Here you can see that I have initialized the pointer pnumber with the address of the variable number just by using the operator & with the variable name:

int number(0);                       // Initialized integer variable
int* pnumber(&number);               // Initialized pointer

When initializing a pointer with the address of another variable, remember that the variable must already have been declared prior to the pointer declaration.

Of course, you may not want to initialize a pointer with the address of a specific variable when you declare it. In this case, you can initialize it with the pointer equivalent of zero. For this, Visual C++ provides the literal nullptr — a pointer literal that does not point to anything — so you can declare and initialize a pointer using the following statement:

int* pnumber(nullptr);              // Pointer not pointing to anything

This ensures that the pointer doesn’t contain an address that will be accepted as valid, and provides the pointer with a value that you can check in an if statement, such as:

if(pnumber == nullptr)
   cout << endl << "pnumber does not point to anything.";

nullptr is a feature introduced by the new standard for C++ that is supported by the Visual C++ 2010 compiler. In the past, 0 or NULL (which is a macro for which the compiler will substitute 0) have been used to initialize a pointer, and of course, these still work. However, it is much better to use nullptr to initialize your pointers.

NoteNote

The reason for introducing nullptr into the C++ language is to remove potential confusion between the literal 0 as an integral value and 0 as a pointer. Having a dual meaning for the literal 0 can cause problems in some circumstances. nullptr is of type std::nullptr_t and cannot be confused with a value of any other type. nullptr can be implicitly converted to any pointer type but cannot be implicitly converted to any integral type except type bool.

Because the literal nullptr can be implicitly converted to type bool, you can check the status of the pointer pnumber like this:

if(!pnumber) cout << endl << "pnumber does not point to anything.";

nullptr converts to the bool value false, and any other pointer value converts to true. Thus, if pnumber contains nullptr, the if expression will be true and will cause the message to be written to the output stream.

Try it Out: Using Pointers

You can try out various aspects of pointer operations with an example:

// Ex4_05.cpp
// Exercising pointers
#include <iostream>
using std::cout;
using std::endl;
using std::hex;
using std::dec;
        
int main()
{
   long* pnumber(nullptr);         // Pointer declaration & initialization
   long number1(55), number2(99);
        
   pnumber = &number1;             // Store address in pointer
   *pnumber += 11;                 // Increment number1 by 11
   cout << endl
        << "number1 = " << number1
        << "   &number1 = " << hex << pnumber;
        
   pnumber = &number2;             // Change pointer to address of number2
   number1 = *pnumber*10;          // 10 times number2
        
   cout << endl
        << "number1 = " << dec << number1
        << "   pnumber = " << hex << pnumber
        << "   *pnumber = " << dec << *pnumber;
        
   cout << endl;
   return 0;
}

You should compile and execute the release version of this example. The debug version will add extra bytes, used for debugging purposes, that will cause the variables to be separated by 12 bytes instead of 4. On my computer, this example generates the following output:

number1 = 66   &number1 = 0012FEC8
number1 = 990   pnumber = 0012FEBC   *pnumber = 99
TipTip

There is no input to this example. All operations are carried out with the initializing values for the variables. After storing the address of number1 in the pointer pnumber, the value of number1 is incremented indirectly through the pointer in this statement:

   *pnumber += 11;                       // Increment number1 by 11

The indirection operator determines that you are adding 11 to the contents of the variable pointed to by pnumber, which is number1. If you forgot the * in this statement, you would be attempting to add 11 to the address stored in the pointer.

The values of number1, and the address of number1 that is stored in pnumber, are displayed. You use the hex manipulator to generate the address output in hexadecimal notation.

You can obtain the value of ordinary integer variables as hexadecimal output by using the manipulator hex. You send it to the output stream in the same way that you have applied endl, with the result that all following output is in hexadecimal notation. If you want the following output to be decimal, you need to use the manipulator dec in the next output statement to switch the output back to decimal mode again.

After the first line of output, the contents of pnumber is set to the address of number2. The variable number1 is then changed to the value of 10 times number2:

   number1 = *pnumber*10;                // 10 times number2

This is calculated by accessing the contents of number2 indirectly through the pointer. The second line of output shows the results of these calculations.

The address values you see in your output may well be different from those shown in the output here since they reflect where the program is loaded in memory, which depends on how your operating system is configured. The 0x prefixing the address values indicates that they are hexadecimal numbers.

Note that the addresses &number1 and pnumber (when it contains &number2) differ by four bytes. This shows that number1 and number2 occupy adjacent memory locations, as each variable of type long occupies four bytes. The output demonstrates that everything is working as you would expect.

Pointers to char

A pointer of type char* has the interesting property that it can be initialized with a string literal. For example, you can declare and initialize such a pointer with the statement:

char* proverb ("A miss is as good as a mile.");

This looks similar to initializing a char array, but it’s slightly different. This creates a string literal (actually an array of type const char) with the character string appearing between the quotes and terminating with '\0', and stores the address of the literal in the pointer proverb. The address of the literal will be the address of its first character. This is shown in Figure 4-6.

Figure 4-6

Referenced Screen

Try it Out: Lucky Stars With Pointers

You could rewrite the lucky stars example using pointers instead of an array to see how that would work:

// Ex4_06.cpp
// Initializing pointers with strings
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
        
int main()
{
   char* pstr1("Robert Redford");
   char* pstr2("Hopalong Cassidy");
   char* pstr3("Lassie");
   char* pstr4("Slim Pickens");
   char* pstr5 ("Boris Karloff");
   char* pstr6("Oliver Hardy");
   char* pstr("Your lucky star is ");
        
   int dice(0);
        
   cout << endl
        << "Pick a lucky star!"
        << "Enter a number between 1 and 6: ";
   cin >> dice;
        
   cout << endl;
   switch(dice)
   {
      case 1: cout << pstr << pstr1;
              break;
      case 2: cout << pstr << pstr2;
              break;
      case 3: cout << pstr << pstr3;
              break;
      case 4: cout << pstr << pstr4;
              break;
      case 5: cout << pstr << pstr5;
              break;
      case 6: cout << pstr << pstr6;
              break;
        
      default: cout << "Sorry, you haven't got a lucky star.";
   }
        
   cout << endl;
   return 0;
}
TipTip

The array in Ex4_04.cpp has been replaced by the six pointers, pstr1 to pstr6, each initialized with a name. You have also declared an additional pointer, pstr, initialized with the phrase that you want to use at the start of a normal output line. Because you have discrete pointers, it is easier to use a switch statement to select the appropriate output message than to use an if, as you did in the original version. Any incorrect values entered are all taken care of by the default option of the switch.

Outputting the string pointed to by a pointer couldn’t be easier. As you can see, you simply write the pointer name. It may cross your mind at this point that in Ex4_05.cpp you wrote a pointer name in the output statement, and the address that it contained was displayed. Why is it different here? The answer is in the way the output operation views a pointer of type ‘pointer to char.’ It treats a pointer of this type in a special way, in that it regards it as a string (which is an array of char), and so outputs the string itself, rather than its address.

Using pointers in the example has eliminated the waste of memory that occurred with the array version of this program, but the program seems a little long-winded now. There must be a better way. Indeed there is — using an array of pointers.

Try it Out: Arrays of Pointers

With an array of pointers of type char, each element can point to an independent string, and the lengths of each of the strings can be different. You can declare an array of pointers in the same way that you declare a normal array. Let’s go straight to rewriting the previous example using a pointer array:

// Ex4_07.cpp
// Initializing pointers with strings
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
        
int main()
{
   char* pstr[] =  { "Robert Redford",      // Initializing a pointer array
                     "Hopalong Cassidy",
                     "Lassie",
                     "Slim Pickens",
                     "Boris Karloff",
                     "Oliver Hardy"
                   };
   char* pstart("Your lucky star is ");
        
   int dice(0);
        
   cout << endl
        << "Pick a lucky star!"
        << "Enter a number between 1 and 6: ";
   cin >> dice;
        
   cout << endl;
   if(dice >= 1 && dice <= 6)                  // Check input validity
      cout << pstart << pstr[dice - 1];        // Output star name
        
   else
      cout << "Sorry, you haven't got a lucky star."; // Invalid input
        
   cout << endl;
   return 0;
}
TipTip

In this case, you are nearly getting the best of all possible worlds. You have a one-dimensional array of pointers to type char declared, such that the compiler works out what the dimension should be from the number of initializing strings. The memory usage that results from this is illustrated in Figure 4-7.

Compared to using a ‘normal’ array, the pointer array generally carries less overhead in terms of space. With an array, you would need to make each row the length of the longest string, and six rows of seventeen bytes each is 102 bytes, so by using a pointer array you have saved a whole -1 bytes! What’s gone wrong? The simple truth is that for this small number of relatively short strings, the size of the extra array of pointers is significant. You would make savings if you were dealing with more strings that were longer and had more variable lengths.

Space saving isn’t the only advantage that you get by using pointers. In a lot of circumstances you save time, too. Think of what happens if you want to move “Oliver Hardy” to the first position and “Robert Redford” to the end. With the pointer array as above, you just need to swap the pointers — the strings themselves stay where they are. If you had stored these simply as strings, as you did in Ex4_04.cpp, a great deal of copying would be necessary — you’d need to copy the whole string “Robert Redford” to a temporary location while you copied “Oliver Hardy” in its place, and then you’d need to copy “Robert Redford” to the end position. This requires significantly more computer time to execute.

Because you are using pstr as the name of the array, the variable holding the start of the output message needs to be different; it is called pstart. You select the string that you want to output by means of a very simple if statement, similar to that of the original version of the example. You either display a star selection or a suitable message if the user enters an invalid value.

Figure 4-7

Referenced Screen

One weakness of the way the program is written is that the code assumes there are six options, even though the compiler is allocating the space for the pointer array from the number of initializing strings that you supply. So if you add a string to the list, you have to alter other parts of the program to take account of this. It would be nice to be able to add strings and have the program automatically adapt to however many strings there are.

The sizeof Operator

A new operator can help us here. The sizeof operator produces an integer value of type size_t that gives the number of bytes occupied by its operand, where size_t is a type defined by the standard library. Many standard library functions return a value of type size_t, and the size_t type is defined within the standard library using a typedef statement to be equivalent to one of the fundamental types, usually unsigned int. The reason for using size_t rather than a fundamental type directly is that it allows flexibility in what the actual type is in different C++ implementations. The C++ standard permits the range of values accommodated by a fundamental type to vary, to make the best of a given hardware architecture, and size_t can be defined to be the equivalent of the most suitable fundamental type in the current machine environment.

Look at this statement that refers to the variable dice from the previous example:

cout << sizeof dice;

The value of the expression sizeof dice is 4 because dice was declared as type int and therefore occupies 4 bytes. Thus this statement outputs the value 4.

The sizeof operator can be applied to an element in an array or to the whole array. When the operator is applied to an array name by itself, it produces the number of bytes occupied by the whole array, whereas when it is applied to a single element with the appropriate index value or values, it results in the number of bytes occupied by that element. Thus, in the last example, you could output the number of elements in the pstr array with the expression:

cout << (sizeof pstr)/(sizeof pstr[0]);

The expression (sizeof pstr)/(sizeof pstr[0]) divides the number of bytes occupied by the whole pointer array, by the number of bytes occupied by the first element of the array. Because each element in the array occupies the same amount of memory, the result is the number of elements in the array.

NoteNote

Remember that pstr is an array of pointers — using the sizeof operator on the array or on individual elements will not tell us anything about the memory occupied by the text strings. pstr[0] is a pointer to a character array and thus occupies just 4 bytes.

You can also apply the sizeof operator to a type name rather than a variable, in which case the result is the number of bytes occupied by a variable of that type. In this case, the type name should be enclosed in parentheses. For example, after executing the statement,

size_t long_size(sizeof(long));

the variable long_size will be initialized with the value 4. The variable long_size is declared to be of type size_t to match the type of the value produced by the sizeof operator. Using a different integer type for long_size may result in a warning message from the compiler.

Try it Out: Using the size of Operator

You can amend the last example to use the sizeof operator so that the code automatically adapts to an arbitrary number of string values from which to select:

// Ex4_08.cpp
// Flexible array management using sizeof
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
        
int main()
{
   char* pstr[] = { "Robert Redford",       // Initializing a pointer array
                    "Hopalong Cassidy",
                    "Lassie",
                    "Slim Pickens",
                    "Boris Karloff",
                    "Oliver Hardy"
                  };
   char* pstart("Your lucky star is ");
        
   int count((sizeof pstr)/(sizeof pstr[0]));  // Number of array elements

   int dice(0);

   cout << endl
        << " Pick a lucky star!"
        << " Enter a number between 1 and " << count << ": ";
   cin >> dice;
        
   cout << endl;
   if(dice >= 1 && dice <= count)               // Check input validity
      cout << pstart << pstr[dice - 1];         // Output star name
   else
      cout << "Sorry, you haven't got a lucky star."; // Invalid input
        
   cout << endl;
   return 0;
}
TipTip

As you can see, the changes required in the example are very simple. You just calculate the number of elements in the pointer array pstr and store the result in count. Then, wherever the total number of elements in the array was referenced as 6, you just use the variable count. You could now just add a few more names to the list of lucky stars, and everything affected in the program is adjusted automatically.

Constant Pointers and Pointers to Constants

The array pstr in the last example is clearly not intended to be modified in the program, and nor are the strings being pointed to, nor is the variable count. It would be a good idea to ensure that these didn’t get modified by mistake in the program. You could very easily protect the variable count from accidental modification by writing this:

const int count = (sizeof pstr)/(sizeof pstr[0]);

However, the array of pointers deserves closer examination. You declared the array like this:

   char* pstr[] = { "Robert Redford",   // Initializing a pointer array
                    "Hopalong Cassidy",
                    "Lassie",
                    "Slim Pickens",
                    "Boris Karloff",
                    "Oliver Hardy"
                  };

Each pointer in the array is initialized with the address of a string literal, “Robert Redford”, “Hopalong Cassidy”, and so on. The type of a string literal is ‘array of const char,’ so you are storing the address of a const array in a non-const pointer. The compiler allows us to use a string literal to initialize an element of an array of char* for reasons of backward compatibility with existing code.

If you try to alter the character array with a statement like this:

*pstr[0] = "Stan Laurel";

the program does not compile.

If you were to reset one of the elements of the array to point to a character using a statement like this:

*pstr[0] = 'X';

the program compiles, but crashes when this statement is executed.

You don’t really want to have unexpected behavior, like the program crashing at run time, and you can prevent it. A far better way of writing the declaration is as follows:


   const char* pstr[] = { "Robert Redford",    // Array of pointers
                          "Hopalong Cassidy",  // to constants
                          "Lassie",
                          "Slim Pickens",
                          "Boris Karloff",
                          "Oliver Hardy"
                        };

In this case, there is no ambiguity about the const-ness of the strings pointed to by the elements of the pointer array. If you now attempt to change these strings, the compiler flags this as an error at compile time.

However, you could still legally write this statement:

pstr[0] = pstr[1];

Those lucky individuals due to be awarded Mr. Redford would get Mr. Cassidy instead because both pointers now point to the same name. Note that this isn’t changing the values of the objects pointed to by the pointer array element — it is changing the value of the pointer stored in pstr[0]. You should therefore inhibit this kind of change as well, because some people may reckon that good old Hoppy may not have the same sex appeal as Robert. You can do this with the following statement:

   // Array of constant pointers to constants
   const char* const pstr[] = { "Robert Redford",
                                "Hopalong Cassidy",
                                "Lassie",
                                "Slim Pickens",
                                "Boris Karloff",
                                "Oliver Hardy"
                              };

To summarize, you can distinguish three situations relating to const, pointers, and the objects to which they point:

  • A pointer to a constant object

  • A constant pointer to an object

  • A constant pointer to a constant object

In the first situation, the object pointed to cannot be modified, but you can set the pointer to point to something else:

const char* pstring("Some text");

In the second, the address stored in the pointer can’t be changed, but the object pointed to can be:

char* const pstring("Some text");

Finally, in the third situation, both the pointer and the object pointed to have been defined as constant and, therefore, neither can be changed:

const char* const pstring("Some text");
NoteNote

Of course, all this applies to pointers that point to any type. A pointer to type char is used here purely for illustrative purposes. In general, to interpret more complex types correctly, you just read them from right to left. The type const char* is a pointer to characters that are const and the type char* const is a const pointer to characters.

Pointers and Arrays

Array names can behave like pointers under some circumstances. In most situations, if you use the name of a one-dimensional array by itself, it is automatically converted to a pointer to the first element of the array. Note that this is not the case when the array name is used as the operand of the sizeof operator.

If you have these declarations,

double* pdata(nullptr);
double data[5];

you can write this assignment:

pdata = data;       // Initialize pointer with the array address

This is assigning the address of the first element of the array data to the pointer pdata. Using the array name by itself refers to the address of the array. If you use the array name data with an index value, it refers to the contents of the element corresponding to that index value. So, if you want to store the address of that element in the pointer, you have to use the address-of operator:

pdata = &data[1];

Here, the pointer pdata contains the address of the second element of the array.

Pointer Arithmetic

You can perform arithmetic operations with pointers. You are limited to addition and subtraction in terms of arithmetic, but you can also perform comparisons of pointer values to produce a logical result. Arithmetic with a pointer implicitly assumes that the pointer points to an array, and that the arithmetic operation is on the address contained in the pointer. For the pointer pdata, for example, you could assign the address of the third element of the array data to a pointer with this statement:

pdata = &data[2];

In this case, the expression pdata+1 would refer to the address of data[3], the fourth element of the data array, so you could make the pointer point to this element by writing this statement:

pdata += 1;          // Increment pdata to the next element

This statement increments the address contained in pdata by the number of bytes occupied by one element of the array data. In general, the expression pdata+n, where n can be any expression resulting in an integer, adds n*sizeof(double) to the address contained in the pointer pdata, because it was declared to be of type pointer to double. This is illustrated in Figure 4-8.

Figure 4-8

Referenced Screen

In other words, incrementing or decrementing a pointer works in terms of the type of the object pointed to. Increasing a pointer to long by one changes its contents to the next long address, and so increments the address by four. Similarly, incrementing a pointer to short by one increments the address by two. The more common notation for incrementing a pointer is using the increment operator. For example:

pdata++;            // Increment pdata to the next element

This is equivalent to (and more common than) the += form. However, I used the preceding += form to make it clear that although the increment value is actually specified as one, the effect is usually an address increment greater than one, except in the case of a pointer to type char.

NoteNote

The address resulting from an arithmetic operation on a pointer can be a value ranging from the address of the first element of the array to the address that is one beyond the last element. Accessing an address that does not refer to an element within the array results in undefined behavior.

You can, of course, dereference a pointer on which you have performed arithmetic (there wouldn’t be much point to it otherwise). For example, assuming that pdata is still pointing to data[2], this statement,

*(pdata + 1) = *(pdata + 2);

is equivalent to this:

data[3] = data[4];

When you want to dereference a pointer after incrementing the address it contains, the parentheses are necessary because the precedence of the indirection operator is higher than that of the arithmetic operators, + and -. If you write the expression *pdata+1, instead of *(pdata+1), this adds one to the value stored at the address contained in pdata, which is equivalent to executing data[2]+1. Because this isn’t an lvalue, its use in the previous assignment statement causes the compiler to generate an error message.

You can use an array name as though it were a pointer for addressing elements of an array. If you have the same one-dimensional array as before, declared as

long data[5];

using pointer notation, you can refer to the element data[3], for example, as *(data+3). This kind of notation can be applied generally so that, corresponding to the elements data[0], data[1], data[2], you can write *data, *(data+1), *(data+2), and so on.

Try it Out: Array Names as Pointers

You could practice this aspect of array addressing with a program to calculate prime numbers (a prime number is divisible only by itself and one).

// Ex4_09.cpp
// Calculating primes
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
using std::setw;
        
int main()
{
   const int MAX(100);            // Number of primes required
   long primes[MAX] = { 2,3,5 };  // First three primes defined
   long trial(5);                 // Candidate prime
   int count(3);                  // Count of primes found
   bool found(false);             // Indicates when a prime is found
        
   do
   {
      trial += 2;                      // Next value for checking
      found = false;                   // Set found indicator
        
      for(int i = 0; i < count; i++)   // Try division by existing primes
      {
         found = (trial % *(primes + i)) == 0;// True for exact division
           if(found)                          // If division is exact
              break;                          // it's not a prime
      }
        

      if (!found)                      // We got one...
         *(primes + count++) = trial;  // ...so save it in primes array
   }while(count < MAX);
        
   // Output primes 5 to a line
   for(int i = 0; i < MAX; i++)
   {
      if(i % 5 == 0)                 // New line on 1st, and every 5th line
         cout << endl;
      cout << setw(10) << *(primes + i);
   }
   cout << endl;
        
   return 0;
}

If you compile and execute this example, you should get the following output:

         2         3         5         7        11
        13        17        19        23        29
        31        37        41        43        47
        53        59        61        67        71
        73        79        83        89        97
       101       103       107       109       113
       127       131       137       139       149
       151       157       163       167       173
       179       181       191       193       197
       199       211       223       227       229
       233       239       241       251       257
       263       269       271       277       281
       283       293       307       311       313
       317       331       337       347       349
       353       359       367       373       379
       383       389       397       401       409
       419       421       431       433       439
       443       449       457       461       463
       467       479       487       491       499
       503       509       521       523       541
TipTip

You have the usual #include statements for the iostream header file for input and output, and for iomanip, because you will use a stream manipulator to set the field width for output.

You use the constant MAX to define the number of primes that you want the program to produce. The primes array, which stores the results, has the first three primes already defined to start the process off. All the work is done in two loops: the outer do-while loop, which picks the next value to be checked and adds the value to the primes array if it is prime, and the inner for loop that actually checks the value to see whether it’s prime or not.

The algorithm in the for loop is very simple and is based on the fact that if a number is not a prime, it must be divisible by one of the primes found so far — all of which are less than the number in question because all numbers are either prime or a product of primes. In fact, only division by primes less than or equal to the square root of the number in question need to be checked, so this example isn’t as efficient as it might be.

         found = (trial % *(primes + i)) == 0;   // True for exact division

This statement sets the variable found to true if there’s no remainder from dividing the value in trial by the current prime *(primes+i) (remember that this is equivalent to primes[i]), and to 0 otherwise. The if statement causes the for loop to be terminated if found has the value true because the candidate in trial can’t be a prime in that case.

After the for loop ends (for whatever reason), it’s necessary to decide whether or not the value in trial was prime. This is indicated by the value in the indicator variable found.

         *(primes + count++) = trial;   // ...so save it in primes array

If trial does contain a prime, this statement stores the value in primes[count] and then increments count through the postfix increment operator.

After MAX number of primes have been found, they are output with a field width of 10 characters, 5 to a line, as a result of this statement:

      if(i % 5 == 0)                 // New line on 1st, and every 5th line
         cout << endl;

This starts a new line when i has the values 0, 5, 10, and so on.

Try it Out: Counting Characters Revisited

To see how handling strings works in pointer notation, you could produce a version of the program you looked at earlier for counting the characters in a string:

// Ex4_10.cpp
// Counting string characters using a pointer
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
   const int MAX(80);                  // Maximum array dimension
   char buffer[MAX];                   // Input buffer
   char* pbuffer(buffer);              // Pointer to array buffer
   cout << endl                        // Prompt for input
        << "Enter a string of less than "
        << MAX << " characters:"
        << endl;
   cin.getline(buffer, MAX, '\n');     // Read a string until \n
   while(*pbuffer)                     // Continue until \0
      pbuffer++;
   cout << endl
        << "The string \"" << buffer
        << "\" has " << pbuffer - buffer << " characters.";
   cout << endl;
   return 0;
}

Here’s an example of typical output from this example:

Enter a string of less than 80 characters:
The tigers of wrath are wiser than the horses of instruction.
The string "The tigers of wrath are wiser than the horses of
instruction." has 61 characters.
TipTip

Here the program operates using the pointer pbuffer rather than the array name buffer. You don’t need the count variable because the pointer is incremented in the while loop until '\0' is found. When the '\0' character is found, pbuffer will contain the address of that position in the string. The count of the number of characters in the string entered is therefore the difference between the address stored in the pointer pbuffer, and the address of the beginning of the array denoted by buffer.

You could also have incremented the pointer in the loop by writing the loop like this:

while(*pbuffer++);                   // Continue until \0

Now the loop contains no statements, only the test condition. This would work adequately, except for the fact that the pointer would be incremented after '\0' was encountered, so the address would be one more than the last position in the string. You would therefore need to express the count of the number of characters in the string as pbuffer–buffer-1.

Note that you can’t use the array name here in the same way that you have used the pointer. The expression buffer++ is strictly illegal because you can’t modify the address value that an array name represents. Even though you can use an array name in an expression as though it is a pointer, it isn’t a pointer, because the address value that it represents is fixed.

Using Pointers with Multidimensional Arrays

Using a pointer to store the address of a one-dimensional array is relatively straightforward, but with multidimensional arrays, things can get a little complicated. If you don’t intend to use pointers with multidimensional arrays, you can skip this section, as it’s a little obscure; however, if you have previous experience with C, this section is worth a glance.

If you have to use a pointer with multidimensional arrays, you need to keep clear in your mind what is happening. By way of illustration, you can use an array beans, declared as follows:

double beans[3][4];

You can declare and assign a value to the pointer pbeans, as follows:

double* pbeans;
pbeans = &beans[0][0];

Here you are setting the pointer to the address of the first element of the array, which is of type double. You could also set the pointer to the address of the first row in the array with the statement:

pbeans = beans[0];

This is equivalent to using the name of a one-dimensional array, which is replaced by its address. You used this in the earlier discussion; however, because beans is a two-dimensional array, you cannot set an address in the pointer with the following statement:

pbeans = beans;           // Will cause an error!!

The problem is one of type. The type of the pointer you have defined is double*, but the array is of type double[3][4]. A pointer to store the address of this array must be of type double*[4]. C++ associates the dimensions of the array with its type, and the statement above is only legal if the pointer has been declared with the dimension required. This is done with a slightly more complicated notation than you have seen so far:

double (*pbeans)[4];

The parentheses here are essential; otherwise, you would be declaring an array of pointers. Now the previous statement is legal, but this pointer can only be used to store addresses of an array with the dimensions shown.

Pointer Notation with Multidimensional Arrays

You can use pointer notation with an array name to reference elements of the array. You can reference each element of the array beans that you declared earlier, which had three rows of four elements, in two ways:

  • Using the array name with two index values

  • Using the array name in pointer notation

Therefore, the following two statements are equivalent:

beans[i][j]
*(*(beans + i) + j)

Let’s look at how these work. The first line uses normal array indexing to refer to the element with offset j in row i of the array.

You can determine the meaning of the second line by working from the inside outwards. beans refers to the address of the first row of the array, so beans+i refers to row i of the array. The expression *(beans+i) is the address of the first element of row i, so *(beans+i)+j is the address of the element in row i with offset j. The whole expression therefore refers to the value of that element.

If you really want to be obscure — and it isn’t recommended that you should be — the following two statements, where you have mixed array and pointer notation, are also legal references to the same element of the array:

*(beans[i] + j)
(*(beans + i))[j]

There is yet another aspect to the use of pointers that is really the most important of all: the ability to allocate memory for variables dynamically. You’ll look into that next.

Ivor Horton’s Beginning Visual C++® 2010, Copyright © 2010 by Ivor Horton, ISBN: 978-0-470-50088-0, Published by Wiley Publishing, Inc., All Rights Reserved. Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc and/or its affiliates.

Show: