Building your own C++ Functions

See also

Text (Chapters 6)

Functions are an essential part of programming. They provide us with a way to break up a big program into smaller, more manageable chunks, and they provide a way for many programmers to work on a big project. We need to understand how they are written and used before our programs can get more interesting!

When we introduced the concept of a function earlier in the course (which we did so you would have some understanding of what was going on in many of our simple graphics exercises), we described a function as a box with some code in it. This is a fairly good model to use for understanding what a function really is. To understand how to write our own functions, let’s look closer at this box!

The box has two important parts - the visible (public) part that can be seen by other parts of your program, and the invisible (private) part that is hidden from view. The user of the function has no need to really know what is going on inside the box, they simple need to understand what the function does, and how to call it. But, clearly, the person who actually writes the code inside the box needs to know in gory detail what the function is up to! The function programmer also needs to know how the function will be called, and how to do the job the function is intended to do!

We need to create both parts of a function for it to work properly, but keeping these two parts separate in our minds is important to making functions work as a vital tool of programming!

The first thing we need to do is to describe the public part of the function:

Defining a Function

We create a function by coming up with a name, which should describe what the function is going to do for us. Next, we identify any information the function will need to accomplish its task; we call this information parameters. Next, we describe what kind of answer the function will produce (if any); this is called the return value. The code that defines the action of the function is not part of this definition, it belongs to the private part of the function.

Here is an example of how it all looks in C++:

float my_sin(float angle) {
    // everything inside the braces is private!
}

I named this function my_sin so it does not interfere with the real C++ sin function we have been using (from the cmath library).

The first float keyword defines the kind of value the function will return, a floating point number in this case. The name of the function is my_sin. The function needs one piece of data to work with, that parameter will be named angle inside the function, and it is a floating point number.

Function prototypes

Since we have completely defined the public part of the function, we can (and should) leave off the function body part when we tell users of the function what they need to know. What we do in this case is a bit odd. We place a semicolon after the close parenthesis that marks the end of the parameter part. Like this:

float my_sin(float angle);

This is called a function prototype and it serves a very important role in programming.

The caller - producer contract

When two programmers sit down and decide how to carve off a chunk of program that will become a function, they need to agree on what the public part of the function will look like. (By the way, you might be both programmers!) That way the programmer who will use the function will know how to call it, and the programmer who will make it work will know what information they will be provided with when the function is called. This is a kind of contract between the two workers.

The prototype also serves another important role in programming.

Defining things before they can be used

The C++ compiler has a simple rule that programmers must follow. Before you can use any name in a program, you must have defined that name before the point where you use it. We have seen this in how we define variables at the top of our code, then use those variables in later code.

The same rule applies in using functions. What we need to do is to tell the compiler everything it needs to know about a function before we can call that function. We do this by placing the function prototype at the top of our program (before that main line). In fact, we place it right up there where we have been putting those include lines.

Including files

You have been doing this all along, without really knowing why. Time to fix that! Those include lines are actually causing the compiler to read another file on your system containing a bunch of function prototypes for various function we can use in our programs. Collections of these functions are called libraries, and these help make you more productive - you do not need to recreate code others have written for you! You can create your own libraries if you wish. I set up a simple library of supporting graphics functions and you access those by including the Graphics.h file in your program. (The actual code for those functions is located in the Graphics.cpp file!) We will use this technique later when we learn how to build bugger programs involving more that one file.

Note

In an include line, any file name surrounded by angle brackets is located with the compiler. These are called system libraries and they are part of any C++ installation. File names surrounded by double quotes are local names you need to place on your system (in with your project code, for example).

Into the void

We need to make one last observation about functions before we look at how to write the actual function code.

Not all functions need parameters to be useful, and not all functions need to return a value! In both cases, we use the word void to mean nothing is here.

For instance, suppose I want to cut by big program into three parts:

  • Input data
  • Process data
  • Output results

I might do so using these three functions:

void get_data(void);
void process_data(void);
void output_results(void);

What should jump out at you right away in looking at this is how does data get from inside one of these functions into the others?

The answer is something called global variables. That is, variables defined outside any function at the very top of your program. We really do not want to use such variables much, but in some cases, they are useful. We will not use them often in this class, except in a few special cases.

Function stubs

I tend to create functions like those shown above when I am first creating a program. As soon as I thinks up a case where some part of the program can be placed in a function, I create the stub of that function using something that looks like the prototypes shown above. Then I can write code that uses these functions well before I figure out what goes inside the functions. We will see an example of this in a bit.

Function implementations

When it is time to actually write the code of the function (perhaps in the same file with the rest of your program), we repeat the function prototype and replace the semicolon with a curly bracket pair, inside of which we place the code that does the function’s work!

The body of the function is the code between the curly braces. If the function returns a value, at some point in that code, we need to include a return statement that causes the function to quit work and hand back the value specified in the statement. The thing after the word return is an expression, usually just the name of the variable you used to calculate the answer, that you want to send back to whoever called this function.

