๐ Arrays โ Storing Many Values
Every variable so far has held exactly one value. What happens when you need to store 100 test scores, or a whole deck of cards?
An array is a fixed-size, ordered collection of values of the same type โ think of it as several labeled boxes in a row, sharing one overall name, each accessed by its position.
int scores[5] = {85, 92, 78, 95, 88}; std::cout << scores[0]; // prints 85 โ the FIRST element std::cout << scores[2]; // prints 78 โ the THIRD element
C++ arrays start counting positions at 0, not 1. A 5-element array has valid positions 0, 1, 2, 3, and 4 โ scores[5] does NOT exist and accessing it is a real, dangerous bug (Module 2 covers exactly why). This trips up nearly every beginner at least once; it's worth deliberately internalizing now.
Arrays and for loops (Phase 2) are natural partners:
for (int i = 0; i < 5; i++) { std::cout << scores[i] << " "; }
This is exactly why for loops conventionally start their counter at 0 โ it lines up perfectly with an array's own zero-based positions.
๐ง Array Bounds & Common Mistakes
This is a genuinely important C++-specific lesson โ one of the language's real, well-known sharp edges, worth understanding clearly rather than discovering the hard way.
int scores[5] = {85, 92, 78, 95, 88}; std::cout << scores[10]; // undefined behavior โ NOT a clean error
int scores[] = {85, 92, 78, 95, 88}; int size = sizeof(scores) / sizeof(scores[0]); // size is 5 โ total bytes divided by one element's byte size
This pattern โ total size divided by one element's size โ is the classic C-style way to compute an array's length. Phase 7's STL introduces std::vector, which tracks its own size directly and avoids needing this pattern at all โ but you'll still see this technique in real C++ code, so it's worth recognizing.
๐ฒ Multi-Dimensional Arrays
Phase 2's nested loops were building toward exactly this: an array of arrays, for anything genuinely grid-shaped.
int board[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; std::cout << board[1][2]; // prints 6 โ row 1, column 2
Think of a tic-tac-toe board, a chessboard, or a spreadsheet โ board[row][col] reads directly as "the value at this row, this column," exactly matching how you'd describe a grid position out loud.
for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { std::cout << board[row][col] << " "; } std::cout << std::endl; }
This is exactly Phase 2 Module 4's nested-loop pattern, now genuinely put to work โ walking through every cell of a real 2D grid, row by row.
board[row][col], what does the outer loop typically represent?๐ std::string in Depth
You've used std::string since Phase 1. Here's what it actually is under the hood, and why it's genuinely easier to work with than it might seem.
Conceptually, "Hello" is a sequence of individual char values โ 'H', 'e', 'l', 'l', 'o' โ stored in order. std::string wraps this idea in a convenient, safe package that manages its own size automatically, unlike a raw array which has a fixed size you must track yourself.
std::string name = "Alex"; std::cout << name[0]; // prints 'A' std::cout << name.length(); // prints 4
Strings are zero-indexed exactly like arrays (Module 1) โ name[0] is the first character. .length() gives you the string's current size, always accurate, without needing the manual sizeof trick from Module 2.
std::string firstName = "Alex"; std::string lastName = "Rivera"; std::string fullName = firstName + " " + lastName; // fullName is "Alex Rivera"
+ concatenates (joins) strings together โ this is a genuine, different meaning of + than the arithmetic addition from Phase 1, and C++ figures out which one you mean based on the types involved.
.length() method return for a std::string?๐ง String Operations & Methods
The handful of std::string operations you'll genuinely reach for constantly, in nearly every real program that handles text.
| Method | What It Does | Example |
|---|---|---|
.substr(start, length) | Extracts part of a string | "Hello".substr(1, 3) โ "ell" |
.find(text) | Finds the position of text within a string (or a special "not found" value) | "Hello".find("llo") โ 2 |
== / != | Compares two strings for equality | name == "Alex" |
.empty() | Checks if a string has zero characters | name.empty() |
std::string sentence = "The quick fox"; if (sentence.find("fox") != std::string::npos) { std::cout << "Found it!" << std::endl; }
std::string::npos is the special value .find() returns when the text isn't found at all โ always compare against it explicitly, rather than assuming any returned value means success.
C++ doesn't have a single built-in "make this uppercase" string method โ instead, you loop through each character and convert it individually with the toupper()/tolower() functions:
std::string text = "hello"; for (int i = 0; i < text.length(); i++) { text[i] = toupper(text[i]); } // text is now "HELLO"
.find() return when the text you're searching for isn't present in the string at all?๐ท๏ธ Functions โ Parameters & Return Values
The Pre-Course covered "named instructions" conceptually, and you've written main() in every program so far. Time to write your own functions properly.
double calculateTax(double price, double rate) { return price * rate; } int main() { double tax = calculateTax(50.00, 0.08); std::cout << tax; // prints 4 return 0; }
| Part | Meaning |
|---|---|
double (before the name) | The type of value this function RETURNS |
calculateTax | The function's name |
(double price, double rate) | The parameters โ information the function needs, each with its own type |
return price * rate; | The value handed back to whoever called the function |
void printWelcome(std::string name) { std::cout << "Welcome, " << name << "!" << std::endl; // no return statement needed }
Some functions just DO something (print a message, update the screen) without handing back a specific value โ void as the return type says exactly that.
void as a function's return type actually mean?๐ฎ Pass by Value vs. Pass by Reference
A genuinely important C++ concept with real, practical consequences โ worth understanding clearly now, since Phase 4's pointers build directly on this idea.
void doubleIt(int num) { num = num * 2; // only changes the local copy } int main() { int x = 5; doubleIt(x); std::cout << x; // still prints 5 โ the original x is unchanged! }
By default, C++ passes a COPY of the argument into a function โ changes made inside the function affect only that local copy, never the original variable back in main().
void doubleIt(int& num) { // note the & num = num * 2; } int main() { int x = 5; doubleIt(x); std::cout << x; // prints 10 โ the original x actually changed! }
Adding & after the type in a parameter makes it a reference โ the function now works with the ORIGINAL variable directly, not a copy. This is exactly how a function can meaningfully modify a caller's variable.
| Situation | Choice |
|---|---|
| The function shouldn't change the caller's data | Pass by value (the default, safer choice) |
| The function genuinely needs to modify the original variable | Pass by reference (&) |
| Passing a large object where copying would be wasteful, but you don't need to modify it | const reference โ best of both: no copy, but protected from changes |
int num (no &). If the function changes num inside its body, what happens to the original variable back in main()?๐ญ Function Overloading
C++ allows multiple functions to share the exact same name, as long as they can genuinely be told apart.
int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int main() { std::cout << add(2, 3); // calls the int version โ 5 std::cout << add(2.5, 3.5); // calls the double version โ 6.0 }
C++ picks which version to actually run based on the TYPES of arguments you pass in โ this is called overloading. It exists so that conceptually-related operations (like "add two things") can share one intuitive name, instead of needing awkward separate names like addInts and addDoubles.
Overloaded functions must differ in their PARAMETER TYPES or COUNT โ not just their return type alone. Two functions with the same name and same exact parameter types, differing only in return type, is a compiler error, since C++ wouldn't be able to tell them apart from the calling code alone.
๐ช Recursion
A function that calls itself. It sounds circular at first, but it's a genuinely powerful, natural way to solve certain problems.
int factorial(int n) { if (n <= 1) { return 1; // base case โ stops the recursion } return n * factorial(n - 1); // recursive case }
factorial(5) is 5 ร 4 ร 3 ร 2 ร 1. Notice the function calls ITSELF with a smaller number each time โ factorial(5) calls factorial(4), which calls factorial(3), and so on, until it hits the base case.
The base case (if (n <= 1) return 1; above) is the condition that stops the recursion. Without one, a recursive function calls itself forever โ this is genuinely the exact same infinite-loop danger from Phase 2, just in recursive form instead of a while loop.
Everything recursion does, a loop can also do โ and vice versa. Recursion tends to read more naturally for problems that are themselves naturally recursive (like navigating a tree-shaped folder structure), while loops are usually clearer and more efficient for simple repeated counting. You'll develop a feel for which fits better with experience.
๐ Capstone โ Word Analyzer
Every idea from this phase, combined: arrays, strings, functions with real parameters, and pass-by-reference โ a program that actually analyzes real text.
Ask the user for a sentence, then report: the total character count, the number of vowels, and the sentence converted to uppercase โ using separate, well-named functions for each job.
#include <iostream> int countVowels(std::string text) { int count = 0; for (int i = 0; i < text.length(); i++) { char c = tolower(text[i]); if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { count++; } } return count; } void toUppercase(std::string& text) { // pass by reference โ modifies the original for (int i = 0; i < text.length(); i++) { text[i] = toupper(text[i]); } } int main() { std::string sentence; std::cout << "Enter a sentence: "; std::getline(std::cin, sentence); std::cout << "Character count: " << sentence.length() << std::endl; std::cout << "Vowel count: " << countVowels(sentence) << std::endl; toUppercase(sentence); std::cout << "Uppercase: " << sentence << std::endl; return 0; }
countVowelspasses by VALUE (Module 7) since it only needs to read the sentence, never modify it.toUppercasedeliberately passes by REFERENCE, since modifying the original string in place is exactly the point โ the whole reason to choose&.- Each job (counting, converting) lives in its own clearly-named function, exactly the Pre-Course's "named instructions" idea, now genuinely doing real work.
Add a Word Count
Write a new function, countWords, that counts how many words are in the sentence (hint: count the spaces, then add one โ but watch out for extra spaces at the start/end). Decide for yourself whether it should pass by value or by reference, and be ready to explain why your choice is the right one.