Functions (Basics)Functions (Basics)Functions
Example: Function that computes mean of 2 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 problems into separate functions
- avoids repeating code for common tasks
// "call" at "call site"
auto result = name(argument1, argument2);
// "call" at "call site"
auto result = name(argument1, argument2);
either one value: int
, double
, …
double square (double x) {
return (x * x);
}
int max (int x, int y) {
if(x > y) return x; else return y;
}
or nothing: void
void print_squares (int n) {
for(int i = 1; i <= n; ++i)
cout << square(i) << '\n';
}
Full Return Type Deduction C++14 (deduction = compiler determines type automatically)
auto foo (int i, double d) {
…
return i;
}
// OK: return type: int
auto foo (int i, double d) {
return i; // int
…
return d; // double
}
// ERROR: Inconsistent return types!
- 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; //
b += 10; // COMPILER ERROR: 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 inside
the function 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!
- functions with the same name but different parameter lists
- cannot overload on return type alone
same name, different parameter lists
int abs (int i) {
return ((i < 0) ? –i : i);
}
double abs (double d) {
return ((d < 0.0) ? –d : d);
}
int a = -5;
double b = -2.23;
auto x = abs(a); // int abs(int)
auto y = abs(b); // double abs(double)
same name, same parameter lists
int foo (int i) {
return (2 * i);
}
double foo (int i) {
return (2.5 * i);
}
DOES NOT COMPILE!
= 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 its signature
Example: Broken! (click!)
COMPILER ERROR: - 'odd'/'even' not known before 'main'!
COMPILER ERROR: - 'odd' not known before 'even'!
int main() {
int i = 0;
cin >> i;
if(odd(i)) cout << " is odd\n";
if(even(i)) cout << " is even\n";
}
bool even(int n) {
return !odd(n);
}
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);
}
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 |