Storing Data

Table of Contents


1. Arrays

c2_u13_ArraysVectors_Title.png

1.1. Array basics

What are arrays?

In C++, arrays are a built-in way to store a series of similar data all under one name.

Before now, if we wanted to store a list of students in a class (or similar kind of data), we would have to declare a bunch of separate variables and write the same code over and over to manage it:

string student1, student2, student3;

cout << "Enter student 1 name: ";
cin >> student1;

cout << "Enter student 2 name: ";
cin >> student2;

cout << "Enter student 3 name: ";
cin >> student3;

This would quickly become unmanagable if you were writing a program with tens, hundreds, or thousands of students stored in it. Instead, we can make use of arrays to store a series of related data together.

string students[100];
for ( int i = 0; i < 100; i++ )
{
  cout << "Enter student " << (i+1) << " name: ";
  cin >> student[i];
}

Arrays allow us to operate on the same name (e.g., student), but addressing different elements of the array with an index number. This way, we can write code to act on the data once, just modifying that index to work with different pieces of data.


Example: student array of size 4:

Element Rai Anuj Rebekah Rose
Index 0 1 2 3

Each item in the array has a corresponding index marking its position in the list, with 0 being the first value. If an array is of size \(n\), then the valid indices are 0 through \(n-1\).

An element is the information stored at that index position, which is essentially a single variable in an array of variables.


Declaring arrays:

In C++, we declare an array similarly to how we declare a variable, except that we need to specify an array size during declaration:

// An array of size 100
string students[100];

Or, if we already have data to put into it, we can initialize it with an initializer list. Then the array will be sized at however many items you give it.

// An array of size 4
string students[] = {"Rai", "Anuj", "Rebekah", "Rose"};

Array size - named constant:
Array sizes must be defined

during its declaration and the array cannot be resized during the program's execution. Because the array size can't change, and because we may need to know the size of the array throughout the program, we will generally use a named constant to store the size of the array.

const int MAX_STUDENTS = 100;
string students[MAX_STUDENTS];

Element count - variable:

Because an array's size cannot be changed after its declaration, it often becomes necessary to overshoot the amount of spaces we need in the array so that we always have enough room for our data. Perhaps a school's classrooms range from 20 to 70 seats, so we would want to declare the array of the biggest size so that we don't run out of space. Because of this, not all spaces in the array may be taken up at any given time.

C++ doesn't have a function to directly get the amount of elements in an array, so generally when declaring an array we need to also have an associated variable to track how many items we've stored in the array.

const int MAX_STUDENTS = 100; // total array size
int studentCount = 0;     // how many elements
string students[MAX_STUDENTS];  // array declaration

students[0] = "Rai";  // setting up first student
studentCount++;     // adding 1 to student count

Array data types:

Arrays can be declared with any data type. To the computer, we are just declaring \(n\) amount of some variable, and it takes care of putting all the \(n\) variables back-to-back in memory.


Valid indices:

As stated earlier, if an array is of size \(n\), then its valid indices are 0 to \(n-1\). In most computer languages, arrays and lists start at index 0 and go up from there, meaning to count for items we have "0, 1, 2, 3".

Context: * A common source of program crashes is *going outside the bounds of an array! This happens if you try to access an invalid index, such as student[-1] or student[100] (for an array of size 100; valid indices are 0-99).


1.2. Accessing indices with variables

Since the array index is always an integer, we could use a variable to determine which item in an array to modify - such as asking the user which item they want to edit.

const int TOTAL_ITEMS = 10;
float prices[TOTAL_ITEMS];

cout << "Edit which item? (0-9): ";
int itemIndex;
cin >> itemIndex;

cout << "Enter price for item: ";
cin >> prices[ itemIndex ];

1.3. Using for loops with arrays

Using for loops is the most common way to iterate over all the data in an array, setting data or accessing data. Have an array of size \(n\)? We want to iterate from i = 0 to \(n-1\), going up by 1 each time.

Initializing an array:
When we declare a variable without initializing it, it will be full of garbage data. This is the same for arrays, and sometimes you want to clean up an array prior to using it.
const int TOTAL_ITEMS = 10;
float prices[TOTAL_ITEMS];

