Contents
This is the final part in how we structure our code. In this final chapter for part 1 we will explore functions both what they are and how to call them, but also how to write our own. We will see how we can break our programs from large chunks of code into smaller modular pieces that can then be assembled in to larger more flexible programs. We've actually been writing a function each time we write our main program. Main itself is a function and we will soon see how we can identify a function.
Functions are really just blocks of code that we have given names to. This allows us to then refer to, or call, these functions later. C++ allows us to move our functions into separate files and thereby allowing us to write our functions once and then use them multiple times. This is a great way to allow us to be more productive and more efficient with our coding.
There are three parts to every function. We can put all three parts in 1 file or we can separate them into 3 separate files. As long as we follow the correct usage for each part it won't matter how we construct our functions.
The first part is the function declaration. This is like a variable declaration that is needed before we can use our variable, we must declare our functions before we can use them.
The second part we need for our function is the function definition. The function definition is where we actually write our code for our functions. We start our function definition with the function header and then add the open and closing curly braces for the block of code that is where our code goes. A function can contain selection and looping statements and other function calls. It cannot contain another function definition.
The third part is where we actually call the function. This is the easiest part since all we need to do is write the name of the function and supply the parameters as needed.
A function declaration has three parts. A function declaration ends with a semicolon.
The parameter list within a set of parenthesis.
• The three parts above without the ending semicolon make up the function header.
Let's look at some simple functions. The first example will accept two numbers and return their sum. While the need to write a function to do this may be extremely limited, it will demonstrate how to put together a function and their pieces.
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
//Function declaration
int sum( int number1, int number2 );
//notice, we have another function here too!
int main()
{
int num1, num2;
cout << "Enter two numbers separated by a space.";
cin >> num1 >> num2;
cout << endl;
//call the function here and store the result in answer
int answer = sum( num1, num2 );
cout << "The sum of " << num1 << " and " << num2 << " is " << answer << endl;
return 0;
}
//function definition
int sum( int number1, int number2 )
{
int answer;
answer = number1 + number2;
return answer;
}
Program 5.1
Now there are lots of ways we can write up this code. For something this small, I might put all the code in one cpp file. If this were a real program where there are hundreds of functions, I would probably put the declarations into a header file, i.e. a file that ends in a .h, and the definitions into a separate cpp file. This way I could break the program into smaller pieces. If there was a natural organization of the functions around areas, e.g. some functions for dealing with files, some function for doing math, some functions for user input, some functions for user output, then I would probably group those functions into separate cpp files as well. This would allow me to reuse the code later and if there was a mistake in part of the program it would narrow down my search for the bugs. A lot of the utility in functions comes not from writing less code, but rather in being able to track down and squash bugs faster.
In our first example we saw how you could give information to a function by passing in information via the parameters in the parenthesis and how the function was able to return 1 answer via the return statement. You can probably imagine situations where a function might need to return more than 1 piece of information and/or where it might need to modify one of the parameters.
To be able to handle the situation as described in the last paragraph we need to discuss how data is passed into and out of functions in C++. By default, C++ uses a passing mechanism that is known as pass-by-value or pass-by-copy. This means that when you pass in a parameter the function gets a copy of the value that you gave it. This is good because if the function modifies the parameter then you value is safe. This is how most functions should accept data and in this way all is good in the C++ world. This also means that if you need to give back a value, since the values are copies, all you can do is use the return statement to give back 1 single answer. This works well for many functions. Think of the mathematical functions, sine, cosine, tangent, absolute value, etc., this is how most of those functions work. You give the function information and it returns a single answer.
Now think about a vending machine. Let's say you are hungry and decide to go and get your favorite candy bar or snack from the vending machine. Let's pretend your favorite snack costs $1.50 and you have two $1 bills. Now, the machine, if it operates like the functions described above can only give you back 1 thing either your 50 cents or your snack. Which do you choose? Luckily for you, the vending machine can do both. Now suppose you have to do that same thing in C++. Luckily for you, C++ can also allow for your function to give back more than 1 piece of information.
To allow for more than 1 piece of information to be returned we need to allow the function to modify our parameters. This is known as pass-by-reference or pass-by-address because when we call the function we pass in the address-of or the reference-to the parameter. Then since the function "knows" the address of our parameter it can go there and update the value that is stored there. If we take the vending machine idea and write a function declaration for that, it might look like this:
Snack vendingMachine( int payment, int& change );
This assumes that Snack is a valid C++ type (it isn't unless we write one). The first parameter represents how much money we give the vending machine. The second parameter is the change the machine will give us. Notice the & on the type of the parameter. The & is the address-of operator and it will give us the address-of the parameter. Adding this & character allows the function to modify our parameter and prevents the copy of the parameter being made.
Let's consider a function that will take two numbers and return the two numbers in sorted order, largest first.
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
//function declaration for our sort function.
void sortTwoNumbers( int& number1, int& number2 );
//our main program
int main()
{
int num1, num2;
cout << "Please enter two numbers: " << endl;
cin >> num1 >> num2;
sortTwoNumbers( num1, num2 );
cout << "The biggest number is: " << num1 << endl;
cout << "The smallest number is: " << num2 << endl;
return 0;
}
void sortTwoNumbers( int& number1, int& number2 )
{
if ( number1 < number2 )
{
int temp = number1;
number1 = number2;
number2 = temp;
}
}
Program 5.2
The main idea behind this program is that the function will compare the two numbers and if it determines that the first number is less than the second number it will swap the two numbers. Since the function uses pass-by-reference the function is allowed to modify the parameters and thus the swapping "sticks". If you take the same program, but remove the & from the declaration and definition, it will no longer work because the parameters will be copies of the original values.
So when learning to use pass-by-reference it can be difficult to know when you should use that mechanism. There are two questions I use to know if I should use pass-by-reference.
If the answers to the two questions above are both "yes", then use pass-by-reference; otherwise stick with pass-by-value.
If we look at program 5.1, suppose the function modified the numbers, the calling program in that case would not want or need to "know" about the changes, so pass-by-value makes sense. In our second program, if we didn't use pass-by-reference, then the sorting wouldn't work.
There are two last concepts to go along with functions: scope and lifetime. These two concepts are easy to define but can be difficult to get all the nuances correct.
Scope is where in the code a variable or function can be seen. The scope of a variable starts from the point of the code where it is declared until the block of code it is in ends. So if you declare a variable inside of main, then it goes until main runs out. If you declare a variable in a function, then the scope ends when the function ends.
Lifetime is defined to be how long a variable is in memory. This is usually the same as the scope of the variable but it can be extended. At the moment, we will simply stick with the default behavior for lifetime.
Scope on the other hand has a few very common ways that it can be misused. If you declare a variable outside of any function, for example at the top of the file, then that variable is said to be in the global scope or a global variable. The problem with using a global variable is that you can access that variable in any of the functions that are defined below. This might sound like a good idea except it by passes the pass-by-value security that C++ uses. It could give functions the ability to modify variables when the functions shouldn't. It also makes your functions harder to fix because the source of the variables they are using might be hard to track down. In general, it is a bad idea to make a variable global. We almost always want our variables declared inside of a local scope of a function.
Functions are our last way that we organize our code. They allow us to put code together under a name so we can call it again as we need. Their purpose isn't to necessarily write less code, but to allow us to organize our code into related units and thereby decreasing the time needed to find and squash bugs in our code.
Written by: David McPherson © 2019