© German Gonzalez-Morris and Ivor Horton 2020
G. Gonzalez-Morris, I. HortonBeginning Chttps://doi.org/10.1007/978-1-4842-5976-4_5

5. Arrays

German Gonzalez-Morris1  and Ivor Horton2
(1)
Santiago, Chile
(2)
STRATFORD UPON AVON, UK
 

You’ll often need to store many data values of a particular kind in your programs. In a program to track the performance of a basketball team, you might want to store the scores for a season of games and the scores for individual players. You could then output the scores for a particular player over the season or work out an ongoing average as the season progresses. Armed with what you’ve learned so far, you could write a program that does this using a different variable for each score. However, if there are a lot of games in the season, this will be rather tedious because you’ll need as many variables for each player as there are games. All your basketball scores are really the same kind of thing. The values are different, but they’re all basketball scores. Ideally, you want to be able to group these values together under a single name—perhaps the name of the player—so that you wouldn’t need separate variables for each item of data.

This chapter will show you how to do just that using arrays. I’ll then show you how powerful referencing a set of values through a single name can be when you process arrays.

In this chapter, you’ll learn
  • What arrays are

  • How to use arrays in your programs

  • How memory is used by an array

  • What a multidimensional array is

  • How to write a program to work out your hat size

  • How to write a game of tic-tac-toe

An Introduction to Arrays

The best way to show you what an array is and how powerful it can be is to go through an example. This will demonstrate how much easier a program becomes when you use an array. For this example, you’ll look at ways in which you can find the average grade score for the students in a class.

Programming Without Arrays

To find the average grade of a class of students, I’ll assume that there are only ten students in the class (mainly to avoid having to type in a lot of numbers). To work out the average of a set of numbers, you add them all together and then divide by how many there are (in this case, ten):
// Program 5.1 Averaging ten grades without storing them
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
  int grade = 0;                       // Stores a grade
  unsigned int count = 10;             // Number of values to be read
  long sum = 0L;                       // Sum of the grades
  float average = 0.0f;                // Average of the grades
  // Read the ten grades to be averaged
  for(unsigned int i = 0 ; i < count ; ++i)
  {
    printf("Enter a grade: ");
    scanf("%d", & grade);              // Read a grade
    sum += grade;                      // Add it to sum
  }
  average = (float)sum/count;          // Calculate the average
  printf(" Average of the ten grades entered is: %f ", average);
  return 0;
}

If you’re interested only in the average, then you don’t have to remember what the previous grades were. You accumulate the sum of all the values, which you then divide by count, which has the value 10. This program uses a single variable, grade, to store each grade as it is entered within the loop. The loop repeats for values of i from 0 to 9, so there are ten iterations.

Let’s assume you want to develop this into a more sophisticated program in which you need to store the values entered. Perhaps you want to output each person’s grade, with the average grade next to it. In the previous program, you had only one variable. Each time you add a grade, the old value is overwritten, and you can’t get it back.

So how can you store all the grades? You could declare ten integer variables to store the grades, but then you can’t use a loop to enter the values. You must include code that will read the values individually. This works, but it’s quite tiresome:
// Program 5.2 Averaging ten grades - storing values the hard way
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
  int grade0 = 0, grade1 = 0, grade2 = 0, grade3 = 0, grade4 = 0;
  int grade5 = 0, grade6 = 0, grade7 = 0, grade8 = 0, grade9 = 0;
  long sum = 0L;          // Sum of the grades
  float average = 0.0f;   // Average of the grades
  // Read the ten grades to be averaged
  printf("Enter the first five grades, ");
  printf("use a space or press Enter between each number. ");
  scanf("%d%d%d%d%d", & grade0, & grade1, & grade2, & grade3, & grade4);
  printf("Enter the last five numbers in the same manner. ");
  scanf("%d%d%d%d%d", & grade5, & grade6, & grade7, & grade8, & grade9);
  // Now we have the ten grades, we can calculate the average
  sum = grade0 + grade1 + grade2 + grade3 + grade4 +
        grade5 + grade6 + grade7 + grade8 + grade9;
  average = (float)sum/10.0f;
  printf(" Average of the ten grades entered is: %f ", average);
  return 0;
}

This is more or less okay for ten students, but what if your class has 30 students or 100 or 1,000? How can you do it then? This approach would become wholly impractical, and an alternative mechanism is essential.

What Is an Array?

An array is a fixed number of data items that are all of the same type. The data items in an array are referred to as elements . The elements in an array are all of type int or of type long or all of any type you choose. The following array declaration is similar to a declaration for a normal variable that contains a single value, except that you’ve placed a number between square brackets [] following the name:
 long numbers[10];

The number between square brackets defines how many elements the array contains and is called the array dimension . An array has a type, which is a combination of the element type and the number of elements in the array. Thus, two arrays are of the same type if they have the same number of elements of the same type.

Each of the data items stored in an array is accessed by the same name; in the previous statement, the array name is numbers. You select a particular element by using an index value between square brackets following the array name. Index values are sequential integers that start from zero, and 0 is the index value for the first array element. The index values for the elements in the numbers array run from 0 to 9, so the index value 0 refers to the first element and the index value 9 refers to the last element. Therefore, you access the elements in the numbers array as numbers[0], numbers[1], numbers[2], and so on, up to numbers[9]. You can see this in Figure 5-1.
../images/311070_6_En_5_Chapter/311070_6_En_5_Fig1_HTML.png
Figure 5-1.

Accessing the elements of an array

Don’t forget, index values start from zero, not one. It’s a common mistake to assume that they start from one when you’re working with arrays for the first time, and this is sometimes referred to as the off-by-one error . In a ten-element array, the index value for the last element is 9. To access the fourth value in your array, you use the expression numbers[3]. You can think of the index value for an array element as the offset from the first element. The first element is the first element, so its offset is 0. The second element is offset by 1 from the first element, the third element is offset by 2 from the first element, and so on.

You can specify an index for an array element by an expression in the square brackets following the array name. The expression must result in an integer value that corresponds to one of the possible index values. For example, you could write numbers[i-2]. If i is 3, this accesses numbers[1], the second element in the array. Thus, you can use a simple integer to explicitly reference the element that you want to access, or you can use an integer expression that’s evaluated during the execution of the program. When you use an expression, the only constraints are that it must produce an integer result and the result must be a legal index value for the array.

Note that if you use an expression for an index value that’s outside the legal range for the array, the program won’t work properly. The compiler can’t check for this, so your program will still compile, but execution is likely to be less than satisfactory. You’ll pick up a junk value from somewhere so that the results are incorrect and may vary from one run to the next. It’s possible that the program may overwrite something important and lock up your computer, so a reboot becomes necessary. It is also possible that the effect will be much more subtle with the program sometimes working and sometimes not, or the program may appear to work, but the results are wrong but not obviously so. It is therefore most important to ensure that your array indexes are always within bounds.

Using an Array

Let’s put what you’ve just learned about arrays into practice in calculating average grades.

Try it out: Averages With Arrays
You can use an array to store all the scores you want to average. This means that all the values will be saved and you’ll be able to reuse them. You can now rewrite the program to average ten scores:
// Program 5.3 Averaging ten grades - storing the values the easy way
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
  int grades[10];                          // Array storing 10 values
  unsigned int count = 10;                 // Number of values to be read
  long sum = 0L;                           // Sum of the numbers
  float average = 0.0f;                    // Average of the numbers
  printf(" Enter the 10 grades: ");      // Prompt for the input
  // Read the ten numbers to be averaged
  for(unsigned int i = 0 ; i < count ; ++i)
  {
    printf("%2u> ",i + 1);
    scanf("%d", &grades[i]);               // Read a grade
    sum += grades[i];                      // Add it to sum
  }
  average = (float)sum/count;              // Calculate the average
  printf(" Average of the ten grades entered is: %.2f ", average);
  return 0;
}
The output from the program looks something like this:
Enter the ten grades:
 1> 450
 2> 765
 3> 562
 4> 700
 5> 598
 6> 635
 7> 501
 8> 720
 9> 689