// Initialize all prices to 0.
for ( int i = 0; i < TOTAL_ITEMS; i++ )
{
  prices[i] = 0;
}

Asking the user to enter all elements:

We can iterate over all the items in an array and have the user enter information for each element by using a loop. Since the first index is 0, we sometimes just add +1 to the index for the user's benefit, since people generally aren't used to lists starting at 0.

const int TOTAL_ITEMS = 10;
float prices[TOTAL_ITEMS];

// Initialize all prices to 0.
for ( int i = 0; i < TOTAL_ITEMS; i++ )
{
  cout << "Enter price for item " << (i+1) << ": ";
  cin >> prices[i];
}

Displaying all items in an array:

We can display all the elements of an array by using a loop as well, though we usually don't want to show all elements of the array - usually just the items we know we're storing data in. Recall that we usually will have an extra integer variable to count how many items have actually been stored in the array, which is different from the total array size.

const int MAX_STUDENTS = 100;
int studentCount = 0;
string students[MAX_STUDENTS];

// ...Let's say 20 students were added here...

// Iterate from index 0 to 19, since we have stored
// 20 students so far.
for ( int i = 0; i < studentCount; i++ )
{
  cout << "Student " << (i+1)
    << " is " << students[i] << endl;
}

You can also use a range-based for loop in versions of C++ from 2011 or later:

for ( auto & student : students )
{
  cout << student << endl;
}

1.4. Using multiple arrays

Let's say we're writing a simple restaurant program where we need a list of dishes and their prices together. Later on, we will write our own data type using structs and classes to keep these items together. But for now, we would implement this relationship by keeping track of two separate arrays with the data we need.

const int MAX_DISHES = 20;
int dishCount = 0;

// Information about a dish
string  dishNames[MAX_DISHES];
float   dishPrices[MAX_DISHES];
bool  dishVegetarian[MAX_DISHES];

// ... Let's say we created some dishes here...

// Display the menu
cout << "What would you like to order?" << endl;
for ( int i = 0; i < dishCount; i++ )
{
  cout << i << ". "
    << dishNames[i]  << " ("
    << dishPrices[i] << " dollars) ";

  if ( dishVegetarian[i] )  cout << "Veg";

  cout << endl;
}

// Get the index of dish they want
int whichDish;
cout << "Selection: ";
cin >> whichDish;

1.5. Arrays as arguments

they could potentially take up a lot of memory. Because of this, passing an array as a pass-by-value parameter would be inefficient - remember that pass-by-value means that the parameter is copied from the caller argument.

C++ automatically passes arrays around as pass-by-reference instead. You don't have to inclue the & symbol in your parameter list for an array, it just happens! But - keep in mind that any problems with pass-by-reference also apply to arrays. If you want to pass an array to a function but you don't want the data changed, then you would need to mark that array parameter as const.

void DisplayAllItems( const string arr[], int size )
{
  for ( int i = 0; i < size; i++ )
  {
    cout << arr[i] << endl;
  }
}

When using an array as a parameter, you don't have to hard-code a size to it. You can leave the square brackets empty (but you DO need square brackets to show that it's an array!) and then pass any array (with matching data type) to the function. However, you will also want to have an int parameter to pass the size of the array as well.


1.6. Array management

Since arrays require quite a bit of management to work with, you could implement some basic functions to do this management, instead of having to re-write the same code over and over. For example…


Clear array:
Sets all elements of this string array to an empty string and resets the elementCount to 0 afterwards.
void Clear( string arr[], int & elementCount )
{
  for ( int i = 0; i < elementCount; i++ )
  {
    arr[i] = "";
  }
  elementCount = 0;
}

Display all elements:

Shows all the elements of an array.

void Display( const string arr[], int elementCount )
{
  for ( int i = 0; i < elementCount; i++ )
  {
    cout << i << "\t" << arr[i] << endl;
  }
}

Add new element to array:

Often in our programs we will only set up one item at a time (perhaps when selected from a menu). This means we need to get the data for the array and figure out where in the array it will go.

void AddItem( string arr[], int & elementCount )
{
  cout << "Enter new element: ";
  cin >> arr[ elementCount ];
  elementCount++;
}

For an array, elementCount starts at 0 when the array is empty. This also happens to be the index where we will insert our first element - at 0.

