Computation
3.5 Functions
for the 50 even values of i. If we saw such code, we would assume it to be an error, probably caused by a sloppy conversion from a while-statement. If you want to increment by 2, say so:
Click here to view code image
// calculate and print a table of squares of even numbers in the [0:100) range:
for (int i = 0; i<100; i+=2)
cout << i << '\t' << square(i) << '\n';
AA
Please note that the cleaner, more explicit version is shorter than the messy one. That’s typical.
Try This
Rewrite the character value example from the previous TRY THIS to use a
for-statement. Then modify your program to also write out a table of the integer values for uppercase letters and digits.
There is also a simpler “range-for-loop” for traversing collections of data, such as vectors; see §3.6.
The first line of this definition tells us that this is a function (that’s what the parentheses mean), that it is called square, that it takes an int argument (here, called x), and that it returns an int (the type of the result comes first in a function declaration); that is, we can use it like this:
Click here to view code image
cout << square(2) << ' ' < square(10) << '\n'; // print 4 100
We don’t have to use the result of a function call, but we do have to give a function exactly the arguments it requires. Consider:
Click here to view code image
square(2); // probably a mistake: unused return value int v1 = square(); // error: argument missing
int v2 = square; // error: parentheses missing int v3 = square(1,2); // error: too many arguments
int v4 = square("two"); // error: wrong type of argument; int expected
CC
Many compilers warn against unused results, and all give errors as indicated.
You might think that a computer should be smart enough to figure out that by the string "two" you really meant the integer 2. However, a C++ compiler deliberately isn’t that smart. It is the compiler’s job to do exactly what you tell it to do after verifying that your code is well formed according to the definition of C++. If the compiler guessed about what you meant, it would occasionally guess wrong, and you – or the users of your program – would be quite annoyed. You’ll find it hard enough to predict what your code will do without having the compiler “help” by second-guessing you.
The function body is the block (§3.4.2.2) that actually does the work.
Click here to view code image
{
return x*x; // return the square of x }
For square, the work is trivial: we produce the square of the argument and return that as our result. Saying that in C++ is easier than saying it in English.
That’s typical for simple ideas. After all, a programming language is designed to state such simple ideas simply and precisely.
The syntax of a function definition can be described like this:
function-definition:
type-identifier function-identifier ( parameter-list ) function-body
That is, a type (the return type), followed by an identifier (the name of the function), followed by a list of parameters in parentheses, followed by the body of the function (the statements to be executed). The list of arguments required by the function is called a parameter list and its elements are called parameters (or formal arguments). The list of parameters can be empty, and if we don’t want to return a result we give void (meaning “nothing”) as the return type. For example:
Click here to view code image
void write_sorry()
// take no argument; return no value {
cout << "Sorry\n";
}
The language-technical aspects of functions will be examined more closely in Chapter 7.
3.5.1 Why bother with functions?
CC
We define a function when we want a separate computation with a name because doing so
Makes the computation logically separate
Makes the program text clearer (by naming the computation)
Makes it possible to use the function in more than one place in our program
Eases testing
We’ll see many examples of each of those reasons as we go along, and we’ll occasionally mention a reason. Note that real-world programs use thousands
of functions, some even hundreds of thousands of functions. Obviously, we would never be able to write or understand such programs if their parts (e.g., computations) were not clearly separated and named. Also, you’ll soon find that many functions are repeatedly useful and you’d soon tire of repeating equivalent code. For example, you might be happy writing x*x and 7*7 and
(x+7)*(x+7), etc. rather than square(x) and square(7) and square(x+7), etc.
However, that’s only because square is a very simple computation. Consider square root (called sqrt in C++): you prefer to write sqrt(x) and sqrt(7) and
sqrt(x+7), etc. rather than repeating the (somewhat complicated and many lines long) code for computing square root. Even better: you don’t have to even look at the computation of square root because knowing that sqrt(x) gives the square root of x is sufficient.
In §7.4, we address many function technicalities, but for now, we’ll just give another example.
If we had wanted to make the loop in main() in the “first program”
(§3.4.2.3) really simple, we could have written Click here to view code image
void print_square(int v) {
cout << v << '\t' << v*v << '\n';
}
int main() {
for (int i = 0; i<100; ++i) print_square(i);
}
Why didn’t we use the version using print_square()? That version is not significantly simpler than the version using square(), and note that
print_square() is a rather specialized function that we could not expect to be able to use later, whereas square() is an obvious candidate for other uses
square() hardly requires documentation, whereas print_square() obviously needs explanation
The underlying reason for both is that print_square() performs two logically
separate actions:
It prints.
It calculates a square.
AA
Programs are usually easier to write and to understand if each function performs a single logical action. Basically, the square() version is the better design.
Finally, why did we use square(i) rather than simply i*i in the first version of the problem? Well, one of the purposes of functions is to simplify code by separating out complicated calculations as named functions, and for the 1949 version of the program there was no hardware that directly implemented
“multiply.” Consequently, in the 1949 version of the program, i*i was actually a fairly complicated calculation, similar to what you’d do by hand using a piece of paper. Also, the writer of that original version, David Wheeler, was the inventor of the function (then called a subroutine) in modern computing, so it seemed appropriate to use it here.
Try This
Implement square() without using the multiplication operator; that is, do the
x*x by repeated addition (start a variable result at 0 and add x to it x times).
Then run some version of “the first program” using that square().
3.5.2 Function declarations
Did you notice that all the information needed to call a function was in the first line of its definition? For example:
int square(int x)
Given that, we know enough to say
int x = square(44);
We don’t really need to look at the function body. In real programs, we most
often don’t want to look at a function body. Why would we want to look at the body of the standard-library sqrt() function? We know it calculates the square root of its argument. Why would we want to see the body of our
square() function? Of course we might just be curious. But almost all of the time, we are just interested in knowing how to call a function – seeing the definition would just be distracting. Fortunately, C++ provides a way of supplying that information separate from the complete function definition. It is called a function declaration:
Click here to view code image
int square(int); // declaration of square double sqrt(double); // declaration of sqrt
Note the terminating semicolons. A semicolon is used in a function
declaration instead of the body used in the corresponding function definition:
Click here to view code image
int square(int x) // definition of square {
return x*x;
}
So, if you just want to use a function, you simply write – or more commonly
import or #include – its declaration. The function definition can be elsewhere.
We’ll discuss where that “elsewhere” might be in §7.3–§7.7.1. This
distinction between declarations and definitions becomes essential in larger programs where we use declarations to keep most of the code out of sight to allow us to concentrate on a single part of a program at a time (§3.2).