Functions (Basics)
Functions (Basics)

Example: Function that computes mean of numbers
double mean (double a, double b) {
  return (a + b) / 2;
}
int main() {
  cout << mean(2, 6);  // prints 4
}
  • encapsulation of implementation details
  • easier reasoning about correctness and testing by breaking down problem into separate functions
  • avoids repeating code for common tasks
// "call" at "call site"
auto result = name(argument1, argument2, ...);

Return Types

Full Return Type Deduction  
auto foo (int i, double d) {
  
  return i;
}
//  deduced return type: int
auto foo (int i, double d) {
  return i;  //  int
  
  return d;  //  double 
} : Inconsistent!

Parameters

  • none: f()
  • one or many: g(int a, double b, int c, …)
  • parameter names have to be unique within list

int foo(int a, int const b) {
  a += 5;   //  OK
  b += 10;  // : can't modify const parameter
  return (a + b);
}

// calling foo: foo(2,9); // const has no effect here

Any 2nd argument passed to foo will be copied into the local variable b ⇒ the fact that b is const has no effect outside of foo.

If you don't need or must not change values of parameters then make them const!

double f (double a, double b = 1.5) {
    return (a * b);
}
int main() {
  cout <<  f(2);     // 1 argument  → 3.0
  cout <<  f(2, 3);  // 2 arguments → 6.0
}
void foo (int i = 0);  
void foo (int n, double x = 2.5);  
void foo (int a, int b = 1, float c = 3.5f);  

void foo (int a, int b = 1, int c );
Each parameter after first default must have default value, too!

Overloading

  • functions with the same name but different parameter lists
  • cannot overload on return type alone

Recursion

= function calling itself
  • needs a break condition
  • looks more elegant than loops but in many cases slower
  • recursion depth is limited (by stack size)
int factorial (int n) {
    // break condition:
    if(n < 2) return 1;  
    // recursive call: n! = n * (n-1)!
    return (n * factorial(n - 1));  
}

  • can only call functions that are already known (from before/above)
  • only one definition allowed per source file (translation unit)
  • ok to have any number of declarations = announcing the existence of a function by specifying it's signature
Example: Broken! (click!)
 - odd/even not known before main!
 - odd not known before even!

int main() { int i = 0; cin >> i; if(odd(i)) cout << " is odd\n"; // odd not known yet! if(even(i)) cout << " is even\n"; // even not known yet! }
bool even(int n) { return !odd(n); // odd not known yet! }
bool odd(int n) { return (n % 2); }
Working (click!)
bool even(int);  // declaration
bool odd(int);   // declaration

int main() { // definition int i = 0; cin >> i; if(odd(i)) cout << " is odd\n"; // OK, already declared if(even(i)) cout << " is even\n"; // OK, already declared }
bool even(int n) { // definition return !odd(n); // OK, already declared }
bool odd(int n) { // definition return (n % 2); }

Design

When implementing a function, think about:
  • Preconditions: What do you expect/demand from input values?
  • Postconditions: What guarantees should you give regarding output values?
  • Invariants: What do callers/users of your function expect to not change?
  • Purpose: Has your function a clearly defined purpose?
  • Name: Does the function's name reflect its purpose?
  • Parameters: Can a caller/user easily confuse their meaning?

Users of a function should not be confused about its purpose, the meaning of its parameters, pre/postconditions and side effects.

Interfaces should be easy to use correctly and hard to use incorrectly. — Scott Meyers

#include <cmath>
double sqrt (double x) √x square root
double pow (double a, double b) ab power
double abs (double x) |x| absolute value
double sin (double x) sin(x) sine
double cos (double x) cos(x) cosine
double exp (double x) ex exponential
double log (double x) log(x) logarithm
double floor (double x) ⌊x⌋ next smaller integer
double ceil (double x) ⌈x⌉ next larger integer
double fmod (double x, double y) remainder of x/y