Arrays
Contents
Background
Arrays are a way of storing a list of items. If we wanted to store five integers up until now, we would create five separate variables, one for each integer. However, arrays allow use to declare one variable and store a number of values in it. For instances, we could create an array that stores a list of names (strings), ages (ints), heights (floats), or even instances of the Student
struct we talked about earlier. In an array, each value gets its own bucket. We can refer to a particular bucket based on its index in the list. Indexes are a little strange as they start at 0, not 1—this is a details that is important to remember! Here's how arrays are typically visualized compare to a variable:
A single int variable: | |
age → |
66
|
An array that contains ints: | |
ages → |
66 53 39 24
|
There are two types of arrays we'll talk about: static and dynamic. Static arrays store their values on the stack, and their size must be known at compile time. This has major repercussions. If you want an array to be sized based on input from the user, then you cannot use static arrays. Dynamic arrays let you allocate memory on the heap, which allows us to specify the size of an array at run-time.
Static arrays
As stated earlier, static arrays are stored in the program's memory stack. A condition of this is we must know the size of the array at compile time. There are two ways we can declare and initialize a static array, as shown below.
In both declarations (lines 8 and 12), on the left hand side we see the type of the array (int
in this case), the name of the array, and then a pair of square brackets with the size or length of the array ([5]
in this case). What's on the right side differs.
In the first example we have a combination declaration and initialization. This is great if we know the exact values that should go in the list. On the right hand side we have a pair of curly braces with each of the elements of the array separated by commas and in the order that we want them to appear in the list. We can only ever use curly braces like this if they are part of a declaration + initialization—we cannot break up the two and still use curly braces.
In the second example, we have a lone declaration, that is, there is no right hand side. Then, on the line below. we start to assign a value to each element of the array. Let's consider the line 13: myIntArray2[0] = 1;
. This says: assign the integer 1 to the element at index 0 of our array. Now, let's stop right there. What's an index again? An index is our way of referring to an element of an index. As strange as it may seem, indexes start at 0, not 1. So, myIntArray2[0]
is talking about the first element of the array. This looks similar to the declaration because of the square brackets, but it's not! The square brackets, when part of a declaration, signify the size of the array. When not part of a declaration, they are used to specify the index of the element to access. We can use myIntArray[0]
just like a variable—we can assign values to it, we can assign it to other variables, and we can use it in expressions (e.g., cout << myIntArray2[0];
.
A word about constants
Let's take a moment to clean up the code from above. I used int literals for the array sizes. This is generally bad form, because anytime you want to know the size, you have to copy an paste the actual value. If you ever want to change the size, you have to change it everywhere you've used it. So, the preferred method for static arrays is to use a global const
value. Here's our code from earlier, but now with constants:
Dynamic arrays
In the long run, static arrays will hold us back since we have to know their size at compile time. Dynamic arrays do not have this same constraint, and they should be easy now that we know about pointers!
Dynamic arrays are arrays that we allocate memory for in the heap using the new
keyword. The process is only slightly different than what we did with static arrays.
First of all, our array, myArray
, is an int
pointer—just like before! But the memory it points to isn't just for an int, it's for three ints. We say this by using square brackets after the type when we allocate memory with the new
keyword: new int[3];
.
Next, we don't need a * to dereference our array—the square brackets are doing that for us automatically. So, myArray[0]
will access the first thing in the array, just like with a static array.
Finally, we cannot use the regular delete
keyword to deallocate our array. Rather, we need to use a special version: delete[]
. And that's it!
So we said that dynamic arrays are nice because we can get the size from the user, right? Here's a demonstration of allocating space for an array based on what the user requests:
This program reads in the size of the array from the user, creates a dynamic array of that size, then prompts the user to enter the value that should be stored at each index. Running this gives the following (the user's input is in orange):
$ ./arrays How many entries should the array be? 4 Please enter a number to store at index 0: 948 Please enter a number to store at index 1: 32 Please enter a number to store at index 2: 39 Please enter a number to store at index 3: 22342(Back to top)
Iterating over arrays
We just saw in the example above that given an array and its size, we can execute a block of code for each item in the array (e.g., set the value at each index, print the value, modify the value, etc.). This is called iterating over an array or walking down an array. A for loop is the most appropriate loop to conduct iteration. We traditionally use an int variable named i
to keep track of the index (think i for index), and we start at 0. Since an array with size
elements has the index range 0–(n-1), our iteration for loop should almost always look like: for(i = 0; i < size; i++)
. If the size is stored in a different variable, than of course use that variable instead of size
.
The example above show how to use a for loop to set values based on user inputs. Another very common operation is to print out the contents of an array, one element at a time. Here's how we can do that in our code from above:
Note that iteration is exactly the same for both static and dynamic arrays.
(Back to top)Passing arrays to functions
Array pointers can be passed just like regular pointers. In fact, they look just like regular pointers, except we access them like arrays. One thing that is typically necessary is to pass the size of the array around with the array. So if you have a function that takes an array, you should also have a parameter for its size. This way, you know how many things the array holds (this is crucial for iteration and checking if an index is out of bounds).
Actually, it turns out that static arrays and dynamic arrays can both be passed to functions the same way—using the pointer notation. This is because static arrays can be treated like pointers, even though they aren't actually pointers. Kinda cool! See the example below.
(Back to top)2D Arrays
So far we have only seen one-dimensional arrays—arrays that store a list of numbers, strings, etc. However, arrays can also store arrays of numbers, strings, etc. When this is the case, we call the array a two-dimensional array. You are actually pretty familiar with 2D arrays in real life, even if you don't think of it that way. Anything that is a table of rows and columns can be thought of as a 2D array: pixels on a screen, values in an Excel table, etc.
A typical way to visualize 2D arrays is as a n×m table, where n is the number of rows and m is the number of columns. They are, respectively, the first and second dimensions of our array. Lets look at a concrete example. Say we want to keep track of the set of exam scores (exams 1, 2, 3, and the final exam) for three students in a class. One way to do this is to create a 2D array with dimensions 3×4 (3 rows, and 4 columns). Here's what it would look like:
scores ↓ |
||
● ● ●
|
→ → → |
99.5 90.3 93.1 89.5 85.3 95.2 88.7 85.2 76.5 82.3 73.4 70.3 |
Our scores
variable refers to an array (the column of boxes to the left of the arrows). This array doesn't contain any exams itself, but each element refers to another array with the exam scores for the respective students—these are the rows to the right of the arrows. So, to find the second student's first exam score, we would need to consider the row at index 1, and the column at index 0: scores[1][0]
. Note that both the row and the column get their own pair of square brackets.
So, how does this play out in code? The example below demonstrates how to created static and dynamic 2D arrays, how to pass them around, and how to iterate over both the rows and columns. Note that unlike 1D arrays, 2D arrays must be passed around different based on whether it is static or dynamic.
This program prints out the following:
99.5 90.3 93.1 89.5 85.3 95.2 88.7 85.2 76.5 82.3 73.4 70.3 99.5 90.3 93.1 89.5 85.3 95.2 88.7 85.2 76.5 82.3 73.4 70.3 99.5 90.3 93.1 89.5 85.3 95.2 88.7 85.2 76.5 82.3 73.4 70.3
One last thing. We can create arrays with as many dimensions as we want. 1D and 2D are the most common, but there is nothing preventing you from creating a 10D array. At that point, however, there tend to be better options, such as relying on structs.
(Back to top)