A Simple Program Using std::vector
Like our last program, this one reads in a list of numbers from std::cin
, and then calculates a few statistics about them. But where the previous version used a primitive array, this version uses std::vector
.
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector<double> values;
while (true) {
double value;
std::cin >> value;
if (!std::cin.good()) {
break;
}
values.push_back(value);
}
double total = 0.0;
for (auto p = values.begin(); p != values.end(); ++p) {
total += *p;
}
std::cout << "Read " << values.size() << " values"
<< ", total " << total
<< ", average " << total/values.size() << std::endl;
std::sort(values.begin(),values.end());
std::cout << "Min is " << values.front()
<< ", max is " << values.back()
<< ", median is " << values[values.size()/2] << std::endl;
return 0;
}
The animated GIF below shows us compiling and using this code.
What's Changed?
Most of the code looks almost the same. But there are some differences.
The Declaration is Different
We used to write
double values[100];
which created an array with 100 default-initialized double
s (i.e., holding elements with indeterminate values).
Now we've written
std::vector<double> values;
and we get a vector of size zero.
If we had wanted 100 value-initialized doubles (all nicely set to 0.0
), we could have written
std::vector<double> values(100);
Notice that we have to use parentheses ((
…)
) here, as curly braces ({
…}
) can be used to provide initial values for the elements; for example, we could write
std::vector<int> favorites{42, 54, 7};
to create a vector
holding the three int
values 42, 54, and 7.
The type is specified in angle brackets (<
…>
) because std::vector
is generic: you can create a std:vector
of any type you like by specifying what type you want in the angle brackets. In C++ parlance, it's a class template, but we'll discuss templates in more depth (including writing your own!) in a future lesson.
A std::vector
Knows How Big It Is
You might also notice that the variable numValues
has vanished. A std::vector
knows how big it is, and we can find that out using the size()
member function.
To avoid resizing too often, a vector often has some empty space to fill before it has to resize and copy all the data. We can find out the size of the primitive array used behind-the-scenes by calling the capacity()
member function. If we know we'll need the array to be a certain size, we can call reserve(n)
to make the compiler set aside a specific capacity, reducing the need to resize and copy and shrink_to_fit()
to throw away spare space if we know an array won't grow any further. But you can usually just ignore those details, exactly as you would ignore them in Python.
Actually, in Python you don't choose to ignore how array resizing works behind the scenes, you have to ignore it, because there's no mechanism provided to control it.
A std::vector
Has a push_back(value)
Function
We can add new elements to the end of a std::vector
using its push_back
function. Calling push_back
increases the size of the array to accommodate the new elements (and may cause the entire contents of the array to be reallocated to a new location and copied if there isn't sufficient capacity in the underlying primitive array).
We can also remove the end element using pop_back()
. The pop_back()
member function does not return the element that was removed. Instead, you can always get at the back element using .back()
(and the frontmost element using .front
). These access functions return a reference that lets you modify the element, so you can say values.back() = 7.3
to change the last element to 7.3.
What if I try to push something that isn't a
double
?If it's a type that can be promoted or converted to a
double
, it will be. Otherwise, you'll get a compiler error.So not undefined behavior, then. I'm so relieved.
It's not the wild west. This is an error the compiler can catch.
A std::vector
Has begin()
and end()
As you might expect, std::vector
supports the classic array-style loop from the beginning of the array to past-the-end.
A std::vector
Has operator[]
As you might expect, std::vector
supports the classic array-element access using square brackets ([
…]
), so we can get specific elements with values[0]
, values[59]
, and so on.
A std::vector
Is a Proper Value, It Doesn't Decay to a Pointer
You can't easily copy or assign a whole primitive array (without resorting to a for
loop), but std::vector
behaves like any other well-behaved object and has a copy constructor and an assignment operator.
A std::vector
Has Even More Functionality
MORE! Oh, yeah!
We can insert
new elements into the array wherever we like, and also erase
existing elements. These member functions have to shift elements around in the array, so they aren't very efficient, but they can still be useful. We can also efficiently swap
two vectors in constant time.
Hold your horses! How on earth can we efficiently `swap`` two vectors in constant time? What if they have lots of elements or are different sizes? Did you make a mistake?
No, it's true. We'll see how it could be true soon enough.
Plus, we can use any of the C++-array algorithms that work on start/past-the-end values.
A std::vector
Always Uses Dynamic Memory Allocation.
The program above may not seem like it's calling new
and delete
, but behind the scenes, inside the code for std::vector
, it is.
Technically,
std::vector
uses an allocator object, and you can control all the details of how its dynamic-memory allocation works.True, but it defaults to
new
anddelete
(viastd::allocator
), and we won't worry here about all the extra control that's available.
The three data members that the std::vector<double>
object probably contains are
- A pointer to the array of values on the heap (i.e., a
double*
pointer pointing to the first element of the array). - A
size_t
holding the number of elements actually in the vector. - A
size_t
holding the capacity of the vector (i.e., including blank spaces that don't have elements in them yet).
But the implementation of
std::vector
is private, so we can only speculate.It's probably a pretty good speculation, though.
(When logged in, completion status appears here.)