pair, struct and enum class
A product has a name, a count and a price; a student has a name and a score. When a few things always go together, keep them together. The simplest way is std::pair: exactly two things, read with .first and .second, just like each pair in last lesson's map. It needs #include <utility>.
1#include <iostream>2#include <string>3#include <utility>4 5int main() {6 std::pair<std::string, int> best{"Ana", 95};7 std::cout << best.first << " scored " << best.second << '\n';8 best.second = 98;9 std::pair<std::string, int> next = std::make_pair("Ben", 90);10 std::cout << best.second << " " << next.first << '\n';11 return 0;12}
Ana scored 95 98 Ben
std::pair<std::string, int>: the first thing is a string, the second an int. std::make_pair("Ben", 90) makes a pair from the two values in the parentheses without writing the types in angle brackets; the function below uses it to return its result.
pair: returning two values at once
A function can return only one value. To return two, return a pair:
1#include <iostream>2#include <utility>3#include <vector>4 5std::pair<int, int> low_high(const std::vector<int>& temps) {6 int low = temps[0];7 int high = temps[0];8 for (int t : temps) {9 if (t < low) {10 low = t;11 }12 if (t > high) {13 high = t;14 }15 }16 return std::make_pair(low, high);17}18 19int main() {20 std::vector<int> week{18, 23, 16, 21, 19};21 std::pair<int, int> r = low_high(week);22 std::cout << "Low " << r.first << ", high " << r.second << '\n';23 return 0;24}
Low 16, high 23
Two pairs can be compared too: .first first, and if those are equal, .second. So a vector of pairs can go straight into sort:
After sorting, which pair is v[1]?
1#include <algorithm>2#include <iostream>3#include <utility>4#include <vector>5 6int main() {7 std::vector<std::pair<int, int>> v{{2, 9}, {1, 5}, {2, 3}};8 std::sort(v.begin(), v.end());9 std::cout << v[1].first << " " << v[1].second << '\n';10 return 0;11}
.first comes first: {1, 5} is the smallest, so it goes first. The other two both have 2 as .first, so .second decides: 3 is less than 9, so {2, 3} comes second.
struct: a type of your own
A pair holds only two things, and their names, first and second, say nothing about what they are. For three or more, or for clear names, define your own type with struct. The variables inside are its members, and you reach them with .:
1#include <iostream>2#include <string>3 4struct Item {5 std::string name;6 int count;7 double price;8};9 10int main() {11 Item pen{"pen", 3, 2.5};12 pen.count += 2;13 std::cout << pen.name << ": " << pen.count << " of them, ";14 std::cout << "$" << pen.count * pen.price << " in all\n";15 return 0;16}
pen: 5 of them, $12.5 in all
struct Item { ... }; defines a new type, Item, and from then on you can declare variables with it just like with int. Item pen{"pen", 3, 2.5} gives the values in the order of the members. Press Step and see pen drawn with its three fields.
The closing curly brace of a struct definition needs a semicolon. It is the one people forget most:
1#include <iostream>2#include <string>3 4struct Item {5 std::string name;6 int count;7}8 9int main() {10 Item cup{"cup", 4};11 std::cout << cup.name << '\n';12 return 0;13}
main.cpp:7:2: error: expected ';' after struct definition
7 | }
| ^
| ;g++ points at the } on line 7 and says expected ';' after struct definition.
A vector of structs
Put a struct in a vector and you have a table: each element is a row and each member a column. Pass one to a function like any other big thing, as const Item&:
1#include <iostream>2#include <string>3#include <vector>4 5struct Item {6 std::string name;7 int count;8 double price;9};10 11double value(const Item& item) {12 return item.count * item.price;13}14 15int main() {16 std::vector<Item> stock{{"pen", 5, 2.5}, {"notebook", 3, 8.0}, {"eraser", 10, 1.5}};17 double total = 0;18 for (const auto& item : stock) {19 std::cout << item.name << ": $" << value(item) << '\n';20 total += value(item);21 }22 std::cout << "Total: $" << total << '\n';23 return 0;24}
pen: $12.5 notebook: $24 eraser: $15 Total: $51.5
Each {...} inside the curly braces is one Item.
Sorting by a member
std::sort doesn't know which of two Items is smaller. You tell it with a function: two parameters, returning true when the first should come before the second. Hand the function's name to sort as the third argument:
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Student {7 std::string name;8 int score;9};10 11bool higher_score(const Student& a, const Student& b) {12 return a.score > b.score;13}14 15int main() {16 std::vector<Student> cls{{"Ana", 82}, {"Ben", 95}, {"Cara", 77}};17 std::sort(cls.begin(), cls.end(), higher_score);18 for (const auto& s : cls) {19 std::cout << s.name << " " << s.score << '\n';20 }21 return 0;22}
Ben 95 Ana 82 Cara 77
There are no parentheses after higher_score: you aren't calling it here, you are handing it to sort, which calls it whenever it needs to compare. With > the higher scores come first; with <, the lower ones. Lesson 22 covers lambdas, which let you write the comparison right inside the call to sort.
enum class: names for choices
Some values can only be one of a few choices: a size is small, medium or large; an order is unpaid, shipped or delivered. Use 0, 1 and 2 and in a few days you'll forget what 2 meant. enum class gives each choice a name:
1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6 Size s = Size::Large;7 int code = s;8 std::cout << code << '\n';9 return 0;10}
main.cpp: In function 'int main()':
main.cpp:7:16: error: cannot convert 'Size' to 'int' in initialization
7 | int code = s;
| ^
| |
| Sizeenum class Size defines a type whose only values are Size::Small, Size::Medium and Size::Large. They are not ints, so int code = s; doesn't compile: cannot convert 'Size' to 'int'. That is on purpose: a size shouldn't be added and subtracted like a number by accident.
When you really want the number behind it, use static_cast<int>:
What does this show?
1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6 Size s = Size::Large;7 std::cout << static_cast<int>(s) << '\n';8 return 0;9}
When you don't give numbers, the first choice is 0 and each next one is 1 more: Small is 0, Medium 1, Large 2.
switch with enum class
enum class and switch go well together: one case per choice, and it reads like a list of what each one means:
1#include <iostream>2#include <string>3 4enum class Status { Unpaid, Shipped, Delivered };5 6std::string describe(Status s) {7 switch (s) {8 case Status::Unpaid:9 return "waiting for payment";10 case Status::Shipped:11 return "on its way";12 case Status::Delivered:13 return "delivered";14 }15 return "unknown";16}17 18int main() {19 Status order = Status::Unpaid;20 std::cout << describe(order) << '\n';21 order = Status::Shipped;22 std::cout << describe(order) << '\n';23 return 0;24}
waiting for payment on its way
Each case returns right away, so no break is needed. The return "unknown"; after the switch makes sure the function always returns something.
Tip: Compile with g++ -Wall and g++ warns you when a switch leaves out one of the choices. Add a new choice to Status later, and the compiler finds every place you need to update.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
The priciest one
✓Finish most_expensive: go through items and return the one with the highest price. main is already written; it asks about two lists.
Priciest: headphones 199 Priciest: pan 268
1#include <iostream>2#include <string>3#include <vector>4 5struct Item {6 std::string name;7 int price;8};9 10Item most_expensive(const std::vector<Item>& items) {11 Item best = items[0];12 13 return best;14}15 16int main() {17 std::vector<Item> desk{{"mouse", 89}, {"headphones", 199}, {"keyboard", 159}};18 std::vector<Item> kitchen{{"kettle", 129}, {"knife", 99}, {"pan", 268}};19 Item a = most_expensive(desk);20 std::cout << "Priciest: " << a.name << " " << a.price << '\n';21 Item b = most_expensive(kitchen);22 std::cout << "Priciest: " << b.name << " " << b.price << '\n';23 return 0;24}
Line up by height
✓Write a comparison function that sorts kids from shortest to tallest, then show each name and height on its own line.
Mia 121 Leo 128 Sam 134
1#include <algorithm>2#include <iostream>3#include <string>4#include <vector>5 6struct Kid {7 std::string name;8 int height;9};10 11int main() {12 std::vector<Kid> kids{{"Leo", 128}, {"Mia", 121}, {"Sam", 134}};13 14 return 0;15}
Fix it: a size is not a number
✓This program doesn't compile. Fix it so it shows Size code: 1.
Size code: 1
1#include <iostream>2 3enum class Size { Small, Medium, Large };4 5int main() {6 Size s = Size::Medium;7 int code = s;8 std::cout << "Size code: " << code << '\n';9 return 0;10}
What you learned
std::pair<A, B>holds two things, read with.firstand.second;std::make_pairworks out the types; a function can return apair.pairs compare by.first, then.second, so they sort as they are.structdefines a type of your own; reach its members with.. The closing curly brace needs a semicolon.- A
std::vector<Item>is a table; pass anItemto functions asconst Item&. - To sort by a member, write a comparison function that returns
booland give it tostd::sortas the third argument. enum classnames choices (Size::Large); it isn't anintwithoutstatic_cast<int>, and it reads best withswitch.