Lesson 3 — Arrays and Functions
← Module home · ← Lesson 2 — The classic passes · Lesson 4 — Two dimensions →
In this lesson you will learn
- what is actually copied when you pass an array — the honest answer
- why array parameters are always references in disguise, and what that costs and buys
const— the promise that makes array parameters safe to share- how the size travels: parameter conventions and partial fills
- the refactorings the array finally enables
1. What gets passed — the surprise
You learned in Functions Lesson 2 that arguments are copied. Arrays break that rule:
void tryToChange(int data[]) {
data[0] = 999; // changes the CALLER's first box!
}
int main() {
int marks[3] = {1, 2, 3};
tryToChange(marks);
std::cout << marks[0] << '\n'; // 999 — not 1
return 0;
}
The honest mechanics: when an array is an argument, what travels is the address of its first box — the function gets a handle on the caller’s original boxes, not a copy of them. (That address is precisely what Unit 13 calls a pointer; you don’t need the machinery yet, only the consequence.)
main: marks ──► [ 1 ][ 2 ][ 3 ]
▲
tryToChange: data ───────────┘ (same boxes, second name)
So array parameters behave like the reference parameters from Unit 08 — automatically, with no &. Two consequences:
- A function can change your data. Useful for fills and in-place edits; dangerous for anything else.
- No copying cost. A 10 000-element array passes as cheaply as a 3-element one — the thing
const&imitated for single values.
2. const — the safety belt
Since the function holds a handle on your originals, C++ lets you decide whether it may write through it:
int sumOf(const int data[], int n); // PROMISE: read-only
void fillOnes(int data[], int n); // no const: may write
Inside sumOf, any attempt to assign data[i] is a compile error — the compiler enforces the promise. House rule from this lesson forward:
Every function that only reads an array takes it
const. Write-access parameters stay plain — but their names should say so (result[],tally[]).
This is the compute/print split extended to data: readers of data are const, writers announce themselves by type.
3. The size travels separately — and how
Here’s the trap the address-mechanics creates: the function receives a handle on box 0 — and nothing else. Inside sumOf, there is no way to ask “how long is this array?” The int n parameter is not decoration; it is the only size information that exists:
int sumOf(const int data[], int n); // size MUST be a parameter
Course conventions (all three appear in real code; the labs name which they use):
- Explicit size parameter —
f(const int data[], int n). The default; explicit and honest. - Capacity + logical size —
f(int data[], int capacity, int& n)for functions that fill: capacity bounds the writes,ncomes back as the reference output (Lab 4 uses this shape). - Sentinel-terminated — the array carries its own end marker (a −1, an empty line). Convenient for literals; hides the size from the type. The labs avoid it except where input format dictates it.
What you cannot do: sizeof(data) inside the function will not give the array’s length — inside, data is just the handle, and sizeof returns the handle’s size, not the row’s. That’s the deep reason the size parameter exists at all.
Partial fills — the MAX_SIZE/n pair crosses the boundary
The Lesson 1 capacity/logical-size pair becomes a calling convention:
// reads up to capacity values; returns how many landed in data
int readData(int data[], int capacity) {
int n = 0;
int value;
while (n < capacity && std::cin >> value && value != -1) {
data[n] = value;
n += 1;
}
return n;
}
int main() {
constexpr int MAX_SIZE = 50;
int data[MAX_SIZE];
int n = readData(data, MAX_SIZE); // fill
std::cout << "Read " << n << " values, sum " << sumOf(data, n) << '\n';
return 0;
}
readData writes (plain array param), sumOf reads (const), and n — the logical size — flows from one to the other. This reader/writer split is the standard shape of every lab in this unit.
4. A function team on real data
Putting it together — the marks program as a team (this is the mini-project’s skeleton in miniature):
#include <iostream>
#include <iomanip>
constexpr int MAX_STUDENTS = 50;
int readMarks(int marks[], int capacity); // fill; returns n
void printMarks(const int marks[], int n); // printer
int sumOf(const int marks[], int n); // accumulator
int indexMax(const int marks[], int n); // champion
int indexMin(const int marks[], int n);
double averageOf(const int marks[], int n); // uses sumOf
int countAtLeast(const int marks[], int n, int cut); // filter counter
int linearSearch(const int marks[], int n, int target); // finder
int main() {
int marks[MAX_STUDENTS];
int n = readMarks(marks, MAX_STUDENTS);
printMarks(marks, n);
std::cout << "Average: " << averageOf(marks, n) << '\n'
<< "Highest: mark of student " << indexMax(marks, n) + 1 << '\n'
<< "Lowest: mark of student " << indexMin(marks, n) + 1 << '\n'
<< "Passed (>=40): " << countAtLeast(marks, n, 40) << '\n';
int probe = linearSearch(marks, n, 91);
if (probe != -1) std::cout << "A 91 was found at position " << probe + 1 << '\n';
return 0;
}
// ... definitions per house style: prototypes -> main -> definitions ...
Compare with the five-m1..m5 signatures of Functions E19: the parameter list collapsed, and every function is table-testable with the driver pattern — a tiny main filling an array by hand and calling one machine. The mini-project assembles exactly this team with validation and a menu.
5. Refactorings the array finally enables
Three walls fall in this lesson; each is a refactoring you can do in old programs:
- The
m1..m5collapse. Every five-parameter statistics signature becomes(const int marks[], int n)— Functions R6’s five machines shrink to one-parameter forms, and a sixth statistic no longer changes any signature. - The re-read hack. Functions C1 answered “above average” by reading the data twice. Now: read once into the array, run
sumOf, thencountAtLeast(marks, n, static_cast<int>(avg) + 1)on the stored data. - The tally graduates. The frequency pass needs its
tally[]inside a function too —void tallyAnswers(const int answers[], int n, int tally[], int options)shows both faces at once:constinput, plain output array (the writer announces itself).
Practice
- Exercises 17–24 — parameters, const, fills, the team
- Debugging 7–8
- Lab 3 or Lab 4
Key takeaways
- Arrays pass by handle, not by copy — functions can (and sometimes should) change the caller’s boxes; no copy cost.
- Read-only array parameters take
const; the compiler enforces the promise. - The size does not travel with the array — pass
nexplicitly (or capacity +int& nfor fills). - The MAX_SIZE/n pair, a
constreading team, and a plain writing reader is the standard program shape from now on.
→ Next: Lesson 4 — Two dimensions