10> 527
Average of the ten grades entered is: 614.70

How It Works

You start off the program with the ubiquitous #include directive for stdio.h because you want to use printf() and scanf(). At the beginning of main(), you declare an array of ten integers and then the other variables that you’ll need for calculation:
  int grades[10];                          // Array storing 10 values
  unsigned int count = 10;                 // Number of values to be read
  long sum = 0L;                           // Sum of the numbers
  float average = 0.0f;                    // Average of the numbers

The count variable is type unsigned int because it must be nonnegative.

You prompt for the input to be entered with this statement:
  printf(" Enter the 10 grades: ");     // Prompt for the input
Next, you have a loop to read the values and accumulate the sum:
  for(unsigned int i = 0 ; i < count ; ++i)
  {
    printf("%2u> ",i + 1);
    scanf("%d", &grades[i]);               // Read a grade
    sum += grades[i];                      // Add it to sum
  }

The for loop is in the standard form with the loop continuing as long as i is less than the limit, count. Because the loop counts from 0 to 9, rather than from 1 to 10, you can use the loop variable i directly to reference each of the members of the array. The printf() call outputs the current value of i + 1 followed by >, so it has the effect you see in the output. By using %2u as the format specifier, you ensure that each value is output in a two-character field, so the numbers are aligned. If you used %u instead, the output for the tenth value would be out of alignment.

You read each grade into element i of the array using the scanf() function ; the first value will be stored in grades[0], the second number entered will be stored in grades[1], and so on up to the tenth value entered, which will be stored in grades[9]. You add each grade value to sum on each loop iteration.

When the loop ends, you calculate the average and display it with these statements:
  average = (float)sum/count;              // Calculate the average
  printf(" Average of the ten grades entered is: %.2f ", average);

You’ve calculated the average by dividing sum by count , the number of grades. Notice how you convert sum (which is type long) to type float in the call to printf(). This conversion ensures that the division is done using floating-point values, so you don’t discard any fractional part of the result. The format specification, %.2f, limits the output value for the average to two decimal places.

Try it out: Using The Element Values
I can expand the previous example to demonstrate one of the advantages of using an array. I’ve made only a minor change to the original program (highlighted in the following code in bold), so the program displays all the values that were typed in. Having the values stored in an array means that you can access those values whenever you want and process them in many different ways:
// Program 5.4 Reusing the numbers stored
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
  int grades[10];                          // Array storing 10 values
  unsigned int count = 10;                 // Number of values to be read
  long sum = 0L;                           // Sum of the numbers
  float average = 0.0f;                    // Average of the numbers
  printf(" Enter the 10 grades: ");      // Prompt for the input
  // Read the ten numbers to be averaged
  for(unsigned int i = 0 ; i < count ; ++i)
  {
    printf("%2u> ",i + 1);
    scanf("%d", &grades[i]);               // Read a grade
    sum += grades[i];                      // Add it to sum
  }
  average = (float)sum/count;              // Calculate the average
  // List the grades
  for(unsigned int i = 0 ; i < count ; ++i)
    printf(" Grade Number %2u is %3d", i + 1, grades[i]);
  printf(" Average of the ten grades entered is: %.2f ", average);
  return 0;
}
Typical output from this program would be as follows:
Enter the 10 grades:
 1> 77
 2> 87
 3> 65
 4> 98
 5> 52
 6> 74
 7> 82
 8> 88
 9> 91
10> 71
Grade Number  1 is  77
Grade Number  2 is  87
Grade Number  3 is  65
Grade Number  4 is  98
Grade Number  5 is  52
Grade Number  6 is  74
Grade Number  7 is  82
Grade Number  8 is  88
Grade Number  9 is  91
Grade Number 10 is  71
Average of the ten grades entered is: 78.50

How It Works

I’ll just explain the new bit where you reuse the array elements in a loop:
  for(unsigned int i = 0 ; i < count ; ++i)
    printf(" Grade Number %2u is %3d", i + 1, grades[i]);

This for loop steps through the elements in the array and outputs each value. You use the loop control variable to produce the sequence number for the value of the number of the element and to access the corresponding array element. These values obviously correspond to the numbers you typed in. To get the grades starting from 1, you use the expression i + 1 in the output statement so grades are numbered from 1 to 10 because i runs from 0 to 9.

Before I go any further with arrays, I’ll explain a bit more about the address of operator and how arrays are stored in memory.

The Address of Operator

The address of operator, &, produces the address in memory of its operand. You have been using the address of operator extensively with the scanf() function . You’ve been using it as a prefix to the name of the variable where the input is to be stored. This makes the address that the variable occupies available to scanf(), which allows the function to store the data that are entered from the keyboard in the variable. When you use the variable name by itself as an argument to a function, only the value stored in the variable is available to the function. Prefixing the variable name with the address of operator makes the address of the variable available to the function. This enables the function to modify the value that’s stored in the variable. You will learn why this is so in Chapter 8. Let’s see what some addresses look like.

Try it out: Using The Address of Operator
The following program outputs the addresses of some variables:
// Program 5.5 Using the & operator
#include<stdio.h>
int main(void)
{
  // Define some integer variables
  long a = 1L;
  long b = 2L;
  long c = 3L;
  // Define some floating-point variables
  double d = 4.0;
  double e = 5.0;
  double f = 6.0;
  printf("A variable of type long occupies %u bytes.", sizeof(long));
  printf(" Here are the addresses of some variables of type long:");
  printf(" The address of a is: %p  The address of b is: %p", &a, &b);
  printf(" The address of c is: %p", &c);
  printf(" A variable of type double occupies %u bytes.", sizeof(double));
  printf(" Here are the addresses of some variables of type double:");
  printf(" The address of d is: %p  The address of e is: %p", &d, &e);
  printf(" The address of f is: %p ", &f);
  return 0;
}
Output from this program will be something like this:
A variable of type long occupies 4 bytes.
Here are the addresses of some variables of type long:
The address of a is: 000000000012ff14  The address of b is: 000000000012ff10
The address of c is: 000000000012ff0c
A variable of type double occupies 8 bytes.
Here are the addresses of some variables of type double:
The address of d is: 000000000012ff00  The address of e is: 000000000012fef8
The address of f is: 000000000012fef0

The addresses that you get will almost certainly be different from these. The addresses depend on the operating system you’re using and how your compiler allocates memory.

How It Works

You declare three variables of type long and three of type double:
  // Define some integer variables
  long a = 1L;
  long b = 2L;
  long c = 3L;
  // Define some floating-point variables
  double d = 4.0;
  double e = 5.0;
  double f = 6.0;
Next, you output the number of bytes occupied by variables of type long, followed by the addresses of the three variables of that type that you created:
  printf("A variable of type long occupies %u bytes.", sizeof(long));
  printf(" Here are the addresses of some variables of type long:");
  printf(" The address of a is: %p  The address of b is: %p", &a, &b);
  printf(" The address of c is: %p", &c);

