In previous tutorials, all the variables covered had been scalar. That means they hold one value each. But there are many occasions when being able to hold a list of values or even a two dimensional list (grid) or three dimensions can be very useful.
This list of values is called an array and it holds the specified number of values all of the same type. If a single int occupies 4 bytes then an array of 10 ints is 40 bytes in size. And they are held together in one place in RAM.
Some Rules With Arrays
Arrays are declared like this: type variable [number of elements] syntax. For example
10 ints in a variable called primes are declared like this:
int primes[10];
You can initialize in the declaration by putting the values inside {} and separating them with commas.
int primes[10]={2,3,5,7,11,13,17,19,23,29};
You don’t have to initialize every value. If you only wanted to initialize the first 8, then this would do. The last two elements are zero.
int primes[10]={2,3,5,7,11,13,17,19};
Finally you can make it read-only by putting a const on the front.
const int primes[10]={2,3,5,7,11,13,17,19,23,29};
The compiler will generate a syntax error if you try to modify any element of the primes array. Here’s an example program.
#include <stdio.h>
const int primes[10]={2,3,5,7,11,13,17,19,23,29};
int main(int argc, char* argv[])
{
int i;
int numelements= sizeof(primes)/sizeof(int) ;
for ( i=0; i<numelements; i++) {
printf("Primes[%d] = %d\n\r",i,primes[i]) ;
}
return 0;
}
When run this outputs:
Primes[0] = 2
Primes[1] = 3
Primes[2] = 5
Primes[3] = 7
Primes[4] = 11
Primes[5] = 13
Primes[6] = 17
Primes[7] = 19
Primes[8] = 23
Primes[9] = 29
I’ve introduced the for statement that lets you create loops and sizeof() function that returns the size of any variable or type. I will cover for loops in detail a future tutorial on loop statements but I’ll explain what it does below.
Let’s look at some of this code to try and understand what it does:
int numelements= sizeof(primes)/sizeof(int);
This declares the int variable numelements and sets it to the sizeof(primes) which is 40 divided by sizeof(int) which is 4, so numelements is set to 40/4 = 10. I could do this explicitly that’s not always the best thing to do. Here’s what that would look like:
int numelements = 10;
But doing it the sizeof() way is usually better. If I change the number of elements in the array say to 20, I don’t have to remember to change it because the sizeof() calculations determines it correctly. Had it explicitly been declared numelements = 10 then I’d have to remember to change that as well.
The for loop starts with 0 and counts up as long as i is less than numelemenets which means it goes u to nine. This is a very important thing to remember, when we count elements in arrays, they start at 0.
Arrays start at 0
Most computer languages, but not all e.g. Visual Basic start arrays with the first element at index 0. I always remember this as the first element is zero distance from the start of the array. Visual Basic starts at 1.
So in a ten element array the indices run from 0 to 9, not 1 to 10.
For Loops
This is a very convenient way to index through the elements of an array. It uses an index variable and has three parts all within brackets and separated by semi-colons ;
for ( i=0; i<numelements; i++) {
In the example, snipped above the three parts of the for loop are:
- i=0
- i<numelements
- i++
The first part is where the loop index is initialized. i=0. You can leave this blank if the loop variable has already been initialized. So this works as well:
int i=0;
for (;i<numelements; i++) {
The second part is where the loop is checked to see if it has finished. Because the last element is at index 9 (not 10), this needs to check if i < numelements. If this is true the loop continues.
Finally the third part is where the loop variable is incremented. It’s traditional to increment but you can call any statement or function here, so long as the loop variable is altered. If you don’t it will loop forever or until it crashes.
Below is an infinite loop and you will have to wait forever for it to finish. Clearly this is not a good thing.
for (;;) {
...
}
Any or all three parts can be empty as the infinite loop above shows. However we do have a way of breaking out of a loop early using the statement break. I’ll cover that in a future tutorial on looping.
The Array Size is always Static
The size must always be specified at compile time, so you can’t do this:
int a= 50;
int values[a];
Because arrays always have to be declared statically, you have to use things call pointers to have dynamic arrays, i.e. arrays that change size at runtime. When we get to pointers in a future tutorial, you’ll see how dynamic data structures can be created.
You can use a #define but it’s still statically declared. Like this:
#define TENCATS 10
int values[TENCATS];
Arrays have dimensions
So far all the arrays I’ve described have been single dimension arrays, like houses in a street. But you can have two, three or many dimensions. The only limit is usually memory. I suspect some compilers possibly also limit you to 256 dimension arrays as it would be a logical limit.
To declare a multi dimension array you put how many elements there are in each dimension, in square brackets. Imagine a set of cubes 2 high, three wide and four deep. You’d declare it as a three dimension array like this:
int cubes[2][3][4];
That has 2 x 3 x 4 = 24 ints. Likewise you might declare a two-dimension chess board with 8 x 8 squares as
int board[8][8];
What is a #define?
It’s a way of giving a name to a piece of text. Before the C compiler does its magic, it runs a program called a pre-processor through your source code looking for things like #defines. Everywhere it finds one, it replaces it with the text,
In the example above I created a #define called TENCATS which has the text 10. Note, I use uppercase for TENCATS. You don’t have to, it is a convention rather than a hard and fast rule, but it tells anyone reading the program that it is a #define. And the text that is represents is not put inside quotes or double quotes.
When the compiler compiles int values[TENCATS] the pre-processor first changes its copy of the source in memory so every instance of TENCATS that it finds becomes becomes 10, and it actually compiles int values[10].
Why does that matter? Why not just use 10? Because we would typically access this array using a loop and we need to know how many elements there are. If you used 10 instead of a #define TENCATS and later needed to change it to 50, then you would have to go through your program and change every relevant 10 to 50. You might change a wrong 10 or maybe overlook one. This can introduce unnecessary bugs. If you use the #define then you only need to change the value once e.g. #define TENCATS 50. The pre-processor will then substitute 50 everywhere that it finds TENCATS.
That’s it for arrays though we’ll revisit them when we look at pointers. In the next tutorial I’ll look at structs.
Link to previous tutorial.
Link to next tutorial.