Lesson 3 — Arrays and Functions

← Module home · ← Lesson 2 — The classic passes · Lesson 4 — Two dimensions →

In this lesson you will learn


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:

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

  1. Explicit size parameter — f(const int data[], int n). The default; explicit and honest.
  2. Capacity + logical size — f(int data[], int capacity, int& n) for functions that fill: capacity bounds the writes, n comes back as the reference output (Lab 4 uses this shape).
  3. 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:

  1. The m1..m5 collapse. 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.
  2. The re-read hack. Functions C1 answered “above average” by reading the data twice. Now: read once into the array, run sumOf, then countAtLeast(marks, n, static_cast<int>(avg) + 1) on the stored data.
  3. 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: const input, plain output array (the writer announces itself).

Practice

Key takeaways

→ Next: Lesson 4 — Two dimensions