You use %u for the value produced by sizeof because it will be an unsigned integer value. You use a new format specifier, %p, to output the addresses of the variables. This format specifier is for outputting a memory address, and the value is presented in hexadecimal format. A memory address is typically 32 or 64 bits, and the size of the address will determine the maximum amount of memory that can be referenced. A memory address on my computer is 64 bits and is presented as 16 hexadecimal digits; on your machine, it may be different.

You then output the size of variables of type double , followed by the addresses of the three variables of that type that you also created:
   printf(" A variable of type double occupies %u bytes.", sizeof(double));
   printf(" Here are the addresses of some variables of type double:");
   printf(" The address of d is: %p  The address of e is: %p", &d, &e);
   printf(" The address of f is: %p ", &f);
The interesting part isn’t the program itself so much as the output. Look at the addresses that are displayed. You can see that the value of the address gets steadily lower in a regular pattern, as shown in Figure 5-2. On my computer, the address of b is 4 lower than that of a, and c is also lower than b by 4. This is because each variable of type long occupies 4 bytes. There’s a similar situation with the variables d, e, and f, except that the difference is 8. This is because 8 bytes are used to store a value of type double .
../images/311070_6_En_5_Chapter/311070_6_En_5_Fig2_HTML.png
Figure 5-2.

Addresses of variables in memory

There’s a gap between the locations of the variables d and c in Figure 5-2. Why is this? Many compilers allocate space for variables at addresses that are a multiple of their size, so 4-byte variables are at addresses that are a multiple of 4, and 8-byte variables are at addresses that are a multiple of 8. This ensures that accessing the memory is done most efficiently. My compiler left the 4-byte gap between d and c to make the address of d a multiple of 8. If the program defined another variable of type long following c, it would occupy the 4-byte gap, and no gap would be apparent.

Caution If the output shows that the addresses for the variables of the same type are separated by greater amounts than the size for the type, it is most likely because you compiled the program as a debug version. In debug mode, your compiler allocates extra space to store additional information about the variable that will be used when you’re executing the program in debug mode.

Arrays and Addresses

Here’s a declaration for an array with four elements:
long number[4];

The array name, number, identifies the address in memory where the array elements are stored. The specific location of an element is found by combining the address corresponding to the array name with the index value, because the index value represents the offset of a number of elements from the beginning of the array.

When you declare an array, you give the compiler all the information it needs to allocate the memory for it. The type of value determines the number of bytes that each element will require, and the array dimension specifies the number of elements. The number of bytes that an array will occupy is the number of elements multiplied by the size of each element. The address of an array element is going to be the address where the array starts, plus the product of the index value for the element and the number of bytes required to store each element. Figure 5-3 represents the way that array variables are held in memory.
../images/311070_6_En_5_Chapter/311070_6_En_5_Fig3_HTML.png
Figure 5-3.

The organization of an array in memory

You can obtain the address of an array element in the same way as for an ordinary variable. For a variable with the name value, you would use the following statement to print its address:
printf(" %p", &value);
To output the address of the third element of an array with the name number, you could write the following:
printf(" %p", &number[2]);
The following fragment sets a value for each element in an array and outputs the address and contents of each element:
int data[5];
for(unsigned int i = 0 ; i < 5 ; ++i)
{
  data[i] = 12*(i + 1);
  printf("data[%d] Address: %p  Contents: %d ", i, &data[i], data[i]);
}
The for loop variable i iterates over all the legal index values for the data array. Within the loop, the value of the element at index position i is set to 12*(i + 1). The output statement displays the current element with its index value, the address of the current array element determined by the current value of i, and the value stored within the element. If you make this fragment into a program, the output will be similar to the following:
data[0] Address: 000000000012fee4  Contents: 12
data[1] Address: 000000000012fee8  Contents: 24
data[2] Address: 000000000012feec  Contents: 36
data[3] Address: 000000000012fef0  Contents: 48
data[4] Address: 000000000012fef4  Contents: 60

The value of i is displayed between the square brackets following the array name. You can see that the address of each element is 4 greater than the previous element, so each element occupies 4 bytes.

Initializing an Array

Of course you will want to assign initial values for the elements of your array most of the time, even if it’s only for safety’s sake. Defining initial values for array elements makes it easier to detect when things go wrong. To initialize the elements of an array, you just specify the list of initial values between braces and separate them by commas in the declaration, for example:
double values[5] = { 1.5, 2.5, 3.5, 4.5, 5.5 };

This declares the values array with five elements. The elements are initialized with values[0] having the value 1.5, value[1] having the initial value 2.5, and so on.

To initialize the whole array, there must be one value for each element. If there are fewer initializing values than elements, the elements without initializing values will be set to 0. Thus, if you write
double values[5] = { 1.5, 2.5, 3.5 };

the first three elements will be initialized with the values between braces, and the last two elements will be initialized with 0.

Knowing that the compiler will supply zeroes for elements for which you don’t provide, an initial value offers an easy way to initialize an entire array to zero. You just need to supply one element with the value of 0:
double values[5] = {0.0};

The entire array will then be initialized with 0.0.

If you put more initializing values than there are array elements, you’ll get an error message from the compiler. However, you can omit the size of the array when you specify a list of initial values. In this case, the compiler will assume that the number of elements is the number of values in the list:
int primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29};

The size of the array is determined by the number of initial values in the list, so the primes array will have ten elements.

Finding the Size of an Array

You’ve already seen that the sizeof operator computes the number of bytes that a variable of a given type occupies. You can apply the sizeof operator to a type name like this:
printf("The size of a variable of type long is %zu bytes. ", sizeof(long));

The parentheses around the type name following the sizeof operator are required. If you leave them out, the code won’t compile. As you know, you can also apply the sizeof operator to a variable, and it will compute the number of bytes occupied by the variable.

Note

The sizeof operator produces a value of type size_t, which is an implementation-defined unsigned integer type. If you use the %u specifier for output and your compiler happens to define size_t as unsigned long or unsigned long long, you may get warning messages from the compiler that the specifier %u does not match the value being output by the printf() function. Using %zu will eliminate the warning messages.

The sizeof operator works with arrays too. Suppose that you declare an array with the following statement:
double values[5] = { 1.5, 2.5, 3.5, 4.5, 5.5 };
You can output the number of bytes that the array occupies with the following statement:
printf("The size of the array, values, is %zu bytes. ", sizeof values);
This will produce the following output:
The size of the array, values, is 40 bytes.
You can also obtain the number of bytes occupied by a single element of the array with the expression sizeof values[0]. This expression will have the value 8. Of course, any legal index value for an element could be used to produce the same result. The memory occupied by an array is the size of a single element multiplied by the number of elements. Thus, you can use the sizeof operator to calculate the number of elements in an array:
size_t element_count = sizeof values/sizeof values[0];

After executing this statement, element_count will contain the number of elements in the values array. I declared element_count to be type size_t because that is the type that the sizeof operator produces.

Because you can apply the sizeof operator to a data type, you could have written the previous statement to calculate the number of array elements as follows:
size_t element_count = sizeof values/sizeof(double);

The result is the same as before because the array is of type double, so sizeof(double) produces the number of bytes occupied by an element. There’s the risk that you might accidentally use the wrong type, so it’s better to use the former statement in practice.

