Functions (Basics) Functions (Basics) Functions
Example
Function that computes mean of 2 numbers
#include <iostream>
double mean (double a, double b) {
return (a + b) / 2;
}
int main () {
std::cout << mean(2, 6) <<'\n'; // 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 );
- functions with the same name but different parameter lists
- cannot overload on return type alone
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)
int foo (int i) {
return (2 * i);
}
double foo (int i) {
return (2.5 * i);
}
Implementation
= 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!)
#include <iostream>
// COMPILER ERROR: - 'odd'/'even' not known before 'main'!
// COMPILER ERROR: - 'odd' not known before 'even'!
int main () {
std::cout << "enter an integer: ";
int i = 0;
std::cin >> i;
if (odd(i)) std::cout << "is odd\n";
if (even(i)) std::cout << "is even\n";
}
bool even (int n) {
return !odd(n);
}
bool odd (int n) {
return (n % 2);
}
Working (click!)
#include <iostream>
bool even (int); // declaration
bool odd (int); // declaration
int main () { // definition of 'main'
std::cout << "enter an integer: ";
int i = 0;
std::cin >> i;
if (odd(i)) std::cout << "is odd\n"; // OK, already declared
if (even(i)) std::cout << "is even\n"; // OK, already declared
}
bool even (int n) { // definition of 'even'
return !odd(n); // OK, already declared
}
bool odd (int n) { // definition of 'odd'
return (n % 2);
}
Design
Interfaces should be easy to use correctly and hard to use incorrectly.
— Scott Meyers
When designing 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?
Precondition Checks
Wide Contract Functions perform precondition checks, i.e., check input parameter values (or program state) for validity
Narrow Contract Functions do not perform precondition checks, i.e., the caller has to make sure that input arguments (and program state) are valid
Attribute [[nodiscard]]
[[nodiscard]]
[[nodiscard]]
C++17
encourages compilers to issue warnings if function return values are discarded
[[nodiscard]] bool prime (int i) { … }
// return value(s) used:
bool const yes = prime(47);
if (prime(47)) { … }
// return value discarded/ignored:
prime(47); // COMPILER WARNING
Example from the standard library:
std::vector
's empty()
function is declared with
[[nodiscard]]
as of C++20, because it can be confused
with clear()
:
std::vector<int> v;
// …
if (v.empty()) { … } // OK
v.empty(); // C++20: COMPILER WARNING
// oops … did someone meant to clear it?
Declare your function return values [[nodiscard]]
- if calling it without using the return value makes no sense in any situation
- if users could be confused about its purpose, if the return value is ignored
No-Throw Guarantee: noexcept
noexcept
noexcept
C++11
C++ has a mechanism for reporting errors using exceptions
like many/most other programming languages.
Don't worry, if you don't know what exceptions are, they will be
explained in a later chapter.
The noexcept
keyword specifies that a function promises
to never throw exceptions / let exceptions escape:
void foo () noexcept { … }
If an exception escapes from a noexcept function anyway, the program will be aborted.
Some Mathematical Functions Mathematical Functions Math
#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 |
Comments…