Skip to main content

Section 15.10 Vectors of Structs

In the previous section, we saw how we can have a struct that contains a vector as one of its members. It is also possible to have a vector whose elements are structs.

Subsection 15.10.1 A Vector of Students

Now imagine we have a large number of students and we would like to be able to write code that loops through all of the students to perform various calculations. We want to think of our data as a list of students. To accommodate this, we would want to make a vector of type Student.
Listing 15.10.1.
// Make the students vector
vector<Student> students;

// Add two students to the vector
Student student1 = {
  "Beth Jones",
  { 84.2, 94.3, 96.7 }
};
Student student2 = {
  "John Smith",
  { 78.2, 88.3, 92.7 }
};
students.push_back(student1);
students.push_back(student2);
That code would result in memory that could be visualized as follows:
A students vector with two student elements
Figure 15.10.2. The memory diagram for the students vector
That memory diagram can be helpful to figure out how we reference a particular piece of information.
  • students is the top level name of the vector.
  • The two elements of the vector are accessed using the at method. So to access the first student, we would use students.at(0). At that point, we are naming the box that is above 0. It is the entire Student struct for Beth.
  • To access a member of the struct, we use .fieldName. So to access the name of that student, we would use students.at(0).name.
  • To access the exam scores of that student, we would use students.at(0).exams. That names the entire vector of scores for that student. To get a particular exam we would use .at to index into the vector. Thus students.at(0).exams.at(1) would get the second score of the first student. (94.3)

Subsection 15.10.2 Avoiding Copies

As we work with a collection of structs, we will want to be careful to avoid making unwanted copies of structs. If we are just reading data, making copies of data will be slightly inefficient, but should be otherwise harmless. However, when we are modifying data, copies can easily introduce bugs. If we by accident make a copy of an item and change it instead of the original, our changes will not be reflected in the vector.
The key to remember is that assignment into a struct variable copies. Consider this failed attempt to set the first student’s first exam score to 100:
Listing 15.10.3.
vector<Student> students;
// assume code here adds some students to the vector
// the first student is "Beth Jones" and has exam scores of 84.2, 94.3, 96.7

// This COPIES the first student
Student s1 = students.at(0);
s1.exams.at(0) = 100.0;
s1 is a copy of the data from the vector. It is a duplicate of the β€œBeth Jones” Student struct:
s1 is a copy of the first student element
Figure 15.10.4. The memory diagram for the students vector and s1
That is great if we wanted to copy the student. But in this case, we were just trying to make it easier to refer to that student. We didn’t want to write out students.at(0).exams.at(0). s1 was just supposed to be a shorter alias for students.at(0). But because s1 is a copy, we never managed to change the score of the original β€œBeth Jones” struct.
To avoid making a copy, we can use a reference. In this situation, we do not want a copy, we just want an alias (temporary nickname) for the existing struct. So we should declare s1 to be a reference variable and use that to refer to the student:
Listing 15.10.5.
vector<Student> students;
// assume code here adds some students to the vector

// Make a reference to the first student
Student& s1 = students.at(0);
s1.exams.at(0) = 100.0; // This modifies the student in the vector
s1 is a reference to the first student element
Figure 15.10.6. The memory diagram for the students vector and s1 as a reference
Now s1.exams.at(0) = 100.0 correctly changes the score in the vector.
A common place to run into this issue is while trying to loop through students using a range-based for loop. Say I want to clear all of the exams for each student. So I write the code below to loop through all the students and call clear on their exams vector:
Listing 15.10.7. Failed attempt to clear all the exams for each student
for (Student s : students) {
  s.exams.clear();
  // BAD: This copies the student and clears the copy's exams
}
In that attempt s is a copy of the β€œcurrent student”. Changing it does not affect the original student in the vector. To avoid this, we should use a reference as the loop variable’s type:
Listing 15.10.8. Correct way to clear all exams for each student
for (Student& s : students) {
    s.exams.clear();
    // GOOD: This uses a reference to avoid copying
}

Insight 15.10.1.

This is an issue with range-based loops because they always use a temporary variable to store β€œthe current element”.
In a counting loop, it is more common to refer to the current element directly using its index: students.at(i). But if you did want to give a temporary name to β€œthe current element”, you would want to use a reference. Something like: Student& s = students.at(i);

Checkpoint 15.10.1.

Hint.
Your answer should have three parts separated by dots. You will need to use at(???) as one part.

Checkpoint 15.10.2.

Hint 1.
Your answer should have four parts separated by dots. You will need to use at(???) for two of the parts.
Hint 2.
You need to name the list, then the item in the list, then the member that is a vector, then the item in that vector.

Checkpoint 15.10.3.

You have attempted of activities on this page.