Although the sizeof operator doesn’t require the use of parentheses when applied to a variable, it’s common practice to use them anyway, so the earlier example could be written as follows:
double values[5] = { 1.5, 2.5, 3.5, 4.5, 5.5 };
size_t element_count = sizeof(values)/sizeof(values[0]);
printf("The size of the array is %zu bytes ", sizeof(values));
printf("and there are %u elements of %zu bytes each ", element_count, sizeof(values[0]));
The output from these statements will be the following:
The size of the array is 40 bytes and there are 5 elements of 8 bytes each
You can use the sizeof operator when you use a loop to process all the elements in an array, for example:
double values[5] = { 1.5, 2.5, 3.5, 4.5, 5.5 };
double sum = 0.0;
for(unsigned int i = 0 ; i < sizeof(values)/sizeof(values[0]) ; ++i)
  sum += values[i];
printf("The sum of the values is %.2f", sum);

This loop totals the values of all the array elements. Using sizeof to compute the number of elements in the array ensures that the upper limit for the loop variable, i, is always correct, whatever the size of the array.

Multidimensional Arrays

Let’s start with two dimensions and work our way up. A two-dimensional array can be declared as follows:
float carrots[25][50];

This declares the carrots array with 25 sets of 50 floating-point elements. Note how each dimension is between its own pair of square brackets.

Similarly, you could declare another two-dimensional array of floating-point numbers with this statement:
float numbers[3][5];
You can visualize a two-dimensional array as a rectangular arrangement like rows of vegetables in a field. You can visualize the numbers array as having three rows and five columns. They’re actually stored in memory sequentially by row, as shown in Figure 5-4. You can see that the rightmost index varies most rapidly. The left index conceptually selects a row, and the right index selects an element within the row.
../images/311070_6_En_5_Chapter/311070_6_En_5_Fig4_HTML.png
Figure 5-4.

Organization of a 3 × 5 element array in memory

There’s another way of looking at a two-dimensional array. Figure 5-4 also illustrates how you can envision a two-dimensional array as a one-dimensional array of elements, where each element is itself a one-dimensional array. You can view the numbers array as a one-dimensional array of three elements, where each element is an array containing five elements of type float. The first row of five is located at the address labeled numbers[0], the next row at numbers[1], and the last row of five elements at numbers[2].

The amount of memory allocated to each element is, of course, dependent on the type of variables that the array contains. An array of type double will need more memory to store each element than an array of type float or type int. Figure 5-5 illustrates how an array with four rows of ten elements of type float is stored.
../images/311070_6_En_5_Chapter/311070_6_En_5_Fig5_HTML.png
Figure 5-5.

Memory occupied by a 4 × 10 array

Because the array elements are of type float, which on my machine occupies 4 bytes, the total memory occupied by this array on my computer will be 4 × 10 × 4 bytes, which amounts to a total of 160 bytes.

A three-dimensional array is a straightforward extension of a two-dimensional array:
double beans[4] [10][20];         // 4 fields, each with 10 rows of 20 beans

This declares an array with 800 elements. You can visualize it as storing yields from bean plants where there are three fields, each containing 10 rows of 20 plants. I’m sure you can see that the idea can be extended to define arrays with as many dimensions as you require.

Initializing Multidimensional Arrays

Initializing a two-dimensional array is similar to what you have seen for a one-dimensional array. The difference is that you put the initial values for each row between braces, {}, and then enclose all the rows between braces:
int numbers[3][4] = {
                      { 10, 20, 30, 40 },        // Values for first row
                      { 15, 25, 35, 45 },        // Values for second row
                      { 47, 48, 49, 50 }         // Values for third row
                    };

Each set of values that initializes the elements in a row is between braces, and the whole lot goes between another pair of braces. The values for a row are separated by commas, and each set of row values is separated from the next set by a comma.

If you specify fewer initializing values than there are elements in a row, the values will be assigned to row elements in sequence, starting with the first. The remaining elements in a row that are left when the initial values have all been assigned will be initialized to 0. You can initialize the whole array to 0 by supplying just one value:
int numbers[3][4] = {0};
For arrays of three or more dimensions, the process is extended. A three-dimensional array, for example, will have three levels of nested braces, with the inner level containing sets of initializing values for a row:
int numbers[2][3][4] = {
                           {                       // First block of 3 rows
                             { 10, 20, 30, 40 },
                             { 15, 25, 35, 45 },
                             { 47, 48, 49, 50 }
                           },
                           {                       // Second block of 3 rows
                             { 10, 20, 30, 40 },
                             { 15, 25, 35, 45 },
                             { 47, 48, 49, 50 }
                          }
                       };

As you can see, the initializing values are between an outer pair of braces that enclose two blocks of three rows, each between braces. Each row is also between braces, so you have three levels of nested braces for a three-dimensional array. This is true generally; for instance, a six-dimensional array will have six levels of nested braces enclosing the initial values for the elements. You can omit the braces around the list for each row, and the initialization will still work; but including the braces for the row values is much safer because you are much less likely to make a mistake. Of course, if you want to supply fewer initial values than there are elements in a row, you must include the braces around the row values.

You need a nested loop to process all the elements in a multidimensional array. The level of nesting will be the number of array dimensions. Here’s how you could sum the elements in the previous numbers array:
int sum = 0;
for(int i = 0 ; i < 2 ; ++i)
{
  for(int j = 0 ; j < 3 ; ++j)
  {
    for(int k = 0 ; k < 4 ; ++k)
    {
      sum += numbers[i][j][k];
    }
  }
}
printf("The sum of the values in the numbers array is %d.", sum);

Each loop iterates over one array dimension. For each value of i, the loop controlled by j will execute completely, and for each value of j, the loop controlled by k will execute completely.

You can use the sizeof operator to work out the number of elements in each dimension of a multidimensional array. You just need to be clear on what the sizeof operator is producing. Here’s the previous loop using the sizeof operator to compute the loop control limits:
for(int i = 0 ; i < sizeof(numbers)/sizeof(numbers[0]) ; ++i)
{
  for(int j = 0 ; j < sizeof(numbers[0])/sizeof(numbers[0][0]) ; ++j)
  {
    for(int k = 0 ; k < sizeof(numbers[0][0])/sizeof(numbers[0][0][0])  ; ++k)
    {
      sum += numbers[i][j][k];
    }
  }
}

You can visualize the numbers array as an array of two-dimensional arrays. The expression sizeof(numbers) results in the number of bytes that the entire numbers array occupies, and sizeof(numbers[0]) results in the number of bytes occupied by one of the two-dimensional subarrays. Thus, the expression sizeof(numbers)/sizeof(numbers[0]) is going to result in the number of elements in the first array dimension. Similarly, you can visualize each two-dimensional subarray as a one-dimensional array of one-dimensional arrays. Dividing the size of a two-dimensional array by the size of one of its subarrays results in the number of subarrays, which is the second numbers dimension. Finally, dividing the size of the one-dimensional sub-subarray by the size of one element results in the third dimension value.

