Contents

Chapter 10

std::vector: A List That Grows

push_back, indexing, and the loop that reads the whole thing.

Chapter 9 left you with a roll function and two dice. Roll ten and you need ten variables. Roll a thousand and the idea collapses. You can’t type a thousand names, and even if you did, you couldn’t loop over them.

What you want is one name holding many values.

A list you can add to

#include <iostream>
#include <vector>

int main() {
    std::vector<int> rolls;

    rolls.push_back(4);
    rolls.push_back(6);
    rolls.push_back(1);

    std::cout << "count: " << rolls.size() << '\n';
}
count: 3

std::vector<int> rolls; makes an empty list of int. Empty, as in genuinely containing nothing: rolls.size() right after that line is 0.

push_back puts a value on the end and the vector gets bigger. There is no maximum you declare up front and no point where it fills up; it makes room as it goes. That is the whole trick, and it is why you will reach for a vector far more often than anything else in this book.

size() is a dot function, like full.size() from chapter 4, and for the same reason: the vector is carrying that number around with it.

Getting at what’s inside

Square brackets, and a number:

std::cout << "first: " << rolls[0] << '\n';
std::cout << "last:  " << rolls[rolls.size() - 1] << '\n';
first: 4
last:  1

Positions start at 0, exactly as string positions did in chapter 4, and for once the two things you have learned agree with each other. Three elements means positions 0, 1 and 2, so the last one is at size() - 1: the same arithmetic, the same off-by-one waiting for you.

You can assign through the brackets too. rolls[0] = 5; replaces the first value. That only works for positions that already exist; brackets never make the vector bigger, only push_back does.

The loop for reading all of it

You could walk a vector with the for loop from chapter 8, and sometimes you have to. Most of the time you want this instead:

std::vector<std::string> names;
names.push_back("Ada");
names.push_back("Grace");
names.push_back("Alan");

for (std::string name : names) {
    std::cout << name << '\n';
}
Ada
Grace
Alan

Read the brackets as “for each name in names”. No counter, no condition, no ++, and no opportunity to get the last position wrong. Each time round, name holds the next element.

This is called a range-for, and it is what you should write whenever you want every element and don’t care where they sit. Use the counting loop when you need the position itself. Printing “3. Alan” needs a number, and range-for hasn’t got one.

Two things worth noticing about that example. The vector holds std::string this time, and nothing else changed: push_back, size and the brackets all behave the same. The <int> slot really is a slot.

And name is a copy of the element, so changing it inside the loop changes nothing in the vector. Same rule as function parameters in chapter 9, same fix in chapter 12, which comes back to this exact loop and improves it.

An empty vector is worth a moment too. Run a range-for over one and the body simply never executes, which is almost always what you wanted. No special case to write.

Filling one from the keyboard

push_back doesn’t care where the value came from, so a loop and a cin are enough to collect input you couldn’t have counted in advance:

std::vector<int> scores;

for (int i = 0; i < 3; ++i) {
    std::cout << "Score " << i + 1 << ": ";
    int score = 0;
    std::cin >> score;
    scores.push_back(score);
}

std::cout << "You entered " << scores.size() << " scores.\n";

Note i + 1 in the prompt. The loop counts from 0 because the positions do, but nobody wants to be asked for “Score 0”, so the display gets the adjustment and the vector doesn’t. Doing it the other way round, counting from 1 and subtracting when you index, is how off-by-one bugs get in.

This version asks for exactly three. Letting the reader stop whenever they like means noticing when input runs out, which is chapter 17’s job.

Vectors go in and out of functions

A vector is a value like any other, so it goes through the brackets of a function the same way an int does: in as a parameter, out as a return.

std::vector<int> first_squares(int how_many) {
    std::vector<int> results;
    for (int i = 0; i < how_many; ++i) {
        results.push_back(i * i);
    }
    return results;
}

int total(std::vector<int> numbers) {
    int sum = 0;
    for (int n : numbers) {
        sum += n;
    }
    return sum;
}

int main() {
    std::vector<int> squares = first_squares(5);
    std::cout << "size:  " << squares.size() << '\n';
    std::cout << "total: " << total(squares) << '\n';
}
size:  5
total: 30

first_squares builds a vector that didn’t exist before and hands it back. The one inside the function stops existing at the closing brace, exactly as chapter 9 said, but the value has already been returned by then.

total is the shape you will write over and over: take a vector, walk it with a range-for, accumulate, return one number. Length, average, largest, how many are negative, all the same five lines with the middle changed.

Both of these copy the whole vector, which for five numbers costs nothing and for a million is real work you didn’t ask for. Chapter 12 is about that, and about the one small change that fixes it.

Exercise 1 · Fill one and read it back

Make a std::vector<int>, push the numbers 1 to 5 into it with a for loop, then print them with a range-for.

Now print them with a counting loop instead, as 1. 1, 2. 2, and so on. You need the position, so this is the case range-for can’t do.

Finally add up all five with a range-for and a running total, and print the sum. You should get 15.

Check yourself

1. What is the size of a vector immediately after std::vector<int> v;?

Yes. It starts genuinely empty, and push_back is what makes it grow.

Not quite. A vector is not like an uninitialised int. It knows perfectly well that it is empty.

Not quite. There is no default capacity you need to think about. It grows as you push.

2. std::vector<int> counts(7, 0); what does that make?

Not quite. That is what the curly-bracket form gives you. Round brackets are an instruction rather than a list.

Yes. Count first, then the value to fill with. Swapping the brackets for curly ones silently gives you two elements instead.

Not quite. Vectors have no size limit you declare. All seven elements exist right away and size() returns 7.

3. When should you use a counting for loop instead of a range-for?

Not quite. A range-for over an empty vector runs its body zero times, which is exactly right. No special case needed.

Yes. Range-for hands you the element and not its position, so numbering output or comparing neighbours needs the counter.

Not quite. What it holds makes no difference. Both loops work the same for any element type.

Project

A thousand dice

Roughly 45 minutes

Chapter 8’s stretch let you roll a random number and chapter 9 gave it a name. One roll tells you nothing. A thousand tells you what a die actually does.

The program rolls a six-sided die 1,000 times, counts how often each face came up, and prints the tally.

1: 167
2: 173
3: 148
4: 170
5: 178
6: 164

Bring roll across from chapter 9 unchanged. Then:

std::vector<int> counts(7, 0);

Seven slots, not six, so that face 4 lives at counts[4] and you never do arithmetic on the index. Slot 0 goes unused, and wasting one int to make the rest of the program obvious is a trade worth making on purpose.

The loop is two lines: roll, then ++counts[value]. Reading that as “add one to the counter for this face” is the whole idea, and it works because the brackets take any int expression, not just a literal.

Then a counting loop from 1 to 6 to print it, since you need the face number itself.

Check your answer. The six counts must add up to exactly 1,000. Add a range-for that sums them and prints the total. If it isn’t 1,000, you have a bug, and this is the first program in the book where you couldn’t have spotted it by reading the output.

Stretch: draw it. A number is harder to read than a picture:

1: **************** 167
2: ***************** 173
3: ************** 148

One star per ten rolls. std::string bar(counts[face] / 10, '*'); builds it, the same round-bracket “this many of these” form as the vector, because std::string supports it too. Integer division from chapter 6 does the rounding, and here that is exactly what you want.

Stretch two, a loaded die. Change roll(6) so the number 6 comes up twice as often as it should, then run the tally and confirm the histogram shows it. Proving a change did what you meant is most of what testing is.