Functions
When the same code is needed in several places, you don't copy it: you give it a name, make it a function, and call it wherever you need it. main is a function itself; now you'll write your own.
1#include <iostream>2 3double with_tax(double price) {4 return price * 1.08;5}6 7int main() {8 std::cout << with_tax(100) << '\n';9 std::cout << with_tax(250) << '\n';10 return 0;11}
108 270
with_tax takes a price, works out the price with tax, and hands it back. main calls it twice, with a different price each time.
Defining and calling
A function definition has four parts:
| Part | In the example above |
|---|---|
| Return type: the type of the value handed back | double |
| Name | with_tax |
| Parameters: in the parentheses, the values a call must give | double price |
| Body: in the curly braces, the code a call runs | return price * 1.08; |
A function that hands nothing back has the return type void. A function that needs nothing has empty parentheses:
1#include <iostream>2 3void line() {4 std::cout << "----------\n";5}6 7int main() {8 line();9 std::cout << "Shopping list\n";10 line();11 return 0;12}
---------- Shopping list ----------
On a call, the program jumps into the function, runs it from top to bottom, then comes back to where it was called and carries on. Press Step to watch it jump in and out. The parentheses are needed: line; on its own does nothing.
Parameters
Parameters let the same function do something slightly different each time. Separate them with commas, each with its type; a call gives the values in the same order:
1#include <iostream>2#include <string>3 4void show_item(std::string name, int count, double price) {5 std::cout << count << " x " << name << ": $" << count * price << '\n';6}7 8int main() {9 show_item("Milk", 2, 6.5);10 show_item("Bread", 1, 12);11 return 0;12}
2 x Milk: $13 1 x Bread: $12
The values given in the call ("Milk", 2, 6.5) are the arguments; they go, in order, into the parameters name, count and price. The wrong number of them, or a type that doesn't fit, is a compile error.
Return values
return does two things: it hands a value back to where the function was called, and it ends the function right away. The value handed back can be stored in a variable, used in a calculation, or shown directly:
1#include <iostream>2 3int bigger(int a, int b) {4 if (a > b) {5 return a;6 }7 return b;8}9 10int main() {11 int best = bigger(78, 91);12 std::cout << best << '\n';13 std::cout << bigger(5, 3) + bigger(2, 4) << '\n';14 return 0;15}
91 9
When a > b is true, return a; ends the function, and return b; never runs.
What does this program show?
1#include <iostream>2 3int twice(int n) {4 return n * 2;5 std::cout << "Done\n";6}7 8int main() {9 std::cout << twice(twice(3)) << '\n';10 return 0;11}
The inner twice(3) goes first and gives 6; the outer twice(6) then gives 12. Once return runs, the function is over, so "Done" is never shown.
Declare first, define later
The compiler reads the code from the top down. If main calls a function written below it, the compiler doesn't know the name yet when it reaches the call:
1#include <iostream>2 3int main() {4 std::cout << square(4) << '\n';5 return 0;6}7 8int square(int n) {9 return n * n;10}
main.cpp: In function 'int main()':
main.cpp:4:18: error: 'square' was not declared in this scope
4 | std::cout << square(4) << '\n';
| ^~~~~~'square' was not declared in this scope: at this point, square hasn't been declared. Either move the function above main, or put a declaration (also called a prototype) at the top: just the return type, name and parameters, ending with a semicolon, no body:
1#include <iostream>2 3int square(int n);4 5int main() {6 std::cout << square(4) << '\n';7 return 0;8}9 10int square(int n) {11 return n * n;12}
16
The declaration tells the compiler "there is a function like this", and the body can come later. With many functions, list the declarations at the top and main right after, so a reader sees at once what the program does.
Pass by value
When you call a function, the argument's value is copied into the parameter. Changing the parameter inside the function changes the copy; the variable outside is untouched:
1#include <iostream>2 3void add_bonus(int score) {4 score += 10;5 std::cout << "In the function: " << score << '\n';6}7 8int main() {9 int score = 80;10 add_bonus(score);11 std::cout << "In main: " << score << '\n';12 return 0;13}
In the function: 90 In main: 80
Both variables are called score, but they are two different variables. Press Step and watch: when add_bonus is called, a new layer appears, the call's own space, with its own score. Adding 10 in the function changes only the score in that layer; when the function ends, the layer is gone, and the score in main stays 80 all along.
To keep a function's result, return it and store it where you called it:
1#include <iostream>2 3int add_bonus(int score) {4 return score + 10;5}6 7int main() {8 int score = 80;9 score = add_bonus(score);10 std::cout << score << '\n';11 return 0;12}
90
(Lesson 10 shows another way: references.)
Returning a std::string
A function can return any type, std::string included. Put a decision in a function, and the code that uses it reads clearly:
1#include <iostream>2#include <string>3 4std::string grade_of(int score) {5 if (score >= 90) {6 return "Excellent";7 } else if (score >= 60) {8 return "Pass";9 }10 return "Fail";11}12 13int main() {14 std::cout << "Ana: " << grade_of(95) << '\n';15 std::cout << "Ben: " << grade_of(72) << '\n';16 std::cout << "Cal: " << grade_of(48) << '\n';17 return 0;18}
Ana: Excellent Ben: Pass Cal: Fail
A missing return
In a function whose return type isn't void, every path must end with return. Miss one and g++ gives a warning (warning:), shown above the output. A warning doesn't stop the compile, and the program still runs:
1#include <iostream>2 3int sign(int n) {4 if (n > 0) {5 return 1;6 } else if (n < 0) {7 return -1;8 }9}10 11int main() {12 int a = sign(5);13 int b = sign(0);14 std::cout << a << " " << b << '\n';15 return 0;16}
main.cpp: In function 'int sign(int)': main.cpp:9:1: warning: control reaches end of non-void function [-Wreturn-type] 9 | } | ^ control reached the end of 'sign' without a return value
What happened: Stopped at line 9. The function ended without returning a value.
control reaches end of non-void function: the function got to its end without a return. sign(5) is fine; for sign(0) neither condition is true, the function hands nothing back, and that is undefined behavior: the program goes wrong right there, before showing anything. Don't skip warnings: add return 0; at the end and run it again.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
Area of a rectangle
✓Write a function area that takes a width and a height (both int) and returns the area. main is already written; the program should show Area: 24.
Area: 24
1#include <iostream>2 3int main() {4 std::cout << "Area: " << area(6, 4) << '\n';5 return 0;6}
Celsius to Fahrenheit
✓Write a function to_fahrenheit that takes degrees Celsius (double) and returns degrees Fahrenheit (Celsius × 1.8 + 32). Show 25 C = 77 F.
25 C = 77 F
1#include <iostream>2 3int main() {4 double c = 25;5 6 return 0;7}
Fix it: the restock that didn't stick
✓restock is meant to add 20 to the stock, but stock in main is still 5. Without references, fix it so the program shows Stock: 25.
Stock: 25
1#include <iostream>2 3void restock(int stock) {4 stock += 20;5}6 7int main() {8 int stock = 5;9 restock(stock);10 std::cout << "Stock: " << stock << '\n';11 return 0;12}
What you learned
- A function = return type + name + parameters + body; one that hands nothing back is
void. Call it with its name and parentheses, giving the arguments. returnhands back a value and ends the function at once.- The compiler reads from the top: define a function before calling it, or declare it first with a prototype,
int square(int n);. - Pass by value: the argument is copied into the parameter, so changing the parameter doesn't change the variable outside; to keep a result,
returnit. - A non-
voidfunction mustreturnon every path; missing one gives the warningcontrol reaches end of non-void function. Don't skip warnings.