A short introduction to computer programming, using C++

Roger Mitton, Birkbeck, University of London

Chapter 4

Functions

Suppose you were writing a program that needed to use a square root. (For readers whose maths is rusty, the square of a number is what you get when you multiply the number by itself. So, for example, the square of 4 is 16. A square root is the opposite. The square root of some number x is that number which, when multiplied by itself, gives you x. So the square root of 16 is 4.)

If the square root is an integer, as in my example, the arithmetic is not too hard. But if the square root is a number with a decimal point in it (a "floating-point number"), it's not so easy. What is the square root of 15? Three point something, but what exactly?

The data types we have looked at so far include integers, strings and booleans (in C++ int, string and bool). For doing calculations involving decimal points, we use a data type called a double. There is also a data type called a float (for "floating-point number"), which we could also use. A double occupies twice as much memory as a float, hence its name. In the days when a computer's memory was very limited, you would use a double only if you needed the extra precision, but, now that computer memories are so much larger than they used to be, the double is more often used. In C++ it is the default for representing floating-point numbers. That is to say, if you had an assignment such as d = -0.23; the -0.23 would, technically, be a double, not a float.

Fortunately, we do not have to invent our own algorithm for calculating square roots since C++ provides a square-root function. If we #include <cmath> then we can use a function called sqrt. For example, we could say double d = sqrt(15.0); or cout << sqrt(15.0); (It's 15.0, a double, rather than 15, an int, since the sqrt function expects to be given a double.)

The sqrt function is one of the functions in the cmath library. That is to say, it is a self-contained unit of program code, placed in one of the standard libraries, which is there for us to use if we want to. It takes number (as a double) and returns a double representing the square root of that number. In the example, we give it (or pass it) the number 15.0, and it gives us back (or returns) 3.87298.

