Raw arrays are how memory actually stores sequences: elements at consecutive addresses, nothing else. std::vector, std::string, and every cache-friendly structure you will ever use are built on exactly this layout, and interviewers probe it to check you know what your abstractions cost underneath.
C arrays and pointer arithmetic
A raw C array is a fixed-length block of elements sitting side by side in memory:
int a[4] = {10, 20, 30, 40};
The array's name converts to a pointer to its first element, and adding to a pointer moves it forward by whole elements (the compiler multiplies by the element size for you):
int* p = a; // points at a[0] std::cout << *p; // 10 std::cout << *(p + 2); // 30, two ints further along p++; // now points at a[1]
In fact a[i] is defined as *(a + i). Indexing is pointer arithmetic. There is no bounds checking: a[100] compiles and reads whatever bytes happen to be there. Undefined behavior again.
You will mostly use std::vector (unit 8) instead of raw arrays, but interviews and real codebases expect you to understand this layer.
Walking a pointer across an array
Each iteration dereferences p to read a value, then advances it one element.
#include <iostream> int main() { int a[4] = {10, 20, 30, 40}; int* p = a; // points at a[0] for (int i = 0; i < 4; i++) { std::cout << *p << "\n"; p++; // step to the next element } return 0; }
Output
10 20 30 40
The initialization int* p = a; needs no &, unlike int* p = &x; for a plain variable. An array's name already converts to the address of its first element, so writing &a[0] would be equivalent but longer.
What p++ adds is 4 bytes rather than 1, because p is an int* and the compiler scales the step by the element size. That scaling is invisible and it is why the same p++ on a char* would move a single byte.
By the end of the loop p points one past the last element, which is a legal address to hold but not to dereference. That distinction is what the next example's loop condition relies on.
Summing an array with no indexing
A loop whose counter is the pointer itself. There is no i and no a[i] anywhere.
#include <iostream> int main() { int a[5] = {2, 4, 6, 8, 10}; int sum = 0; for (int* p = a; p != a + 5; p++) { sum += *p; } std::cout << sum << "\n"; return 0; }
Output
30p holds an address and *p is the int living there, so sum += *p; accumulates values rather than addresses. Forgetting the star would be a compile error here, which is a small mercy.
The bound a + 5 is the address one past the last element, and C++ guarantees that this address is valid to compute and compare against even though dereferencing it is undefined behavior. That one-past-the-end marker is the standard shape for a range in C++, and it is exactly the begin/end iterator pair that unit 8 introduces for std::vector.
Note that the condition is != rather than <. Both work for a contiguous array, but != is the form that generalizes to iterators over linked structures, where no ordering comparison is available.
References vs pointers, the final scorecard
A reference (int&) is a permanent alias. A pointer (int*) is a variable holding an address. Both let one piece of code touch another's data.
reference int& | pointer int* | |
|---|---|---|
| can be null | no, must bind at creation | yes, nullptr |
| can re-target later | no, bound once | yes, assign a new address |
| syntax to use | just the name | *p to dereference |
| arithmetic | none | p++, p + i |
Rule of thumb in modern C++: use references when you can, pointers when you must (optional "might not exist" values, dynamic memory, walking arrays, linked structures). That is why lesson 4-2's function parameters used references: the callee always had something real to bind to.
What is true of references but not pointers
A reference must be bound to a real variable when it is created, and it can never be null. That is the property pointers do not share.
The binding also happens exactly once. A reference stays attached to that one variable for its whole life, so there is no reseating either, and an assignment through a reference writes to the target rather than rebinding the alias:
int x = 1, y = 2; int& r = x; // r is now permanently an alias for x r = y; // this sets x to 2. It does NOT make r refer to y. int* p = &x; p = &y; // this really does repoint p at y
Pointers can be null, can be redirected, and support arithmetic, which is precisely why they are both more flexible and more dangerous. A reference parameter needs no null check, while a pointer parameter always raises the question of whether the caller might pass nullptr.
The same sum with an external counter
A variant that keeps a separate loop counter and uses the pointer purely for access, which is a common shape in older C-style code.
#include <iostream> int main() { int a[4] = {10, 20, 30, 40}; int* p = a; int sum = 0; for (int i = 0; i < 4; i++) { sum += *p; p++; } std::cout << "sum = " << sum << "\n"; return 0; }
Output
sum = 100The two statements in the body can be collapsed into sum += *p++;, which dereferences the current position and then advances the pointer. That works because postfix ++ yields the old value, and while it is idiomatic C, spelling both steps out is easier to read.
This version has a weakness worth naming. The count 4 appears in the loop condition and nowhere near the array declaration, so changing the array to five elements silently leaves the sum wrong. The previous example's p != a + 5 has the same problem, and unit 8's range-based for over a std::vector is the fix for both, since the container carries its own size.