Chapter     5

Functions, the Building Blocks of C++

Modern games consist of hundreds of thousands of lines of code and can be worked on by teams consisting of hundreds of people. C++ provides the ability to separate code into blocks called functions to allow us to build more modular programs.

A function allows us to write a routine that can be reused throughout our program. The benefits of this are significant. You saw this in the last chapter when we looked at some of the C++ functions for working with strings. We did not have to write our own code to determine if two strings were the same; instead, we could simply pass them both into a function and the return value told us the result of the function.

This chapter covers how we write functions, including how to pass values to a function, how to pass pointers to functions, and a special type of pointer, the reference.

Writing Our First Function

A C++ compiler requires that functions are declared and defined before they can be used, just as with variables. It’s also possible to define and declare a function at the same time. Listing 5-1 shows the code for a simple program that calls a single function.

Listing 5-1. Our First Function

#include <iostream>
 
using namespace std;
 
void PrintHello()
{
        cout << "Hello!" << endl;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
        PrintHello();
 
        return 0;
}

Listing 5-1 contains a function named PrintHello. Functions in C++ always follow the same format and PrintHello is as simple as a function can be. It does not return any values to the code that calls the function and we also cannot pass any parameters to the function. To tell the C++ compiler that we do not want the function to return a value, we specify void as its return type. We can call a function by placing its name in our code followed by parentheses, as we have done with PrintHello.

Passing Parameters to Functions

Functions tend to be more useful when we can pass values to them. We do this by specifying parameters in the function signature. Listing 5-2 shows a function that contains parameters in its signature.

Listing 5-2. Passing Values to Functions Through Parameters

#include <iostream>
 
using namespace std;
 
void PrintSum(int valueA, int valueB)
{
        cout << valueA + valueB << endl;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
        PrintSum(3, 6);
 
        return 0;
}

The function PrintSum takes two parameters. Both parameters are of the type int and are named valueA and valueB. When we call PrintSum in _tmain we pass it the values 3 and 6. Inside the function the variable valueA contains the value 3 and valueB contains 6. The output from the program is 9. Another way to achieve this result would be to return the sum from the function.

Return Values

Functions can return values to the calling code to be stored within variables or passed as parameters to other functions. Listing 5-3 shows how to return a value from the function.

Listing 5-3. Returning Values from Functions

#include <iostream>
 
using namespace std;
 
int ReturnSum(int valueA, int valueB)
{
        return valueA + valueB;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
        int sum = ReturnSum(3, 6);
        cout << sum << endl;
 
        return 0;
}

The function ReturnSum has a new return type in its signature: It now reads int rather than void. Inside the function you now see that we use the return keyword to return the value from the + operator. We store the returned value from ReturnSum into the variable sum, which is then used with cout to print the result 9 to the console.

Any of the built-in types can be used as parameter or return types. The functions we have looked at in Listings 5-1, 5-2, and 5-3 all pass and return their function by value. This means that the compiler will make a copy of the values when they are being passed to or from the function. Sometimes we would like to be able to alter the values that come into our function so that the calling code can access the new values, and we can do this by passing in pointers.

Passing by Pointer

Listing 5-4 alters our previous example to pass the result of our sum back to the calling code using a pointer.

Listing 5-4. Passing by Pointer

#include <iostream>
 
using namespace std;
 
void ReturnSum(int inValueA, int inValueB, int* outValue)
{
        *outValue = inValueA + inValueB;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
        int sum = 0;
        ReturnSum(3, 6, &sum);
        cout << sum << endl;
 
        return 0;
}

Listing 5-4 initializes the variable sum to contain the value 0. ReturnSum now does not return a value; instead it takes a pointer to an int as its third parameter. When we call ReturnSum we pass the address of sum using &sum. Inside ReturnSum we alter the value stored at the pointer using the following line:

*outValue = inValueA + inValueB;

The pointer dereference operator is used to tell the compiler that we want to change the value stored at the pointer and not the pointer itself. In Listing 5-4 this achieves the result of altering the value of sum from 0 to 9.

Note  Pointers can be set to not contain a value. This is achieved by assigning nullptr to them. We’ll cover nullptr in Chapter 6 when we look at flow control statements.

As well as being able to pass values by pointer, we can also pass them by reference.

Passing by Reference

