Skip to content
Lesson 8 of 15

Step 1 of 5 · Reading · ~3 min

Learn

Functions and References

Functions

By the end of this lesson you can write a function that takes typed parameters and returns a value, pick a return type wide enough for the result, overload one name for several types, and give a parameter a default.

The shape of a function

A function is a named block of code that takes inputs (its parameters) and hands back one result with return. The type written before the name is the return type: the type of the value the caller receives.

#include <iostream>

double celsiusToF(double c) {
    return c * 9.0 / 5.0 + 32.0;
}

int main() {
    std::cout << celsiusToF(100.0) << "\n";   // 212
    std::cout << celsiusToF(-40.0) << "\n";   // -40
    return 0;
}

The call celsiusToF(100.0) copies 100.0 into c, runs the body, and the whole call expression becomes the returned value, which main then prints. The function itself prints nothing; that split (logic in functions, input and output in main) is the habit to keep.

The compiler reads top to bottom, so a function must be declared before main calls it: define it above main, or put a prototype such as double celsiusToF(double c); at the top and the body below.

The return type is a promise about size

Whatever the body computes is converted to the return type on the way out. Pick one too narrow and the result is wrong before anyone sees it:

int cubeNarrow(int x) { return x * x * x; }
long long cubeWide(long long x) { return x * x * x; }

cubeNarrow(2000)   // -589934592  (8,000,000,000 does not fit in int)
cubeWide(2000)     // 8000000000

Widening only the return type is not enough. In long long cubeHalf(int x) { return x * x * x; } the multiplication is still int * int, so it overflows first and the damaged value is widened afterwards. As the Numbers lesson said: the operand types decide the arithmetic. Make the parameter itself long long and every multiplication happens in 64 bits.

Overloading and default arguments

Two functions may share a name if their parameter lists differ. This is overloading: the compiler picks the version whose parameter types match the arguments.

long long cube(long long x) { return x * x * x; }
double cube(double x)       { return x * x * x; }

cube(3LL)   // 27    (long long version)
cube(1.5)   // 3.375 (double version)

A default argument fills in a parameter the caller leaves out. Defaults must be the rightmost parameters:

int area(int w, int h = 1) { return w * h; }
area(6, 4)   // 24
area(6)      // 6, because h defaults to 1

Arguments are copies

A parameter is a private copy of the argument. Changing it inside the function does not touch the caller's variable:

void addTen(int v) { v += 10; }
int score = 5;
addTen(score);   // score is still 5

To change a value, return the new one and assign it: score = plusTen(score);. The next lesson shows the other way, reference parameters.

Common mistakes

  • Return type too narrow. A result near 10 billion in an int function comes back negative or scrambled; 46341 squared in int gives -2147479015 instead of 2147488281.
  • Widening the return but not the parameter. long long f(int x) still multiplies in int, so it overflows exactly like the narrow version.
  • Doubling instead of multiplying by itself. x * 2 or x + x agrees with the right answer only at 0 and 2; for 4 it gives 8.
  • Printing inside the function. If the function prints and main also prints the returned value, the number appears twice, as in 1616.

Your exercise

Complete the square function so it returns its argument multiplied by itself; main already reads the number and prints what square returns. The input can be as large as one million in magnitude, so the starter's int signature is too narrow: fix both the parameter type and the return type.

Up nextReferences vs PointersFunctions and References

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…