Arrays |
So far, the datatypes we encountered have been 'atomic', meaning each var of such types holds just ONE piece of data (eg an int var holds a single integer).
But there is a frequent need for a SEQUENCE datatype, where a var can hold multiple pieces of a datatype (eg a variable to hold integer ages of students in a class).
In C/C++, such a datatype is called an array (in Python, it is called a 'list' type).
If we know how many array 'elements' (items) we need, we can allocate an array like so:
int agesOfStudents[15];
float mathConstants[3] = {2.718, 3.1415, 1.618}; // can also say mathConstants[] =
string firstNames[] = {"John", "Mary", "Ashley", "Cimberly", "Omar"}; // can also say firstNames[5] =
The [ ] after a variable name is what makes that variable be an array (capable of holding multiple items, not just a single one).
As the above shows, we can create a 'static' (FIXED SIZE) array in one of two ways - we can either declare an array but not initialize it (this is equivalent to 'int i;'), or declare it **and** initialize it too (this is equivalent to 'int i=6;'). Later, we'll learn about creating 'dynamic' arrays.
Note that when we declare AND init an array, we can optionally leave out the number of items we need - that is why we can say firstNames[]= instead of firstNames[5]=.
The number of items ("elements", or components) of an array is variously refered to as the array's count, length or size. Count/length/size is simply an int - eg. for the mathConstants array above, the length is 3.
In C++, an array's size (eg. 3 for mathConstants[] above) is not knowable just from the array's name (mathConstants does not tell us that its count in 3, for example); in comparison, Java, JavaScript etc do provide a way to get an array's size, given the name.
In an array, the elements are numbered #0, #1, #2.. 0,1,2 are the array's indices, aka subscripts. So, for mathConstants above:
cout << mathConstants[0] << endl; // will print 2.718
cout << mathConstants[1] << endl; // will print 3.1415
cout << mathConstants[2] << endl; // will print 1.618
Here is the above piece of code in runnable form.
Here is how we get 1.618... :)
A VERY important aspect of arrays this: all the elements/items/components of an array are located adjacent to each other in memory, ie as a contiguous BLOCK of data as shown below. This makes the entire block accessible as a whole, for doing things like transfering images from a camera to a hard drive, doing pixel calculations on all pixels of an image, etc. Being available as a block makes array elements easy (efficient) to access.
Below is a program that stores a bunch of 0..1 random #s in an array, sums them all up and finds the mean. We expect the mean to be 0.5.
#include <iostream>
using namespace std;
int main()
{
int n=10;
double randomNumbers[n]; //array that can hold 10 doubles
// NOTE the use of 'i' as the array index - this is a
// VERY COMMON usage idiom!!
for(int i=0; i<n; i++) {
randomNumbers[i] = ((double)rand())/RAND_MAX;
}// next
// print, just to look at the #s we've generated
for(int i=0; i<n; i++) {
cout << randomNumbers[i] << endl;
}// next
// the following way of "summing into" a variable is
// QUITE commonplace
double sum=0.0;
for(int i=0; i<n; i++) {
sum += randomNumbers[i];
}// next
cout << "Sum: " << sum << endl;
cout << "Mean: " << sum/n << endl;
}// main()
Run the above program..
Exercise: in the above program, allocate (at the very bottom of main()) another array, call it double rN2[n], then do these:
• copy the elements of randomNumbers[] into rN2[]; print rN2[] to verify
• next, copy the elements in reverse order, ie. randomNumbers[0] would get assigned to rN[9], etc.; print rN2[] to verify
Here is what the above exercise looks like.
Try this on your own: how would you SHUFFLE the contents of randomNumbers[], ie. 'scramble' all the items? Hint: think about random pairwise exchange, ie. swapping elements at two random indices, a bunch of times (the more number of times, the better):
C++ only has one-dimensional arrays, the kind we've seen (eg. randomNumbers[]). But in the real-world, data can exist in 2D (eg. matrices, image pixels), 3D (eg. Minecraft worlds, CAT scan data) or even 120D(!) [eg. Amazon's customer data].
So we need a way to create 2D, etc. arrays - what we do is simply extend the [] syntax. Eg. here is how to declare a 2D array, with and without initialization:
// no initialization
double m[2][2]; // m is 2x2 array, eg. can store a 2x2 matrix.
m[0][0]=1; m[0][1]=2; // first row of a 2x2 matrix
m[1][0]=3; m[1][1]=4; // second row
// in contiguous memory, the elements are stored as follows:
// [we store the first row as a 1D array, then the second row, then third...]
// m[0][0]
// m[0][1]
// m[1][0]
// m[1][1]
// with initialization
double n[2][2] = { {5,6}, {7,8} }; // reminder: a 2D array is an array of 1D arrays
Note that if we use a 2D array to hold a matrix (like with 'm' and 'n' above), as required by C++ we specify the array indexes as array[row#][column#], and NOT array[column#][row#]. Such a scheme, where the row index comes first, is called a 'row major order' [row-major memory allocation scheme].
EXERCISE: using the 2D arrays 'm' and 'n' above, write code that performs matrix multiplication of 'm' and 'n'; print out the result, and verify "by hand" that the result is correct.
2D arrays and higher dim arrays are extensively used in linear algebra calculations, 3D graphics, image processing, robotics, volumetric calculations, machine learning, etc.