Just for fun, let’s look at the code that calculates the sin of an angle, using a technique you might find doing some research on the Internet.

Here is how the sin function gets its answer:

  • sin(x) = x - x^3 /3! + x^5 /5! - x^7 /7! + …

Note

That funny upward pointing character is called a caret, but in this case it means “raised to the power of whatever number comes next”. So, “X^3” means “X*X*X” or “X raised to the third power” or “X cubed”.

Mathematicians call this a Taylor series for sin, but that is not important to us here. The formula is, though, since we will use that formula to do our calculations. If you look closely at this formula, you can see a pattern than tells you what the next term will be. You can keep going with more terms pretty much forever. The more terms you use, the better the answer will be. Unfortunately, C++ has no special power operator (which means raise the number x to this power - multiply it times itself this many times), so we need to do repeated multiplication. And, in case you don’t know what a factorial is, here is the formula for that:

  • 5! = 5*4*3*2*1

Note

I know this math stuff is causing several of you some problems. Please do not let that keep you from trying this out. You will find that computers can help you with math, just as calculators do! You will get past this problem soon enough!

Local Variables

We know we need to create containers to hold the data we will manipulate in our programs. The function may need containers to do its work, as well. We let the function declare variables as needed inside the function. These local variables are created when the function starts work, and are destroyed when the function stops work. If you activate the function again, there is no memory of what was in these containers previously.

Strictly speaking, the parameters are also local variables, but they have a special property. They are assigned values by the caller of the function.

Coding the function body

Once we have the actual work we need to do inside the function figured out, and have containers for the local variables we need to use, the actual coding of the function is just like any other programming problem. However, in this case, we focus on the work of just the function, not the rest of the program. In fact, if we do this right, the programmer who writes the function code does not need to know anything about the program that will use that function.

The ideal world of programming is filled up with good solid general purpose functions that do one job well, and we use that function as a tool in constructing our new program. If every programmer had to start from scratch, we would not get very far in our world of computer programming!

We will look at the code for implementing the rest of the my_sin function later. For now, let’s look at how we call the function.

Calling a Function

When we want the code inside the function to do its thing, we call the function by creating a statement that contains the name of the function. That reference will include parentheses inside of which we will provide actual parameters we want the function to use to do its work. The parameters are separated by commas if there are more than one of them.

Note

In case a function does not need parameters, you still need to supply the open and close parentheses, just put nothing between them!

Parameters can be literal values, or the names of variables defined in our calling code, or expressions the system will evaluate before calling the function. The system will pass the data we specify into the function when the function wakes up. The function has no idea where the parameter information came from, it just uses the values passed in and refers to those values using the parameter names we defined when we wrote the function.

Parameter names defined in the function code do not need to be the same as the names from the caller code we place between the parentheses when we call the function.

Phew - sounds complicated, but it is actually fairly simple. (There are more rules on these parameters, but this is enough for now!)

Here is code that demonstrates calling the function:

float mySin, myCos;
float myAngle = 37.0;
float PI = (float)acos(-1.0); // remember why?

myAngle = myAngle * PI / 180.0;
mySin = my_sin(myAngle);
mySin = my_sin(180 * PI);           // the expression is evaluated and the result passed in

myCos = sqrt(1.0 - my_sin(myAngle)*my_sin(myAngle));        // sqrt is a new function

In each of these examples, the reference to the my_sin function is part of an expression that is being evaluated. Once the entire expression has been evaluated, the value is stored in the container named on the left side of the equal (assignment) operator. The normal rules of evaluating expressions are still being followed, but when the system gets to the reference to the function, the expression evaluation pauses for a bit while the code for the function figures out the answer needed. That answer is returned to the expression evaluator which continues on with its work.

Functions with no Parameters

It is quite common to set up functions that do not need parameters to work, but still return a value. For example, suppose you want to write a chunk of code that gets a number from the user:

int GetUserValue(void) {
    int value;
    while(true) {
        cout << "Enter an integer between 1 and 10:";
        cin >> value;
        if(value < 1 || value > 10)
            cout << endl << "Bad input, try again" << endl;
        else break;
    }
    return value;
}

Notice that we place the word void inside the parameter area on the function definition. This means that the parameter list is empty.

With this function defined, the calling code can be easier to follow:

// working...
// get the value from the user
number = GetUserValue();
// use the number

The code is much easier to follow when the details of getting a number from the user are wrapped up and placed in another spot in our program.

Functions with no Value

Wait a minute, functions with no value? What I really mean is functions that return no value! (They have a value, as we shall see!)

Functions do not need to return values to be useful. When we started this course, we talked about diagramming process boxes, and a function is a great way to box up the code related to doing a process. We can even use a name that describes the process. The previous example shows this. GetUserValue is a good name describing the process we want to accomplish.

Try this:

int main(int argc, char **argv) {
    int number, factorial;

    DisplayHeading();
    DisplayInstructions();
    number = GetUserNumber();
    factorial = EvaluateFactorial(number);
    ShowResults(factorial);
    DisplayFooter();

    return EXIT_SUCCESS;
}