Try it out: Using Multidimensional Arrays
Let’s move away from vegetables and turn to a more practical application. You can use arrays in a program to avoid a significant health and safety issue. As you may know, wearing a hat that is too large is dangerous. It can fall over your eyes, so you may bump into things, causing injury or even death. Equally, wearing a hat that is too small can result in persistent headaches and make you look foolish. This invaluable program will use arrays to work out your correct hat size in the units commonly used in the United States and the United Kingdom, where hat sizes typically vary from 6 1/2 to 7 7/8. Other countries have more transparent hat sizes that cause fewer problems at home, but if you are a foreign visitor to the United States or the United Kingdom, buying a hat while you are away can be even more hazardous. You just enter the circumference of your head, in inches of course, and your hat size will be displayed instantly:
// Program 5.6 Know your hat size - if you dare...
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
   /******************************************************
    * The size array stores hat sizes from 6 1/2 to 7 7/8 *
    * Each row defines one character of a size value so   *
    * a size is selected by using the same index for each *
    * the three rows. e.g. Index 2 selects 6 3/4.         *
    ******************************************************/
   char size[3][12] = {           // Hat sizes as characters
      {'6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7'},
      {'1', '5', '3', '7', ' ', '1', '1', '3', '1', '5', '3', '7'},
      {'2', '8', '4', '8', ' ', '8', '4', '8', '2', '8', '4', '8'}
                      };
  int headsize[12] =              // Values in 1/8 inches
      {164,166,169,172,175,178,181,184,188,191,194,197};
  float cranium = 0.0f;               // Head circumference in decimal inches
  int your_head = 0;                  // Headsize in whole eighths  
  bool hat_found = false;             // Indicates when a hat is found to fit
  // Get the circumference of the head
  printf(" Enter the circumference of your head above your eyebrows "
       "in inches as a decimal value: ");
  scanf(" %f", &cranium);
  your_head = (int)(8.0f*cranium);     // Convert to whole eighths of an inch
  /*****************************************************************
   * Search for a hat size:                                        *
   * Either your head corresponds to the 1st headsize element or   *
   * a fit is when your_head is greater that one headsize element  *
   * and less than or equal to the next.                           *
   * In this case the size is the second headsize value.           *
   *****************************************************************/
  size_t i = 0;                 // Loop counter
  if(your_head == headsize[i])  // Check for min size fit
    hat_found = true;
  else
  {
    for (i = 1 ; i < sizeof(headsize) ; ++i)
    {
      // Find head size in the headsize array
      if(your_head > headsize[i - 1] && your_head <= headsize[i])
      {
        hat_found = true;
         break;
      }
    }
  }
  if(hat_found)
  {
    printf(" Your hat size is %c %c%c%c ",
           size[0][i], size[1][i],
           (size[1][i]==' ') ? ' ' : '/', size[2][i]);
  }
  else  // If no hat was found, the head is too small, or too large
  {
    if(your_head < headsize[0])        // check for too small
      printf(" You are the proverbial pinhead. No hat for"
                                                 " you I'm afraid. ");
    else                               // It must be too large
      printf(" You, in technical parlance, are a fathead."
                                  " No hat for you, I'm afraid. ");
  }
  return 0;
}
Typical output from this program would be this:
Enter the circumference of your head above your eyebrows in inches as a decimal
 value: 22.5
Your hat size is 7 1/4
or possibly this:
Enter the circumference of your head above your eyebrows in inches as a decimal
 value: 29
You, in technical parlance, are a fathead. No hat for you I'm afraid.

How It Works

Before I start discussing this example, I should give you a word of caution. Do not allow large football players to use it to determine their hat size unless they’re known for their sense of humor.

The example looks a bit complicated because of the nature of the problem, but it does illustrate using arrays. Let’s go through what’s happening.

The first declaration in the body of main() is a two-dimensional array:
   char size[3][12] = {           // Hat sizes as characters
      {'6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7'},
      {'1', '5', '3', '7', ' ', '1', '1', '3', '1', '5', '3', '7'},
      {'2', '8', '4', '8', ' ', '8', '4', '8', '2', '8', '4', '8'}
                      };

Apart from hats that are designated as “one size fits all” or as small, medium, and large, hats are typically available in sizes from 6 1/2 to 7 7/8 in increments of 1/8 inch. The size array shows one way in which you could store such sizes in the program. This array corresponds to 12 possible hat sizes, each of which comprises three values. For each hat size, you store three characters, making it more convenient to output the fractional sizes. The smallest hat size is 6 1/2, so the first three characters corresponding to the first size are in size[0][0], size[1][0], and size[2][0]. They contain the characters '6', '1', and '2', representing the size 6 1/2. The biggest hat size is 7 7/8, and it’s stored in the elements size[0][11], size[1][11], size[2][11].

You then declare the headsize array that provides the reference head dimensions in this declaration:
   int headsize[12] =         // Values in 1/8 inches
                      {164,166,169,172,175,178,181,184,188,191,194,197};

The values in the array are all whole eighths of an inch. They correspond to the values in the size array containing the hat sizes. This means that a head size of 164 eighths of an inch (20.5 inches) will give a hat size of 6 1/2, and at the other end of the scale, 197 eighths corresponds to a hat size of 7 7/8.

Notice that the head sizes don’t run consecutively. You could get a head size of 171, for example, which doesn’t fall into a definite hat size. You need to be aware of this later in the program so that you can decide which is the closest hat size for the head size.

After declaring your arrays, you then declare the variables you’re going to need:
  float cranium = 0.0f;               // Head circumference in decimal inches
  int your_head = 0;                  // Headsize in whole eighths
  bool hat_found = false;             // Indicates when a hat is found to fit

Notice that cranium is declared as type float, but your_head is type int. This becomes important later. You declare the variable hat_found as type bool so you use the symbol false to initialize this. The hat_found variable will record when you have found a size that fits.

Next, you prompt for your head size to be entered in inches, and the value is stored in the variable cranium (remember it’s type float, so you can store values that aren’t whole numbers):
  printf(" Enter the circumference of your head above your eyebrows "
      "in inches as a decimal value: ");
  scanf(" %f", &cranium);
The value stored in cranium is then converted into eighths of an inch with this statement:
your_head = (int)(8.0f*cranium);

Because cranium contains the circumference of a head in inches, multiplying by 8.0f results in the number of eighths of an inch that that represents. Thus, the value stored in your_head will then be in the same units as the values stored in the array headsize. Note that you need to cast the result of the multiplication to type int here to avoid a warning message from the compiler. The code will still work if you omit the cast, but the compiler must then insert the cast to type int. The warning is because this cast potentially loses information. The parentheses around the expression (8.0f*cranium) are also necessary; without them, you would only cast the value 8.0f to type int, not the whole expression.

You use the value stored in your_head to find the closest value in the headsize array:
  size_t i = 0;                 // Loop counter
  if(your_head == headsize[i])  // Check for min size fit
    hat_found = true;
  else
  {
    for (i = 1 ; i < sizeof(headsize) ; ++i)
    {
      // Find head size in the headsize array
      if(your_head > headsize[i - 1] && your_head <= headsize[i])
      {
        hat_found = true;
         break;
      }
    }
  }

You declare the loop index, i, before the loop because you want to use the value outside the loop. The if-else statement first checks for the head size matching the first headsize array element, in which case you have found it. When this is not the case, the for loop is executed. The loop index runs from the second element in the array to the last element. This is necessary because you use i - 1 to index the headsize array in the if expression. On each loop iteration, you compare your head size with a pair of successive values stored in the headsize array to find the element value that is greater than or equal to your input size with the preceding value less than your input size. The index found will correspond to the hat size that fits.

Next, you output the hat size if the value of hat_found is true:
  if(hat_found)
  {
    printf(" Your hat size is %c %c%c%c ",
           size[0][i], size[1][i],
           (size[1][i]==' ') ? ' ' : '/', size[2][i]);
  }

As I said earlier, the hat sizes are stored in the array size as characters to simplify the outputting of fractions. The printf() uses the conditional operator to decide when to output a space and when to output a slash (/) for the fractional output value. For example, the fifth element of the headsize array corresponds to a hat size of exactly 7. You want to output 7 rather than 7/. Therefore, you customize the printf() output depending on whether or not the size[1][i] element contains a space character. In this way, you omit the slash for any size where the numerator of the fractional part is a space, so this will still work even if you add new sizes to the array.

