C++: The Basics - eps1.15_Functions
Hello and welcome. So far in the series of articles C++: The Basics we have seen simple programs or developed them ourselves. They consisted of a main()
function where everything happened.
This works quite well for small and not too large programs. But the bigger and more complex our code gets, the longer main()
becomes.
Don’t you also have the feeling to lose the overview with growing code length? It becomes more and more exhausting to find certain parts or to see connections.
The solution: Structure your source code with the help of more functions!
What are functions?
You already know the main()
function. It is the starting point and therefore essential for every program. Everything that your application should execute has to go there. But you don’t have to write every statement, branch and loop line by line.
With additional functions it is possible to divide and organize your code into smaller, logical blocks.
You can think of functions as subroutines. They have the ability to take parameters, perform a task independently and return a value.
With an example, you will surely see the necessity of subdividing into functions better. For this, let’s take a topic from geometry (him again with his math…).
We want to have our application calculate the diameter, area and circumference of a circle of arbitrary radius. For this we need to implement the circle number Pi
and the required mathematical formulas.
// listing1: computing the area and circumference of a circle with given radius
#include <iostream>
const double Pi = 3.14159265;
// functions
double diameter(double radius)
{
return 2 * radius;
}
double area(double radius)
{
return Pi * radius * radius;
}
double circumference(double radius)
{
return 2 * Pi * radius;
};
// main function
int main()
{
double radius = 0.0;
std::cout << "Enter radius: ";
std::cin >> radius;
// call function "diameter"
std::cout << "diameter is: " << diameter(radius) << std::endl;
// call function "area"
std::cout << "area is: " << area(radius) << std::endl;
// call function "circumference"
std::cout << "circumference is: " << circumference(radius) << std::endl;
return 0;
}
Our main()
function is nice and lean in listing1 and delegates all tasks to other functions. Not only does this make the source code much more concise, we’ve also made parts of it reusable. The single functions can be called again as often as you think it makes sense.
You surely noticed that the constants and the functions are placed before the main()
function. This is by no means random or coincidental. If you want to use a function in another function, then this function must be known to the compiler beforehand. If it encounters a function during the build process that it does not know, it throws an error message and aborts the process. This also applies to variables and constants.
But you don’t have to put everything completely in front of the main()
function. Can you remember the difference between declaration and definition?
From prototype, definition, function call and arguments.
Let’s briefly consider what all we need to use functions.
First of all, so that the compiler knows about the function and can use it, we need to declare it. But then what it actually does is not yet clear. For this we need its definition. Finally, we can use it with a function call.
This results in the three components:
- Declaration
- Definition
- Function Call
The declaration of a function is also called function prototype. A prototype consists of the return value type, function name and is followed by the function parameters in brackets ()
. The prototype is terminated with a semicolon ;
(see listing2).
// listing2: function prototype
// |Return value |Function parameters
double diameter (double radius);
// |Function name
A prototype thus shows the compiler the name of the function, which parameters it accepts and which data type it will return. Only now it can do something with it in an instruction.
This is completely sufficient for the compiler. It does not need to know more. The connection between the call and the implementation of a function takes over afterwards the Linker.
Therefore, the definition can also be at any other place in the source code. It is the actual implementation of the function and always consists of a statement block in braces {}
(see listing3).
// listing3: function definition
double diameter (double radius)
{
return 2 * radius;
}
As long as a function does not have the return type void
, there must be a return
statement. In listing3 the function has double
as return value. So return
is also expected to have a double
variable.
The function name is followed by the function parameter radius
in brackets ()
. This parameter is used as argument
when calling the function. Arguments are therefore the values that a function requires in its parameter list when called.
// listing4: function call
diameter (3.0);
The function call is quite simple. You only need to write the function name and then give the required argument in brackets ()
. Of course, a semicolon ;
follows at the end.
Would our example from listing1 look different with the new knowledge? Clearly yes!
// listing5: function prototypes and implementations
#include <iostream>
const double Pi = 3.14159265;
// prototypes: function declarations
double diameter(double radius);
double area(double radius);
double circumference(double radius);
int main()
{
double radius = 0.0;
std::cout << "Enter radius: ";
std::cin >> radius;
// call function "diameter"
std::cout << "diameter is: " << diameter(radius) << std::endl;
// call function "area"
std::cout << "area is: " << area(radius) << std::endl;
// call function "circumference"
std::cout << "circumference is: " << circumference(radius) << std::endl;
return 0;
}
// implementation: function definitions
double diameter(double radius)
{
return 2 * radius;
}
double area(double radius)
{
return Pi * radius * radius;
}
double circumference(double radius)
{
return 2 * Pi * radius;
}
We would first declare the prototypes and immediately follow them with the main()
function. In order to be able to read the code well, we, just like the compiler, first only want to know which functions exist and what their parameters are.
Because actually we are interested in what the program outputs at the end. Deeper details about the implementation of the individual functions are not yet important for this in the first step and complicate the overview. Therefore the definitions can be placed after the main()
function.
If you now argue with the fact that the source code serves only for communication with the machine and it does not care about a clearer structuring, then this is only half the truth.
The programming language and your code serve as interface between humans and machine.
Of course your code must be translatable for the compiler into machine code. But other humans should also be able to understand what you are trying to accomplish with your lines. I know, this makes the task even harder, because you have to reach two totally different target groups with your code. But this is also the great art of programming.
Functions with multiple parameters
So far, our functions have wanted one argument and returned you a value. But there are tasks that require more than one parameter. Let’s stay with geometry. What if we want to calculate the surface of a three-dimensional body instead of an area?
For example, the surface of a cylinder. This consists of the circular top, the cresiform bottom and the surface of the side.
A function that calculates the surface of a cylinder needs the two parameters double radius
and double height
. And this is quite simple. Additional parameters are simply written into the parenthesis ()
in the prototype. In each case separated with a comma ,
.
// listing6: function with multiple parameters
// prototype: function declaration
double cylinderSurface(double radius, double height);
// implementation: function definition
double cylinderSurface(double radius, double height)
{
return 2 * radius * radius + 2 * radius * radius + 2 * radius * height;
}
Mmh, the formula in the definition is quite long and hard to keep track of. We could reuse the functions from our other examples and make the code more modular. Because if you look closely, the surface of a cylinder consists of two circular areas and the circumference multiplied by the height of the body.
// listing7: surface of a cylinder
#include <iostream>
const double Pi = 3.14159265;
// prototypes: function declarations
double area(double radius);
double circumference(double radius);
double cylinderSurface(double radius, double height);
int main()
{
double radius = 0.0;
double height = 0.0;
std::cout << "Enter radius: ";
std::cin >> radius;
std::cout << "Enter height: ";
std::cin >> height;
// call function "cylinderSurface"
std::cout << "The surface of the cylinder is: " << cylinderSurface(radius, height) << std::endl;
return 0;
}
// implementation: function definition
double area(double radius)
{
return Pi * radius * radius;
}
double circumference(double radius)
{
return 2 * Pi * radius;
}
double cylinderSurface(double radius, double height)
{
// call functions "area" and "circumference"
return 2 * area(radius) + circumference(radius) * height;
}
Neither parameter, nor return value
There is also the case that a function has no parameters and also returns no value. If the dialog of our program is too unfriendly or too cold for you, you might want to call a function at the beginning that welcomes the user.
That’s all the function is supposed to do. It just does this one output to the command line.
// listing8: function with no parameters and no retrun values
#include <iostream>
// prototype: function declaration
void welcome();
int main()
{
welcome();
return 0;
}
// implementation: function definition
void welcome()
{
std::cout << "Welcome!" << std::endl;
}
The void
data type indicates that nothing is to be returned. Therefore, no return
statement is required in the definition. However, it is up to you whether you omit the statement or symbolically write an empty return
statement.
// listing9: empty return statement
#include <iostream>
void welcome()
{
std::cout << "Welcome!" << std::endl;
return; // empty return statement
}
Function parameter with default value
One more thing about functions I would like to show you.
“One more… but then it’s over!” - Mrs. Hansen
In our example, we have fixed the value of Pi
as a constant. But maybe you want to give the user the chance to choose himself with which precision and how many decimal places he would like to use Pi
. No problem at all. We can create functions with two parameters.
But what if the user agrees with our Pi
or has no idea what value to enter?
The solution is not complicated. We can give each parameter of a function in the prototype a Default Value. For Pi
in our circular area function area
it looks like this:
//listing10: function parameter default value
double area(double radius, double Pi = 3.14159265);
Because the second parameter has a Default Value, it is optional and does not have to be set with an argument during the function call.
// listing11: function parameter with default value
#include <iostream>
// prototype: function declarations
double area(double radius, double Pi = 3.14159265);
int main()
{
double radius = 0.0;
std::cout << "Enter radius: ";
std::cin >> radius;
std::cout << "Do you want to have a different value of pi? (y/n) ";
char charPi;
std::cin >> charPi;
if( charPi == 'y')
{
std::cout << "Enter new Pi value: ";
double newPi;
std::cin >> newPi;
// call function "area with second argument"
std::cout << "The area is: " << area(radius, newPi) << std::endl;
}
else
{
// call function "area without second argument"
std::cout << "The area is: " << area(radius) << std::endl;
}
return 0;
}
// implementation: function definition
double area(double radius, double Pi)
{
return Pi * radius * radius;
}
Whether listing11 is the best and most beautiful source code example is not to be discussed. But I think you can see from it how default values work.
That sticks
We learned about Functions and saw how we used them to modularize our code. This not only increased readability and clarity, but also made parts of the source code reusable.
You can now create prototypes, definitions and function calls. You have complete freedom of design when it comes to the return value or the parameters.
Functions are a powerful tool with which you can implement larger programs more cleverly and clearly. Definitely a technique worth mastering.
I wish you maximum success!
Sources
- [1] B. Stroustrup, A Tour of C++. Pearson Education, 2. Auflage, 29. Juni 2018.
- [2] B. Stroustrup, Programming: Principles and Practice Using C++. Addison Wesley, 2. Auflage, 15. Mai 2014.
- [3] U. Breymann, Der C++ Programmierer. C++ lernen – professionell anwenden – Lösungen nutzen. Aktuell zu C++17. München: Carl Hanser Verlag GmbH & Co. KG; 5. Auflage, 6. November 2017