Dataslope logoDataslope

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:

Code Block
C++ 20 (202002L)

Raw arrays have two well-known weaknesses:

  1. They forget their size. Pass one to a function and you get a pointer; you must pass the length separately.
  2. 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.

Code Block
C++ 20 (202002L)

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.

Code Block
C++ 20 (202002L)

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.

Code Block
C++ 20 (202002L)

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) throws std::out_of_range if i is invalid. In tight loops use []; in code that handles untrusted input, prefer at.

Two-dimensional data

A grid is naturally a vector of vectors:

Code Block
C++ 20 (202002L)

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

Challenge
C++ 20 (202002L)
Vector statistics

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.

Challenge
C++ 20 (202002L)
Reverse a string

Read the literal string "computational" (already in main) and print its reverse: lanoitatupmoc. You may use indexing or a standard algorithm.

Test your understanding

QuestionSelect one

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.

QuestionSelect one

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).

QuestionSelect one

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.

On this page