Functions that do one job
Functions and library types12 min readLesson 1 of 5
1.1 Name a piece of work
A function packages a task behind a name. It has a return type, a name, a parameter list, and a body. Calling the function runs the body. A function that returns no value uses void. main is a function, and every other function you write runs only when something calls it.
Split a program into functions when a block has one clear job or when you need the same work twice. Each function should have a name that describes its result, such as calculate_total or is_valid.
#include <iostream>
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(7, 5);
std::cout << "Sum: " << result << "\n";
return 0;
}Output
Sum: 12
1.1.1 Parameters and arguments
A parameter is a name in the function definition. An argument is the value supplied by the caller. int add(int a, int b) declares two parameters. add(7, 5) passes two arguments.
Arguments are matched by position and type. The compiler converts some compatible values and rejects others. A non-void function other than main must return a value on every path that can reach the end. main returns 0 if execution falls off its end.
#include <iostream>
void print_line(int width) {
for (int i = 0; i < width; ++i) {
std::cout << "-";
}
std::cout << "\n";
}
int main() {
print_line(8);
std::cout << "Title\n";
print_line(8);
return 0;
}Output
-------- Title --------
1.1.2 Return values and early exits
return sends a value back to the caller and ends the function immediately. A bool function often uses an early return for a special case, then handles the normal result.
Keep the number of exit points small enough to follow. One return at the end is easy to read. Several early returns also work when each one handles a distinct, simple case.
#include <iostream>
bool is_even(int number) {
if (number % 2 == 0) {
return true;
}
return false;
}
int main() {
std::cout << std::boolalpha;
std::cout << "8 is even: " << is_even(8) << "\n";
std::cout << "7 is even: " << is_even(7) << "\n";
return 0;
}Output
8 is even: true 7 is even: false
1.1.3 Pass by value and by reference
By default, C++ passes arguments by value, which means the function gets a copy. Changing the copy leaves the caller's variable alone. Put & after the type to pass by reference: void add_bonus(int& score). The parameter now names the caller's variable, so changes are visible.
Use a reference when the function must modify the argument. Use a const reference for a large object that should be read, not copied and not changed: void print(const std::string& text).
#include <iostream>
void add_bonus(int score) {
score += 5;
}
void add_bonus_ref(int& score) {
score += 5;
}
int main() {
int score = 70;
add_bonus(score);
std::cout << "After value: " << score << "\n";
add_bonus_ref(score);
std::cout << "After reference: " << score << "\n";
return 0;
}Output
After value: 70 After reference: 75
1.1.4 Default arguments and prototypes
A default argument supplies a value when the caller omits one. Specify the default once, in the declaration or the definition, and put parameters with defaults at the end. double price_with_tax(double price, double rate = 0.05); lets price_with_tax(100.0) use five percent.
A function prototype is a declaration without the body. It lets main call a function that is defined later in the file. The prototype must match the definition's return type and parameter types.
Two functions can share a name when their parameter lists differ. This is function overloading. print(int) and print(const std::string&) are distinct functions, and the compiler picks one from the argument types. Return type alone cannot distinguish overloads.
#include <iostream>
double price_with_tax(double price, double rate = 0.05);
int main() {
std::cout << "Food: " << price_with_tax(100.0) << "\n";
std::cout << "Luxury: " << price_with_tax(100.0, 0.18) << "\n";
return 0;
}
double price_with_tax(double price, double rate) {
return price * (1.0 + rate);
}Output
Food: 105 Luxury: 118
1.1.5 Local state
A variable declared inside a function is local to that function and disappears when the call ends. Each call gets a fresh variable unless the variable is static. A static local keeps its value between calls, and it is easy to misuse before you understand lifetime.
Prefer parameters and return values over hidden state. A function that reads and writes globals is harder to test because its behavior depends on something outside the call.
#include <iostream>
#include <string>
#include <cstddef>
std::size_t length_of(const std::string& text) {
return text.size();
}
int main() {
std::string word = "compiler";
std::cout << "Length: " << length_of(word) << "\n";
return 0;
}Output
Length: 8
1.1.6 Common mistakes
- Forgetting the return type or writing
voidwhen a value is needed. - Leaving a value-returning function without a return on some path.
- Adding a semicolon after the parameter list.
- Declaring a prototype with types that differ from the definition.
- Passing a value when the function expects a reference, or the reverse.
- Returning a reference to a local variable. The local dies when the call ends.
1.1.7 Practice
- Write
square(int)and call it frommain. - Write
bool is_passing(int score)with a pass mark of 40. - Write a
voidfunction that prints a separator line. - Write a function that accepts a
std::stringby const reference and returns its length. - Add a default argument for a tip rate and call the function with and without it.
