Union organizers at the Federation of Aligned C++ Programmers (FOACP), have demanded that more of their statements work together in solidarity. The C++ language designers agreed to their negotiating demands and added a feature to the language.
When statements in C++ program work together to accomplish a particular task that none of them could accomplish on their own, it makes sense to group those statements together in a unit and give them a name. We call the unit a function and the name, if it's a good one, usually describes the action or task that those statements are performing.
Precisely, a function is a combination of
- A set of statements
- A name for those statements.
Names of functions must follow the same rules as the names for variables.
Just like you have to declare a variable before you use it, programmers have to declare a function before they can use it. A function declaration is a way to communicate to other programmers how to use a function.
Some functions calculate a result, some functions perform side effects (see below) and some do a combination of both. We use a function by calling it (sometimes developers sometimes use the term invoke as a synonym for call). A function call (or invocation) is just like any ordinary expression. The value of the function call expression is the function's result.
One of the really cool things about functions is that we can use them and take advantage of their calculations without having to know how they do the work! For instance, although we have no idea how to perform number-theoretic calculations, if another programmer defined a function that performed factorization of a very large number into two primes, we could still use their work! (PS: If a programmer did do that, they would be worth a ton of money!).
Working with something (like a function) without worrying about its details is called abstraction. Abstraction is the process of removing the detail to simplify and focus attention on the essence (J. Kramer, ``Is abstraction the key to computing?,'' Communications of the ACM, vol. 50, no. 4, pp. 37–42, Apr. 2007, doi: 10.1145/1232743.1232745.). There's another way to say the same thing that is a little pithier: Abstraction is the process of remembering what is important in a given context and forgetting what's not.
To reiterate, abstraction is just a fancy term for hiding details. The person who writes a function that we use knows the details of how the function does its work but we, the users, do not. We (again, the users) only care about what the function does and not how it does it.
If I take medicine to control my headaches but it makes me dizzy, I would say that medicine has a side effect. Side effects from medicine are physical effects on your body that are unrelated to the medicine's purpose. But medicines aren't the only things that can have side effects -- functions can have side effects, too.
To understand side effects, we have to recognize that programs have state when they are executing. A program's state at a certain point of execution consists of the value of all the variables in memory at the time. In other words, a program's state is a combination of
- a point of execution in a program, and
- the contents of the program's variables at that point.
Any change to the state of the program that may affect the user of the function is known as a side effect. When we learn about global variables and defining functions, we will see an actual example of a function that has a side effect -- it will be perfectly clear then.
In the meantime, know that side effects are bad!
In "Math World" we could write a function that squares a number. We would write that like
Like
We could declare the function
int f(int x);
But, that really leaves something to be desired: What does f
do? Because we aren't charged by the character, programmers usually give their functions name that describe their utility. In this case, the function square
:
int square(int x);
The function's name is square
. Inside the (
and )
are the function's parameters. In this case, there is one parameter named x
and its type is int
. The square
function returns a value whose type is int
(we can tell because of the int
to the left of the function name).
As we have said before, it is really important that as we become better C++ programmers and grow as computer scientists, we learn how to communicate with our peers. How do we talk about function declarations? Well, when you are talking to other programmers, you would speak the function declaration above as "The square
function takes a single parameter (whose type is an int
) and returns an int
."
Here's another example:
double pow(double base, double exp);
The function's name is pow
. This function has two parameters and they are both double
s. The pow
function returns a value whose type is double
(or "The pow
function returns a double
.").
Again, in order to use a function, we have to call it (or invoke it). We can call a function that returns a value anywhere that we could write an expression. Wait, that's cool: calling a function that returns a value is an expression just like any other expression! For example, we could assign the result of a call to square
to a variable:
#include <iostream>
int square(int x);
int main() {
int two{2};
int square_of_two{square(two)};
return 0;
}
In this call to square
, we are passing an argument of two
. Arguments are part of the invocation of a function and pair up with parameters. As we said above, the parameters are the variables that the person implementing the function gets to use. They have to get their value from somewhere, don't they?! In this case we are pairing the argument two
with the parameter x
. Notice that the type of the argument and the type of the parameters have to match. This is very important! After this line of code executes, the variable square_of_two
will hold the value 4
.
Or,
#include <iostream>
double pow(double base, double exp);
int main() {
double three{3};
double three_cubed{pow(three, three)};
return 0;
}
In the invocation of pow
in this snippet we are passing arguments of three
and three
. The first three
is paired with the base
parameter and the second three
is paired with the exp
parameter. After the line of code with the function invocation executes, the variable three_cubed
will hold the value 27. But wait, 3 and 3 are int
s and base
and exp
are double
s. Didn't I just say that the types of the arguments must match the type of the parameters?! Yes, I did. In this case, C++ coerces the int
s to the higher rank of double
(just the way that it would when it coerces 2
to 2.0
in 3.0/2
-- what we learned last class!).
But, we can do more with function calls than just assign their results to values. We can use them as part of an expression that is assigned to another variable:
#include <iostream>
double pow(double base, double exp);
int main() {
double three{3};
double v{8.0 + pow(three, three)};
return 0;
}
Upon initialization of v
its value is 35
. We can also use function calls in std::cout
statements:
#include <iostream>
double pow(double base, double exp);
int main() {
double three{3};
std::cout << "Three cubed: " << pow (three, three) << "\n";
return 0;
}
which prints
Three cubed: 27
Solidarity.
There's something curious in the language we have used so far when talking about functions in this edition of the C++ Times. Did you notice it? We have referred to function declarations and not definitions. Like variables, we know that functions have to be declared before they are used. We normally say, though, declaration/definition for variables. Why the difference?
There is an important, but subtle, difference in C++ between the declaration and definition of a variable/function. The point of doing either is so that we can use that function or variable in our code. The declaration gives only enough information for the programmer to write that name in their source code and for the compiler to generate code that uses that name. A definition, on the other hand, is what the compiler needs in order to make such an entity exist. A variable needs some space in memory to exist and a function needs some code to execute -- just what the definition of a variable and function provide, respectively.
While it is possible to declare a variable separately from its definition, it is not common. That's why we typically say declaration/definition when we refer to declaring/defining (sorry!) a variable.
It is very common to declare a function separately from its definition. And that is just what we have been doing in this edition of the Times. The ability to keep the declaration and definition separate improves our code by keeping up the abstraction -- the programmer does not know (nor can they know) how the function does what it does if they only have access to the declaration! No Lyin' Eyes here.