Functions Labs (8)

← Module home · House rules for all eight labs: prototypes → main → definitions; no mutable globals; compute/print split (Lesson 1 §6); every pure function ships with its test table; main narrates. Lab-0 conventions otherwise apply.

Lab 1 — The conversion desk

Scenario. A weather station converts between scales all day. Build the conversion desk, not one program.

Requirements. Functions: toCelsius(double f), toFahrenheit(double c), and void printRow(double v, char scale) (prints one aligned conversion line). main: read a scale choice, read a value (validated), print the converted value with both units, then Another? (y/n).

Function table (fill this first, in your write-up):

Function Takes Returns Alone-testable
toCelsius double f double yes — pure
toFahrenheit double c double yes
printRow value, scale void via its parts

Test rows (expected values you must verify by hand):

call expected
toCelsius(212) 100
toCelsius(32) 0
toCelsius(-40) -40
toFahrenheit(37) 98.6
toFahrenheit(-40) -40

Student tasks. 1. Write the table, then the machines, then main. 2. Why is −40 in the table twice? 3. Which function would survive a switch to Kelvin unchanged — and which two new machines would join it (C2)? 4. Prove the y/n wrapper is gallery C, not new design.

Extensions. ⭐ Add Kelvin pair. ⭐⭐ printTable(double lo, double hi, double step, char from) — a loop inside a printer. ⭐⭐⭐ Reject Kelvin inputs below 0 K in the reader — validation that knows domain physics.

Guidance. The smallest lab on purpose: every design habit (table first, split, narrator main) fits on one page. The −40 anchor is the course’s favourite correctness check.


Lab 2 — The statistics team

Scenario. A coach logs athlete scores (0–10, one decimal) until sentinel −1 and wants a session report. This lab is C1 made concrete.

Requirements. main owns all state. Functions: double readScore() (validated reader), void updateChampions(double v, double& lo, double& hi, bool& first) (seeds or challenges both champions), void printReport(int count, double total, double lo, double hi) (guards count = 0). Report: count, total, average (2 dp), min, max.

Test rows (drive the machines, not the console):

updateChampions call lo/hi before after
(7.5, 0, 0, first=true) — 7.5 / 7.5 (seed)
(3.0, 7.5, 7.5, first=false) 7.5/7.5 3.0 / 7.5
(9.9, 3.0, 7.5, first=false) 3.0/7.5 3.0 / 9.9
(7.5, 3.0, 9.9, first=false) 3.0/9.9 3.0 / 9.9 (tie: no change)

Student tasks. 1. Trace the champion seeding — why the first flag beats lo = 999? (Connect to Iteration S12.) 2. printReport(0, ...) must print No scores yet. — where does that guard live and why not in main? 3. The reader is thin: what doesn’t it do (no messages policy? state yours)? 4. Write main’s narration in pseudocode before C++.

Extensions. ⭐ Count above-average (the C1 wall — feel it, descope honestly). ⭐⭐ Standard deviation is off-limits (needs arrays) — write a comment in printReport saying exactly what data it would need. ⭐⭐⭐ Split the report into printSummary and printChampions — when does splitting a printer earn its keep?

Guidance. First lab where a reference carries state between functions — the first flag pattern is the honest way to seed champions without globals. The reader/print split is enforced by the function table.


Lab 3 — The wire tracing desk

Scenario. A debugging classroom needs a demo program that visibly shows pass-by-value vs pass-by-reference. Build the teaching artefact.

Requirements. Functions: void byValue(int x) (prints inside, adds 100), void byRef(int& x) (same, with &), void swapPair(int& a, int& b), void minMax2(int a, int b, int& lo, int& hi). main demonstrates each with printed before/after and labels. Every demo must print enough for a student to see the box diagram happen.

Test rows:

call before after why
byValue(n), n=5 5 5 copy died
byRef(n), n=5 5 105 same box
swapPair(a,b), 1/2 1 2 2 1 wires
minMax2(8,3,...) — lo 3 hi 8 outputs right

Student tasks. 1. Draw the box diagrams for all four demos (on paper, in the write-up). 2. minMax2(8, 3) vs minMax2(3, 8) — prove the champion seed isn’t order-dependent. 3. Add one demo that shouldn’t compile (byRef(5)) as a comment — explain the error message in a comment. 4. Which demo would break silently if someone removed one &? (Trade: compile error vs wrong answer.)