Hey, wait a minute. That main thing looks like a function! Exactly right! The main function is nothing more than a special function in your program that is activated by the operating system when you decide to run your program. The parameters for your program come from the operating system. We will show how this is done in a later lecture.

The DisplayHeading, DisplayInstructions, and DisplayFooter functions take no parameters, and deliver no results. The activation statement is just a reference to the function name with the parentheses, empty this time.

Notice that we do not need a return statement inside the function if we are not returning a value.

Doesn’t the program seem pretty clear, even when we do not see the function definition?

That is the point. You can use functions to break up the logic of your program the same way we used process boxes in our diagramming. Pick good names and you can almost read the program the same way you read the diagrams.

Where do we put the function declarations?

As we said before:

  • A name must have been declared before it can be used in a program.

That means that we cannot call a function if the function has not been declared yet. So, our example program above would need to be written with the function declarations above the main function.

If we want to do this, there is no need to split up the function into two separate parts. We simple place the full definition of the function at the top of the program like this:

void DisplayHeading(void) {
    // code to display heading
}

void DisplayInstructions(void) {
    // code to display instructions
}

...

int main(int argc, char ** argv) {
    DisplayHeading();
    DisplayInstructions();
    // more main program code
}

Notice that the code for the functions is missing. This code will compile and run. But the functions do not do anything. That is fine for now, since we are just starting with the program development.

Alternatively, we can choose to place the full function definition below the main program function. In this case, the code would look like this:

void DisplayHeading(void);
void DisplayInstructions(void);

int main(int argc, char ** argv) {
    DisplayHeading();
    DisplayInstructions();
    // more main program code
}

void DisplayHeading(void) {
    // code to display heading
}

void DisplayInstructions(void) {
    // code to display instructions
}

Here, the compiler is happy since it knows exactly how our two functions should be called when it processes the code inside the main function. It figured this out by processing the prototype lines at the top of the code. This program works exactly like the previous one. Which one you choose to use is up to you!

More Programming in Baby Steps

I have been trying to get you to write your programs in small steps. Once we have functions available to us, we can create programs in a very easy way, and work with code that does something right away. Here is how it goes:

#include <iostream>
using namespace std;

int main(int argc, char ** argv) {
    // do something useful
    cout "DoSomethingUseful()" << endl;

    return EXIT_SUCCESS;
}

Cute, it prints out the name of a function! What good is that?

Continuing:

#include <iostream>
using namespace std;

void DoSomethingUseful(void) {
    cout << "Doing Something Useful!" << endl;
}

int main(int argc, char ** argv) {
    // do something useful
    DoSomethingUseful();

    return EXIT_SUCCESS;
}

Here, I have refined the first code by adding in the function I need. I replaced the output statement in the main program with a call to the function. That function does not really do anything useful at the moment, but it does do something. It displays a message and quits. Trust me, I actually code this way, and make sure everything works before I move on. It actually saves me time in the long run!

This kind of function is sometimes called a stub, meaning it is just a placeholder for a real function that will be refined later. We can call the function and see that the program works, even if it is not finished yet!

Let’s try another step:

#include <iostream>
using namespace std;

void DisplayInstructions(void) {
    cout << "Enter a number between 0 and 20:";
    cout << endl;  // replace with a read later
}

void CalculateFactorial(int number) {
    cout << "Calculating the factorial of " << number << endl;
}

int main(int argc, char ** argv) {

    // display instructions
    DisplayInstructions();

    // do something useful
    CalculateFactorial(5);

    return EXIT_SUCCESS;
}

Here, I renamed the DoSomethingUseful function to CalculateFactorial, and changed the message to say what would happen eventually. I also added another simple function to display instructions.

I modified the CalculateFactorial function so it can get a value through a parameter.

Next, I see that the instructions are really the point where I want to get a user number. Let’s refine the program and do that:

#include <iostream>
using namespace std;

int GetUserValue(void) {
    int value;
    while(true) {
        cout << "Enter an integer between 1 and 10:";
        cin >> value;
        if(value < 1 || value > 10)
            cout << endl << "Bad input, try again" << endl;
        else break;
    }
    return value;
}

void CalculateFactorial(int number) {
    cout << "Calculating the factorial of " << number << endl;
}

int main(int argc, char ** argv) {
    int num;

    // display instructions
    num = GetUserValue();

    // do something useful
    CalculateFactorial(num);

    return EXIT_SUCCESS;
}

Here, I expanded the body of the GetUserValue function as we showed earlier. The routine gets a number from the user, and that number is passed into the CalculateFactorial function.

This should be enough to give you an idea how Baby Steps works. At each point in the process, I made tiny modifications and kept working with a program that actually runs and does something. If I make a mistake, the tiny steps were where the mistake happened, and I should not proceed until I work through the problem.

Programming is much more fun when things work more than when they don’t!