Element        
Index 0 1 2 3

Once we add our first element, our elementCount will be 1, and the next index to insert data at will also be 1.

Element Cats      
Index 0 1 2 3

Then, elementCount will be 2, and the next index to insert at will be 2.

Element Cats Dogs    
Index 0 1 2 3

Because of this, the elementCount variable both tells us how many items have been stored in the array and what is the next index to store new information at.


Deleting an element from an array

Generally, when using an array to store data, we want to avoid gaps in the array, like this:

Element "A" "B" "" "C" "" "D"
Index 0 1 2 3 4 5

Because of these gaps, it makes it more difficult to add items to the list (have to search the array for an empty spot before adding a new item - that's time consuming, process-wise). We usually design our data storage in arrays so that everything is contiguous, starting from 0, leaving all the empty space at the end of the array:

Element "A" "B" "C" "D" "" ""
Index 0 1 2 3 4 5

Let's say we have data stored in the array above, but want to delete "B". We could set it to an empty string, but that would leave us with a gap:

Element "A" "B" "C" "D" "" ""
Index 0 1 2 3 4 5

We would want to do a shift-left operation to move "C" to 1 and "D" to 2, giving us:

Element "A" "C" "D" "" "" ""
Index 0 1 2 3 4 5

In code, it would look like:

void DeleteItem( int deleteIndex,
  string arr[], int & elementCount )
{
  // Shift left
  for ( int i = deleteIndex; i < elementCount-1; i++ )
  {
  arr[i] = arr[i+1];
  }

  elementCount--;
}

Inserting an element into an array

Sometimes we want to insert some data in between other items, such as if we wanted to maintain a sorted order or were ranking the data in the array.

Let's say we want to insert "C" before the "D" in this array:

Element "A" "B" "D" "E" "" ""
Index 0 1 2 3 4 5

This would be at index 2, but if we just put the "C" at index 2, then we would overwrite "D" - we don't want to lose data!

What we're going to have to do is move 2 to 3, but we don't want to overwrite "E" either, so we move 3 to 4. But the order we do this matters - we need to start at the end of the list and shift everything until 2 right by one, to get this array:

Element "A" "B" "" "D" "E" ""
Index 0 1 2 3 4 5

Then we could add our new data in the position we want.

In code, it would look like this:

void InsertItem( string newData, int insertIndex,
  string arr[], int & elementCount )
{
  // Shift right
  for ( int i = elementCount-1; i >= insertIndex; i-- )
  {
  arr[i] = arr[i-1];
  }

  // Insert new item
  arr[ insertIndex ] = newData;

  elementCount++;
}

1.7. Multidimensional arrays

We can also declare multidimensional arrays, such as 2D or 3D arrays. As with a 1D array, there is just one data type that all the elements share.

string spreadsheet[32][32]; // rows and columns
int vertices[4][4][4];    // x, y, z

Let's say we wanted to turn a day planner into a program. Perhaps we originally stored the day plan like this:

Day Time Task
Monday 8:00 Work meeting with Bob
Monday 13:00 Project review
Tuesday 10:00 Customer meeting
Tuesday 12:00 Crying in car
Wednesady 14:00 Sprint Retrospective

We can convert this into a 2D array of strings (the "task"), with one dimension being for "day of the week" and the other dimension being for "hour of the day"…

int DAYS_OF_WEEK = 7;
int HOURS_IN_DAY = 24;

string todo[ DAYS_OF_WEEK ][ HOURS_IN_DAY ];

We could ask the user what day they want to set a task for, and if they type "Sunday" that could translate to 0, and "Saturday" could translate to 6 (or however you want to organize your week)…

Sunday Monday Tuesday Wednesday Thursday Friday Saturday
0 1 2 3 4 5 6

We could ask them next for what hour the task is at, and that can map to the second index: "0" for midnight, "8" for 8 am, "13" for 1 pm, and so on to 23.

With this information, we can get the user's todo task and store it at the appropriate indices:

int day, hour;

cout << "Enter the day:" << endl;
cout << "0. Sunday   1. Monday 2. Tuesday 3. Wednesday "
   << "4. Thursday 5. Friday 6. Saturday" << endl;
cout << "Day: ";
cin >> day;

cout << "Enter the hour (0 to 23): ";
cin >> hour;

cout << "Enter your task: ";
cin >> todo[ day ][ hour ];

With the user's week planned out, you could then display it back to the user by using a nested for loop: One loop for the day, one loop for the hour.

cout << "YOUR SCHEDULE" << endl;

for ( int day = 0; day < DAYS_OF_WEEK; day++ )
{
  for ( int hour = 0; hour < HOURS_IN_DAY; hour++ )
  {
    cout << day << ", "
         << hour << ": "
         << todo[ day ][ hour ] << endl;
  }
}

Though you'd probably want to do some extra formatting; such as determining that if the day is 0, then write "Sunday" instead of the number "0".


1.8. Dynamic Array

We can use Dynamic Arrays to allocate space for an array at run-time, without having to know or hard-code the array size in our code. To do this, we need to allocate memory on the heap via a pointer.


Creating a dynamic array
We don't have to know the size of the array at compile-time, so we can do things like ask the user to enter a size, or otherwise base its size off a variable.
int size;
cout << "Enter size: ";
cin >> size;

string* products = new string[size];

Setting elements of the array
We can access elements of the dynamic array with the subscript operator, as before. However, we won't know the size of the array unless we use the size variable, so it'd be better to use a for loop to assign values instead of this example:
products[0] = "Pencil";
products[1] = "Eraser";
products[2] = "Pencil case";
products[3] = "Pencil sharpener";
products[4] = "Ruler";

Iterating over the array
Accessing elements of the array and iterating over the array is done the same way as with a traditional array.
for ( unsigned int i = 0; i < size; i++ )
  {
    cout << i << ". "; // Display index
    cout << products[i] << endl; // Display element at that index
  }

Freeing the memory when done
Before your pointer loses scope we need to make sure to free the space that we allocated:
delete [] products;

1.9. C++ Standard Template Library: Arrays

If we put #include <array> at the top of our file, we can use an array object instead of a traditional array to store data. They are basically interchangible, ecxept the array object gives us access to the .size() function, making our job slightly easier. However, the array still can't be resized.


Creating an STL array
array<string, 5> products;

Setting elements of the array
products[0] = "Pencil";
products[1] = "Eraser";
products[2] = "Pencil case";
products[3] = "Pencil sharpener";
products[4] = "Ruler";

Iterating over the array
for ( unsigned int i = 0; i < products.size(); i++ )
  {
    cout << i << ". "; // Display index
    cout << products[i] << endl; // Display element at that index
  }

1.10. C++ Standard Template Library: Vectors

The C++ Standard Template Library contains a special structure called a vector. A vector is a class (something we'll learn about later) and it is implemented on top of a dynamic array (which we will learn about later with pointers). Basically, it's a resizable array and it has functionality to make managing data a bit more managable.

Generally, in Computer Science curriculum, we teach you how to build data structures (structures that store data) like vectors, lists, and other items because it's important to know how they work (thus why we're covering arrays), but for your own projects and in the real world, you would probably use a vector over an array.


Declaring a vector
Vectors are a type of templated

object, meaning it can store any data type - you just have to specify what kind of type the vector stores. The format of a vector declaration looks like this:

// Declaring vectors
vector<string>  students;
vector<float> prices;

Adding data:
You can add data to a vector by using its push_back function, passing the data to add as the argument:
vector<string>  students;
students.push_back( "Rai" );

Getting the size of the vector
The size function will return the amount of elements currently stored in the vector.
cout << "There are "
  << students.size() << " students" << endl;

Clearing the vector:
You can erase all the data in a vector with the clear function:
students.clear();

Accessing elements by index:
Accessing an element at some index looks just like it does with an array:
cout << "Student: " << students[0] << endl;

Iterating over a vector:
You can use a for loop to iterate over all the elements of a vector, similar to an array:
cout << "Students:" << endl;

for ( int i = 0; i < students.size(); i++ )
{
  cout << i << "\t" << students[i] << endl;
}

You can also use C++11 style range-based for loop if you don't need the index. It allows you to iterate over all the elements of students, using an alias of student for each element.

for ( auto & student : students )
{
  cout << student << endl;
}

Author: Rachel Wil Sha Singh

Created: 2023-10-17 Tue 16:29

Validate