Note that we do not know how it does what it does, and we do not need to know. To us, it is a "black box". To use it, we just need to know its name (sqrt), what it takes (a double) and what it returns (also a double). (We also need to know that it is in the cmath library and to remember to #include <cmath>. This is one of the nuisances of the library system.)

If we had two variables of type double, d1 and d2, we could say d1 = sqrt(d2); In this case the value of d2 is passed to the sqrt function. Or we could say d1 = sqrt(d2/3.7); or d1 = sqrt(d2/3.7 * 5.4 + 8.913);. The expression in the brackets (which is known as the argument) is evaluated, and its value is passed to the sqrt function.

Using a function is known as calling a function; sqrt(d2) is an example of a function call.

Writing your own functions

With square-roots, we were in luck; the C++ language designers had decided to include a square-root function in the standard library. But what if we need a function that is not provided in the library? Then we write our own.

Suppose we want a square function; square(x) will return x * x. You may feel that we hardly need a function for this since writing, say, double d2 = square(d1); is no easier than writing double d2 = d1 * d1; But suppose it's

double d3 = square(d1/3.706 * (d2/9.854 + 4.6));
That is, actually, easier, clearer and less error-prone than writing
double d3 = (d1/3.706 * (d2/9.854 + 4.6)) * (d1/3.706 * (d2/9.854 + 4.6));
So, as well as being a simple example, the square function might actually be useful.

The first thing we have to decide, in writing the square function, is what type of function it is, i.e. what type of thing it is going to return. It's clear from the examples above that the square function is going to return a double, so that is the first thing we write down when defining the function.

Next we have to think of a good name for it. I've already done that in calling it square.

Now we have to decide what we are going to pass to it when we call it — the things that it needs to be given (if any) to do its job. In the case of square, this is simple; it needs a double. We can now write the first line of the function definition:

double square(double dx)
This says, "We are defining a function called square that takes a double [that's the part in brackets] and also returns a double [that's the first word on the line]."

The thing in brackets, that I have called dx, is a parameter. It is declared like a variable — a type name followed by an identifier — and it behaves like a variable within the function. Its purpose is to receive the value that gets passed when the function is called. I'll come back to that in a second. First let's complete the function definition.

We have written the first line, sometimes called the function header. Now we write the body of the function; this is where we specify what the function does. Since the purpose of a function is to return a value, we need to calculate the value to be returned. We do this in an ordinary block of code and return it with the keyword return. (A block, you will recall, is some lines of program code between curly braces.) We could do it like this:

double square(double dx)
{	double	lv;
	lv = dx * dx;
	return lv;
}

lv is an example of a local variable i.e. a variable that is accessible only within the function. The parameter dx also behaves like a local variable. The difference is that, whereas the value of the local variable lv is initially undefined (unless we choose to initialize it), the parameter dx will already have been given a value by the time this code executes.

Suppose that our function is called in the following way:

int main()
{	double	d1 = 1.5;
	cout  << "The square of " << d1 << " is " << square(d1)  << "." << endl;
}
When the computer executes the call square(d1) it evaluates the argument, i.e. it works out that the current value of d1 is 1.5, and then passes this value to the function. This value is then used to initialize the parameter dx. So, by the time the body of the function begins to execute, dx already has the value 1.5. The function body then evaluates dx * dx (i.e. multiplies 1.5 by 1.5), assigns the result (2.25) to be the value of lv and then returns the value of lv. So the value of the expression square(d1) is 2.25, and the program's output is, "The square of 1.5 is 2.25."

The process by which a value is passed to the function and becomes the initial value of the parameter is known as parameter passing. This is the standard term though actually it is slightly odd since it's really the argument, or rather the value of the argument, that gets passed; the parameter is on the receiving end. When writing a function, bear in mind that, when your function code executes, the parameters will already have values. You don't have to do anything (for example with cin >>) to put values into them; they already have values thanks to the parameter passing.

Note that the argument does not have to have the same name as the parameter. Equally, it would not matter if it did. The argument and the parameter are two different entities. It is the mechanism of parameter passing that links them, not their names.

Returning to our square function, we can simplify it. The local variable is not really necessary, and we could write the function more simply as follows:

double square(double dx)
{	return dx * dx;
}

It only remains to say where the function definition goes in relation to the other parts of the program. For now, it will be simplest to put it after the using namespace std; and before the int main(). So, the whole program looks like this:

#include <iostream>
using namespace std;

double square(double dx)
{	return dx * dx;
}

int main()
{	double	d1 = 1.5;
	cout << "The square of " << d1 << " is " << square(d1) << "." << endl;
}
In this example, we are calling the function only once, but we could call it many times if we wanted to, and we could, if we wanted, give it a different argument each time. For example:
#include <iostream>
using namespace std;

double square(double dx)
{	return dx * dx;
}

int main()
{	double	d1 = 1.5, d2 = 10.1;
	cout << "The square of " << d1 << " is " << square(d1) << "." << endl;
	cout << "The square of " << d2 << " is " << square(d2) << "." << endl;
	cout << "The square of " << d1 + d2 << " is " << square(d1 + d2) << "." << endl;
	cout << "The square of " << square(d1) << " is " << square(square(d1)) << "." << endl;
}

Exercise 4A

Write a function, called triplus (just the function definition, not the call or any of the rest of the program) that takes an int as its parameter and returns that number times three plus one. If the call was triplus(3) it would return 10; for triplus(8) it would return 25.

To check your answer, click on Answers to the exercises.

More parameters, and of different types

We can also have strings, both as parameters and as return types. The following is a function that takes a string as its parameter and returns the last character. For example, if place was a string with the value "Hornby", last(place) would return the single-character string "y".

string last(string s)
{	return s.substr(s.length()-1);   // or return s.substr(s.length()-1, 1);
}

Sometimes the function needs more than one thing to do its job. If, for example, we wanted a function that returned the longer of two strings (or either if they are the same length), it would have to have two parameters, and we would write them in a comma-separated list, each one with its data type:

string longer(string a, string b)
{	if (a.length() > b.length())
		return a;
	else	return b;
}
The arguments, similarly, are presented in a comma-separated list:
string	one = "Quernmore", two = "Abbeystead";
string	three = longer(one, two);
The parameters do not all have to be of the same data type. For example, if we wanted a function that returned the first n characters of a string, we would have to pass both the string and the value of n:
string firstpart(string s, int n)
{	return s.substr(0, n);
}
If our place variable held the string "Ulverston", then firstpart(place, 5) would return the value "Ulver". Note that the order of the arguments has to match the order of the parameters (in this case, a string first followed by an int). Note also that you have to state the data type for each parameter in a parameter list, even if they are all the same. For example, a function that took three integers as its parameters and returned an integer would have a header something like this:
int func(int a, int b, int c)
In all the examples so far, the return type has been the same as the type of the parameters, or of one of the parameters, but the return type can be different. The following function, very like longer above, returns the length of the longer string:
int longer_length(string a, string b)
{	if (a.length() > b.length())
		return a.length();
	else	return b.length();
}

Exercise 4B

Change our last function into a lastpart function that returns the last n characters of a string. For example if place held the value "Cartmel", lastpart(place, 3) would return "mel". See if you can modify it further so that, if the value of n is longer than the string (such as lastpart(place, 10) for "Cartmel") it returns the full string.

To check your answer, click on Answers to the exercises.

Procedures (void functions)

All the functions we have seen so far return a value. That is what they are for. But it is possible, in C++, to write a function that returns no value. You call such a function, not for what it returns (it returns nothing), but for what it does. Some other languages draw a distinction between functions (that return values) and procedures (that don't). In C++, both are called functions; it's just that some functions return no value. But I find it helpful to keep the term "procedure"; by "procedure" I simply mean "void function".

You begin the definition of one of these with the word void as the return type. For example, we could write a procedure (void function) to take a string and to write it out with a few stars to left and right:

void stars(string s)
{	cout << "*** " << s << " ***" << endl;
}
We might call it as follows:
stars("Urgent");
and it would output the following line:
*** Urgent ***

A (non-void) function call is an expression, something that gets evaluated and returns a value, such as d2 = square(d1); or if (sqrt(d) > 20.0) or cout << lastpart(place, 3); By contrast, a call to a void function is a statement, such as stars("Urgent"); This is how it might look in a program:

#include <iostream>
#include <string>
using namespace std;

void stars(string s)
{	cout << "*** " << s << " ***" << endl;
}

int main()
{	string	message = "This is important.";
	stars(message);
}
Note the difference between that and this alternative way of producing the same result:
#include <iostream>
#include <string>
using namespace std;

string starred(string s)
{	return "*** " + s + " ***";
}

int main()
{	string	message = "This is important.";
	cout << starred(message) << endl;
}
The same work is being done but it is divided between the parts of the program in a different way. In the first, main simply hands over a message to stars, and stars both adds the stars and outputs it. In the second, main asks starred to return a new version of the message, with added stars, and then main itself takes care of the outputting.

Which is better? For these tiny programs, it hardly matters. In the context of a larger program, it would depend on what else the program wanted to do with these starred messages. If it had various messages and sometimes output starred versions but sometimes did something else with a starred version, such as assigning it to be the value of another string:

string	urgent_message = starred(message);
then the second would be better. But if the starring was only ever needed in conjunction with the outputting, the first might be simpler.

In the examples we have seen so far, it has always been main that did the calling. But in fact it is possible for one function to call another. This enables us to have a third version:

#include <iostream>
#include <string>
using namespace std;

string starred(string s)
{	return "*** " + s + " ***";
}

void out_starred(string s)
{	cout << starred(s) << endl;
}

int main()
{	string	message = "This is important.";
	out_starred(message);
}
Given what we have covered so far, it would be necessary to lay out the program in that order — the starred function first, then the out_starred procedure, and then main.

Although procedures and functions generally have parameters, they don't have to. In other words, the parameter list can be empty. This procedure just outputs the string "Ouch!" whenever it is called:

void ouch()
{	cout << "Ouch!";
}
Note that you still need the brackets for the parameter list, even if there is nothing inside them. You would call it thus:
ouch();
Note, also, that you still need the brackets for the argument list, even though there are no arguments.

The most obvious difference between a procedure definition and a (non-void) function definition is that the former begins with the word void. But there is another. A (non-void) function always contains a return whereas a procedure does not have to. If there is no return in a procedure, it will simply execute and then the flow of program control will return to the line after the call. For example:

#include <iostream>
using namespace std;

// Assume that the first and last functions are defined here

void test_and_display(string s)
{	cout << "The string is " << s << endl;
	cout << "The length is " << s.length() << endl;
	cout << "The first character is " << first(s) << endl;
	cout << "The last character is " << last(s) << endl;
//	Control is returned from here.
}

int main()
{	string	userstring;
	cout  << "Please key in a string: ";
	cin >> userstring;
	test_and_display(userstring);
//	Control is returned to here.
	cout << "Thank you and goodbye" << endl;
}
But you can have an explicit return if you want. Of course, this is not like return dx * dx; or return a.length(); because a void function does not return anything. It is simply return; You might use this if, in some circumstances, you did not want the procedure to execute in full. For example, the test_and_display procedure would crash or produce peculiar output if it was given an empty string (a string of length zero, i.e. with no characters at all). We could prevent this by inserting some lines:
void test_and_display(string s)
{	if (s.length() == 0)
	{	cout << "Empty string." << endl;
		cout << "Zero length, no characters." << endl;
		return;		// Either control is returned from here
	}
	cout << "The string is " << s << endl;
		....
//	Or control is returned from here.
}
Or, if you preferred, you could achieve the same effect with an else:
void test_and_display(string s)
{	if (s.length() == 0)
	{	cout << "Empty string." << endl;
		cout << "Zero length, no characters." << endl;
	}
	else
	{	cout << "The string is " << s << endl;
		....
	}
//	Control is returned from here.
}

Exercise 4C

Write a procedure (void function) that takes two integers and displays the sum, the difference (the larger minus the smaller), the product, the quotient (the larger divided by the smaller) and the remainder. For example, if given 13 and 5 (or 5 and 13), it would output the following:

The sum is 18.
The difference is 8.
The product is 65.
The quotient is 2, remainder 3.
Be careful not to divide by zero.

To check your answers, click on Answers to the exercises.

The main function

In most respects, main is an ordinary function. As you can see from its header — int main() — it has an empty parameter list and returns an int. (Actually, it can have parameters, but that takes us beyond this introductory lesson.) But, if it's a function, where is the function call? Where does main get called from? It gets called from the operating system, the program (Windows, Unix or whatever) that is running all the time a computer is switched on and which gives it its basic repertoire of behaviour — maintaining the screen display, managing the file storage system and so on. When main calls, for example, test_and_display, it hands over control to the function and waits till it returns. In much the same way, when the operating system calls main, it hands over control to your program and waits till main returns, i.e. until your program terminates.

Since main is an int function, it returns an int at the end, to the operating system. In some operating systems, Unix for example, it is possible to chain a series of programs together, each one passing on the results of its work to the next one. When one has finished, the operating system calls the next. In such a system, it is very useful for the individual programs to be able to signal to the operating system whether they were able to do their part successfully or whether something went wrong, in which case the whole chain of processing should be aborted. The integers returned from main can be used for that. By convention, a return value of zero from main means "Everything OK" and any other value means "Something went wrong." Since things can go wrong in different ways, it is possible to arrange for different return values to have different meanings.

It will not have escaped your notice that not one of the main functions that we have had so far in these notes appears to return anything. Should we not be returning an int since it's an int function? We could, if we wanted, put a return 0; as the last line of main (if we were programming in C, rather than C++, it would be obligatory), but we don't have to. If we leave it out, the compiler puts it in for us. This is another respect in which main differs from other functions. With all other (non-void) functions, an explicit return is essential; with main, you can leave the compiler to put it in for you.

Returning from main allows us to terminate the program in a simple way. If something goes wrong such that there is no point in continuing the program (if, for example, we tried to read in a file of data but could not find it), we can terminate simply with, say, return 1; (1 for "something's wrong" rather than 0 for "all's well"). Note, however, that it is only when we are in main that we can terminate the program with a return. If we are in any other function, a return will simply terminate that particular function.

Exercise 4D

Write a function that takes a string as its parameter. If the string has fewer than three characters, it returns an empty string. Otherwise it returns the middle section of the string, i.e. the string minus its first and last characters. For example, if it was passed the string "Kendal", it would return "enda".

Exercise 4E

Write a procedure (void function) that takes a string as its parameter. If the string is empty, it does nothing. If the string has only one character, it outputs that character followed by a newline character. Otherwise it outputs the characters separated by spaces, with a newline on the end. For example, if it was passed the string "Carnforth", it would output:

C a r n f o r t h

Exercise 4F

Copying the firstpart function (above) into your program in the right place, write a program that takes a string from the user and then, using a loop and using firstpart, writes out a triangle of letters based on that string. For example, if the user keyed in the name "Morecambe", the computer would display the following:

M
Mo
Mor
More
Morec
Moreca
Morecam
Morecamb
Morecambe

To check your answers, click on Answers to the exercises.