Arrays

Defining and Using Arrays

An “array” is essentially a collection of variables stored sequentially in memory. Defining an array is much quicker and more manageable than defining multiple variables. You simply need to indicate the data type of the values the array will store, the name of the array, and the number of values you want to be stored. There are three main ways you can define an array:

1. Define an uninitialized array for a specific number of values.
• i.e., type arrayName[numValuesToBeStored];
2. Define an initialized array or a specific number of values.
• i.e., type arrayName[3] = {value0, value1, value2};
3. Define an initialized array and let the computer figure out how many values are listed.
• i.e., type arrayName[ ] = {value0, value1, value2};

Here are a few examples of how you can define arrays:

                   //define an array of 5 uninitialized integers
int myInts[5];

//define an array of 3 initialized integers
int myInts2[3] = {2, 4, -8 };

//define an array of 3 initialized and 2 uninitialized integers
int myInts3[5] = {2, 4, -8};

//define two array's that have the same size using a variable
int arraySize = 3;
int array1[arraySize] = {2, 4, -8 };
int array2[arraySize];

/*define an array of initialized integers
here the computer counts how many ints you have listed automatically*/
int myInts4[ ] = {2, 4, -8};

//define an array of characters
char myCharacters[4] = {'a', 'b', 'c', 'd'};

//define an array of characters using a string (technically this is a C-String)
char arrayString[ ] = "abcd";

//define an array of String objects
String myStrings[4] = { "text0", "text1", "text2", "text3"};


An easy way to identify that an array is being defined, versus a regular variable, are the square brackets “[]” that follow the name. When defining an array, the number inside of the square brackets lets the computer know how many values you want to store. If you want to define multiple arrays of the same size, you may do so using a variable (see lines 14-16). This can come in handy, but know that you cannot use a variable to change the size of an array while your sketch is running. When defining an array of uninitialized values (like on line 2), you must provide a number greater than zero, otherwise the array will be invalid. Defining an array with initial values (as seen line 6) requires that you provide the correct number of initial values listed inside of the square brackets. It is okay to exceed the value count (like on line 10) because it will only create extra uninitialized values. Finally, it is useful to note that it is possible for arrays to store other data structures, such as objects or structs (see line 33).

Accessing Array Elements

As you can see, using arrays to store a lot of values is much easier than defining multiple variables. Accessing the values you stored in an array is also pretty straightforward. The values stored in an array are referred to as the array's “elements.” To access a specific element of an array, you need to know the corresponding “index.” An array's index is an integer that keeps track of the position of an element in the array. The relationship between an array index and an element's position is not as simple as you might think. The index of an element is always the element's position minus one. In other words, the first element of an array has an index of 0, the 2nd element has an index of 1, the 3rd has an index of 2, and so on. The reason for this odd numbering is due to how an array actually works under the hood. To keep things simple we will not delve into this now. An example showing you how to store and access values using arrays is provided below.

                    //define initialized array
char lettersLowerCase[3] = {'a','b','c'};
//            Element index:  0   1   2

//define uninitialized array
char lettersUpperCase[3];

//Store capital 'A' as 1st element (index 0) of the array
lettersUpperCase[0] = 'A';

//Store capital 'B' as 2nd element (index 1) of the array
lettersUpperCase[1] = 'B';

//Store capital 'C' as 3rd element (index 2) of the array
lettersUpperCase[2] = 'C';

//Retrieve lower case 'a' from array
char lowerA = lettersLowerCase[0];

//Retrieve upper case 'C' from array
char upperB = lettersLowerCase[2];

//Replace lower case 'c' with upper case 'B'
lettersLowerCase[2] = lettersUpperCase[1];


Copying Arrays

An easy method for accessing arrays is to use a for loop counter as the index. A common application for for loops and arrays is array duplication. Copying an array is not as easy as using the assignment operator, like with normal variables. An array must be copied one element at a time. For example:

                  double array1[5] = {1.1, 5.231, 3.42, 4.7, 8.06 };
double array2[5];

//INCORRECT copying method using the assignment operator alone
array2 = array1;

//CORRECT copying method using for-loops
for(int i = 0; i < 5; i++)
{
array2[i] = array1[i];
}


Exceeding an Array's Boundaries

When manipulating an array, it is important to know the number of elements it contains. Using an index that does not correspond to an array's element (referred to as exceeding the array's boundaries) will cause problems. MPIDE does not check to see if you have exceeded an array's boundary, so no errors will be produced during verification. This means that the problems will only show up while you are running the sketch. There are two kinds of problems that occur if you exceed an array's boundaries. The first is if you try to retrieve an element using an invalid index. Any data you obtain using this index will be an irrelevant value from a random memory location. While this seems harmless enough, it can cause your sketch to misbehave and can be very difficult to debug. The second is if you try storing a value using an invalid index. This will overwrite a random piece of memory and can cause your sketch to crash or seriously malfunction. Examples of invalid indices are shown below.

                  //define initialized array
int array1[4] = {2, 4, -8, 9 };
//valid indices: 0  1   2  3

//the index of 4 is invalid so a random piece of memory will be overwritten
array1[4] = 5;

//the index of -1 is invalid, var will contain invalid data
int var = array1[-1];

