More Vectors
Let’s look at some more ways we can work with vectors.
Initializing
We can initialize a vector when we declare it. The following creates a vector with 5 elements, all initialized to 0:
std::vector<double> container(5, 0.0);
Notice that we are using ()
here instead of {}
. As we’ll see
later, this means that we are calling a function here to do the
initialization (the constructor).
Here we instead initialize a vector by telling it the values of each of the elements:
std::vector<double> container2{10.0, 20.0, 30.0};
Size
As we saw earlier, we can always get the number of elements in a
vector via the size()
function:
std::vector<int> int_vec{1, 2, 3, 4, 5};
int nlen = int_vec.size();
Note
size()
technically returns a value of type std::size_t
, and
here we implicitly cast it to an int
. We learn more about
casting later.
try it…
We saw that we access an element via []
. What happens if we
access the vector out of bounds?
Bounds, iterators, and pointers
There are 2 ways to access the beginning and end of a vector
:
.cbegin()
,.begin()
: these will give you an iterator that points to the first element. The difference is that accessing with.cbegin()
will not allow you to modify the vector (thec
is forconst
). You can increment an iterator loop over the contents of the vector..cend()
,.end()
: these will return an iterator that points to one past the last element.
An iterator can be thought of as a special type of pointer—a topic that we will discuss much more later. Iterators have restrictions on their use, depending on the container—this makes them more safe to use.
If we think about an iterator like:
auto it = container.cbegin();
Then we can access the next element in container
by incrementing the iterator, it++
.
If we want to see the value in container
that the iterator is
pointing to, then we need to dereference it—this is done with the
*
operator:
std::cout << "cbegin is " << *it << std::endl;
Here’s an example of looping over an entire vector using iterators:
#include <iostream>
#include <vector>
int main() {
std::vector<int> container{0, 1, 1, 2, 3, 5, 8, 13, 21};
for (auto it = container.cbegin(); it < container.cend(); ++it) {
std::cout << *it << std::endl;
}
}
Reverse iterators
We can use std::rbegin()
/ std::rend()
to iterate through a container
in reverse.
#include <iostream>
#include <vector>
int main() {
std::vector<double> v{1.0, 2.0, 4.0, 8.0, 16.0, 32.0};
for (auto it = v.rbegin(); it < v.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
}
Algorithms
These can also be used in some powerful algorithms provided by the
algorithms
header.
Tip
A nice overview of the different algorithms that work on the standard C++ containers is provided by “hacking C++”: C++ Standard Library Algorithms
Finding an element
Here’s an example of using find
on a vector
(using std::find):
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> container{100, 200, 300, 400, 500, 600};
// search through the entire vector to find the first instance of the
// element "400"
auto pos = std::find(container.cbegin(), container.cend(), 400);
std::cout << "element found: " << *pos << std::endl;
// output the remaining elements after the one we searched for
for (auto it = pos+1; it < container.cend(); ++it) {
std::cout << *it << std::endl;
}
}
If we want to know the index of the element we found, we could use std::distance()
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> container{100, 200, 300, 400, 500, 600};
auto pos = std::find(container.cbegin(), container.cend(), 400);
// here we seek the distance from the beginning of the vector
auto idx = std::distance(container.cbegin(), pos);
std::cout << "index = " << idx << std::endl;
// note: we need to be careful here and ensure that idx is >= 0.
// and < the size of the vector. We could be accessing out
// of bounds if the value we searched for was not in the vector
std::cout << "value = " << container[idx] << std::endl;
}
Inserting
We saw that .push_back()
is used to add an element to the end of a
vector. To insert in the middle of the vector, we use
.insert(it_pos)
, where it_pos
is an iterator pointing to the
element in the vector we want to insert in front of. (Note:
insert()
can actually allow you to insert multiple elements by
specifying an additional argument.)
Here’s an example: we start with a vector with the elements 100
, 200
,
300
and then use insert()
to put 150
in between 100
and 200
.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> int_vec{100, 200, 300};
auto it = std::find(int_vec.cbegin(), int_vec.cend(), 200);
int_vec.insert(it, 150);
for (auto e : int_vec) {
std::cout << e << std::endl;
}
}
Erasing
Erasing works similar to inserting. We give an iterator pointing to the start and end of the range we want to erase, and all elements up to, but not including the end, are erased.
The end point being exclusive rather than inclusive is consistent
with .end()
returning an iterator that points one-past the end of
the vector.
Here’s an example that removes the first 4 elements of a vector.
What happens if we try to remove past the end? To be save, we should
always add a check on whether our end is past .end()
.
#include <iostream>
#include <vector>
int main() {
std::vector<int> int_vec{-1, 10, 2, 4, 6, 19, -100, 2, 4};
std::cout << "initial vector: ";
for (auto e : int_vec) {
std::cout << e << " ";
}
std::cout << std::endl;
auto it = int_vec.begin();
int_vec.erase(it, it+4);
std::cout << "updated vector: ";
for (auto e : int_vec) {
std::cout << e << " ";
}
std::cout << std::endl;
}
try it…
What happens if you use .cbegin()
and/or .cend()
instead
.begin()
and .end()
?
Remember that the c
in those functions is for const
—it
provides read-only access to the elements through the iterator.
Sorting
try it…
Let’s try to understand how the sort
function works.
https://www.cplusplus.com/reference/algorithm/sort/
Resize and clear
If we have an existing vector we can resize it with .resize(num,
init)
where num
is the number of new elements and (optionally) init
is
their initial value.
We can remove everything from the vector using .clear()
. Here’s an example:
#include <iostream>
#include <vector>
int main() {
std::vector<double> container{0.0, 1.0, 2.0};
std::cout << "current contents: ";
for (auto e : container) {
std::cout << e << " ";
}
std::cout << std::endl;
container.resize(10, 0.0);
std::cout << "new contents: ";
for (auto e : container) {
std::cout << e << " ";
}
std::cout << std::endl;
container.clear();
std::cout << "after clear: ";
for (auto e : container) {
std::cout << e << " ";
}
std::cout << std::endl;
}