Grouping Memory: Structs — and the Midterm
Did the Resurrection happen — and what does lived faith look like?
Chapter 4 — Grouping Memory: Structs — and the Midterm
“A just balance and scales are the LORD’s; all the weights in the bag are his work.” — Proverbs 16:11
“You don’t fight at the level of your hopes. You fight at the level of your training.” — boxing folk wisdom
This week merges Coding 1 chapters 7 and 8 — structs and midterm review. If you also have the sixteen-week book, everything from both chapters is here, resequenced so the new material and the review reinforce each other instead of sitting in separate weeks.
Your Week at a Glance
Twelve honest hours, four sessions of roughly three. The heaviest week in the course: it carries a new concept and the midterm. Do not stack sessions 3 and 4 on the same day.
| Session | ~Time | What you do | Checkpoint at the end |
|---|---|---|---|
| 1 — Learn the struct | 3 hrs | Read §4.1–§4.8. Type struct_basics.cpp, struct_array.cpp, struct_function.cpp, struct_return.cpp, and struct_nested.cpp by hand — do not paste — and run each one. Do Reps 1–6 in the exercises. | You can define a struct, make an array of them, and print that array through a const& helper function without looking anything up. |
| 2 — Bundle it into a program | 3 hrs | Read §4.9–§4.11. Study evidence_inventory.cpp and struct_remove_shift.cpp. Then read only the description of the review problem in §4.12, build it yourself, and compare against review_worked_example.cpp. Reps 7–12. | Your version of the review problem compiles with zero warnings and reports 108 total chapters. |
| 3 — Drill and take Part A | 3 hrs | Work §4.13 (reproduce at least three of the compiler errors on purpose — see them with your own eyes). Take the §4.15 checkpoint honestly. Finish the remaining reps. Then do sample_midterm.cpp closed-book in about 60 minutes. Finally, sit Midterm Part A in Canvas. | Part A submitted. Your sample-midterm solution compiles clean and produces the report the prompt asks for. |
| 4 — Midterm Part B | 3 hrs | Mission Trip Simulator, the take-home practical. One sitting if you can. Write the reflection block last, while the code is fresh. | OnlineGDB link plus the authorship/reflection block submitted before the window closes. |
Two scheduling warnings, because nobody is going to say them out loud:
- Session 4 is three hours of concentration, not three hours of elapsed time with your phone on. Squeeze it into the last ninety minutes before the deadline and you will submit something you cannot explain — see §4.17 for what happens to code you cannot explain.
- If session 3’s checkpoint goes badly, repeat session 3 before starting Part B. An hour of re-drilling is cheaper than the practical.
Why This Matters
Last week ended in deliberate pain. You built the Manuscript Database out of parallel arrays: names[], centuries[], types[], one count. Add a manuscript, touch all three arrays. Remove one, shift all three. Sort by century, and swap the name and the type at every step — or the data silently corrupts itself, names[3] describing one manuscript while centuries[3] describes another, with no error message anywhere, ever.
This week is the relief. A struct lets you bundle the data that belongs together under one name. A struct says: a Manuscript is a thing that has a name, a century, and a type — and from now on, when you handle a Manuscript, you handle all three together. Not three arrays. One array of complete records. The category of bug you fought last week stops being possible.
Structs are also the on-ramp to the back half of this course. A struct is a class without behavior — just data, bundled. Chapter 5 adds methods, private, and rules; get fluent with structs and the leap to classes is mostly vocabulary.
And in the same week, you are examined on all of it. That is not a scheduling accident — see §4.9.
For the apologetics theme: this is the evidence week. Each piece of evidence for a historical claim is a bundle — the claim, the source, the type, its strength, the earliest credible attestation. Treating each as one record is exactly the move a scholar makes when inventorying a case. Habermas’s “minimal facts” approach to the Resurrection is, at the data-modeling level, an array of structs. You are about to build it.
4.1 — The Struct Declaration
A struct definition looks like this:
struct Manuscript {
string name;
int century;
string type;
};
Read it out loud: “There is a type called Manuscript. A Manuscript has three fields: a name, which is a string; a century, which is an int; and a type, which is a string.”
Note the semicolon after the closing brace. C++ requires it on type declarations, and if you forget it the error lands on the line after your struct — see §4.13, bug 1, for the exact message.
Once the type exists, you declare variables of it like any other type:
Manuscript m; // a Manuscript named m
and you reach its fields with the dot operator:
m.name = "P52";
m.century = 2;
m.type = "papyrus";
Three fields, one container. m.name behaves like every other string — m.name.length(), m.name == "P52", m.name.substr(0, 1) all work — and m.century is an ordinary int. The struct adds no magic; it adds grouping.
Struct definitions go at the top of the file, under the #includes and above every function that uses them. Put one below a function that mentions the type and the compiler rejects the file (§4.13, bug 4).
4.2 — Three Ways to Get a Struct Into a Usable State
There are three, and you will use all three.
1. Brace initialization — fill every field at once, in declaration order:
Manuscript p52 = {"P52", 2, "papyrus"};
Manuscript sinaiticus = {"Codex Sinaiticus", 4, "uncial"};
Order matters, and it is the order in the struct definition — not alphabetical, not the order you happen to be thinking in. Too many values and the compiler stops you outright (§4.13, bug 3). Too few is legal — the leftovers are value-initialized to 0 and "" — but it is not silent. Under this course’s flags, Manuscript m = {"P52", 2}; builds and warns:
manuscript.cpp:12:29: warning: missing initializer for member 'Manuscript::type' [-Wmissing-field-initializers]
12 | Manuscript m = {"P52", 2};
| ^
In this course a warning is a failure, so fill every field, or use form 3 below and assign afterward.
2. Declare, then assign field by field:
Manuscript sinaiticus;
sinaiticus.name = "Codex Sinaiticus";
sinaiticus.century = 4;
sinaiticus.type = "uncial";
Verbose, but necessary when the values are not known until the program runs.
3. Value-initialize with {}, then fill in what you know:
Manuscript fragment{}; // every int is 0, every string is ""
fragment.name = "(unidentified fragment)";
Those two characters prevent an entire class of bug. Without them, Manuscript fragment; leaves the int fields holding whatever bytes were already at that address — §4.13, bug 12, shows three consecutive runs of one unchanged binary printing three different numbers.
code/struct_basics.cpp does all three side by side. It prints exactly:
P52 - 2nd c. - papyrus
Codex Sinaiticus - 4th c. - uncial
(unidentified fragment) - century field is 0, type field is ""
The last line is proof the {} did its job: a clean 0 and an empty string, not junk.
Coach’s Note — C++ also has designated initializers —
Manuscript p52 = {.name = "P52", .century = 2, .type = "papyrus"};— which name the fields explicitly. GCC and Clang accept them under-std=c++17as an extension; they became standard in C++20. This course will not lean on them; the exam and the projects assume positional braces. Know they exist; write the positional form.
4.3 — Arrays of Structs: Retiring Parallel Arrays
The move the whole chapter is for. Instead of:
string names[MAX];
int centuries[MAX];
string types[MAX];
int count = 0;
you write:
Manuscript manuscripts[MAX];
int count = 0;
One array. One count. The array-plus-count pattern from Chapter 3 is unchanged — count is still the logical size, MAX the physical size — but now each slot holds everything about one manuscript.
Access combines two operators you already know: index first, then dot.
manuscripts[0].name = "P52";
manuscripts[0].century = 2;
manuscripts[0].type = "papyrus";
manuscripts[1] = {"Codex Sinaiticus", 4, "uncial"}; // whole record at once
Iterating is the same loop you have written twenty times:
for (int i = 0; i < count; i++) {
cout << manuscripts[i].name
<< " (" << manuscripts[i].century << ", "
<< manuscripts[i].type << ")" << endl;
}
code/struct_array.cpp is that whole idea in one short program — five manuscripts in one array, printed through a const& helper, then scanned twice: once to find the earliest, once to accumulate the centuries and divide with a cast. Type it yourself before you read it. It prints:
All manuscripts:
P52 - 2nd c. - papyrus
Codex Sinaiticus - 4th c. - uncial
Codex Vaticanus - 4th c. - uncial
Codex Bezae - 5th c. - uncial
Bodmer Papyri - 3rd c. - papyrus
Oldest: P52 - 2nd c. - papyrus
Average century: 3.6
Note 3.6, not 3. The centuries total 18 across 5 records, and static_cast<double>(18) / 5 is what keeps the .6.
And here is the payoff. Removing an element means shifting everything after it down one slot:
for (int j = i; j < count - 1; j++) {
manuscripts[j] = manuscripts[j + 1];
}
count--;
That one line manuscripts[j] = manuscripts[j + 1]; copies all three fields. Name, century, and type travel together and cannot drift out of sync, because they are not in separate arrays anymore. Last week the same removal was three assignments that had to stay in lockstep forever.
code/struct_remove_shift.cpp is the full pattern — add, find_index, remove_by_name — over an array of structs. Its actual output:
After 5 adds (count = 5):
0: P52 (century 2, papyrus)
1: Bodmer Papyri (century 3, papyrus)
2: Codex Sinaiticus (century 4, uncial)
3: Codex Vaticanus (century 4, uncial)
4: Codex Bezae (century 5, uncial)
find_index("Codex Vaticanus") = 3
find_index("Codex Alexandrinus") = -1
remove_by_name("Bodmer Papyri") returned true
After the shift (count = 4):
0: P52 (century 2, papyrus)
1: Codex Sinaiticus (century 4, uncial)
2: Codex Vaticanus (century 4, uncial)
3: Codex Bezae (century 5, uncial)
remove_by_name("Codex Alexandrinus") returned false and count is still 4
Read the last two lines. Removing something that is not there returns false and changes nothing — guard clauses and Chapter 3’s -1-means-not-found convention doing their work. Structs did not replace those habits; they ride on top of them.
4.4 — Structs as Function Parameters
Structs go into functions like anything else, with the three passing modes from Chapter 3 — and now the choice actually matters.
By value — the function gets a private copy:
void mark_disputed_locally(Manuscript m) {
m.type = m.type + " (disputed)"; // edits the copy; caller sees nothing
}
By reference (&) — the function edits the caller’s record:
void mark_disputed(Manuscript& m) {
m.type = m.type + " (disputed)"; // edits the original
}
By const reference (const&) — no copy is made and the compiler forbids modification:
void print_manuscript(const Manuscript& m) {
cout << m.name << " - " << m.century << " - " << m.type << endl;
}
code/struct_function.cpp runs all three against the same record. Its actual output:
Before:
Codex Bezae - 5th c. - uncial
After mark_disputed_locally (by value):
Codex Bezae - 5th c. - uncial
After mark_disputed (by reference):
Codex Bezae - 5th c. - uncial (disputed)
The middle block is the lesson. The by-value function ran, did its work, and the caller’s manuscript is untouched. Nothing failed; no warning. If you meant to modify the original, that is a silent bug and the fix is one &.
Which to choose:
const&for read-only access. Default to this for structs. It costs nothing at the call site and makes a promise the compiler enforces.&when the function must change the caller’s record —mark_disputed,add,remove_by_name.- By value when you want a scratch copy the caller should not see changed. Rarer than beginners think.
Arrays of structs follow the array rules you already know: void list_all(const Manuscript ms[], int count) — never write & on an array parameter. You do not need one: an array argument is not copied, so the function already writes into the caller’s array. (What actually crosses the call is the address of the first element; the mechanism is Chapter 6, and the working rule above is all you need this week.) const still buys you compiler-enforced read-only access — see bug 8 in §4.13 for what happens when you drop it.
And the one you will need constantly: the count must be a reference when the function changes it.
bool add(Manuscript ms[], int& count, const Manuscript& m) {
if (count >= MAX) {
return false;
}
ms[count] = m;
count++;
return true;
}
The array is shared automatically; the count is a plain int and is not. Without int&, the caller’s count never grows, every add overwrites slot 0, and the list stays at zero entries with no error message. This is the most common Week 4 bug that compiles cleanly.
Coach’s Note —
const Manuscript&reads aloud as “constant reference to a Manuscript”: this function borrows your Manuscript and promises not to change it. That promise has teeth — try to assign to a field inside aconst&function and the compiler rejects the file outright (§4.13, bug 7). That is the rare case where the compiler enforces a comment. Use it.
4.5 — Returning Structs
Functions can hand a struct back. Two forms:
Manuscript make_manuscript_long(string n, int c, string t) {
Manuscript m;
m.name = n;
m.century = c;
m.type = t;
return m;
}
Manuscript make_manuscript(string n, int c, string t) {
return {n, c, t}; // same effect, shorter
}
The second form returns a brace list and lets C++ build the Manuscript on the spot. Mechanically, C++17 normally builds the returned struct straight into the caller’s storage — no copy happens at all — and falls back to a cheap move when it cannot. You do not have to think about it. Returning a struct works like returning an int.
The more valuable use: a function can only return one thing, so when you need several, make that one thing a struct. Chapter 3 forced you to fake this with output parameters (int& out_total, double& out_average). Now — this next block is a sketch, not a compilable program; the scanning loop that declares and fills total is elided at the // ... line:
struct Summary {
int count;
int earliest_century;
int latest_century;
double average_century;
};
Summary summarize(const Manuscript ms[], int count) {
Summary s{}; // all fields zeroed
s.count = count;
if (count == 0) {
return s; // guard clause: nothing to summarize
}
// ...scan, accumulate, compute...
s.average_century = static_cast<double>(total) / count;
return s;
}
code/struct_return.cpp is that sketch filled in and running. Actual output:
P52 (papyrus)
Bodmer Papyri (papyrus)
Codex Sinaiticus (uncial)
Codex Bezae (uncial)
Records: 4
Earliest: century 2
Latest: century 5
Average: century 3.5
Summarizing zero records is safe: count = 0, average = 0
Three things to notice. Summary s{} value-initializes, so the empty case returns clean zeros. The guard clause returns early instead of dividing by zero. And static_cast<double>(total) / count is Week 1’s integer-division fix — without it, 14 / 4 prints 3, not 3.5. Four weeks in, that trap is still the trap.
4.6 — Structs Inside Structs
A struct field can itself be a struct:
struct Date {
int year;
int month;
int day;
};
struct Manuscript {
string name;
Date catalogued; // a struct inside a struct
int century;
string type;
};
You reach nested fields by chaining the dot:
Manuscript a{};
a.catalogued.year = 2011;
a.catalogued.month = 3;
and you initialize them with nested braces — inner braces fill the inner struct:
Manuscript b = {"Codex Sinaiticus", {2012, 9, 14}, 4, "uncial"};
code/struct_nested.cpp demonstrates both, and deliberately never assigns a.catalogued.day. Actual output:
P52 (century 2, papyrus) catalogued 2011-3-0
Codex Sinaiticus (century 4, uncial) catalogued 2012-9-14
a.catalogued.day was never assigned; it holds 0
The 0 is there because of the {} on Manuscript a{}. Delete those two characters and that field holds whatever was in memory. That is the whole argument for value-initialization, in one line of output.
Nesting suits genuinely hierarchical data and is easy to overdo. Two levels is plenty. If you are writing a.b.c.d.e, ask whether part of it should be its own variable or its own function.
4.7 — Worked Example: The Evidence Inventory
Everything so far, assembled into a program you could show someone. code/evidence_inventory.cpp holds an inventory of evidence records:
struct Evidence {
string claim;
string source;
string type;
int strength_rating; // 1-10
int year; // earliest credibly attested, AD
};
Five fields. As parallel arrays that is five arrays to keep in lockstep — which is precisely why this data shape wants a struct.
The program seeds itself with the Resurrection “minimal facts” set (Jesus’ death by crucifixion; the empty tomb tradition; the post-mortem appearance tradition; the conversion of Paul, a persecutor; the conversion of James, a skeptic brother; the martyrdom of multiple eyewitnesses), then offers a menu: list, remove by claim, average strength, find by claim, exit. Every operation you have learned is in there: find_evidence (linear search, -1 when absent), remove_evidence (find, shift down, count--), add_evidence (guard on MAX_EVIDENCE, assign, then increment), average_strength (accumulate, guard count == 0, cast before dividing), print_evidence(const Evidence&, int) (read-only, no copy), and a menu loop over cin that survives end-of-input.
That last one deserves a look, because it is the difference between a program and a program that hangs:
while (true) {
print_menu(count);
if (!(cin >> choice)) { // EOF, or the user typed letters
cout << endl << "(no more input - exiting)" << endl;
break;
}
cin.ignore(numeric_limits<streamsize>::max(), '\n');
// ...dispatch on choice...
}
Recall from Week 2 that a failed cin >> sets the target to 0 and jams cin permanently. A loop written cin >> choice; with no check would spin on choice == 0 until you kill it; if (!(cin >> choice)) ends it cleanly instead. The cin.ignore(...) throws away the rest of the line so the getline calls that follow read the claim the user actually typed, not a leftover newline.
Feeding the program the sample input listed in its header comment (3, 4, a claim, 2, a claim, 3, 5) produces, among the menus, these actual lines:
Average strength: 8.66667
...
Claim to find: 4. Conversion of Paul (a persecutor)
Source: Acts 9; Galatians 1; Pauline epistles
Type: historical
Strength: 10/10
Year: AD 35
...
Claim to remove: Removed.
...
Average strength: 8.8
Trace the average. Strengths 10, 8, 9, 10, 8, 7 total 52, and 52 / 6 is 8.66667. Removing the entry rated 8 leaves 44 across 5, which is 8.8. If either had printed whole, the cast was missing.
One thing to be clear about, since the program prints them next to real citations: the claims, sources, and Scripture references are carried over unchanged from Coding 1’s Project 7 starter, but the strength_rating and year fields are the book’s own editorial weighting — sample data for the exercise, not scholarly figures. No historian assigns the empty tomb an 8 out of 10. They are there so the arithmetic has something to chew on, and you should treat any number you invent for your own project the same way: label it.
Run it, then run it with < /dev/null and confirm it exits with (no more input - exiting) instead of hanging. A program you cannot get out of is a program you cannot submit.
4.8 — Foreshadowing Classes
A struct is data with no enforcement — anyone can reach in:
Manuscript m = {"P52", 2, "papyrus"};
m.century = -999; // 100% legal. m is now nonsense.
A class is a struct with rules: it can mark fields private so outside code cannot touch them, and define methods that enforce invariants — a set_century(int c) that refuses a negative value. That is Chapter 5.
Notice what you have written all chapter: free functions whose first parameter is a struct — print_manuscript(const Manuscript&), mark_disputed(Manuscript&), add(Manuscript[], int&, const Manuscript&). Those are methods. They are simply not inside the type yet.
Coach’s Note — When Chapter 5 moves those functions inside the type and the first parameter disappears, nothing conceptually new happens — the object you used to pass explicitly becomes the one the method is called on. If that sentence makes sense to you now, Chapter 5 will cost you a day instead of a week. Come back and re-read it after you have written your first class.
4.9 — The Bridge: Why Structs and the Midterm Share a Week
Why does the exam land in the same week as a new concept? Three reasons, none of them administrative.
First, the struct completes the toolkit. Weeks 1 through 3 gave you the three pillars — memory, questions, repetition — plus the two ways to organize them: functions, which group behavior, and arrays, which group values of one type. The struct is the last missing move: grouping values of different types that describe one thing. With it, the procedural half of this course is complete, and a cumulative test becomes possible for the first time.
Second, structs are the concept that forces combination. Every earlier week could be drilled in isolation — a loop rep is a loop rep. A struct cannot be. You cannot demonstrate one without an array to hold them, a loop to walk the array, a conditional per element, a function to keep main readable, and a cast so the average comes out right. Learning structs is combination practice, and combination is exactly what the students who get crushed on midterms never trained: they studied each chapter alone and never worked the seams.
Third, this is the last honest checkpoint before the material gets heavier. Week 5 turns structs into classes; Week 6 adds pointers and inheritance; Week 7 switches languages. A soft procedural foundation gets amplified by those weeks, not exposed by them. The midterm is the diagnostic, taken while there is still time to act on it.
So the rest of this chapter runs: everything you know, compressed (§4.10); the patterns that combine it (§4.11); one worked problem using all of it (§4.12); the errors that cost exam time (§4.13); three warm-up reps (§4.14); an honest self-test (§4.15); the stuck-at-midnight ladder (§4.16); then the exam (§4.17) and a study plan for the week (§4.18).
4.10 — Weeks 1–4, Compressed
Read straight through. If a paragraph does not ring a bell, re-read that chapter first — that gap is what the exam will find.
Week 1 — The sport, and the memory it runs on. (Chapter 1) Programming is a sport; the skill is articulating a solution clearly enough that a machine can follow it. The three pillars — memory, asking questions, repetition — combine into every nontrivial program ever written. Program anatomy: #include, using namespace std;, int main(), return 0;. Output with cout << and endl, input with cin >>. The five types: int, double, bool, char, string. Declare, initialize, assign. Arithmetic including %. The integer-division trap — 7 / 2 is 3 and the .5 is gone forever; fix with static_cast<double>(x) / y. Booleans are values, and one cout << boolalpha; makes them print true/false instead of 1/0.
Week 2 — Asking questions and doing them again. (Chapter 2) if / else if / else. Comparison operators — == for equality, while = in a condition assigns and then tests whatever it just assigned (if (x = 5) is always true and leaves x at 5). It is not silent under this course’s flags: -Wall answers with suggest parentheses around assignment used as truth value, which is one more reason the warnings stay on. Boolean combinators &&, ||, !, and short-circuit evaluation. switch for integral dispatch, break in every case. Always brace if bodies. Never compare doubles with ==; compare the difference against a tolerance. Then repetition: for, while, do…while. Counters and accumulators. Sentinel loops with break, continue used sparingly, nested loops. Off-by-one — < versus <=. Recognizing an infinite loop and escaping one. And the cin failure mode: a bad read sets the variable to 0 and jams the stream, so check cin or write while (cin >> x).
Week 3 — Functions and collections. (Chapter 3) A function is a return type, a name, a parameter list, a body, and a return; void returns nothing. Prototypes let helpers live below main. Scope: locals and parameters die when the function ends; global const for constants, never global mutable state. Pass by value (default), by & when the function must change the caller’s variable, by const& for large read-only inputs. Composition — small trustworthy functions combined into bigger ones. Guard clauses instead of one giant if around the body. Then collections: fixed-size arrays, 0-based, last valid index size - 1, no runtime bounds checking. The array-plus-count pattern. An array argument is never copied, so a function writes straight into the caller’s array — and never takes an &. Linear search returning -1. Selection sort — find the minimum in the unsorted remainder, swap, repeat; O(n²), fine for twenty records. Strings: .length(), .substr(start, len), .find() checked against string::npos and never -1, == for content, < for alphabetical order between string variables (not literals), + for concatenation, a hand-written to_lower for case-insensitive comparison, and cin.ignore() between a cin >> and a getline. Parallel arrays for multi-field records — legal, clunky, now obsolete.
Week 4 — Structs. (this chapter) A struct bundles related data of different types under one name, semicolon after the closing brace. Three initialization forms; {} to value-initialize. Arrays of structs replace parallel arrays and make shift-down removal a single assignment. Pass by value, &, or const&. Return structs, including a summary struct that carries several computed values at once. Nest when the data is genuinely hierarchical. A struct is a class without rules.
That is the whole procedural toolkit. There is nothing else in the first half of this course.
4.11 — The Patterns That Repeat
Concepts are not what gets tested; combinations are. Here are the seven that keep showing up. The exam will ask for two or three at once.
Each block below is a skeleton, not a runnable program — SomeStruct, do_thing_1, work_cost, EXIT_CHOICE and the rest are placeholders for whatever the prompt calls them. Do not paste them and expect a build; learn the shape and retype it with your own names. Pattern 7 is the exception: it is drawn from code/random_days.cpp, which does run.
Pattern 1 — Menu-driven main.
int choice = 0;
while (true) {
print_menu();
if (!(cin >> choice)) break; // EOF-safe
cin.ignore(numeric_limits<streamsize>::max(), '\n');
if (choice == 1) { do_thing_1(); }
else if (choice == 2) { do_thing_2(); }
else if (choice == EXIT_CHOICE) { break; }
else { cout << "Invalid choice." << endl; }
}
Pattern 2 — Array plus count.
const int MAX = 20;
SomeStruct things[MAX];
int count = 0;
bool add(SomeStruct arr[], int& count, const SomeStruct& s) {
if (count >= MAX) return false;
arr[count] = s;
count++;
return true;
}
Note the int&. This is the one people forget.
Pattern 3 — Accumulate, then divide.
int total = 0;
for (int i = 0; i < count; i++) {
total += things[i].some_field;
}
double avg = static_cast<double>(total) / count; // guard count == 0 first
Pattern 4 — Filter / find.
int find_index(const SomeStruct arr[], int count, const string& target) {
for (int i = 0; i < count; i++) {
if (arr[i].name == target) return i;
}
return -1;
}
Pattern 5 — Phase-by-phase update.
for (int day = 1; day <= num_days; day++) {
morning_phase(team, count);
work_phase(team, count);
meal_phase(team, count);
evening_phase(team, count);
}
Outer loop over time; each phase a function that walks the collection and updates state. Every “run a simulation” prompt is this.
Pattern 6 — Per-element decision.
for (int i = 0; i < count; i++) {
if (members[i].energy < THRESHOLD) {
members[i].skipped_phases++;
} else {
members[i].energy -= work_cost(members[i].role);
}
}
Loops, conditionals, structs, and arrays in six lines. Learn this cold.
Coach’s Note — If you can write Pattern 5 and Pattern 6 in combination, from scratch, in fifteen minutes, you are ready for the practical. That combination is the engine of every simulation prompt in this course. Time yourself on it. Not “does it feel familiar” — time yourself.
Pattern 7 — Simple randomness with rand() and srand().
#include <cstdlib> // for rand() and srand()
srand(42); // seed ONCE, at the top of main
int roll = rand() % 100; // 0..99
if (roll < 60) { /* normal day */ }
else if (roll < 85) { /* breakthrough day */ }
else { /* hard conversation day */ }
srand(seed) seeds the generator; the same seed replays the same sequence on the same platform. rand() returns an int between 0 and RAND_MAX; modulo it down (% 100 for 0–99, % 6 + 1 for a die). Call srand once, never in a loop.
code/random_days.cpp tallies thirty simulated days. One actual run, built on macOS, printed:
Over 30 simulated days:
normal days: 21
breakthrough days: 7
hard-conversation days: 2
total (must be 30): 30
Your three tallies may well differ, and that is correct. The C++ standard does not say how rand() must generate its numbers, only that seeding is repeatable — so seed 42 is guaranteed to replay the same sequence for you, and guaranteed nothing across machines. (Worth noting what does not change it: compiling the same file on the same Mac with Apple’s g++ and with GCC 14 gave byte-identical tallies, because the generator lives in the system library, not in the compiler.) The checkable fact is the last line: the counts must sum to 30. When self-checking a program that uses randomness, check an invariant like that, never a specific number.
(C++11’s <random> is better and entirely unnecessary here. Not on the exam.)
4.12 — A Worked Review Problem
Do this properly: read the problem statement, close this file, and build it. Compare afterward. Reading a solution is not practice; producing one is.
The problem
Build a Bible Reading Plan Tracker.
Six readers. Each has a name, a plan ("gospels", "wholebible", "psalms", or anything else), a chapters-read count starting at 0, a days-missed count starting at 0, and stamina starting at 100.
Chapters per day by plan: gospels 3, wholebible 4, psalms 2, anything unrecognized 3. A day’s reading costs 10 + chapters_per_day * 3.
Simulate 7 days, each with two phases:
- Reading phase. For each reader: if stamina is below 40, they miss the day (
days_missed++, no stamina cost). Otherwise they read their chapters and pay the cost. - Rest phase. Every reader recovers 6 stamina, capped at 100.
Then print a per-reader report — name, plan, chapters read, days missed, stamina, and on pace if they missed nothing, else behind — followed by total chapters, average chapters per reader per day, the reader with the most chapters, and the result of searching for "Lin" and for "Priscilla".
Requirements: one struct; an array of structs; each phase its own function; main orchestrates and holds no phase logic; compiles clean under -Wall -Wextra.
What the solution has to contain
Every element of the toolkit — that is the point:
| Piece | Week |
|---|---|
const int DAYS, MAX_STAMINA, MIN_STAMINA_TO_READ, REST_RECOVERY | 1 — named constants, not magic numbers |
static_cast<double>(total) / (N * DAYS) | 1 — integer-division trap |
if (stamina < MIN) … else … per reader | 2 — per-element decision |
for (int day = 1; day <= DAYS; day++) around phase calls | 2 — phase loop |
chapters_per_day(const string& plan) with if/else if chain | 2/3 — string dispatch (switch will not take a string) |
reading_phase(Reader[], int), rest_phase(Reader[], int) | 3 — one function, one job |
find_by_name returning -1, index_of_most_read | 3 — search and find-the-extreme |
struct Reader { … }, Reader readers[N] = { … } | 4 — struct and array of structs |
const Reader& r = readers[i]; inside the report loop | 4 — read-only alias, no copy |
The actual output
code/review_worked_example.cpp is the reference. It prints exactly:
=== Bible Reading Plan Tracker ===
6 readers, 7 days, starting stamina 100.
=== After 7 days ===
Maya (gospels): 18 chapters, 1 missed, stamina 28 - behind
Marcus (wholebible): 20 chapters, 2 missed, stamina 32 - behind
Lin (psalms): 14 chapters, 0 missed, stamina 30 - on pace
Jonah (gospels): 18 chapters, 1 missed, stamina 28 - behind
Sade (wholebible): 20 chapters, 2 missed, stamina 32 - behind
Tomas (proverbs): 18 chapters, 1 missed, stamina 28 - behind
Total chapters read: 108
Average per reader per day: 2.57143
(without the cast, 108 / 42 would print 2)
Most chapters: Marcus with 20
find_by_name("Lin") = 2
find_by_name("Priscilla") = -1
Four things to check in your own version, in order of how often they go wrong:
Tomas’s plan"proverbs"is unrecognized, so he falls to the default 3 chapters and behaves like the gospels readers. Leave the final unconditionalreturn 3;off the bottom ofchapters_per_dayand GCC warnscontrol reaches end of non-void function [-Wreturn-type]— Tomas then gets whatever happened to be in the return register. See §4.13, bug 11.- The average is 2.57143, not 2.
108 / 42in integer arithmetic is2. If yours prints2, you dropped the cast — Week 1’s trap, four weeks later, on your own program. Most chapters: Marcus, not Sade, though both have 20. The scan uses strict>, so the first maximum wins the tie.>=gives Sade. Neither is wrong in general; know which you wrote and why.- Lin never misses a day. Psalms costs 16 and rest returns 6, so she drifts down 10 a day: 100, 90, 80, 70, 60, 50, 40 — and 40 is not below 40, so she still reads on the last day. If your Lin missed one, you wrote
<=where the spec says “below”.
That last one is an off-by-one hiding in a boundary comparison, in a program with no loop indices in sight. Cumulative problems do that: old bugs come back wearing new clothes.
4.13 — Common Bugs (Week 4 Edition)
Every compiler message below is copied out of a real build: a deliberately broken program, compiled with g++ -std=c++17 -Wall -Wextra under GCC 14. The runtime transcripts in bugs 12, 13 and 15 are real runs of those same programs. Wording shifts between compiler versions, and Clang phrases several of these differently from GCC; the phrase in bold — or the [-Wname] tag in brackets — is what to search for.
Bug 1 — Missing semicolon after the struct definition.
manuscript.cpp:7:2: error: expected ';' after struct definition
7 | }
| ^
| ;
What it means: you wrote struct Manuscript { ... } with no ;. The error points at the closing brace, and if main follows immediately the errors cascade for pages.
Fix: add the ;. Type declarations need a trailing semicolon; functions do not. Yes, it is inconsistent. struct X { ... };
Bug 2 — Misspelled field name.
manuscript.cpp:10:15: error: 'struct Manuscript' has no member named 'centruy'; did you mean 'century'?
What it means: the dot operator can only reach fields that exist. has no member named means a typo, a field you forgot to declare, or the wrong struct type.
Fix: the compiler suggests the correction. Read it.
Bug 3 — Too many values in the brace list.
manuscript.cpp:9:40: error: too many initializers for 'Manuscript'
9 | Manuscript m = {"P52", 2, "papyrus"};
What it means: more values than the struct has fields — usually a field you added to your notes but not to the struct, or the wrong struct type.
Fix: count fields, count values, match the order. Too few values is legal and zero-fills the rest — but it is not silent. -Wall -Wextra answers with missing initializer for member 'Manuscript::type' [-Wmissing-field-initializers], and in this course that warning is a failure. See §4.2.
Bug 4 — Struct used before it is defined.
manuscript.cpp:4:29: error: 'Manuscript' does not name a type
manuscript.cpp:5:15: error: request for member 'name' in 'm', which is of non-class type 'const int'
manuscript.cpp:13:22: error: invalid initialization of reference of type 'const int&' from expression of type 'Manuscript'
What it means: a function that uses Manuscript sits above struct Manuscript { … };. The compiler reads top to bottom, hits an unknown type, falls back to int, and everything downstream is nonsense. does not name a type is the only line that matters; the other two are collateral damage.
Fix: move all struct definitions to the top, under the #includes. Fix the first error and recompile — never chase the later ones.
Bug 5 — Comparing two structs with ==.
manuscript.cpp:11:11: error: no match for 'operator==' (operand types are 'Manuscript' and 'Manuscript')
11 | if (a == b) {
followed by well over a hundred more lines from inside the standard library — GCC 14 offers nineteen note: candidate: suggestions here, about 150 lines in all, none of them relevant. Ignore every one.
What it means: C++ gives structs no built-in ==; it has no idea which fields should count.
Fix: compare field by field, in your own function:
bool same(const Manuscript& a, const Manuscript& b) {
return a.name == b.name && a.century == b.century && a.type == b.type;
}
Bug 6 — Printing a struct directly.
manuscript.cpp:10:10: error: no match for 'operator<<' (operand types are 'std::ostream' ... and 'Manuscript')
10 | cout << m << endl;
What it means: cout knows int, double, string, and friends. It does not know your struct, and it will not guess.
Fix: write void print_manuscript(const Manuscript& m) and call it. You wanted that function anyway.
Bug 7 — Assigning through a const& parameter.
manuscript.cpp:9:15: error: assignment of member 'Manuscript::century' in read-only object
9 | m.century = m.century + 1;
What it means: you promised const and then modified. The compiler is holding you to it.
Fix: if the function should modify, drop const and keep &. If not, stop modifying — you probably meant to compute a value and return it.
Bug 8 — Passing a const array to a non-const parameter.
inventory.cpp:15:10: error: invalid conversion from 'const Manuscript*' to 'Manuscript*' [-fpermissive]
15 | wipe(ms, 2);
What it means: the array is const at the call site and the parameter is not, so the call would silently discard the promise.
Fix: if the function only reads, declare const Manuscript ms[]. Push const outward; never strip it.
Bug 9 — Dotting the array instead of an element.
inventory.cpp:10:16: error: request for member 'name' in 'ms', which is of non-class type 'Manuscript [2]'
10 | cout << ms.name << endl;
What it means: ms is an array of Manuscripts. An array has no fields; its elements do.
Fix: index first, then dot: ms[i].name. non-class type is almost always this or Bug 4.
Bug 10 — Assigning one struct type to a different one.
inventory.cpp:14:18: error: conversion from 'Manuscript' to non-scalar type 'Evidence' requested
14 | Evidence e = m;
What it means: two structs with identical layouts are still different types. C++ will not convert between them. Fix: copy field by field, or write a conversion function returning the target type.
Bug 11 — A function that forgets to return on some path.
There are two flavours, and GCC words them differently. If the function body has no return anywhere:
century.cpp:6:1: warning: no return statement in function returning non-void [-Wreturn-type]
6 | }
| ^
If it has an if/else if chain that returns from every branch but nothing at the bottom — the far more common Week 4 version, and exactly what §4.12 warns about:
reader.cpp:9:1: warning: control reaches end of non-void function [-Wreturn-type]
9 | }
| ^
(Clang phrases the same two as non-void function does not return a value and non-void function does not return a value in all control paths. Search for -Wreturn-type and you will find either.)
What it means: a warning, so the program builds — and then hands back whatever was in the return register at runtime. In this course, -Wall -Wextra warnings are errors. Treat them that way.
Fix: every path ends in a return. For an if/else if dispatch chain, that means a final unconditional return at the bottom — the return 3; at the end of chapters_per_day.
Bug 12 — Reading an uninitialized struct field.
There is no compiler message for this one, which is what makes it dangerous. Manuscript m; followed by cout << "Century: " << m.century; compiled clean under -Wall -Wextra and then printed this, on three back-to-back runs of the one unchanged binary:
Century: 1804627896
Century: 1870540728
Century: 1865805752
What it means: the field held whatever bytes were already at that address. The value is undefined, and “undefined” is not a synonym for “random” — you may get a different number every run, as above; you may get 0 and conclude the bug is not there; you may get the same number every run right up until the day you change something unrelated. Undefined behaviour that looks stable is the worst kind, because you will trust it.
Fix: Manuscript m{}; — value-initialize. Or brace-initialize every field with real values.
Bug 13 — Walking one past the end of a struct array.
Also no compiler message. This loop ran for (int i = 0; i <= 3; i++) over a 3-element array, printing ms[i].century each time, and then printed ms[3].name:
2
5
3
1
<150 MB of binary garbage>
The first three numbers are the real data. The 1 is ms[3].century, which does not exist. Then printing ms[3].name treated whatever was sitting past the end of the array as a string — a length and a pointer — and cout dutifully dumped a hundred and fifty megabytes of unrelated memory into the terminal before the program exited, reporting success.
What it means: ms[3] is out of bounds, and out-of-bounds is undefined behaviour: C++ does not bounds-check arrays, so nothing is obliged to happen. On another machine the identical program may crash with a segmentation fault, or print four plausible-looking numbers and appear to work. None of those outcomes is more correct than the others, and the quiet one is the most dangerous.
Fix: i < count, never i <= count. When a struct array crashes, floods the screen, or prints one row too many, check the loop bounds before anything else.
Bug 14 — The modification that “doesn’t stick.”
No message at all. A function takes Manuscript m instead of Manuscript& m, changes it, and the caller sees nothing — the middle block of §4.4’s output. The same failure hits int count instead of int& count, producing a list that stubbornly stays empty.
Fix: if the function’s job is to change the caller’s data, the parameter needs &.
Bug 15 — getline after cin >> reads an empty string.
No message. This program read the century, then the name:
Century: Name: [] century 2
The name came back empty.
What it means: cin >> m.century left the newline in the buffer; getline consumed exactly that and stopped.
Fix: put cin.ignore(); — or, more robustly, cin.ignore(numeric_limits<streamsize>::max(), '\n'); with #include <limits> — between the formatted read and the getline.
4.14 — Reps
Three warm-ups; the full set — with the exact output you should see for each — is in the exercises.
Rep 1. Declare a Point struct with int x and int y. Create three points, one with each of the three initialization forms from §4.2, and print all three.
Rep 2. Declare Manuscript (name, century, type). Make an array of 3, fill it with a brace list, and print it as a table with a loop.
Rep 3. Write void print_manuscript(const Manuscript& m) and use it in Rep 2’s loop. Then try assigning to m.century inside it and read the error — it should be Bug 7.
Full set in the exercises.
4.15 — Checkpoint: Can You Do This Yet?
Blank file. AI off. No looking at this chapter, your notes, or your earlier code. 45 minutes for all eight.
struct TeamMemberwith astring name, astring role, and anint energy— semicolon in the right place.- An array of 5
TeamMembers initialized with a single brace list. void print_member(const TeamMember& m)printing one member on one line.int find_member(const TeamMember team[], int count, const string& name)returning the index or-1.bool add_member(TeamMember team[], int& count, const TeamMember& m)with aMAXguard — and say out loud why parameter two has&and parameter three hasconst&.double average_energy(const TeamMember team[], int count)returning0.0for an empty array, otherwise the true fractional average.- A function that lowers each member’s energy by a per-role cost (
"medic"12,"builder"18, anything else 15), skipping anyone already below 20 energy. struct Report { int total_energy; int lowest_index; }and a function that computes and returns one.
The pass bar: seven of eight, compiling with zero warnings, in 45 minutes, with no reference.
Five or six is a gap, not a hole — re-drill §4.3 through §4.5, redo Reps 7–12, and retake this checkpoint before session 3 ends.
Four or fewer: do not start Midterm Part B yet. Go back to §4.1, retype struct_array.cpp and struct_remove_shift.cpp by hand, and work the checkpoint again. Part B is three hours of writing exactly these functions against a new prompt. Going in without them is not a test of your knowledge — it is three hours of frustration and a score that tells you nothing you did not already know.
4.16 — When You’re Stuck (and Nobody’s in the Room)
It is 11:40 p.m., Part B is due tomorrow, your program will not compile — or it compiles and prints nonsense — and nobody is awake. Work the ladder in order. Most Week 4 problems die on rung 2 or 3.
Rung 1 — Read the first error only, and read its line number. Struct errors cascade: one missing semicolon, or one type used before it is defined, can produce forty messages, and messages 2 through 40 are lies. Scroll up to the first, fix it, recompile. Never work bottom-up.
Rung 2 — Match the message against §4.13, where fifteen are decoded with their real text. Phrases to search for: expected ';' after struct definition, has no member named, too many initializers, missing initializer for member, does not name a type, no match for 'operator==', no match for 'operator<<', read-only object, non-class type, no return statement, control reaches end of non-void function.
Rung 3 — If it compiles but misbehaves, print the struct. The Week 4 version of rubber-ducking. Write a print_member and call it before and after every phase:
cout << "[before work] "; print_member(team[i]);
work_phase(team, count);
cout << "[after work] "; print_member(team[i]);
Nine times out of ten that tells you instantly which of three things is wrong: the field never changed (missing &), it changed by the wrong amount (wrong cost function or wrong branch), or it is garbage (missing {} or an out-of-bounds index).
Rung 4 — Cut it down to twenty lines. Copy the program into a new OnlineGDB tab and delete everything not involved in the bug: one struct, one array of two elements, one function call, one cout. Bugs invisible in 200 lines are obvious in 20, and half the time the cutting-down finds it before you finish.
Rung 5 — Check the four Week 4 usual suspects, in order: is count passed as int& in every function that changes it; are the loop bounds i < count everywhere with no <=; did you value-initialize ({}) every struct you did not brace-fill; is every division that should produce a fraction wrapped in static_cast<double>?
Rung 6 — Post to the discussion board with a reproducible case. Use this shape; a vague post gets vague answers, and you may not get a second round before the deadline:
Title: Week 4 — struct array
add()leaves count at 0 What I expected: after threeaddcalls,countis 3 andlist_allprints three rows. What happened:countprints 0 andlist_allprints(empty). Smallest program that shows it: (paste the 20-line cut-down, not the whole project) What I already tried: printedcountinsideadd— it is 1, 2, 3 in there, but 0 back inmain.
That last line turns a forty-minute thread into a one-reply thread.
Rung 7 — Email the instructor. At rung 7, not rung 1, and with this template:
Subject: Coding 1 Week 4 — [one-line symptom]
Chapter 4, [section or the exercises rep number, or “Midterm Part B”]. Goal: what the program is supposed to do. Symptom: the exact compiler message, or the exact wrong output, pasted as text. Cut-down program: OnlineGDB share link to the smallest version that still fails. Ladder: which rungs above I have already worked, and what each one told me. Specific question: the one thing I want answered.
One rung this ladder does not have: asking an AI to write the code. During Part B that is a violation (§4.17). Outside Part B it is legal and still a bad trade this week — every rep you outsource now is a rep you will discover you are missing on exam day.
4.17 — This Week’s Exam
There is no project this week. The graded item is the Midterm, in two parts — Part A is 50 points, Part B is 100, for 150 together — and it is worth 25% of your course grade.
Notice the split. The auto-graded half is deliberately the smaller half: this exam is unproctored, and the program you write cold is the real evidence.
Part A — Code Reading & Tracing (auto-graded quiz, 50 points)
A Canvas quiz drawn from a pool over Weeks 1–4: multiple choice, true/false, and matching. It is a programming exam, not a vocabulary quiz. The questions look like: here is a fifteen-line program, what does it print; here is a loop, what is x afterward; which line causes the compiler error; which of these four for headers walks the array correctly.
You get your score immediately, and every question carries a written rationale — read all of them, including the ones you got right. Take Part A in session 3, after the reps and before Part B. It is the cheapest diagnostic you will get: if you miss the tracing questions, you know what to review before spending three hours on the practical.
Part B — Mission Trip Simulator (take-home practical, 100 points)
A real program, written cold, in C++, submitted as an OnlineGDB link exactly as you have submitted every project. Sized for about three focused hours, inside a stated 24-hour window during Week 4.
The shape is no secret. The practical will require you to:
- define one or more structs;
- initialize an array of structs with a count;
- run a loop that advances state across iterations — phases of a day, days of a trip;
- apply conditional logic per element;
- keep each phase in its own function, with
mainas the orchestrator; - compute aggregate statistics at the end and print a structured report.
That is Patterns 5, 6, 2, and 3 from §4.11 combined. If you built §4.12’s review problem yourself, you have already written this program once, with different nouns.
It comes in Normal / Medium / Hard tiers like every project: Normal completion is the passing target and the full 100 points, with Medium and Hard adding extra credit. Do not start Medium until Normal compiles clean.
The rules, stated plainly
Part B is open-book and open-notes. Use this chapter, your own earlier code, and whatever C++ reference you like. That is not a loophole; it is what working programmers do.
Part B is AI-off, on the honor system. Nobody is watching, so the exam is built to detect the difference instead:
- A short authorship and reflection block submitted with your code.
- Two or three short written questions about your own design decisions — why that data layout, why that function takes a reference, what you would change with another hour. Only the person who wrote the code can answer them.
- Code you cannot explain is treated as not submitted. Stated without drama: that is simply what the rubric measures.
The reason is practical, not moral. Weeks 5 through 8 build directly on this material, and the final is the same test in Java. A midterm score that reflects an AI’s ability tells you nothing you can act on; it just postpones the reckoning to Week 8, when there is no time left to fix anything.
Coach’s Note — A midterm score is a measurement, and a measurement is only worth what you do with it. The move that pays is unglamorous and always available: read the number, find which reps the misses point at, redo those reps with the AI off, and put the skill in your own hands while there are still four weeks left to use it. A low score you act on in Week 4 is worth more than a high score you cannot reproduce in Week 8. Whatever the number is, it is information. Use it.
4.18 — Your Study Plan
Four sessions, one of which is the exam. Where to spend the other three:
Session 1 — build the muscle. Type every program in code/ by hand. Not copy-paste: typing puts the syntax in your fingers, and syntax is what you will be missing at hour two of the practical. Then Reps 1–6.
Session 2 — combine. Do §4.12 the hard way: read the problem, close the book, build it, compare. Highest-value hour of the week. If your version differs from the reference in the four ways listed there, you found four exam mistakes early.
Session 3 — pressure-test, in this order:
-
Reproduce three bugs from §4.13 on purpose. Delete a semicolon. Drop an
&. Change a<to a<=. Recognizing an error you caused yourself takes seconds; recognizing one you only read about takes twenty minutes. -
code/sample_midterm.cpp. Read the prompt at the top. Close it. Solve it from scratch in about 60 minutes, AI off, then compare. The reference prints a banner and three phase markers first; its closing report reads:=== End-of-day report === Maya (math): focus = 65 - thriving Marcus (reading): focus = 85 - thriving Lin (writing): focus = 75 - thriving Jonah (science): focus = 71 - thrivingTrace Maya by hand before you accept it: 100, minus math’s 25, plus the break’s 15, minus 25 again — 65.
All four land on
thriving, which means theneeds restbranch never ran. Before calling it done, prove that branch works: temporarily change math’s lesson cost to 45 and confirm Maya readsfocus = 25 - needs rest. Testing only the path your data happens to take is how a rubric finds a bug you never saw. -
Take Part A, then read every rationale.
Session 4 — take Part B, and learn nothing new that day. Skim §4.11’s patterns and the pre-flight list below, then start writing. Cramming a new concept an hour before a three-hour practical does not add a skill; it adds anxiety and eats the hour.
Pre-flight checklist
Read this before you start Part B, and again if you get stuck. These ten cost more exam points than everything else combined:
- Integer division.
total / countwith twoints truncates. Cast. - Off-by-one.
i <= countreads one past the end. Use<. =instead of==inside anif. Watch forsuggest parentheses around assignment used as truth value.- Missing
break;in aswitchcase. - A function modifies its parameter and it “doesn’t stick.” Add
&. - Missing
#include—<string>forstring,<cstdlib>forrand,<limits>fornumeric_limits. - Comparing
doubles with==. Compare the difference against a tolerance. - Reading an uninitialized variable or struct field. Initialize at declaration;
{}on structs. - Missing
;after a struct declaration. countpassed asintinstead ofint&to a function that adds or removes.
If you started late
It happens. The compressed two-session version:
- Session A: Type
struct_array.cpp,struct_function.cpp, andstruct_remove_shift.cpp. Read §4.11 patterns once and §4.13 twice. Do Reps 1–6. - Session B: Build §4.12’s review problem end to end, skipping nothing. Take the §4.15 checkpoint. Then Part A.
Then take Part B. Be honest about the trade: one pass over the combination patterns instead of two, which gets you through Normal tier and leaves Medium and Hard mostly out of reach. That is a real cost, not a technicality — and still far better than not preparing.
4.19 — Coach’s Final Word for Week 4
Half the course is behind you. In four weeks you have learned to store data, ask questions of it, repeat work over it, package that work into functions, hold many values at once, and bundle related values into records. Every program you write for the rest of your life, in any language, is built out of those six moves. Nothing after this week replaces them; it only adds ways to organize them.
The struct is the smallest unit of object-oriented thinking. From here on, every design decision in this course starts with the same question: what type should this be, what data does it hold, what operations belong with it? Sometimes the answer is a struct, sometimes a class, sometimes something with inheritance hanging off it. The question never changes.
About the exam: you are taking it alone, at a desk nobody else is sitting at. That is a harder test of honesty than a room full of people, and it is the same test you will face every day of a working career, where nobody checks whether you understood the code you shipped. Write what is yours. Submit what is yours. If the score is low you will know precisely what to fix, and you will have four weeks to fix it — which is exactly why this exam sits here and not in Week 8.
Then rest a day. Week 5 turns all of this into classes, and it moves fast.
Up next: Work every rep in the exercises — each one states the exact output you should see, so you can grade yourself. Then take Midterm Part A and Midterm Part B — Mission Trip Simulator in Canvas. If you need a refresher on OnlineGDB projects and how to share a link, see Appendix A. When the midterm is behind you, open Chapter 5 — from struct to class.
Week 4 Knowledge Check
struct P { string n; int a; };
P x{"Ana", 30};
P y = x;
y.a = 99;
cout << x.a << " " << y.a; struct Item { string label; int qty; };
void bump(Item it) { it.qty += 10; }
void bumpRef(Item& it) { it.qty += 10; }
Item a{"tomb", 1};
bump(a); cout << a.qty << " ";
bumpRef(a); cout << a.qty;