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.
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.
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 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)
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:
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:
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:
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
Figure15.10.6.The memory diagram for the students vector and s1 as a reference
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:
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:
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);