Arrays in C: Storing Many Values Under One Name

Every variable so far has held exactly one value. In Using Library Functions: #include, getch(), and the Input Buffer you used functions that other people wrote; in this lesson you learn to keep a whole list of values under one name. You start with five test scores in five separate variables, feel how quickly that gets painful, then store them in one array, visit every score with a short loop, work out the average, and break it on purpose by reaching past the end.

In this lesson you will

  • See why five separate variables (score1 to score5) do not scale
  • Declare an array, give it a size, and read and write its elements by index, counting from 0
  • Fill an array in one line with an initialiser list { }
  • Meet a first for loop, just enough to visit every index, and use it to print, total and average the scores
  • Read values into the array with scanf
  • Break it on purpose: go past the end, forget the size, copy with =, and use <= in the loop

Start a new project for this lesson. Copy 003-FunctionDemoCProgram to a new folder named 004-ArraysCProgram, save the project under the new name, and update its title and output filename, as before. Then replace main.c with each program below in turn.

Try it: five scores, five variables

Suppose a teacher wants the total of five test scores. With what you know so far, you need one variable per score:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int score1 = 72;
    int score2 = 85;
    int score3 = 64;
    int score4 = 90;
    int score5 = 58;

    int total = score1 + score2 + score3 + score4 + score5;
    printf("Total: %d\n", total);
    return 0;
}
Total: 369

It works, but imagine the class has 30 students. That is 30 declarations, and a sum line with 30 names in it. Miss one and the total is wrong with no error. Every new score means editing the program in several places. What we want is one name for the whole list of scores, and a number that says which score we mean.

Try it: one array, five boxes

Replace main with this:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[5];

    scores[0] = 72;
    scores[1] = 85;
    scores[2] = 64;
    scores[3] = 90;
    scores[4] = 58;

    printf("First score: %d\n", scores[0]);
    printf("Last score: %d\n", scores[4]);

    scores[2] = 70;
    printf("Third score, corrected: %d\n", scores[2]);
    return 0;
}
First score: 72
Last score: 58
Third score, corrected: 70

What just happened

int scores[5]; declares an array of 5 int variables side by side in memory, all called scores. The number in square brackets in the declaration is the size: how many elements (boxes) the array has. After that, a number in square brackets is an index: which box you mean. scores[0] = 72; writes to a box, and scores[0] inside printf reads from it. Each element behaves exactly like an ordinary int variable.

Counting starts at 0. An array of size 5 has indexes 0, 1, 2, 3 and 4. There is no scores[5]. In memory it looks like this:

Elementscores[0]scores[1]scores[2]scores[3]scores[4]
Index01234
Contents7285709058
Which score1st2nd3rd4th5th

Notice that the 1st score is at index 0 and the 5th is at index 4: the index is always one less than the position. Changing scores[2] to 70 changed one box and left the others alone.

Try it: fill it in one line

Writing each element on its own line is still long. You can fill an array as you declare it with an initialiser list: the values in curly braces, separated by commas, in index order:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[5] = {72, 85, 64, 90, 58};

    printf("Second score: %d\n", scores[1]);
    printf("Fourth score: %d\n", scores[3]);
    return 0;
}
Second score: 85
Fourth score: 90

The first value goes into scores[0], the next into scores[1], and so on. If you give fewer values than the size, the rest are set to 0, so int scores[5] = {0}; is a handy way to start with every box at 0, just like the defensive initialisation in the scanf lesson.

Now break the declaration:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[];

    printf("Second score: %d\n", scores[1]);
    printf("Fourth score: %d\n", scores[3]);
    return 0;
}

Break it on purpose: forget the size

Change the declaration to int scores[];, with no size and no initialiser list. Build it. The build stops with:

main.c:6:9: error: array size missing in 'scores'

C must know how many boxes to set aside before the program runs. Either put the size in the brackets, or give an initialiser list so the compiler can count the values: int scores[] = {72, 85, 64, 90, 58}; makes an array of 5. Put the 5 and the list back.

