Defining and calling functions in C++ is how you turn one long program into a set of named, testable pieces. A function takes inputs, does one job, and hands back a result. Once you can write one, you can write a hundred, and a program becomes a set of small parts rather than one enormous block.
The shape of a function
A C++ function has four parts: a return type, a name, a parameter list, and a body.
#include
double line_total(double unit_price, int quantity) {
return unit_price * quantity;
}
int main() {
double total = line_total(4.50, 3);
std::cout << "line total: " << total << '\n';
return 0;
}Read the definition as a sentence: "a double named line_total takes a unit price and a quantity, and gives back a double." The return statement is what produces that value.
Return types and the return statement
The type before the function name is the return type. It tells the caller what kind of value to expect, and the compiler uses it to check that every path through the function returns something.
int,double,booland the other numeric types return a value.voidmeans the function returns nothing at all.- A pointer type returns an address.
- A class type returns an object.
A function with a non-void return type must return a value on every path, and the compiler enforces it. Leaving a return off the end of a non-void function is a compile error in C++, which is a genuine improvement over C.
#include
int cakes_baked() {
return 24;
}
void announce_order(int order_number) {
std::cout << "order " << order_number << " received\n";
}
int main() {
std::cout << "cakes baked: " << cakes_baked() << '\n';
announce_order(4821);
return 0;
}A void function performs an action but produces no value. Printing is the classic example: the interesting part is what appears on the screen, not a number to use later.
Parameters and arguments
A parameter is the name in the function definition. An argument is the value you pass at the call site. The two terms are often used interchangeably, but keeping them apart makes conversations clearer.
Parameters come in three useful shapes. Value parameters receive a copy. Reference parameters receive an alias to the caller's object. Pointer parameters receive an address. The next lesson covers the difference between the first two in depth.
Local scope and shadowing
Everything you declare inside a function is local to it and disappears when the function returns. That is what makes functions safe to use: two functions can both have a variable called total without any interference.
#include
int main() {
{
int total = 10; // a different variable entirely
std::cout << "inner total: " << total << '\n';
}
int total = 99;
std::cout << "outer total: " << total << '\n';
return 0;
}The two total variables are unrelated. The inner one is created when its block starts and destroyed when the block ends.
Calling a function before it is defined
C++ requires a declaration before a call. The simplest way to provide one is to define the function before you call it, which is what the examples above do. When the definition comes later, add a forward declaration — a prototype — above the call.
#include
double apply_discount(double total, double percent); // declaration
int main() {
std::cout << "after discount: " << apply_discount(100.0, 10.0) << '\n';
return 0;
}
double apply_discount(double total, double percent) { // definition
return total - total * percent / 100.0;
}A prototype repeats the signature without the body. It exists so the compiler knows the return type and the parameter types before it reaches the definition.
Functions that call functions
Real code is layered. A small function that computes one thing becomes the building block of a larger one.
#include
double line_total(double unit_price, int quantity) {
return unit_price * quantity;
}
double subtotal(const double lines[], int count) {
double sum = 0.0;
for (int i = 0; i < count; ++i) {
sum += lines[i];
}
return sum;
}
int main() {
double lines[3] = {13.50, 9.00, 22.50};
double before_tax = subtotal(lines, 3);
std::cout << "subtotal: " << before_tax << '\n';
std::cout << "with tax: " << before_tax * 1.08 << '\n';
return 0;
}Each function does one job: one multiplies, one sums, one prints. That is the shape of code that stays readable as it grows.
Returning more than one value
A function has exactly one return type, so it returns one thing. When you need two or three results, the usual approach is to bundle them into a small struct and return that.
#include
struct PriceBreakdown {
double subtotal;
double discount;
double total;
};
PriceBreakdown price_order(double subtotal, double percent) {
PriceBreakdown result;
result.subtotal = subtotal;
result.discount = subtotal * percent / 100.0;
result.total = subtotal - result.discount;
return result;
}
int main() {
PriceBreakdown p = price_order(24.00, 15.0);
std::cout << "subtotal: " << p.subtotal << '\n';
std::cout << "discount: " << p.discount << '\n';
std::cout << "total: " << p.total << '\n';
return 0;
}The alternative — passing extra pointers or references as out-parameters — works, but it makes the call site harder to read and easier to get wrong. Returning a struct keeps the result in one place and lets the compiler check it.
Documenting a function
A short comment above each function tells the reader what it does, what it expects and what it returns. The comment above the declaration in the header is the one that matters, because that is the interface other programmers use.
// Returns the price of one order line after the quantity is applied.
// unit_price: price of a single item in pounds
// quantity: number of items ordered
double line_total(double unit_price, int quantity) {
return unit_price * quantity;
}Quick reference
- A definition is a return type, a name, a parameter list and a body.
- The return type tells the caller what to expect;
voidmeans no value comes back. - Every path through a non-void function must return a value.
- Parameters are the names in the definition; arguments are the values at the call.
- Declarations inside a function are local and vanish when it returns.
- A prototype lets you call a function whose definition appears later in the file.
- Small functions that call small functions are easier to test than one long function.
Try it yourself: write a function that converts a quantity and unit price into a line total, then a function that sums an array of line totals, then a function that applies a percentage discount. Print each stage separately so you can check the arithmetic, and finally move the definitions below [[main]] and add prototypes so it still compiles.