/*retrieving a value from an uninitialized array will also yield invalid data
even if the index is within the array's boundaries */
int array2[4];
int var2 = array2[2];

//copying arrays of mismatched sizes can exceed an array's boundaries
int arraySize = 4;
int array3[arraySize] == {-1, 7, 2, 6 };
int array4[2];

for(int i = 0; i < arraySize; i++)
{
/*when i equals will exceed array4's valid index range and
cause a random piece of memory to be overwritten*/
array4[i] = array3[i];
}


Calculating the Number of Elements in an Array

So far we have gone over how to access the elements of an array, and why we shouldn't exceed an array's boundaries. Still, sometimes it is difficult to determine the number of elements an array has. In fact, the whole point of initializing an array using open square brackets is to save us the time of counting the number of elements we have listed. This brings an important question to mind; how do we determine the number of elements in an array that have been initialized with open brackets? The solution is to use the sizeof operator. This operator will tell us the memory size of any variable, object, or struct in bytes. For example:

char types take up exactly 1 byte of memory and int types take up exactly 4 bytes of memory, so:

                  char a = 'a';//sizeof(a) would be 1 byte

int one = 1;//sizeof(one) would be 4 bytes

//Array's can hold multiple copies of a data type so
char letters[ ] = {'a','b','c'};//sizeof(letters) would be 3 bytes

int numbers[ ] = {9, 14, -89};//sizeof(numbers) would be 12 bytes


From here it is easy to see how to calculate the number of element in each array. Dividing the total size of an array (in bytes) by the size of a single element (in bytes), we can figure out how many elements the array has. The code for this would look as follows.

                  int numbers[ ] = {9, 14, -89};

// numElements = (size of the entire array in bytes)/(size of the first element in bytes)
int numElements = sizeof(numbers)/sizeof(numbers[0]);

//so numElements = (12 bytes) / (4 bytes)


Obviously, the final result is correct since 12 bytes divided by 4 bytes is 3 (the same number of elements we listed out). Even though this is a convenient little trick, there is a catch. This calculation will only work when it is done in the same scope where the array was defined. This means that the calculation will not work on arrays passed in as arguments of a function. This annoying flaw is again due to how arrays actually work under the hood, which we will not discuss now.

Passing Arrays Into Functions

We will briefly go over how to pass an array into a function. Defining an array as a function argument is done the same way as defining an array normally. Simply include the type and the name of the array followed by empty square brackets. As an example, we can write a print array function that takes an array as an argument and prints out its values.

                  void printArray(int array[], int arraySize)
{
//Print each element from one array to another
for(int i = 0; i < arraySize; i++)
{
Serial.println(array[i]);
}
}


When passing arrays into functions it is also important to note that a local copy of the array is not made. This means that if you modify an array passed into a function, the original array is being modified. For example:

                  void modifyArray(int array[], int arraySize)
{
//set all the elements of an array to 1
for(int i = 0; i < arraySize; i++)
{
array[i] = 1;
}
}

void setup()
{
int array1[5] = {0,1,2,3,4};

//array1's values before calling modifyArray() are {1,2,3,4}

modifyArray(array1[], 5)

//array1's values after calling modifyArray() are {1,1,1,1}
}


The moral here is that whenever you are passing arrays into functions, be careful that you don't overwrite any of its values unintentionally.

Multidimensional Arrays

It was stated earlier that arrays can store data structures (like objects or structs) as well as basic data types. It is also possible for arrays to store other arrays. An array that stores multiple arrays is referred to as multidimensional. Multidimensional arrays can come in very handy for representing 2D and 3D coordinate systems. They are also very useful for organizing data in general. To define a multidimensional array, you only need to add an additional pair of square brackets after the name. To initialize a multi-dimensional array, you must enclose multiple sets of curly braces “{}” within each other. For example:

									//define an uninitialized 2D array
int blankArray2D[2][3];

//define an initialized 2D array
int array2D[2][3] =
{
{    1,    2,    3},
{    4,    5,    6}
};

/*define an initialized 2D array on one line
Row num: |----Row 1----|   |----Row 2----|
Col num: |  1   2   3  |   |  1   2   3  | */
int array2D_OneLine[2][3] = { {  1,  2,  3  },  {  4,  5,  6  } };

//define an uninitialized 3D array
int blankArray3D[2][3][4];

//define an initialized 3D array
int array3D[2][3][4] =
{
{
{1,2,3,4}, {1,2,3,4}, {1,2,3,4}
},

{
{1,2,3,4}, {1,2,3,4}, {1,2,3,4}
},
};

/*define an initialized 3D array on one line
|---------------X1--------------|  |---------------X2--------------|
|                               |  |                               |
A[X][Y][Z]	  ||---Y1--|  |---Y2--|  |---Y3--||  ||---Y1--|  |---Y2--|  |---Y3--||*/
int A[2][3][4] = {{{1,2,3,4}, {1,2,3,4}, {1,2,3,4}}, {{1,2,3,4}, {1,2,3,4}, {1,2,3,4}}};


Unfortunately, when defining initialized multidimensional arrays, MPIDE will not allow you to use the open bracket notation. This means you will have to count the number of values you listed for each dimension. Accessing multidimensional arrays works the same way as regular arrays, except you have an index for each dimension of the array.