Functions
In C++ a function names a piece of work. It takes inputs, returns a result and hides the steps in between, which is how a file of ten lines becomes a program of ten functions that each fit on a screen. Everything in the rest of the course is a function: the standard library is thousands of them.
This lesson writes functions with parameters and return values, shows what a parameter copy means, and ends with the error that a call before the declaration produces. It is the last lesson of the first part; the next one starts references, which is where passing values gets interesting.
Define a function and call it
A function definition has a return type, a name, a parameter list and a body. The call passes arguments, the body reads them through the parameters and returns a value of the declared type, and the caller continues with that value.
The compiler must see a declaration before the call: the definition above main is one, a prototype is another. The definition can appear later in the file as long as a declaration comes first, which is what separate compilation does with headers.
The program calls square twice with different arguments. Each call is independent: the parameter gets a copy of the argument, the body runs, and the result replaces the call in the expression.
#include <iostream>
int square(int value)
{
return value * value;
}
int main()
{
std::cout << square(4) << ' ' << square(7) << '\n';
}
g++ -std=c++23 -Wall -Wextra square.cpp -o square
./square
16 49
Parameters are copies
A parameter passed by value is a new object, initialised from the argument. Changing it inside the function changes the copy, and the caller's variable is untouched when the function returns.
That is the right default for small types such as int and double, where the copy is a register move. For a std::string or a std::vector the copy can allocate and copy every element, and lesson 011 replaces it with a reference.
The program prints the value inside the function and then the value in main. The two lines differ, and the second one is the proof that count never changed.
#include <iostream>
void bump(int value)
{
value = value + 1;
std::cout << "inside: " << value << '\n';
}
int main()
{
int count = 41;
bump(count);
std::cout << "outside: " << count << '\n';
}
g++ -std=c++23 -Wall -Wextra by_value.cpp -o by_value
./by_value
inside: 42
outside: 41
Return values and early return
A function returns as soon as it reaches a return statement, so an early return is a way to leave the exceptional case behind and keep the rest of the body flat. The compiler checks that every path returns a value when the return type is not void.
A bool return turns a question into a function: is_even reads better at the call site than the modulo expression repeated in three places. A std::string return builds and returns a value, and the copy is elided by the compiler since C++17.
The loop calls both functions three times. label uses the conditional operator to pick one of two strings, which is a compact way to return a result that depends on one condition.
#include <iostream>
#include <string>
bool is_even(int value)
{
if (value % 2 != 0) {
return false;
}
return true;
}
std::string label(int value)
{
return is_even(value) ? "even" : "odd";
}
int main()
{
for (int value = 1; value <= 3; ++value) {
std::cout << value << " is " << label(value) << '\n';
}
}
g++ -std=c++23 -Wall -Wextra early_return.cpp -o early_return
./early_return
1 is odd
2 is even
3 is odd
main with arguments
main can take two parameters: the number of arguments on the command line and an array of C strings holding them. The first element is the name the program was started with, so argc is at least 1 and the arguments a user typed start at index 1.
This is how a command-line tool receives its input, and lesson 076 turns the raw pointers into std::string_view and adds option parsing. For now the loop prints every argument with its index, which is the fastest way to see what the shell actually passed.
Quotes are handled by the shell before the program starts: a quoted argument with spaces arrives as one element. The program never sees the quotes.
#include <iostream>
int main(int argc, char** argv)
{
std::cout << "arguments: " << argc << '\n';
for (int i = 0; i < argc; ++i) {
std::cout << i << ": " << argv[i] << '\n';
}
}
g++ -std=c++23 -Wall -Wextra args.cpp -o args
./args one two
arguments: 3
0: ./args
1: one
2: two
Call before the declaration
A function must be declared before the compiler sees a call. The program below defines twice after main, so main calls a name the compiler has never met, and the compiler stops at the call instead of searching the file ahead.
The message says the name was not declared in this scope and points at the call. The fix is a declaration above main: int twice(int value); This is exactly what a header does for a function defined in another file.
The rule explains the shape of every C++ project: declarations first, definitions after, and headers to share the declarations between files.
#include <iostream>
int main()
{
std::cout << twice(4) << '\n';
}
int twice(int value)
{
return value * 2;
}
g++ -std=c++23 -Wall -Wextra not_declared.cpp -o not_declared
not_declared.cpp: In function 'int main()':
not_declared.cpp:5:18: error: 'twice' was not declared in this scope
5 | std::cout << twice(4) << '\n';
| ^~~~~
Exercises
- Write a function max_of_three(int, int, int) and call it with values that make each parameter the largest once.
- Add a declaration of twice above main in not_declared.cpp and compile it again. Does the definition still work where it is?
- Change args.cpp to print only the arguments after the program name, and run it with a quoted argument that contains a space.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does a function give a program?
Answer
A name for a piece of work with inputs and a result
The name, the parameters and the return value are the interface, and the body is hidden behind it.
-
What does passing a parameter by value copy?
Answer
The argument, so the function works on its own copy
A value parameter is a new object initialised from the argument, which is why a change inside does not escape.
-
What does the value returned by main become?
Answer
The exit status of the process
The runtime passes the return value to the operating system as the status.
-
Why is a function declared before it is used?
Answer
The compiler needs the signature at the call site
A call is checked against the declaration, and a definition provides that declaration as well.
-
What does this program print?
#include <iostream> int square(int value) { return value * value; } int main() { std::cout << square(4) << ' ' << square(7); }Answer
16 49
The function returns the square of each argument, and the two calls print 16 and 49.
-
What does this program print?
#include <iostream> void bump(int value) { value = 42; std::cout << "inside: " << value << '\n'; } int main() { int number = 41; bump(number); std::cout << "outside: " << number; }Answer
inside: 42 / outside: 41
The parameter is a copy, so the assignment inside the function does not reach the caller's variable.
-
What does this program print?
#include <iostream> const char* parity(int value) { if (value % 2 == 0) return "even"; return "odd"; } int main() { for (int i = 1; i <= 3; ++i) std::cout << i << " is " << parity(i) << '\n'; }Answer
1 is odd / 2 is even / 3 is odd
The early return picks the parity, and the loop prints one line per value.
-
This program is built with
g++ -std=c++23 args.cpp -o argsand run as./q one two. What does it print?#include <iostream> int main(int argc, char** argv) { std::cout << "arguments: " << argc - 1 << '\n'; for (int i = 1; i < argc; ++i) std::cout << i << ": " << argv[i] << '\n'; }Answer
arguments: 2 / 1: one / 2: two
argc counts the program name too, so the two user arguments are counted by argc - 1.
-
What does the compiler say when
twiceis called before any declaration?Answer
'twice' was not declared in this scope
The name is unknown at the call, and the compiler stops before the linker sees the program.
-
What is the value of
argcfor a program run with no arguments?Answer
1
The program name is the first element, so argc is at least one.
The code on this page was run with g++ 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev