Chapter 7 - Notes
Functions
Allows one to compartmentalise and organise a program's execution logic
Can be viewed as a subprogram that optionally takes parameters and returns a value.
The syntax for defining a function is as follows:
Function Prototypes
A function prototype is a declaration of a function, in the following syntax:
RETURN_TYPE FUNCTION_NAME(PARAMETER_1_TYPE PARAMETER_1_NAME, ...); RETURN_TYPE: the type of the value returned from the functionFUNCTION_NAME: the name of the function that is being declaredPARAMETER_1_TYPE: the type of first parameterPARAMETER_1_NAME: the name given for the parameter
The number of parameters that a function requires is optional, from 0 (no parameters) to 256 parameters.
Multiple parameters are specified separated by a comma (,).
Function Definition
A function definition is the actual meat and potatoes of a function, where the code to be executed is defined.
RETURN_TYPE FUNCTION_NAME(PARAMETER_1_TYPE PARAMETER_1_NAME, ...) {
STATEMENT_1;
STATEMENT_2;
...
return VALUE;
}Much like before, instead of ending the function prototype with a semicolon (;) it is followed instead by a statement block ({...}).
Within the block is the code that will be executed when the function is invoked.
Function Call & Arguments
The code defined as part of a function is not executed unless it is called/invoked.
If the function's definition contains parameters, then the function needs to be called with arguments.
Functions Without Return Types
Functions that do not return a value are defined as returning the type
void.returnstatements can still be used for such functions on their own (e.g.return;)
Functions Parameters with Default Values
Default parameters are parameters that assume a default value if one is not provided by the function caller.
void greet(std::string name = "stranger") {
std::cout << "Hello there, " << name << std::endl;
}
int main() {
greet("Andrew"); // prints 'Hello there, Andrew'
greet(); // prints 'Hello there, stranger'
}Recursive Functions
Recursive functions are functions that calls itself.
Such functions should have a clearly defined base case where it returns without invoking itself again.
The function should get closer to the base case with each recursive call.
Function Overloading
Multiple functions can share the same name; what has to be unique is the function prototype.
float sum(float a) {
return a;
}
int sum(int a) {
return a;
}
int sum(int a, int b) {
return a + b;
}
int sum(int a, int b, int c) {
return a + b + c;
}Arrays as Function Parameters
Array parameters can be specified with the
ELEMENT_TYPE PARAMETER_NAME[]syntax.
void printNumbers(int numbers[], int length); The
lengthparameter is supposed to denote the length of the array that is passed in, so that you may enumerate over the array without stepping out of bounds.
Array decay
Typically we define an array like this:
int numbers[10] = {0};This defines a variable called numbers, with the type int[10] - meaning that the length of the array is embedded into variable type; which is why within the same scope we are able to use a range-based for-loop to enumerate over the array.
When creating a function that accepts an array as a parameter, the array is now of the type int[], meaning it has lost information about its size.
This is known as array decay – the array's type has decayed from one that is more specific (int[10]) to one that is less specific (int[]).
To circumvent this, we pass in the length of the array as another parameter to the function.
Reference Function Parameters
Reference parameters allow us to modify the value of an argument from within the function call
This is done by including an ampersand (&) after the type of the parameter.
void foo(int& x) {
x = 10;
}
int a = 5;
std::cout << a << std::endl; // This prints 5
foo(a);
std::cout << a << std::endl; // This prints 10This is useful for:
Emulating multiple return types – using multiple reference variables, we can return more than one value from a function.
Efficiency – if the size of the parameter is large, we can gain some efficiency by passing them by reference.
Changing values – if we need to alter the value of an argument from inside a function
Inline Functions
Preamble
When a function in a program is invoked, there is a couple of 'setup' steps that the program must perform behind the scenes before the code in the function is actually run:
Push all arguments to the stack to be used as parameters
Store the instruction to be executed after the function exits
Jump to the function that is to be executed
And once the code in the function is all run:
Store the return value of the function
Jump back to the instruction that was previously stored
The overhead of this is usually negligible, but for a simple/small function that will be executed in a tight loop this overhead might impact the performance of the program.
Inlining Functions
The keyword inline when used when defining a function hints to the compiler that the function is to be expanded inline whenever it is called.
inline double multiply(double a, double b) {
return a * b;
}
int main() {
int a, b;
std::cout << "Enter two numbers: ";
cin >> a >> b;
std::cout << "The product is: " << multiply(a, b) << std::endl;
}In the above example, the function multiply is expanded inline, and becomes equivalent to:
int main() {
int a, b;
std::cout << "Enter two numbers: ";
cin >> a >> b;
std::cout << "The product is: " << a * b << std::endl;
}Pros:
Can improve the performance of your program by avoiding the various overhead of function calls
It increases locality of reference by utilizing instruction cache
Cons:
Can potentially can code bloat if the inlined function is not small, increasing the executable size
When to use:
When the function is simple & small
When the function is called in a tight loop
Function Pointers
Function pointers are a way to assign a function to a variable.
At the moment we only use functions as a symbol that we call or invoke; function pointers allow us to perform some logic around the function.
void SayHelloWorld() {
std::cout << "Hello world!" << std::endl;
}
int main() {
auto function = SayHelloWorld;
function(); // This is the same as `SayHelloWorld()`
}The
autokeyword is useful for creating function pointers.The actual type of a function is
<return_type>(*<variable_name>)(<parameter_type>, <parameter_type>, ...)The declaration
void(*hello_function)()defines a function variable:called
hello_functionreturns a
voidtypeand takes no parameters
Lambda Functions
Lambda functions give us a way to define a function without actually creating a function.
The syntax for a lambda function is as follows:
[ <captures> ] ( <params> ) { <body> }<captures>: refer to variables that exist within the scope where the lambda is defined<params>: refer to any parameters that the lambda function accepts<body>: refers to the body of the lambda function
Last updated
Was this helpful?