Of course, it may be that no hat was found because either the head was too small or too large for the hat sizes available, and the else clause for the if statement deals with that situation because the else executes if hat_found is false:
  // If no hat was found, the head is too small, or too large
  else
  {
    if(your_head < headsize[0])        // check for too small
      printf(" You are the proverbial pinhead. No hat for"
                                                 " you I'm afraid. ");
    else                               // It must be too large
      printf(" You, in technical parlance, are a fathead."
                                  " No hat for you, I'm afraid. ");
  }

If the value in your_head is less than the first headsize element , the head is too small for the available hats; otherwise, it must be too large.

Remember that if you lie about the size of your head when you use this program, your hat won’t fit. The more mathematically astute, and any hatters reading this book, will appreciate that the hat size is simply the diameter of a notionally circular head. Therefore, if you have the circumference of your head in inches, you can produce your hat size by dividing this value by π.

Constant Arrays

We have seen constants in integers so far. In present times, the immutable concept can be a beneficial and safe way to code (trendy in functional programming), but this is not the case; however, we would like to achieve the same behavior.

Sometimes we need to manage constant values of past events, such as the best 10 ELO scores of all time; this can implement like
  const int elo[10] = {2882, 2851, 2844, 2830, 2822, 2820, 2819, 2817, 2816, 2810};

The keyword "const" can also be used for generating string literal or constant string (to learn more details of strings, please check the next chapter).

This means that if, after definition, we try to modify the value, a compilation error will be thrown (see Program 5.6a).

Another practical example is to give relative values to chess pieces and add them up, retrieving a final score to choose which is the best move (a technique for pruning in a search tree algorithm).

The value can vary depending on the state of the game (opening, middle game, ending). We will use the most traditional values:

Values of the pieces: pawn = 1 knight = 3 bishop = 3 rook = 5 queen = 9
// Program 5.1b  Modifying constant array
#include <stdio.h>
//these constants are redundant for educational purpose
// King's value is undefined
#define king 0
#define pawn 1
#define knight 2
#define bishop 3
#define rook 4
#define queen 5
//white pieces respectively:
// 6 7 8 9 10
int main(void)
{
  const int values[6] = {0, 1, 3, 3, 5, 9};
  int board[8][8] = { // chessboard
          { 0, 0, 4, 0, 0, 0, 0, 0,},
          { 4, 0, 0, 2, 5, 0, 1, 0,},
          { 1, 0, 0, 0, 1, 0, 0, 1,},
          { 0, 0, 1, 0, 0, 0, 0, 0,},
          { 0, 0, 0, 1, 6, 6, 0, 0,},
          { 0, 0, 0, 0, 0, 0, 0,10,},
          { 6, 6, 0, 0, 8, 0, 6, 6,},
          { 0, 0, 9, 0, 0, 9, 0, 0,}
  };
  int score = 0;
  for(int i=0; i<8; i++) {
    for(int j=0; j<8; j++) {
      //we separate colors:
          if(board[i][j]<6)
            score += values[board[i][j]];
          else
            score -= values[board[i][j]%6];
        }
  }
  printf(" Score : %d ", score);
  if(score>0) printf(" Spassky is winning to Fischer");
  else printf(" Fischer is winning to Spassky");
  printf("... for now ! ");
  return 0;
}

Then we can iterate the chessboard and check the final score.

Variable-Length Arrays

So far, all the arrays have had fixed dimensions that you specify in the code. You can also define arrays where the dimensions are determined at runtime, when you execute the program. Here’s an example:
size_t size = 0;
printf("Enter the number of elements you want to store: ");
scanf("%zd", &size);
float values[size];

In this fragment, you read a value from the keyboard into size. The value of size is then used to specify the dimension for the values array. Because size_t is an implementation-defined integer type, you may get compiler errors if you try to read such a value using %d. The z in %zd tells the compiler that it applies to size_t, so the compiler will make the specifier suitable for reading whatever integer type size_t is.

You can also define arrays with two or more dimensions with any or all of the dimensions determined at execution time, for example:
size_t rows = 0;
size_t columns = 0;
printf("Enter the number of rows you want to store: ");
scanf("%zd", &rows);
printf("Enter the number of columns in a row: ");
scanf("%zd", &columns);
float beans[rows][columns];

Here you read both dimensions for a two-dimensional array from the keyboard. Both array dimensions are determined at execution time.

A C11-conforming compiler does not have to implement support for variable-length arrays (VLAs) because it is an optional feature. If it does not, the symbol __STDC_NO_VLA__ must be defined as 1. You can check for support for variable-length arrays using this code:
#ifdef   __STDC_NO_VLA__
  printf("Variable length arrays are not supported. ");
  exit(1);
#endif

This uses preprocessor directives that you’ll learn about in Chapter 13. The printf() statement and the following exit() statement will be included in the program if the symbol __STDC_NO_VLA__ is defined. If you place this fragment at the beginning of main(), if variable-length arrays are not supported, you’ll see a message from the printf() function call, and the program will end immediately. Despite the fact that constant __STDC_NO_VLA__ is 1 in Visual Studio 2019, this compiler does not support VLA.

You can see a variable-length, one-dimensional array working in a revised version of Program 5.3 as shown in Program 5.7.

Try it out: Using a Variable-Length Array
This program calculates an average grade, but this time allocates an array that accommodates the exact number of grades to be entered:
// Program 5.7 Averaging a variable number of grades
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
  size_t nGrades = 0;                           // Number of grades
  printf("Enter the number of grades: ");
  scanf("%zd", &nGrades);
  int grades[nGrades];                          // Array storing nGrades values
  long sum = 0L;                                // Sum of the numbers
  float average = 0.0f;                         // Average of the numbers
  printf(" Enter the %zd grades: ", nGrades); // Prompt for the input
  // Read the ten numbers to be averaged
  for(size_t i = 0 ; i < nGrades ; ++i)
  {
    printf("%2zd> ",i + 1);
    scanf("%d", &grades[i]);                    // Read a grade
    sum += grades[i];                           // Add it to sum
  }
  printf("The grades you entered are: ");
  for(size_t i = 0 ; i < nGrades ; ++i)
  {
    printf("Grade[%2zd] = %3d  ", i + 1, grades[i]);
    if((i+1) % 5 == 0)                          // After 5 values
      printf(" ");                             // Go to a new line
  }
  average = (float)sum/nGrades;                 // Calculate the average
  printf(" Average of the %zd grades entered is: %.2f ", nGrades, average);
}
Here is some typical output:
Enter the number of grades: 12
Enter the 12 grades:
 1> 56
 2> 67
 3> 78
 4> 67
 5> 68
 6> 56
 7> 88
 8> 98
 9> 76
10> 75
11> 87
12> 72
The grades you entered are:
Grade[ 1] =  56  Grade[ 2] =  67  Grade[ 3] =  78  Grade[ 4] =  67  Grade[ 5] =  68
Grade[ 6] =  56  Grade[ 7] =  88  Grade[ 8] =  98  Grade[ 9] =  76  Grade[10] =  75
Grade[11] =  87  Grade[12] =  72
Average of the 12 grades entered is: 74.00

How It Works

You define a variable, nGrades, that will store the number of grades to be entered and read the value from the keyboard:
  size_t nGrades = 0;                           // Number of grades
  printf("Enter the number of grades: ");
  scanf("%zd", &nGrades);
You use the value read into nGrades to define the grades array with the exact number of elements required:
  int grades[nGrades];                         // Array storing nGrades values