Extensions. ⭐ Add void addTax(double& price) and use it on three prices — write-back as ordinary tool. ⭐⭐ A demo where by-value is the right tool (protecting a caller) — narrate why. ⭐⭐⭐ void sort3(int& a, int& b, int& c) using only swapPair calls — three comparisons, order them carefully.

Guidance. The lab is a teaching aid — grading is on diagram quality and explanation accuracy, not cleverness. Task 4 is the unit’s safety argument in miniature.


Lab 4 — The warehouse dispatch

Scenario. A warehouse prices orders: unit price × qty, category discount (A 10% over 1000, B 5% over 500, C none), 200 delivery flat (free over 2000 net), receipt printed. Two clerks use it alternately — the discount machine is shared logic (see R3).

Requirements. Functions: double lineCost(double price, int qty); double categoryDiscount(char cat, double amount); double deliveryFee(double net); void printReceipt(int orderNo, double sub, double disc, double deliv, double total). main: sentinel loop on order number 0; per order read price, qty, category (validated A/B/C), print receipt; running day total at close.

Test rows (machines):

call expected
lineCost(120, 10) 1200
categoryDiscount('A', 1200) 120
categoryDiscount('A', 900) 0
categoryDiscount('B', 600) 30
categoryDiscount('C', 9999) 0
deliveryFee(1500) 200
deliveryFee(2500) 0

Student tasks. 1. Boundary rows: A at exactly 1000 (discount or not — which comparison did you pick, and does the table prove it?), B at 500, delivery at exactly 2000. 2. The shared discount machine: what makes it stealable into another program — and what would you rename first? 3. Day total: who owns the accumulator, and why isn’t it a global? 4. Order number 0 must print nothing — which layer owns that rule?

Extensions. ⭐ Category D: 15% over 3000 — one new row in one table; which function changed? ⭐⭐ Two-item orders: read a second line, subtotal first (decisions Lab 8 composition). ⭐⭐⭐ Make the discount ladder itself a function-of-function: applyLadder(amount) calling categoryDiscount — when does wrapping stop earning its keep?

Guidance. Four pure machines + one narrator: the lab is the compute/print split at bill scale. Task 1’s boundary rows are the deliverable — code that passes all seven rows but lacks the boundary rows isn’t done.


Lab 5 — The validation suite

Scenario. Every future lab needs bulletproof input. Build the reader suite as a program: a test bench that exercises each reader.

Requirements. Functions: int readInt(const char* prompt) (non-numeric re-prompt only); int readIntInRange(const char* prompt, int lo, int hi); double readDoubleInRange(const char* prompt, double lo, double hi); char readChoice(const char* prompt, const char* allowed) (re-prompt until the char is in the allowed set); bool askYesNo(const char* prompt). main: exercise each reader with a scripted bad-good sequence, printing what was accepted.

Test script (feed exactly this; note every response):

reader keystrokes expected
readInt abc, 12 re-prompt once, accept 12
readIntInRange 1..10 0, 11, 5 two range messages, accept 5
readDoubleInRange 0..1 x, -0.1, 0.5 two messages, accept 0.5
readChoice "ABC" Z, B reject Z, accept B
askYesNo maybe, Y reject, accept

Student tasks. 1. Where does clear() sit relative to ignore() and why (order matters — cite I/O Lesson 2)? 2. readChoice’s allowed-set check is a loop with a scan — write it with only decisions+loops (no string indexing tricks — that’s the Strings module). 3. Signatures: which parameters are const char* and why not const& yet? 4. Which reader goes in your library today, and what’s its contract line?

Extensions. ⭐ Add retry counters returned by reference. ⭐⭐ readIntInRange with an exclusive upper bound flag — or is a second function more honest? ⭐⭐⭐ Merge the family into one template-shaped design on paper — what would a single function need to know to serve all four? (This is C8 and, much later, real templates.)

Guidance. Infrastructure lab — the outputs are boring on purpose; the signatures are the product. These readers appear in every remaining lab; write them once, carefully.


Lab 6 — The refactor rescue

Scenario. Your instructor hands you a 70-line single-main grading program (below, condensed). Rescue it using the walkthrough method.

Requirements (the patient, condensed). Reads 5 marks with re-prompt validation; computes average, highest, lowest, fails count; prints a report with a dashed divider and a pass/fail verdict per mark. Everything inline.

Required function team (the rescue target):

Function Signature sketch
readMark int readMark(int index)
average5, highest5, lowest5, countFails one machine each, 5 int params
printDivider void printDivider(int width)
printReport takes the five stats
main narrator