Try it: visit every index with a for loop

Printing each score with its own printf is no better than five variables. The point of an index is that it can be a variable: scores[i] means “the box whose number is in i”. A for loop lets i take the values 0, 1, 2, 3, 4 in turn. Loops get a lesson of their own later. For now, learn this one pattern:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[5] = {72, 85, 64, 90, 58};
    int i;

    for (i = 0; i < 5; i++)
    {
        printf("scores[%d] = %d\n", i, scores[i]);
    }
    return 0;
}
scores[0] = 72
scores[1] = 85
scores[2] = 64
scores[3] = 90
scores[4] = 58

Read for (i = 0; i < 5; i++) in three parts, separated by semicolons:

  • i = 0: start with i at 0, the first index.
  • i < 5: keep going while i is less than 5, the size. When i reaches 5 the loop stops.
  • i++: after each pass, add 1 to i. It is short for i = i + 1.

The statements in the curly braces run once for each value of i. Change the 5 to 3 and only the first three scores print. Change it back. With 30 students, you would change the array size and the 5 in the loop, and nothing else.

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[5] = {72, 85, 64, 90, 58};
    int i;

    for (i = 0; i <= 5; i++)
    {
        printf("scores[%d] = %d\n", i, scores[i]);
    }
    return 0;
}

Break it on purpose: use <= in the loop

Change i < 5 to i <= 5 (“less than or equal to”). Build and run. There is no error and no warning. On our test computer it printed:

scores[0] = 72
scores[1] = 85
scores[2] = 64
scores[3] = 90
scores[4] = 58
scores[5] = 0

Six lines from a five-box array. With <=, the loop also runs when i is 5, and reads scores[5], a box that does not exist. The value you see is whatever happens to be in the memory just after the array, so your number may be different, and it may change from run to run. This is called an off-by-one error. For an array of size 5, the loop test is i < 5, never i <= 5. Change it back.

Try it: total and average

The loop can do more than print. Here it adds each score to a running total, then divides by 5:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[5] = {72, 85, 64, 90, 58};
    int i;
    int total = 0;
    float average = 0;

    for (i = 0; i < 5; i++)
    {
        total = total + scores[i];
    }

    average = total / 5;

    printf("Total: %d\n", total);
    printf("Average: %.2f\n", average);
    return 0;
}
Total: 369
Average: 73.00

The total is right, but 369 ÷ 5 is 73.8, not 73.00. You have seen this before in the int vs float lesson. total and 5 are both int, so C does whole-number division and truncates the answer to 73 before it is stored in the float. Storing it in a float afterwards cannot bring the .8 back. Make one side of the division a decimal number:

    average = total / 5.0;
Total: 369
Average: 73.80

Trace the loop once by hand: total starts at 0, then becomes 72, 157, 221, 311 and finally 369. If you want to watch it happen, set a breakpoint on the total = total + scores[i]; line and add i and total as watches, as in the debugging lesson.

Try it: read the scores in

Now let the user type the scores. Each element is an ordinary int, so scanf needs its location, with &, just like &x in the scanf lesson:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[5] = {0};
    int i;
    int total = 0;

    for (i = 0; i < 5; i++)
    {
        printf("Enter score %d: ", i + 1);
        scanf("%d", &scores[i]);
    }

    for (i = 0; i < 5; i++)
    {
        total = total + scores[i];
    }

    printf("You entered: %d %d %d %d %d\n", scores[0], scores[1], scores[2], scores[3], scores[4]);
    printf("Average: %.2f\n", total / 5.0);
    return 0;
}

Build and run. Type each score and press Enter:

Enter score 1: 72
Enter score 2: 85
Enter score 3: 64
Enter score 4: 90
Enter score 5: 58
You entered: 72 85 64 90 58
Average: 73.80