Obviously, the value for the array dimension must be defined prior to this statement.

The rest of the code is what you have seen before, except that input and output of the size_t values use the %zd specifier. Note how the remainder operator is used in the loop that outputs the grades to start a new line after every fifth output value.

Note

The Microsoft Windows command line may be too narrow to display five grades. If so, you can output fewer per line by changing the code, or you can change the default size of the window by clicking the icon at the left of the title bar and selecting Properties from the menu.

Designing a Program

Now that you’ve learned about arrays, let’s see how you can apply them in a bigger problem. Let’s try writing another game—tic-tac-toe—also known as noughts and crosses.

The Problem

Implementing the game with the computer as the adversary is beyond what I have covered up to now, so you are just going to write a program that allows two people to play tic-tac-toe.

The Analysis

Tic-tac-toe is played on a 3 × 3 grid of squares. Two players take turns entering either an X or an O in the grid. The first player to get three of his or her symbols in a line horizontally, vertically, or diagonally is the winner. You know how the game works, but how does that translate into designing your program? You’ll need the following:
  • A 3 × 3 grid in which to store the turns of the two players: That’s easy. You can use a two-dimensional array with three rows of three elements.

  • A simple way for a player to select a square on his or her turn: You can label the nine squares with digits from 1 to 9. A player will just need to enter the number of the square to select it.

  • A way to get the two players to take alternate turns: You can identify the two players as 1 and 2, with player 1 going first. You can then determine the player number by the number of the turn. On odd-numbered turns, it’s player 1. On even-numbered turns, it’s player 2.

  • Some way of specifying where to place the player symbol on the grid and checking to see if it’s a valid selection: A valid selection is a digit from 1 to 9. If you label the first row of squares with 1, 2, and 3, the second row with 4, 5, and 6, and the third row with 7, 8, and 9, you can calculate a row index and a column index from the square number. Let’s assume the player’s choice is stored in a variable, choice.
    • If you subtract 1 from the player’s chosen square number in choice, the square numbers are effectively 0 through 8, as shown in the following image:

      ../images/311070_6_En_5_Chapter/311070_6_En_5_Figa_HTML.gif

    • Then the expression choice/3 gives the row number, as you can see here:

      ../images/311070_6_En_5_Chapter/311070_6_En_5_Figb_HTML.gif

    • The expression choice%3 will give the column number:

      ../images/311070_6_En_5_Chapter/311070_6_En_5_Figc_HTML.gif

  • A method of finding out if one of the players has won: After each turn, you must check to see if any row, column, or diagonal in the board grid contains identical symbols. If it does, the last player has won.

  • A way to detect the end of the game: Because the board has nine squares, a game consists of up to nine turns. The game ends when a winner is discovered or after nine turns.

The Solution

This section outlines the steps you’ll take to solve the problem.

Step 1

You can first add the code for the main game loop and the code to display the board:
// Program 5.8 Tic-Tac-Toe
#include <stdio.h>
int main(void)
{
  int player = 0;                       // Current player number - 1 or 2
  int winner = 0;                       // The winning player number
  char board[3][3] = {                  // The board
              {'1','2','3'},            // Initial values are characters '1' to '9'
              {'4','5','6'},            // used to select a vacant square
              {'7','8','9'}             // for a player's turn.
                     };
  // The main game loop. The game continues for up to 9 turns
  // as long as there is no winner
  for(unsigned int i = 0; i < 9 && winner == 0; ++i)
  {
    // Display the board
    printf(" ");
    printf(" %c | %c | %c ", board[0][0], board[0][1], board[0][2]);
    printf("---+---+--- ");
    printf(" %c | %c | %c ", board[1][0], board[1][1], board[1][2]);
    printf("---+---+--- ");
    printf(" %c | %c | %c ", board[2][0], board[2][1], board[2][2]);
    player = i%2 + 1;                   // Select player
    /* Code to play the game */
  }
  /* Code to output the result */
  return 0;
}

Here, you’ve declared the following variables: i, for the loop variable; player, which stores the identifier for the current player, 1 or 2; winner, which contains the identifier for the winning player; and the array board, which is of type char, because you want to place the symbol 'X' or 'O' in the squares. You initialize the array with the characters for the digits that identify the squares. The main game loop continues for as long as the loop condition is true. It will be false if winner contains a value other than 0 (which indicates that a winner has been found) or the loop counter is equal to or greater than 9 (which will be the case when all nine squares on the board have been filled).

When you display the grid in the loop, you use vertical bars and dash characters to delineate the squares. When a player selects a square, the symbol for that player will replace the digit character.

Step 2

Next, you can implement the code for the player to select a square and to ensure that the square is valid:
// Program 5.8 Tic-Tac-Toe
#include <stdio.h>
int main(void)
{
  int player = 0;                       // Current player number - 1 or 2
  int winner = 0;                       // The winning player number
  int choice = 0;                   // Chosen square
  unsigned int row = 0;             // Row index for a square
  unsigned int column = 0;          // Column index for a square
  char board[3][3] = {                  // The board
              {'1','2','3'},            // Initial values are characters '1' to '9'
              {'4','5','6'},            // used to select a vacant square
              {'7','8','9'}             // for a player's turn.
                     };
  // The main game loop. The game continues for up to 9 turns
  // as long as there is no winner
  for(unsigned int i = 0; i < 9 && winner == 0; ++i)
  {
    // Code to display the board as before...
    player = i%2 + 1;                   // Select player
    // Get valid player square selection
    do
    {
      printf("Player %d, please enter a valid square number "
             "for where you want to place your %c: ",
              player,(player == 1) ? 'X' : 'O');
      scanf("%d", &choice);
      row = --choice/3;                 // Get row index of square
      column = choice % 3;              // Get column index of square
    }while(choice < 0 || choice > 8 || board[row][column] > '9');
    // Insert player symbol
    board[row][column] = (player == 1) ? 'X' : 'O';
    /* Code to check for a winner */
  }
    /* Code to output the result      */
  return 0;
}
You prompt the current player for input in the do-while loop and read the square number into choice. You use this value to compute the row and column index values in the array using the expressions you saw earlier. You store the row and column index values in row and column. The do-while loop condition verifies that the square selected is valid. There are three ways an invalid choice could be made:
  • The square number is less than the minimum, 0.

  • The square number is greater than the maximum, 8.

  • The square number selects a square that already contains 'X' or 'O'.

In the latter case, the contents of the square will have a value greater than the character '9', because the character codes for 'X' and 'O' are greater than the character code for '9'. If the choice falls on any of these conditions, you just repeat the request to select a valid square.

Step 3

You can add the code to check for a winning line next. This needs to be executed after every turn:
// Program 5.8 Tic-Tac-Toe
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
  // Variable declarations as before...
  // Definition of the board array as before...
  // The main game loop. The game continues for up to 9 turns
  // as long as there is no winner
  for(size_t i = 0; i < 9 && winner == 0; ++i)
  {
    // Code to display the board as before...
    player = i%2 + 1;                   // Select player
    // Loop to get valid player square selection as before...
    // Insert player symbol
    board[row][column] = (player == 1) ? 'X' : 'O';
    // Check for a winning line - diagonals first
    if((board[0][0]==board[1][1] && board[0][0]==board[2][2]) ||
       (board[0][2]==board[1][1] && board[0][2]==board[2][0]))
      winner = player;
    else
    {
      // Check rows and columns for a winning line
      for(unsigned int line = 0; line <= 2; ++line)
      {
        if((board[line][0] == board[line][1] && board[line][0] == board[line][2]) ||
           (board[0][line] == board[1][line] && board[0][line] == board[2][line]))
          winner = player;
      }
    }
  }
  /* Code to output the result */
  return 0;
}