Baseline rows (record BEFORE touching anything — marks sets):

marks average highest lowest fails
55 38 91 40 62 57.2 91 38 1
40 40 40 40 40 40 40 40 0
10 20 30 39 5 20.8 39 5 5

Student tasks. 1. Extract in checklist order (computers → printers → readers), compiling and re-running the baseline after each extraction. 2. The five-machines design takes 20 parameters across four functions — refactor within the rescue: which two machines collapse into one with two references, and does the table survive? 3. Prove byte-identical output on all three baseline rows. 4. Write the one-paragraph “what I’d do differently starting fresh” note.

Extensions. ⭐ printVerdict per mark (the per-mark pass/fail line) — pure or printer? Decide with the split rule. ⭐⭐ The iteration Lab 1 was the same program in loop form — map its variables onto your functions’ parameters in a table. ⭐⭐⭐ The 5-mark limit is the wall: write the comment naming exactly what data structure removes it (Stage D).

Guidance. The flagship rescue. Grading weight: baseline discipline and step-by-step verification over the final shape — a slightly ugly design reached safely beats a elegant one reached with a broken step.


Lab 7 — The divide-and-conquer desk

Scenario. A maths-tutor tool that answers three question types — quotient/remainder, GCD (two ways), and prime check — reusing your library.

Requirements. Functions: void divMod(int a, int b, int& q, int& r) (guard documented); int gcdEuclid(int a, int b); int gcdTrial(int a, int b); bool isPrime(long long n) (library carry-over); void runTutor() (the do-while menu). main narrates: greet → runTutor → farewell.

Test rows:

call expected
divMod(17, 5) q 3, r 2
divMod(20, 5) q 4, r 0
gcdEuclid(48, 18) 6
gcdTrial(48, 18) 6
isPrime(4729) true
isPrime(1) false

Student tasks. 1. Both GCD machines on (48, 18): which needed fewer loop passes, and why does Euclid win (Iteration C5)? 2. runTutor calls machines and prints — cite the line in the house rules this lab enforces. 3. divMod(5, 0) — the guard is documented, not checked: what does the caller owe, and which lab’s readers would enforce it instead? 4. Which of the five functions existed before this lab? Count your library growth honestly.

Extensions. ⭐ lcm(a, b) via a*b/gcd — one line, but write its table (including a = 0). ⭐⭐ gcdTrial with the early-exit upgrade from S28. ⭐⭐⭐ isPrime with the √n bound — measure pass-count difference on 4729 and say when the upgrade matters.

Guidance. The lab where the library concept becomes real: three of five machines already existed. The menu is C6’s skeleton fleshed out.


Lab 8 — The bank-at-the-desk

Scenario. A teller console: deposit, withdraw, balance report, transaction count, quit. The D5/R4/C10 arc, now built right from the start.

Requirements. main owns balance and txCount. Functions: double readAmount(const char* prompt) (positive, 2-dp-ish, validated reader); double deposit(double balance, double amount); bool withdraw(double& balance, double amount) — false + unchanged on insufficiency, true + updated on success; void printBalance(double balance, int txCount). Menu loop until quit; report at quit.

Test rows (machines, no console):

call before after returns
deposit(100, 50) 100 (return) 150 —
withdraw(100, 30) 100 130 true
withdraw(100, 130) 100 100 false
withdraw(30, 30) 30 0 true

Student tasks. 1. withdraw returns bool and writes balance& — why two channels? What breaks if success were signalled by returning the new balance (think: is 0 a balance or a failure?). 2. The boundary row withdraw(30, 30): exactly-equal passes — which comparison decided that, and does the table prove it? 3. printBalance before any transaction: whose job is the No transactions yet. guard? 4. Map every state variable to its owner — prove no function reaches past its parameters.

Extensions. ⭐ A per-transaction fee of 5 on withdrawals over 5000 — which machine grows, and which table gains rows? ⭐⭐ Transaction log — you can’t store it (Stage D wall); write the wall-comment naming what’s needed. ⭐⭐⭐ Two accounts: the functions already take balance — prove no signature changes, only main grows. That’s the test of stateless design.

Guidance. The capstone lab: every house rule exercised at once, and the extension proves the design’s point — stateless machines scale by caller growth, not signature churn.


After the labs

Eight labs, one pattern: table first, machines next, narrator last. The mini-project now asks you to run that pattern at menu-plus-six-tools scale — and to rebuild your own iteration mini-project as a function team.