C++ · Intermediate
Vectors in C++ (std::vector)
In short: std::vector is the standard library's growable array: it owns a contiguous block of elements, push_back appends, size() reports the count, [] and at() index it, and it can be passed by reference, iterated with a range-for and nested to make grids. It is the default container for a sequence whose length is decided at run time.
An array that grows
A built-in array has a length fixed when it is declared, so a program reading an unknown number of values must guess a maximum. std::vector<T>, from <vector>, removes the guess: it starts empty or at a chosen size and grows as you push_back elements, keeping them in one contiguous block just like an array. Indexing with v[i] is as fast as array indexing, and because a vector knows its own size(), a function receiving one needs no separate length parameter.
Growth is automatic. When the block is full, the vector allocates a larger one, moves the elements across and frees the old block; the new block is larger by a factor rather than by one, so the average cost of a push_back stays constant. That reallocation is also the vector's one trap: any pointer, reference or iterator into the old block is invalid afterwards. If you know roughly how many elements are coming, reserve(n) allocates once up front.
v[i] does no bounds checking: an index past the end reads or writes memory you do not own, with no error message. v.at(i) checks and throws std::out_of_range, worth its small cost when the index comes from input. front() and back() give the first and last element, empty() says whether there are any, and pop_back() removes the last. insert and erase take an iterator position such as v.begin() + i; elements after that point shift, so these cost time proportional to the distance to the end and suit small vectors or positions near the back.
Indexes and size() have type std::size_t, an unsigned integer. Comparing it with a signed int counter draws a warning under -Wall, so declare counters as std::size_t or use a range-for, for (int n : boarded), which cannot run out of range. Write for (const std::string& w : words) to avoid copying each element and for (int& x : v) to modify in place.
A vector passed by value is copied, every element included. Pass const std::vector<T>& to read and std::vector<T>& to modify. A vector of vectors, std::vector<std::vector<char>>, makes a grid; the constructor form seats(rows, std::vector<char>(cols, '.')) builds one already filled. Sorting and searching a vector are the subject of STL algorithms.
Syntax
#include <vector>
std::vector<int> a; // empty
std::vector<int> b(5); // five zeros
std::vector<int> c(5, 7); // five sevens
std::vector<int> d{5, 7}; // two elements: 5 and 7
a.push_back(12); // append
a.pop_back(); // remove the last element
a.size(); a.empty(); // count; whether there are none
a[0]; a.at(0); // unchecked / checked access
a.front(); a.back();
a.insert(a.begin() + 1, 99); // insert before index 1
a.erase(a.begin()); // remove index 0
a.resize(10); // new elements are value-initialised (0)
a.clear(); // remove everything
for (int x : a) { /* read each */ }
for (int& x : a) { x *= 2; } // modify in place
void total(const std::vector<int>& v); // pass by const referencestd::vector<int> b(5) and std::vector<int> d{5} are different: parentheses give a count, braces give the elements.
Reading values into a vector and walking it
Passengers boarding at each stop. The count comes first, then the values, and the vector grows one push_back at a time.
#include <iostream>
#include <vector>
int main() {
int stops;
std::cin >> stops;
std::vector<int> boarded; // starts empty, size 0
for (int i = 0; i < stops; ++i) {
int n;
std::cin >> n;
boarded.push_back(n); // grows by one element
}
int total = 0;
for (int n : boarded) total += n; // range-for visits each element
std::cout << "stops: " << boarded.size() << "\n";
std::cout << "first: " << boarded.front() << ", last: " << boarded.back() << "\n";
std::size_t best = 0;
for (std::size_t i = 1; i < boarded.size(); ++i) {
if (boarded[i] > boarded[best]) best = i;
}
std::cout << "busiest stop: index " << best << " with " << boarded[best] << "\n";
std::cout << "average: " << static_cast<double>(total) / boarded.size() << "\n";
return 0;
}Input given to the program: 5 ↵ 12 7 19 4 8
Output
stops: 5 first: 12, last: 8 busiest stop: index 2 with 19 average: 10
boarded starts empty and ends with five elements, so no maximum had to be guessed. The range-for reads every element for the total; the indexed loop is used where the index itself matters, and best is a std::size_t to match size(). Casting total to double before dividing avoids integer division, though here the average happens to be whole.
Passing a vector to functions, erasing and checked access
Tags read one per line until the input ends. One function reads the vector through a const reference; another removes short tags through a non-const reference.
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
int longest(const std::vector<std::string>& words) { // read only, no copy made
std::size_t best = 0;
for (const std::string& w : words) {
if (w.size() > best) best = w.size();
}
return static_cast<int>(best);
}
void dropShort(std::vector<std::string>& words, std::size_t minLen) { // changes the caller's vector
for (std::size_t i = 0; i < words.size(); ) {
if (words[i].size() < minLen) {
words.erase(words.begin() + i); // later elements shift down: do not advance i
} else {
++i;
}
}
}
int main() {
std::vector<std::string> tags;
std::string line;
while (std::getline(std::cin, line)) {
if (!line.empty()) tags.push_back(line);
}
std::cout << "longest tag: " << longest(tags) << " chars\n";
dropShort(tags, 4);
tags.insert(tags.begin(), "all"); // insert at the front
for (std::size_t i = 0; i < tags.size(); ++i) {
std::cout << i << ": " << tags[i] << "\n";
}
try {
std::cout << tags.at(10) << "\n"; // at() checks the index
} catch (const std::out_of_range&) {
std::cout << "no element at 10\n";
}
return 0;
}Input given to the program: sale ↵ new ↵ vegan ↵ hot ↵ gluten-free
Output
longest tag: 11 chars 0: all 1: sale 2: vegan 3: gluten-free no element at 10
longest cannot modify words and makes no copy. dropShort erases in place: after an erase the next element has moved into index i, so i is only advanced when nothing was removed. insert at begin() shifts every element up by one. at(10) on a four-element vector throws std::out_of_range, which the catch turns into a message; tags[10] would have been undefined behaviour with no message at all.
A two-dimensional vector
A seating chart built as a vector of rows, each row a vector of characters. Bookings mark seats with X.
#include <iostream>
#include <vector>
int main() {
int rows, cols;
std::cin >> rows >> cols;
// rows vectors, each holding cols copies of '.'
std::vector<std::vector<char>> seats(rows, std::vector<char>(cols, '.'));
int bookings;
std::cin >> bookings;
for (int i = 0; i < bookings; ++i) {
int r, c;
std::cin >> r >> c;
seats[r][c] = 'X';
}
int free = 0;
for (const std::vector<char>& row : seats) {
for (char s : row) {
std::cout << s;
if (s == '.') ++free;
}
std::cout << "\n";
}
std::cout << free << " free of " << rows * cols << "\n";
return 0;
}Input given to the program: 3 4 ↵ 3 ↵ 0 1 ↵ 1 3 ↵ 2 0
Output
.X.. ...X X... 9 free of 12
The constructor seats(rows, std::vector<char>(cols, '.')) creates rows copies of a row that already holds cols dots, so every seat exists before any booking is read. seats[r][c] indexes the row first, then the column. The nested range-for takes each row by const& to avoid copying it and each seat by value.
Operations and their cost
| Operation | Effect | Cost |
|---|---|---|
| push_back(x) | append at the end | constant on average |
| pop_back() | remove the last element | constant |
| v[i], v.at(i) | element i; at() throws on a bad index | constant |
| insert(pos, x), erase(pos) | shift every later element | linear in the elements after pos |
| size(), empty() | count; whether there are none | constant |
| clear() | remove every element | linear |
Common mistakes
Indexing past the end with []
Why it goes wrong:
v[v.size()]is one past the last element. There is no check, so the program reads or writes memory it does not own and may print garbage or crash later, far from the bug.Fix: Keep indexes below
size(), or useat()where the index comes from input.C++ · fixif (i < v.size()) std::cout << v[i];Erasing elements inside a range-for
Why it goes wrong:
eraseinvalidates iterators into the vector, including the hidden one driving the range-for, so the loop continues over invalid memory.Fix: Use an index loop that advances only when nothing was erased, or the remove-erase idiom from the algorithms lesson.
Keeping a pointer or reference to an element across a push_back
Why it goes wrong: A
push_backthat triggers reallocation moves every element to a new block, soint& first = v[0]now refers to freed memory.Fix: Hold indexes instead of references while the vector is still growing, or call
reservefirst.Passing a vector by value when it only needs to be read
Why it goes wrong: Every element is copied on each call; for a vector of a million entries that dominates the run time and is easy to miss because the code is correct.
Fix: Take
const std::vector<T>&.C++ · fixint longest(const std::vector<std::string>& words);
Where you use this
A vector is the first thing to reach for whenever a program collects values: readings from a file whose length is unknown, the results of a query, the lines of a document, the neighbours of a node in a graph. push_back and pop_back at the end also make it a perfectly good stack, and a vector of vectors is the usual representation of a grid, a matrix or an adjacency list.
It is the container most other library pieces assume. STL algorithms sort and search it through iterators, maps often hold a vector as their value type to group items under a key, and std::priority_queue is built on top of one.
std::map<std::string, std::vector<std::string>> byCategory;
byCategory[category].push_back(itemName); // group items under a keyKey points
std::vector<T>is a contiguous, growable array;push_backappends andsize()reports the count.[]is unchecked;at()throwsstd::out_of_range.- Use
std::size_tfor indexes, or a range-for; take elements byconst&to avoid copies. - Pass vectors by
const&to read and by&to modify; passing by value copies everything. - Reallocation on growth invalidates pointers, references and iterators into the vector.
std::vector<std::vector<T>>builds grids;v(rows, std::vector<T>(cols, value))fills one.
Try it yourself
The program reads a count and that many integers into a vector and prints them in order. Change the printing loop so the values come out in reverse order, separated by single spaces, so the input 3 8 1 6 prints 6 1 8 3.
#include <iostream>
#include <vector>
int main() {
int n;
std::cin >> n;
std::vector<int> v;
for (int i = 0; i < n; ++i) {
int x;
std::cin >> x;
v.push_back(x);
}
// print v from the last element to the first
for (std::size_t i = 0; i < v.size(); ++i) {
std::cout << v[i];
if (i + 1 < v.size()) std::cout << " ";
}
std::cout << "\n";
return 0;
}4 ↵ 3 8 1 66 1 8 3#include <iostream>
#include <vector>
int main() {
int n;
std::cin >> n;
std::vector<int> v;
for (int i = 0; i < n; ++i) {
int x;
std::cin >> x;
v.push_back(x);
}
for (std::size_t i = v.size(); i-- > 0; ) {
std::cout << v[i];
if (i > 0) std::cout << " ";
}
std::cout << "\n";
return 0;
}Practise this
Exercises for this lesson are in the C++ practice set.
Related lessons
- Arrays in C++Declaring fixed-size C++ arrays, zero-based indexing, initialising, range-for loops, std::array, 2D arrays and why out-of-bounds access is undefined behaviour.10 min
- Loops in C++: for, while and do-whileC++ loops explained: the three parts of a for loop, while and do-while, reading input until it ends, nested loops, and when to reach for break or continue.9 min
- Templates in C++How C++ templates generate a function or class for each type you use, how type deduction works, and what C++17 adds with if constexpr and deduction guides.11 min
- Maps and Sets in C++How std::map and std::set store sorted keys in C++, how [] and find differ, how to count and look up safely, and when unordered_map is the better choice.12 min
Frequently asked questions
When should I use std::vector instead of a built-in array in C++?
Almost always. A vector knows its size, grows on demand, can be returned from a function and passed by reference without a separate length, and is checked by at(). A built-in array is only preferable for a small fixed-size buffer whose length is a compile-time constant, and even then std::array is safer.
How do I remove an element from a vector by value?
Find its position and erase it: v.erase(std::find(v.begin(), v.end(), value)), after checking that find did not return end(). To remove every occurrence, use std::remove followed by erase, which the STL algorithms lesson explains; C++20 adds std::erase(v, value) that does both steps.
How this page was checked. Every program on it was run with GCC 13 (C++17) at build time by the publishing checks, and the output shown is what it printed. Running C++ inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C++17) locally.