Section 1.11 Defining C++ Functions
In general, we can hide the details of any computation by defining a function. A function definition requires a name, a group of parameters, a return type, and a body. It may either return a variable, value, or nothing (specified by the keyword void). For example, the simple function defined in Listing 1.11.1 returns an integer which is the double of the value you pass into it.
The syntax for this function definition includes the name,
timesTwo
, and a parenthesized list of formal parameters and their types. For this function an int
named num
is the only formal parameter, which suggests that timesTwo
needs only one piece of data to do its work. The details, hidden “inside the box,” simply compute the result of num*2
and return it. We can invoke or call the timesTwo
function by asking the C++ to evaluate it, passing an actual parameter value, in this case, 3
. Note that the call to timesTwo
returns an integer that can in turn be passed to another invocation.Let us look at a similar function in Listing 1.11.2.
timesTwoVoid
behaves very similarly to timesTwo
. However, there is one key difference between them. Instead of the int
in timesTwo
, timesTwoVoid
has a void
in front of its function definition. Unlike timesTwo
, timesTwoVoid
is a non-fruitful function meaning it does not return a value even though it can still print something out.We could go a step further and implement our own square root function by using a well-known technique called “Newton’s Method.” Newton’s Method for approximating square roots performs an iterative computation that converges on the correct value. The equation \(newguess = \frac {1}{2} * (oldguess + \frac {n}{oldguess})\) takes a value \(n\) and repeatedly guesses the square root by making each \(newguess\) the \(oldguess\) in the subsequent iteration. The initial guess used here is \(\frac {n}{2}\text{.}\) Listing 1.11.3 shows a function definition that accepts a value \(n\) and returns the square root of \(n\) after making 20 guesses. Again, the details of Newton’s Method are hidden inside the function definition and the user does not have to know anything about the implementation to use the function for its intended purpose. Listing 1.11.3 also shows the use of the // characters as a comment marker. Any characters that follow the // on a line are ignored.
Subsection 1.11.1 Parameter Passing: by Value versus by Reference
In all of the functions we have written thus far, we have used a function calling mechanism called pass-by-value. Calling a function by value involves copying the contents of the arguments into the memory locations of the corresponding formal parameters. If the function changes the values of the parameters, the original contents in the memory referenced by the arguments of the calling function do not change.
Consider the following two function definitions:
void functionExample( int inputVar ) { /*return type void which indicates that nothing is being returned*/ int nextVar = inputVar * 2; inputVar = 4; cout << "nextVar = " << nextVar << " inputVar = " << inputVar; } void callingFunction() { /*return type int which indicates that nothing is being returned*/ int myVar = 10; functionExample( myVar ); cout << "myVar = " << myVar; }
When the function
callingFunction()
executes, it calls functionExample(...)
with the variable myVar having the value 10. Within functionExample(...)
, the value of 10 is copied from myVar to the formal parameter inputVar, so the value of nextVar is 10x2, or 20. The next statement changes the contents of inputVar to 4, so the cout
statement within this function produces the output:nextVar = 20 inputVar = 4
Notice what happens when
functionExample(...)
ends and execution returns to callingFunction()
. The contents of myVar is still the same, as the location for myVar differs from where inputVar is stored. Thus, myVar still has the value 10, and the cout
statement after the function call will produce the output:myVar = 10
In other words, any changes to the variables are local to the function, which is exactly what we want.
However, there is a problem.
We have seen examples of C++ functions that return no value or a single value. How about when we want the function to return more than one value? We need another function calling mechanism called pass-by-reference. When using this mechanism, the actual location in memory referenced by the arguments are sent rather than the values in that location. To let the compiler know that you intend to use pass by reference, you attach an “&” to the end of the type name in the formal parameter list in the function declaration and header. When you do this, any changes to the values of the parameters will change the value of the arguments as well.
An example of a function where this is useful is a function that takes two values as input and swaps their order. Consider the following program fragment of a function called
swap_values(...)
that swaps its two inputs and the main()
function that calls swap_values(...)
.For the program in Listing 1.11.4 to reverse the order of the integers the users types in, the function
swap_values(...)
must be able to change the values of the arguments. Try removing one or both of the &
’s in this code to see what happens.Subsection 1.11.2 Arrays as Parameters in Functions
Functions can be used with array parameters to maintain a structured design. However, a formal parameter for an array is neither a call-by-value nor a call-by-reference, but a new type of parameter pass called an array parameter. In a function definition, an array parameter looks like a pass-by-value parameter because there is no ampersand symbol (&), but the variable name is instead followed by a set of square brackets ([ and ]).
The following example function returns the average hours worked over the array of integers (note that we need to also pass in the number of elements in that array because the array parameter list[] does not include that information):
double average( int list[], int length ) { // It is correct syntax to omit the array length on the array itself. double total = 0; //return type double which indicates that a decimal is being returned int count; for( count = 0; count < length; count++ ) { total += double(list[count]); }; return (total / length); }
Array parameters look like pass by value, but they are effectively similar to pass by reference parameters. When they execute, the functions with these parameters do not make private copies of the arrays. Instead, the reference is passed to reduce the impact on memory. Arrays can therefore always be permanently changed when passed as arguments to functions.
After a call to the following function, each element in the third array argument is equal to the sum of the corresponding two elements in the first and second arguments:
void add_lists( int first[], int second[], int total[], int length ) { //return type void which indicates that nothing is returned int count; for( count = 0; count < length; count++ ) { total[count] = first[count] + second[count]; };}
Upon further examination, we can see that the first two arrays do not change values. To prevent ourselves from accidentally modifying any of these arrays, we can add the modifier
const
in the function head:void add_lists( const int first[], const int second[], int total[], int length ) { //return type void which indicates that nothing is returned int count; for( count = 0; count < length; count++ ) { total[count] = first[count] + second[count]; };}
These changes would ensure that the compiler will then not accept any statements within the function’s definition that potentially modify the elements of the arrays first or second.
Subsection 1.11.3 Function Overloading
Function overloading is the ability to create multiple functions with identical names but different implementations. Not all languages support function overloading. Python does not, for example, but an optional parameter can often be used to accomplish the same task.
In C++ programming, two or more functions can have the same name when they can be distinguished by the parameters. Hence, C++ allows function overloading when either the data types of the parameters differ or the number of parameters differ.
Overloading is a nice feature of the C++ language. See Exploration 1.11.1 for an example of overloading in C++ and Python
Reading Questions Reading Questions
Take a look at the code below:
#include <iostream> using namespace std; void dogWalk(int steps){ for (int step = 0; step < steps; step++){ cout << "dog walked "<< step << " steps!"<< endl; } } int main() { dogWalk(11); return 0; }
1.
- void
- Correct, nothing is returned!
- int
- Not quite, check the value preceding the name of the function!
- dog
- Not quite, dog is not even a data type!
- dogWalk
- Not quite, that is the name of the function itself!
What is the return type of the first function defined in this code?
2.
- Helps keep consintency in the way your functions are named across your program.
- Take a look at the other answers as well...
- Functions that do similar tasks differ based on parameters rather than by name.
- Take a look at the other answers as well...
- A function in essence can fulfill multiple tasks depending on the parameters.
- Take a look at the other answers as well...
- Removes the limit on how many parameters can be written or passed.
- Wrong! function overloading has nothing to do with removing the limit of parameters.
What are benefits of function overloading?
Note 1.11.5. Self Check.
Here’s a self check that really covers everything so far. You may have heard of the infinite monkey theorem? The theorem states that a monkey hitting keys at random on a typewriter keyboard for an infinite amount of time will almost surely type a given text, such as the complete works of William Shakespeare. Well, suppose we replace a monkey with a C++ function. How long do you think it would take for a C++ function to generate just one sentence of Shakespeare? The sentence we’ll shoot for is: “methinks it is like a weasel”
You’re not going to want to run this one in the browser, so fire up your favorite C++ IDE. The way we’ll simulate this is to write a function that generates a string that is 28 characters long by choosing random letters from the 26 letters in the alphabet plus the space. We’ll write another function that will score each generated string by comparing the randomly generated string to the goal. Hint: You will need to import the <random> library for this.
A third function will repeatedly call generate and score, then if 100% of the letters are correct we are done. If the letters are not correct then we will generate a whole new string. To make it easier to follow your program’s progress this third function should print out the best string generated so far and its score every 1000 tries.
Note 1.11.6. Self Check Challenge.
See if you can improve upon the program in the self check by keeping letters that are correct and only modifying one character in the best string so far. This is a type of algorithm in the class of “hill climbing” algorithms, that is we only keep the result if it is better than the previous one.
You have attempted of activities on this page.