Skip to main content

Section 15.10 Vectors of Structs

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. 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.1 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 then modify the copy but expect the original to change, that will cause unexpected results in our code.
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 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. Remember that a reference is an alias - another name for a thing that already exists. In this situation, that is what we really wantβ€”a new name 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. Say I want to clear all of the exams for each student. I can loop through the students using an enhanced for loop or a counting loop. I just need to make sure that I use a reference to store any temporary name for the student:
Listing 15.10.7.
for (Student& s : students) {
    s.exams.clear();
}

// or

for (size_t i = 0; i < students.size(); ++i) {
    Student& s = students.at(i);
    s.exams.clear();
}
But if I forget to use a reference, I will make a copy of the student and then clear the exams for that copy. The original student will be unchanged:
Listing 15.10.8.
for (Student s : students) { {
  s.exams.clear();
  // BAD: This copies the student...

for (size_t i = 0; i < students.size(); ++i) {
  Student s = students.at(i);
  s.exams.clear();
  // BAD: This copies the student...

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.
Your answer should have four parts separated by dots. You will need to use at(???) for two of the parts.

Checkpoint 15.10.3.

You have attempted of activities on this page.