When we pass by pointer we must explicitly dereference the pointer to be able to retrieve its value. There’s also a possibility that the pointer might not point to a valid memory address but instead be assigned a nullptr. If we dereference a nullptr our program will crash. If we use a reference then we do not have to deference to obtain the value, we do not have to worry about nullptrs and we do not have to use the address of operator when passing the initial variable to the function. You can see this in Listing 5-5.

Listing 5-5. Passing by Reference

#include <iostream>
 
using namespace std;
 
void ReturnSum(int inValueA, int inValueB, int& outValue)
{
        outValue = inValueA + inValueB;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
        int sum = 0;
        ReturnSum(3, 6, sum);
        cout << sum << endl;
 
        return 0;
}

We now use the reference operator on the type for outValue; this looks like int&. We’re beginning to see the same operators being used for different purposes in different contexts. When defining a variable, the & operator after the typename tells the compiler to make the variable a reference. When placed before a variable name, when assigning to a variable, or when passing to a function it becomes the address-of operator.

Sooner or later in our programs we will want to pass large amounts of data into a function. One option could be to add many parameters to the function signature, but this makes the code difficult to maintain, as every time we change the function signature we will also have to change every call. Luckily structures provide an alternative.

Structures

So far all of the variables we have been using have been single variables. Individual variables are useful, but sometimes they can only take us so far. We will alter the sum example once again to show how we can use a structure with a function. Listing 5-6 shows an example of passing a structure to a function.

Listing 5-6. Passing a Structure to a Function

#include <iostream>
 
using namespace std;
 
struct SumParameters
{
        int     valueA;
        int     valueB;
        int     result;
};
 
void ReturnSum(SumParameters& params)
{
        params.result = params.valueA + params.valueB;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
        SumParameters sum;
        sum.valueA = 3;
        sum.valueB = 6;
        sum.result = 0;
        ReturnSum(sum);
        cout << sum.result << endl;
 
        return 0;
}

Listing 5-6 begins by defining our structure. We do this using the struct keyword. Immediately after the keyword is the name we would like to use for our structure. This name behaves as though we have created a new type. We can see this in the first line of _tmain where we declare the variable sum.

Once we have a variable that is of a structure type, we can use the . operator to access its member variables. We assign the values 3, 6, and 0 to the members of sum and then pass it by reference to the function ReturnSum. The compiler would have made a copy of all three members if we had passed the structure by value and therefore we would not have been able to read the proper result of the sum from the result member.

You can use the information you have learned about functions in this chapter to modify the game we have been writing.

Adding Functions to Text Adventure

We began our Text Adventure game in Chapter 4. At that time you didn’t know that we could store data in structures or that we could use functions to make more readable programs. In this chapter we will create a structure for the player and create a function that will be responsible for welcoming the player. We do both of these in Listing 5-7.

Listing 5-7. Adding the Player struct and WelcomePlayer Function to Text Adventure

#include <iostream>
#include <string>
 
using namespace std;
 
struct Player
{
        string m_name;
};
 
void WelcomePlayer(Player& player)
{
        cout << "Welcome to Text Adventure!" << endl << endl;
        cout << "What is your name?" << endl << endl;
 
        cin >> player.m_name;
 
        cout << endl << "Hello " << player.m_name << endl;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
        Player player;
        WelcomePlayer(player);
 
        return 0;
}

Our program is now much more readable than before. The Player structure will be responsible for storing all of the data related to our player object; for now we only need to keep track of the player’s name. We pass the player variable by reference to the WelcomePlayer function. This allows us to add all of the code necessary to welcome our players to a single function and this function can assign the player’s name from cin directly into the Player reference.

Our _tmain function now has code that is almost plain English. This is the goal of our code for the most part. Code that is human readable is very valuable when writing game programs. A lot of the code written, especially code to be reused between projects, must be maintained by teams of programmers. Having code that can be understood just by reading through it reduces the time it takes to understand and make alterations to the code.

Summary

This chapter has covered the C-style functions and structures you can use while writing programs in C++. Functions are the basic building blocks that allow us to write procedural programs. We have learned how to use parameters and return values to help us write readable programs. We’ve also looked at how pointers and references can be used to pass data into and out of functions. Finally, we looked at how we can group variables into structures. Grouping data in this way allows us to create our own types within the language. These types also aid us in writing programs that other programmers can easily understand. You saw this put to good effect in our Text Adventure game when we created a Player structure to group together data relating to our player.

Our programs are beginning to take a real form but they are still limited to carrying out basic arithmetic. In the next chapter we will look at control flow statements that will allow our programs to make decisions or repeat tasks over a large set of data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.162.79