Storing Sequences in Arrays - Becoming a Functional C++Programmer - C++ For Dummies (2014)

C++ For Dummies (2014)

Part II

Becoming a Functional C++Programmer

Chapter 7

Storing Sequences in Arrays

In This Chapter

arrow Considering the need for something like an array

arrow Introducing the array data type

arrow Using an array

arrow Using the most common type of array — the character string

An array is a sequence of variables that shares the same name and that is referenced using an index. Arrays are useful little critters that allow you to store a large number of values of the same type that are related in some way — for example, the batting averages of all the players on the same team might be a good candidate for storage within an array. Arrays can be multidimensional, too, allowing you, for example, to store an array of batting averages within an array of months, which allows you to work with the batting averages of the team as they occur by month.

In this chapter, you find out how to initialize and use arrays for fun and profit. You also find out about an especially useful form of array called a char string.

Arraying the Arguments for Arrays

Consider the following problem. You need a program that can read a sequence of numbers from the keyboard and display their sum. You guessed it — the program stops reading in numbers as soon as you enter a negative number. Unlike similar programs in Chapters 5 and 6, however, this program will output all the numbers entered before displaying the average.

You could try to store numbers in a set of independent variables, as in

cin >> value1;
if (value1 >= 0)
{
cin >> value2;
if (value2 >= 0)
{
...

You can see that this approach can’t handle sequences involving more than just a few numbers. Besides, it’s ugly. What we need is some type of structure that has a name like a variable but that can store more than one value. May I present to you, Ms. A. Ray.

An array solves the problem of sequences nicely. For example, the following snippet declares an array valueArray that has storage for up to 128 int values. It then populates the array with numbers entered from the keyboard:

int nValue;

// declare an array capable of holding up to 128 ints
int nValueArray[128];

// define an index used to access subsequent members of
// of the array; don't exceed the 128 int limit
for (int i = 0; i < 128; i++)
{
cin >> nValue;

// exit the loop when the user enters a negative
// number
if (nValue < 0)
{
break;
}
nValueArray[i] = nValue;
}

The second line of this snippet declares an array nValueArray. Array declarations begin with the type of the array members: in this case, int. This is followed by the name of the array. The last elements of an array declaration are open and closed brackets containing the maximum number of elements that the array can hold. In this code snippet, nValueArray can store up to 128 integers.

image The size of an array must be a constant expression — this means an expression that C++ can calculate when it does the build.

image The 2014 standards allows the program to declare the size of an array with any expression as long as its value is known when the declaration is encountered. However, once declared, the size of the array is fixed.

This snippet reads a number from the keyboard and stores it into each subsequent member of the array nValueArray. You access an individual element of an array by providing the name of the array followed by brackets containing the index. The first integer in the array is nValueArray[0],the second is nValueArray[1], and so on.

In use, nValueArray[i] represents the ith element in the array. The index variable i must be a counting variable — that is, i must be a char, an int, or a long. If nValueArray is an array of ints, nValueArray[i] is an int.

Using an array

The following program inputs a sequence of integer values from the keyboard until the user enters a negative number. The program then displays the numbers input and reports their sum.

// ArrayDemo - demonstrate the use of arrays
// by reading a sequence of integers
// and then displaying them and their sum
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

// prototype declarations
int readArray(int integerArray[], int maxNumElements);
int sumArray(int integerArray[], int numElements);
void displayArray(int integerArray[], int numElements);

int main(int nNumberofArgs, char* pszArgs[])
{
// input the loop count
cout << "This program sums values entered "
<< "by the user\n";
cout << "Terminate the loop by entering "
<< "a negative number\n";
cout << endl;

// read numbers to be summed from the user into a
// local array
int inputValues[128];
int numberOfValues = readArray(inputValues, 128);

// now output the values and the sum of the values
displayArray(inputValues, numberOfValues);
cout << "The sum is "
<< sumArray(inputValues, numberOfValues)
<< endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

// readArray - read integers from the operator into
// 'integerArray' until operator enters neg.
// Return the number of elements stored.
int readArray(int integerArray[], int maxNumElements)
{
int numberOfValues;
for(numberOfValues = 0;
numberOfValues < maxNumElements;
numberOfValues++)
{
// fetch another number
int integerValue;
cout << "Enter next number: ";
cin >> integerValue;

// if it's negative...
if (integerValue < 0)
{
// ...then exit
break;
}

// ... otherwise store the number
// into the storage array
integerArray[numberOfValues] = integerValue;
}

// return the number of elements read
return numberOfValues;
}

// displayArray - display the members of an
// array of length sizeOfloatArray
void displayArray(int integerArray[], int numElements)
{
cout << "The value of the array is:" << endl;
for (int i = 0; i < numElements; i++)
{
cout << i << ": " << integerArray[i] << endl;
}
cout << endl;
}

// sumArray - return the sum of the members of an
// integer array
int sumArray(int integerArray[], int numElements)
{
int accumulator = 0;
for (int i = 0; i < numElements; i++)
{
accumulator += integerArray[i];
}
return accumulator;
}

The program ArrayDemo begins with prototype declarations of the functions readArray(), sumArray(), and displayArray(), which it will need later. The main program starts with a prompt to the user to input data to be summed. The program then declares an array inputValues[] to be used to store the values input by the user. The main program passes this array to readArray(), along with the length of the array — readArray() cannot read more than 128 values even if the user does not enter a negative number since that’s all the room allocated in the inputValues[] array.

image The array inputValues is declared as 128 integers long. If you’re thinking that this must be more than enough, don’t count on it. No matter how large you make the array, always put a check to make sure that you do not exceed the limits of the array. Writing more data than an array can hold causes your program to perform erratically and often to crash. This is discussed in detail in Chapter 28.

The main function then calls displayArray() to print the contents of the array. Finally, the function calls sumArray() to add the elements in the array.

The readArray() function takes two arguments: the integerArray[] into which to store the values it reads and maxNumElements, the maximum number of integer values for which there is room at the inn. The function begins with a for loop that reads integer values. Every non-negative value that the function reads is saved into integerArray[]. The first element goes into integerArray[0], the second into integerArray[1], and so forth.

Once the user enters a negative number, the program breaks out of the loop and returns the total numberOfValues input.

The displayArray() function also uses a for loop to traverse the elements of the array, starting at 0 and continuing to the last element, which is numElements - 1. The final function, sumArray(), also iterates through the array but sums the elements stored there into accumulator, which it then returns to the caller.

Notice, yet again, that the index i in the displayArray() and sumArray() functions is initialized to 0 and not to 1. In addition, notice how the for loop terminates as soon as i reaches numElements. The output from a sample run appears as follows:

This program sums values entered by the user
Terminate the loop by entering a negative number

Enter next number: 10
Enter next number: 20
Enter next number: 30
Enter next number: 40
Enter next number: -1
The value of the array is:
0: 10
1: 20
2: 30
3: 40

The sum is 100
Press Enter to continue...

image Just to keep non-programmers guessing, the term iterate means to traverse through a set of objects such as an array. Programmers say that the preceding functions iterate through the array.

Initializing an array

A local variable does not start life with a valid value, not even the value 0. Said another way, a local variable contains garbage until you actually store something in it. Locally declared arrays are the same — each element contains garbage until you actually assign something to it. You should initialize local variables when you declare them. This rule is even truer for arrays. It is far too easy to access uninitialized array elements thinking that they are valid values.

image By “local variable”, I'm talking about the normal variables declared within a function. C++ purists actually call these automatic variables to differentiate them from static variables (discussed in Chapter 18).

Fortunately, a small array may be initialized at the time it is declared with an initializer list. The following code snippet demonstrates how this is done:

float floatArray[5] = {0.0, 1.0, 2.0, 3.0, 4.0};

This initializes floatArray[0] to 0, floatArray[1] to 1.0, floatArray[2] to 2.0, and so on.

C++ pads the initialization list with 0s if the number of elements in the list is less than the size of the array. In fact, an empty initializer list can be used to initialize an array to 0:

int nArray[128] = {}; // initialize array to all 0's

The number of initialization constants can determine the size of the array. For example, you could have determined that floatArray has five elements just by counting the values within the braces. C++ can count as well (here’s at least one thing C++ can do for itself).

float floatArray[] = {0.0, 1.0, 2.0, 3.0, 4.0};

Accessing too far into an array

Mathematicians start counting arrays with 1. Most program languages start with an offset of 1 as well. C++ arrays begin counting at 0. The first member of a C++ array is valueArray[0]. That makes the last element of a 128-integer array integerArray[127] and not integerArray[128].

Unfortunately for the programmer, C++ does not check to see whether the index you are using is within the range of the array. C++ is perfectly happy giving you access to integerArray[200]. Our integerArray yard is only 128 integers long — 200 is 72 integers into someone else’s yard. No telling who lives there and what he’s storing at that location. Reading from integerArray[200] will return some unknown and unpredictable value. Writing to that location generates unpredictable results. It may do nothing — the house may be abandoned and the yard unused. On the other hand, it might overwrite some data, thereby confusing the neighbor and making the program act in a seemingly random fashion. Or it might crash the program.

image The most common wrong way to access an array is to read or write location integerArray[128]. Although it’s only one element beyond the end of the array, reading or writing this location is just as dangerous as using any other incorrect address.

Arraying range-based for loops

image You can access the elements of an array using a range-based for loop in some cases. The following for loop initializes all of the members of nArray to 0:

int nArray[128];
for(int& n: nArray)
{
n = 0;
}

This for loop says assign the variable n to be a reference to each element of nArray in turn. A 0 is then assigned to each element in nArray through the reference n.

image The following range-based for loop has no effect:

int nArray[128];
for(int n: nArray)
{
n = 0;
}

Without the ampersand (&), n is assigned the value of each element of nArray in turn. The variable n is then overwritten with a 0, leaving the value of nArray unchanged. Compare this to passing arguments to functions by value versus passing by reference, as described in Chapter 6.

image Range-based for loops can be used only where C++ knows the size of the array at build time. A range-based for loop would not work within the displayArray() function, for example. This function is built to handle arrays of any size. You get really strange build time error messages when you use range-based for loops on arrays where the size is not known. I have more to say about this in Chapter 26.

Defining and using arrays of arrays

Arrays are adept at storing sequences of numbers. Some applications require sequences of sequences. A classic example of this matrix configuration is the spreadsheet. Laid out like a chessboard, each element in the spreadsheet has both an x and a y offset.

C++ implements the matrix as follows:

int intMatrix[10][5];

This matrix is 10 elements in one dimension and 5 in another, for a total of 50 elements. In other words, intMatrix is a 10-element array, each element of which is a 5-int array. As you might expect, one corner of the matrix is in intMatrix[0][0], while the other corner is intMatrix[9][4].

Whether you consider intMatrix to be 10 elements long in the x dimension or in the y dimension is a matter of taste. A matrix can be initialized in the same way that an array is:

int intMatrix[2][3] = {{1, 2, 3}, {4, 5, 6}};

This line initializes the 3-element array intMatrix[0] to 1, 2, and 3; and the 3-element array intMatrix[1] to 4, 5, and 6.

Using Arrays of Characters

The elements of an array can be of any type. Arrays of floats, doubles, and longs are all possible; however, arrays of characters have particular significance.

Creating an array of characters

Human words and sentences can be expressed as an array of characters. An array of characters containing my first name would appear as

char sMyName[] = {'S', 't', 'e', 'p', 'h', 'e', 'n'};

The following small program displays my name:

// CharDisplay - output a character array to
// standard output, the MS-DOS window
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

// prototype declarations
void displayCharArray(char charArray[], int sizeOfArray);

int main(int nNumberofArgs, char* pszArgs[])
{
char charMyName[]={'S', 't', 'e', 'p', 'h', 'e', 'n'};
displayCharArray(charMyName, 7);
cout << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

// displayCharArray - display an array of characters
// by outputing one character at
// a time
void displayCharArray(char charArray[], int sizeOfArray)
{
for(int i = 0; i< sizeOfArray; i++)
{
cout << charArray[i];
}
}

The program declares a fixed array of characters charMyName containing — you guessed it — my name (what better name?). This array is passed to the function displayCharArray() along with its length. The displayCharArray() function is identical to the displayArray() function in the earlier example program except that this version displays chars rather than ints.

This program works fine; however, it is inconvenient to pass the length of the array with the array itself. If we could come up with a rule for determining the end of the string of characters, we wouldn’t need to pass its length — you would know that the string was complete when you encountered the special rule that told you so.

Creating a string of characters

In many cases, all values for each element are possible. However, C++ reserves the special “character” 0 as the non-character. You can use ‘\0' to mark the end of a character array. (The numeric value of ‘\0' is 0, but the type of ‘\0' is char.)

image The character ‘\y' is the character whose octal value is y. The character ‘\0' is the character with a value of 0, otherwise known as the null character. Using that rule, the previous small program becomes

// DisplayString - output a character array to
// standard output, the MS-DOS window
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

// prototype declarations
void displayString(char stringArray[]);

int main(int nNumberofArgs, char* pszArgs[])
{
char charMyName[] =
{'S', 't', 'e', 'p', 'h', 'e', 'n', '\0'};
displayString(charMyName);
cout << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

// displayString - display a character string
// one character at a time
void displayString(char stringArray[])
{
for(int i = 0; stringArray[i] != '\0'; i++)
{
cout << stringArray[i];
}
}

The declaration of charMyName declares the character array with the extra null character ‘\0' on the end. The displayString program iterates through the character array until a null character is encountered.

The function displayString() is simpler to use than its displayCharArray() predecessor because it is no longer necessary to pass along the length of the character array. This secret handshake of terminating a character array with a null is so convenient that it is used throughout the C++ language. C++ even gives such an array a special name.

image A string of characters is a null-terminated character array. It is officially known as a null-terminated byte string, or NTBS. The simpler term C-string is also used to differentiate from the C++ type string.

The choice of ‘\0' as the terminating character was not random. Remember that 0 is the only numeric value that converts to false; all other values translate to true. This means that the for loop could be (and usually is) written as

for(int i = 0; stringArray[i]; i++)

This whole business of null-terminated character strings is so ingrained in the C++ language psyche that C++ uses a string of characters surrounded by double quotes to be an array of characters automatically terminated with a ‘\0' character. The following are identical declarations:

char szMyName[] = "Stephen";
char szAlsoMyName[] =
{'S', 't', 'e', 'p', 'h', 'e', 'n', '\0'};

The naming convention used here is exactly that, a convention. C++ does not care. The prefix sz stands for zero-terminated string.

image The string Stephen is eight characters long and not seven — the null character after the n is assumed. The string "" is one character long, consisting of just the null character.

Manipulating Strings with Character

The following Concatenate program inputs two strings from the keyboard and concatenates them into a single string:

// Concatenate - concatenate two strings
// with a " - " in the middle
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

// prototype declarations
void concatString(char szTarget[], const char szSource[]);

int main(int nNumberofArgs, char* pszArgs[])
{
// read first string...
char szString1[256];
cout << "Enter string #1:";
cin.getline(szString1, 128);

// ...now the second string...
char szString2[128];
cout << "Enter string #2:";
cin.getline(szString2, 128);

// ...concatenate a " - " onto the first...
concatString(szString1, " - ");

// ...now add the second string...
concatString(szString1, szString2);

// ...and display the result
cout << "\n" << szString1 << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

// concatString - concatenate the szSource string
// onto the end of the szTarget string
void concatString(char szTarget[], const char szSource[])
{
// find the end of the first string
int targetIndex = 0;
while(szTarget[targetIndex])
{
targetIndex++;
}

// tack the second onto the end of the first
int sourceIndex = 0;
while(szSource[sourceIndex])
{
szTarget[targetIndex] =
szSource[sourceIndex];
targetIndex++;
sourceIndex++;
}

// tack on the terminating null
szTarget[targetIndex] = '\0';
}

The Concatenate program reads two character strings and appends them together with a " - " in the middle.

The program begins by reading a string from the keyboard. The program does not use the normal cin >> szString1 for two reasons. First, the cin >> operation stops reading when any type of whitespace is encountered. Characters up to the first whitespace are read, the whitespace character is tossed, and the remaining characters are left in the input hopper for the next cin >> statement. Thus, if I were to enter “the Dog”, szString2 would be filled with “the” and the word “Dog” would be left in the input buffer.

The second reason is that the getline() allows the programmer to specify the size of the buffer. The call to getline(szString2, 128) will not read more than 128 bytes no matter how many are input.

Instead, the call to getline() inputs an entire line up to but not including the newline at the end. We’ll review this function with other file I/O functions in detail in Chapter 23.

After reading the first string into szString1[], the program appends " - " onto the end by calling concatString(). It concatenates the second string by calling concatString() with szString2[].

The concatString() function accepts a target string, szTarget, and a source string, szSource. The function begins by scanning szTarget for the terminating null character, which it stores in targetIndex. The function then enters a second loop in which it copies characters from the szSource intoszTarget starting at the terminating null. The final statement in concatString() slaps a terminating null on the completed string.

An example output from the program appears as follows:

Enter string #1:this is a string
Enter string #2:THIS IS A STRING

this is a string - THIS IS A STRING
Press Enter to continue...

Adding Some Library Functions

The C++ programmer is often required to manipulate zero-terminated strings. C++ provides a number of standard string-manipulation functions to make the job easier. A few of these functions are listed in Table 7-1.

Table 7-1 String-Handling Functions

Name

Operation

int strlen(string)

Returns the number of characters in a string (not including the terminating null).

char* strcpy(target, source)

Copies the source string into a target array.

char* strcat(target, source)

Concatenates the source string onto the end of the target string.

char* strncpy(target, source, n)

Copies a string up to n characters from the source string into a target array.

char* strncat(target, source, n)

Concatenates the source string onto the end of the target string or n characters, whichever comes first.

char* strstr(string, pattern)

Returns the address of the first occurrence of pattern in string. Returns a null if pattern is not found.

int strcmp(source1, source2)

Compares two strings. Returns -1 if source1 occurs before source2 in the dictionary and 1 if later. Returns 0 if the two strings match exactly.

int strncmp(source1, source2, n)

Compares the first n characters in two strings.

image You need to add the statement #include <cstring> to the beginning of any program that uses a str... function because this include file contains the prototype declarations that C++ requires to check up on your work.

image The arguments to the str...() functions appear backward to any reasonable individual (you might consider this an acid test for “reasonable”). For example, the function strcat(target, source) tacks the second string source onto the end of the first argument target.

The strncpy() and strncat() functions are similar to their strcpy() and strcat() counterparts except that they accept the length of the target buffer as one of their arguments. The call strncpy(szTarget, szSource, 128) says “copy the characters in szSource into szTarget until you copy a null character or until you've copied 128 characters, whichever comes first.” This avoids inadvertently writing beyond the end of the source string array.

Making Room for Wide Strings

The standard C++ library includes similar functions to handle wide character strings. A few of these functions are listed in Table 7-2.

Table 7-2 Wide String-Handling Functions

Name

Operation

int wcslen(string)

Returns the number of wide characters in a string, not including the terminating null.

wchar_t* wcscpy(target, source)

Copies the source wide string into a target array.

wchar_t* wcscat(target, source)

Concatenates the source wide string onto the end of the target wide string.

wchar_t* wcsncpy(target, source, n)

Copies a wide string up to n characters from the source string into a target array.

wchar_t* wcsncat(target, source, n)

Concatenates the source string onto the end of the target string or n characters, whichever comes first.

wchar_t* wcsstr(string, pattern)

Finds the address of the first occurrence of pattern in string. Returns a null if pattern is not found.

int wcscmp(source1, source2)

Compares two wide strings. Returns -1 if source1 occurs before source2 in the dictionary and 1 if later. Returns 0 if the two strings match exactly.

int wcsncmp(source1, source2, n)

Compares the first n wide characters in two wide strings.

image Remember from Chapter 2 that wide characters are used for applications that must support foreign languages, where a measly 255 different characters may not be enough.

The following shows a wide character version of the Concatenate program:

// ConcatenateWide - concatenate two wide strings
// with a " - " in the middle using library routines
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

int main(int nNumberofArgs, char* pszArgs[])
{
// read first string...
wchar_t wszString1[260];
cout << "Enter string #1:";
wcin.getline(wszString1, 128);

// ...now the second string...
wchar_t wszString2[128];
cout << "Enter string #2:";
wcin.getline(wszString2, 128);

// now tack the second onto the end of the first
// with a dash in between
wcsncat(wszString1, L" - ", 260);
wcsncat(wszString1, wszString2, 260);

wcout << L"\n" << wszString1 << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The wide character string program looks similar to its single-byte character string cousin except for the following differences:

· Variables are declared wchar_t rather than char.

· Constant characters and constant strings appear preceded by an L, as in L“This is a wide string”.

· The objects wcin and wcout are used in place of cin and cout for input and output.

· The wcs … functions appear in place of the narrow str … functions.

image The output from ConcatenateWide appears identical to that of the char-based Concatenate program to those of us who do most of their input/output in European languages. The topic of writing programs capable of handling multiple languages with different alphabets and rules of grammar is known as localization and beyond the scope of a beginning book.

image ANSI C++ includes a type string designed to make it easier to manipulate strings of text. However, this type makes use of features of the language that you haven't seen yet. I return to the string type in Chapter 13.