To check for a winning line, you compare one element in a line with the other two to test for equality. If all three are identical, then you have a winning line. You check both diagonals in the board array with the if expression, and if either diagonal has identical symbols in all three elements, you set winner to the current player. The current player must be the winner because he or she was the last to place a symbol on a square. If neither diagonal has identical symbols, you check the rows and the columns in the else clause using a for loop. The for loop body consists of one if statement that checks both a row and a column for identical elements on each iteration. If either is found, winner is set to the current player. Of course, if winner is set to a value here, the main loop condition will be false, so the loop ends and execution continues with the code following the main loop.

Step 4

The final task is to display the grid with the final position and to display a message for the result. If winner is 0, the game is a draw; otherwise, winner contains the winning player number. Here’s the complete code for the program:
// Program 5.8 Tic-Tac-Toe
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
  int player = 0;                       // Current player number - 1 or 2
  int winner = 0;                       // The winning player number
  int choice = 0;                       // Chosen square
  unsigned int row = 0;                 // Row index for a square
  unsigned int column = 0;              // Column index for a square
  char board[3][3] = {                  // The board
              {'1','2','3'},            // Initial values are characters '1' to '9'
              {'4','5','6'},            // used to select a vacant square
              {'7','8','9'}             // for a player's turn.
                     };
  // The main game loop. The game continues for up to 9 turns
  // as long as there is no winner
  for(unsigned int i = 0; i < 9 && winner == 0; ++i)
  {
    // Display the board
    printf(" ");
    printf(" %c | %c | %c ", board[0][0], board[0][1], board[0][2]);
    printf("---+---+--- ");
    printf(" %c | %c | %c ", board[1][0], board[1][1], board[1][2]);
    printf("---+---+--- ");
    printf(" %c | %c | %c ", board[2][0], board[2][1], board[2][2]);
    player = i%2 + 1;                   // Select player
    // Get valid player square selection
    do
    {
      printf("Player %d, please enter a valid square number "
             "for where you want to place your %c: ",
              player,(player == 1) ? 'X' : 'O');
      scanf("%d", &choice);
      row = --choice/3;                 // Get row index of square
      column = choice % 3;              // Get column index of square
    }while(choice < 0 || choice > 8|| board[row][column] > '9');
    // Insert player symbol
    board[row][column] = (player == 1) ? 'X' : 'O';
    // Check for a winning line – diagonals first
    if((board[0][0]==board[1][1] && board[0][0]==board[2][2]) ||
       (board[0][2]==board[1][1] && board[0][2]==board[2][0]))
      winner = player;
    else
    {
      // Check rows and columns for a winning line
      for(unsigned int line = 0; line <= 2; ++line)
      {
        if((board[line][0] == board[line][1] && board[line][0] == board[line][2]) ||
           (board[0][line] == board[1][line] && board[0][line] == board[2][line]))
          winner = player;
      }
    }
  }
  // Game is over so display the final board
  printf(" ");
  printf(" %c | %c | %c ", board[0][0], board[0][1], board[0][2]);
  printf("---+---+--- ");
  printf(" %c | %c | %c ", board[1][0], board[1][1], board[1][2]);
  printf("---+---+--- ");
  printf(" %c | %c | %c ", board[2][0], board[2][1], board[2][2]);
  // Display result message
  if(winner)
    printf(" Congratulations, player %d, YOU ARE THE WINNER! ", winner);
  else
    printf(" How boring, it is a draw ");
  return 0;
}
Typical output from this program and a very bad player number 2 would be as follows:
1 | 2 | 3
---+---+---
 4 | 5 | 6
---+---+---
 7 | 8 | 9
Player 1, please enter a valid square number for where you want to place your X: 1
 X | 2 | 3
---+---+---
 4 | 5 | 6
---+---+---
 7 | 8 | 9
Player 2, please enter a valid square number for where you want to place your O: 3
 X | 2 | O
---+---+---
 4 | 5 | 6
---+---+---
 7 | 8 | 9
Player 1, please enter a valid square number for where you want to place your X: 5
 X | 2 | O
---+---+---
 4 | X | 6
---+---+---
 7 | 8 | 9
Player 2, please enter a valid square number for where you want to place your O: 6
 X | 2 | O
---+---+---
 4 | X | O
---+---+---
 7 | 8 | 9
Player 1, please enter a valid square number for where you want to place your X: 9
 X | 2 | O
---+---+---
 4 | X | O
---+---+---
 7 | 8 | X
Congratulations, player 1, YOU ARE THE WINNER!

Summary

This chapter explored the ideas behind arrays. An array is a fixed number of elements of the same type, and you access any element within the array using the array name and one or more index values. Index values for an array are unsigned integer values starting from zero, and there is one index for each array dimension.

Processing an array with a loop provides a powerful programming capability. The amount of program code you need for the operation on array elements within a loop is essentially the same, regardless of how many elements there are. You have also seen how you can organize your data using multidimensional arrays. You can structure an array such that each array dimension selects a set of elements with a particular characteristic, such as the data pertaining to a particular time or location. By applying nested loops to multidimensional arrays, you can process all the array elements with a very small amount of code.

Up until now, you’ve mainly concentrated on processing numbers. The examples haven’t really dealt with text to any great extent. In the next chapter, you’ll learn how you can process and analyze strings of characters, but first some exercises to establish what you have learned in this chapter.

Exercises

The following exercises enable you to try out what you’ve learned about arrays. If you get stuck, look back over the chapter for help. If you’re still stuck, you can download the solutions from the Source Code/Download area of the Apress website (www.apress.com), but that really should be a last resort.

Exercise 5-1. Write a program that will read five values of type double from the keyboard and store them in an array. Calculate the reciprocal of each value (the reciprocal of value x is 1.0/x) and store it in a separate array. Output the values of the reciprocals and calculate and output the sum of the reciprocals.

Exercise 5-2. Define an array, data, with 100 elements of type double. Write a loop that will store the following sequence of values in corresponding elements of the array:
1/(2*3*4)  1/(4*5*6)  1/(6*7*8) … up to 1/(200*201*202)
Write another loop that will calculate the following:
data[0] - data[1] + data[2] - data[3] + … -data[99]

Multiply the result of this by 4.0, add 3.0, and output the final result. Do you recognize the value you get?

Exercise 5-3. Write a program that will read five values from the keyboard and store them in an array of type float with the name amounts. Create two arrays of five elements of type long with the names dollars and cents. Store the whole number part of each value in the amounts array in the corresponding element of dollars and the fractional part of the amount as a two-digit integer in cents (e.g., 2.75 in amounts[1] would result in 2 being stored in dollars[1] and 75 being stored in cents[1]). Output the values from the two arrays of type long as monetary amounts (e.g., $2.75).

Exercise 5-4. Define a two-dimensional array, data[11][5], of type double. Initialize the elements in the first column with values from 2.0 to 3.0 inclusive in steps of 0.1. If the first element in a row has value x, populate the remaining elements in each row with the values 1/x, x2, x3, and x4 . Output the values in the array with each row on a separate line and with a heading for each column.

Exercise 5-5. Write a program that will calculate the average grade for the students in each of an arbitrary number of classes. The program should read in all the grades for students in all classes before calculating the averages. Output the student grades for each class followed by the average for that class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.8.247