&scores[i] means “the location of box i”. Each element is one int box, so scanf needs the & in front of it, exactly as for a single variable. The prompt shows i + 1 so that people see 1 to 5, while the program uses indexes 0 to 4.

Try it: one past the end

Replace main with this short program, which asks for the element after the last one:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[5] = {72, 85, 64, 90, 58};

    printf("scores[4] = %d\n", scores[4]);
    printf("scores[5] = %d\n", scores[5]);
    return 0;
}

Break it on purpose: ask for scores[5]

Build and run. On our test computer, GCC gave no error and no warning (even with -Wall), and printed:

scores[4] = 58
scores[5] = 0

C does not check indexes. It works out where box 5 would be and reads that memory, even though it is not part of the array. This is out of bounds access, and the C standard calls the result undefined behaviour: it may print a garbage number, it may print 0 and look fine, it may give a different answer on another computer, or the program may crash. Writing out of bounds, such as scores[5] = 99;, is worse, because it can change some other variable without warning. Keeping every index between 0 and size − 1 is your job, not the compiler’s.

Try it: copy one array into another

Suppose you want a backup of the scores before changing them:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int scores[5] = {72, 85, 64, 90, 58};
    int backup[5];

    backup = scores;
    printf("backup[0] = %d\n", backup[0]);
    return 0;
}

Break it on purpose: copy an array with =

Build it. The build stops with:

main.c:9:12: error: assignment to expression with array type

After it is declared, a whole array cannot be assigned with =. Instead, copy the elements one at a time with the loop.

Replace the backup = scores; line with a loop (and declare int i; with the other variables):

    for (i = 0; i < 5; i++)
    {
        backup[i] = scores[i];
    }
backup[0] = 72

Common mistakes

  • Using index 5 in an array of size 5. The last index is always size − 1.
  • Writing i <= 5 instead of i < 5 in the loop.
  • Forgetting that total / 5 with two ints truncates. Use 5.0.
  • Leaving out the & in scanf("%d", &scores[i]);.
  • Trying to copy or assign a whole array with =.

Key terms

  • Array: a row of variables of the same type under one name, such as int scores[5].
  • Element: one box in an array, such as scores[2].
  • Size: the number of elements, given in the declaration. It cannot change while the program runs.
  • Index: the position of an element, counted from 0. Valid indexes run from 0 to size − 1.
  • Initialiser list: values in curly braces that fill an array as it is declared, such as {72, 85, 64, 90, 58}.
  • for loop: repeats a block of statements; here, once for each index from 0 to size − 1.
  • Off-by-one error: a loop or index that goes one step too far (or stops one step short).
  • Out of bounds: using an index outside the array. C does not check it; the result is undefined behaviour.

Quick check

  1. An array is declared float temps[7];. What are its first and last valid indexes?

    Show answer

    temps[0] and temps[6].

  2. What is in each element after int counts[4] = {3, 9};?

    Show answer

    counts[0] is 3, counts[1] is 9, and counts[2] and counts[3] are 0.

  3. A program prints scores[5] from a size-5 array and gets 0, with no error. Is the program correct?

    Show answer

    No. scores[5] is out of bounds. C does not check it, so the 0 is just whatever was in that memory: undefined behaviour that may print something else, or crash, elsewhere.

  4. A total of 47 is divided by 4 with average = total / 4; and average is a float. What prints with %.2f, and how do you fix it?

    Show answer

    11.00, because int ÷ int truncates. Use total / 4.0 to get 11.75.

  5. Why does scanf("%d", &scores[i]); need the &?

    Show answer

    Each element is an ordinary int variable, and scanf needs its location, just like &x.

Coming up: text is an array too

Every array in this lesson held numbers, but the type in each box can be anything, including char, a single character. Put a row of characters in an array and you have a word. In the next lesson you will see that a string in C is just an array of char, so everything here, from indexes starting at 0 to = refusing to copy, carries straight over.

Part of the Introduction to C series. Next: Strings in C: char Arrays, %s, fgets, and strlen