Arrays and Strings
Storing many values at once — from raw C arrays to std::array, std::vector, and std::string.
Almost every program needs to hold many values of the same type:
a list of scores, the pixels in an image, the characters of a name.
C++ gives you several tools for this, ranging from very low-level
(raw arrays) to very high-level (std::vector). Knowing which to
pick is one of the most useful judgement calls in the language.
Raw C arrays
The oldest form, inherited from C:
int scores[5] = {90, 85, 72, 95, 88};This puts five ints in contiguous memory. The size is part of
the type (int[5]) and is fixed at compile time.
You index it with [i] starting at 0:
Raw arrays have two well-known weaknesses:
- They forget their size. Pass one to a function and you get a pointer; you must pass the length separately.
- They don't bounds-check.
scores[10]compiles and silently reads random memory.
These two problems together cause a huge fraction of all C/C++ bugs. For new code, prefer the standard library containers.
std::array<T, N>: a safer fixed array
std::array wraps a raw array and remembers its size. It can be
copied, returned from functions, and works with all standard
algorithms.
Use std::array whenever the size is known at compile time.
std::vector<T>: a growable array
std::vector is the workhorse container of C++. It looks like an
array but can grow at runtime. You'll reach for it constantly.
Under the hood:
A vector owns three things internally: a pointer to its heap buffer, its current size (how many elements you've put in), and its capacity (how many it could hold before having to reallocate).
push_back is cheap on average because the vector doubles its
capacity when it runs out of room. Occasional growth is expensive
(it has to copy/move everything), but amortized over many pushes the
cost per element stays constant.
std::string: a vector of characters, basically
A std::string is to char what std::vector is to int. It owns
its character storage, knows its length, and resizes as needed.
Beware the C-style string (const char*, char[]). It's a raw
array of characters terminated by a '\0' byte. You'll meet it when
calling C libraries, but in pure C++ code you should almost always
use std::string.
Iterating safely
There are three common patterns:
// 1. classic index loop
for (size_t i = 0; i < v.size(); ++i) {
use(v[i]);
}
// 2. range-based for (read-only)
for (int x : v) {
use(x);
}
// 3. range-based for (by reference, to modify)
for (int& x : v) {
x *= 2;
}Prefer #2 and #3 when you don't actually need the index — they're shorter and harder to get wrong.
Indexing safety.
v[i]does not bounds-check.v.at(i)throwsstd::out_of_rangeifiis invalid. In tight loops use[]; in code that handles untrusted input, preferat.
Two-dimensional data
A grid is naturally a vector of vectors:
For performance-critical code you'd use a single flat vector and
compute the index yourself (r * cols + c). That keeps everything
in one contiguous block — friendlier to the CPU cache.
Challenges
Read 5 ints from the input {4, 8, 15, 16, 23} (already wired in main). Print the smallest, the largest, and the sum, separated by spaces. Expected output: 4 23 66.
Read the literal string "computational" (already in main) and print its reverse: lanoitatupmoc. You may use indexing or a standard algorithm.
Test your understanding
What is the main difference between a raw int[10] and std::array<int, 10>?
Raw arrays are faster at runtime.
std::array knows its size, can be copied and returned by value, and works with standard algorithms, while raw arrays decay to pointers and forget their length.
They are interchangeable; the names are just different.
Raw arrays live on the heap.
Why is push_back on a std::vector considered O(1) amortized?
It is always O(1).
It is O(n) every time.
Most pushes are O(1); when capacity is exceeded the vector reallocates and copies, but it grows geometrically, so the average cost per push is constant.
It is O(log n).
What does v.at(i) do that v[i] does not?
It allocates new storage.
It checks the index against the vector's size and throws std::out_of_range if it is invalid; [] performs no such check.
It returns a copy instead of a reference.
It is significantly faster.
Next: how to bundle multiple values together into a single named type — structs and aggregates.