Book Excerpt: Creating Games in C++

By David Conger [08.18.06]

 The following is an excerpt of the Introducing Object-Oriented Programming chapter in Creating Games in C++: A Step by Step Guide (ISBN 0735714347) published by New Riders and distributed by Peachpit Press February, 2006.

--

Before we start writing games, we should talk a bit more about C++. Specifically, we need to discuss how to create your own data types, how to write conditions, and how to use if-else statements to make decisions. In addition, I'll explain how to keep the type names you create from conflicting with other type names in your game.

Software Objects

Chapter 2 showed that C++ programs use variables to store information. Every variable has a data type. As you may recall, the purpose of the data type is to specify what kind of information the variable holds. For instance, integer variables hold integers (numbers with no fractional parts), floating-point numbers contain real numbers (numbers with fractional parts), strings hold groups of characters, and so on.

The C++ language would be extremely limited if it could only use the small group of data types it defines. We would have a tough time writing our programs that way. As game programmers, we usually need to create data types that represent things the player sees on the screen.

For example, when your game shows a dragon on the screen your code should be able to define a variable of type dragon. The dragon variable would store all the information needed to represent a dragon in the program. This information might include the dragon's current position, how far it can spit fire, and how many fireberries it's eaten (more fireberries means it spits fire farther). When the dragon moves, the dragon variable must keep track of which way it's moving and how fast.

To define our own types to represent things we need in games, we use a technique called object-oriented programming. In object-oriented programming, you define your types to represent anything you want. When you declare a variable of that type, you are said to be creating a software object. The objects you create can be anything in the real world or anything you can imagine. If you want to define an object to represent dragons, you can. If you need an object to represent walls, cars, feet, or trees, you can define those too. Anything can become objects in software—you're limited only by your own imagination.

Software objects contain data. The data describes the thing the object represents. In our dragon example, a dragon variable stores such information as the dragon's current position, how far it can spit fire, and how many fireberries it's eaten. That information describes a dragon. This is the way software objects work. We use them so that we can create variables that store all the information needed to describe an object.

All software objects behave exactly the way you tell them to. You define the set of actions, also called operations, that the software can perform on your objects. For example, consider the case of integers. What operations can we perform on integers?

Because we all know basic math, you and I can easily state that the most fundamental operations we can perform on integers are addition, subtraction, multiplication, and division. In C++, these four operations are built into the int data type. We saw how to use them in chapter 2.

All of the data types in C++ have a specific group of operations that programs can perform on them. If you make your own software objects, you're making your own data types. When you do, you must also define the set of operations that programs can perform on them. All of this is accomplished with C++ classes, so that's what we'll discuss next.


Classes

To define objects in C++, you create classes. Defining your own classes is actually easier than explaining it. So rather than starting with a long explanation, let's go through the sample program in Listing 3.1.

Example 3.1. Defining a software object with a C++ class

1 #include
2 #include
3
4 using namespace std;
5
6 class my_object
7 {
8 public:
9 int i;
10 float f;
11 };
12
13 int main(int argc, char *argv[])
14 {
15 my_object anObject;
16
17 anObject.i = 10;
18 anObject.f = 3.14159;
19
20 cout << "i = " << anObject.i << endl;
21 cout << "f = " << anObject.f << endl;
22 system("PAUSE");
23 return (EXIT_SUCCESS);
24 }

The short program in Listing 3.1 demonstrates how to define a class in C++. It uses the C++ keyword class on line 6 to define a new type: my_object. Everything in the class definition must be inside the opening and closing braces. The definition of the contents of the my_object type spans lines 7–11.

This program uses the my_object type to declare a variable in the main() function on line 13 As you can see, declaring a variable of type my_object is done in just the same way as declaring integers or floating-point numbers. Just as with any other variable type, variables of type my_object can be accessed in the functions where they are declared. Another way to say that is that the rules of scope work the same way for both objects and built-in types such as int.

Member Data

The my_object type contains an integer called i and a floating-point number called f. Programs using the my_object type store data in i and f. Therefore, i and f are collectively referred to as the class's member data. Confusingly, when we talk about just i or f by themselves, we call them data members. In other words, one item of member data is called a data member. And I'll bet you thought programmers had no sense of humor.

A class's member data stores all of the information that describes the object the class represents. As mentioned earlier, that information is also called the class's attributes.

The member data in the my_object class in Listing 3.1 is defined as being public. This means that any function can access the member data directly. For example, on line 17, the main() function stores an integer in i. It does so by stating the variable name (anObject), followed by a period, and then the data member name (i).

After storing values in the member data of the variable anObject, the program in Listing 3.1 prints the values it stored. When it does, line 20 shows that it uses anObject.i just as if it was a regular int variable. Specifically, it uses the insertion operator to put the value in anObject.i into the stream cout. On line 21, it does the same with anObject.f. Figure 3.1 shows the output.


Figure 3.1 The output of the program in Listing 3.1.

As you can see, the program printed the values in anObject.i and anObject.f in just the way it prints integer or floating-point variables.

You can declare more than one object of the same type in a function. Listing 3.2 shows how.

Example 3.2. Declaring two objects of the same type in a function

1 #include
2 #include
3
4 using namespace std;
5
6 class my_object
7 {
8 public:
9 int i;
10 float f;
11 };
12
13 int main(int argc, char *argv[])
14 {
15 my_object anObject, anotherObject;
16
17 anObject.i = 10;
18 anObject.f = 3.14159;
19
20 anotherObject.i = -10;
21 anotherObject.f = 0.123456;
22
23 cout << "anObject.i = " << anObject.i << endl;
24 cout << "anObject.f = " << anObject.f << endl;
25
26 cout << "anotherObject.i = " << anotherObject.i << endl;
27 cout << "anotherObject.f = " << anotherObject.f << endl;
28
29 system("PAUSE");
30 return (EXIT_SUCCESS);
31 }

32

As the program in Listing 3.2 illustrates, you can declare as many objects of the same type as you need. Each object has its own member data, in this case i and f. The value in anObject.i has nothing to do with the value in anotherObject.i; they are completely independent of each other. The same is true for anObject.f and anotherObject.f. You can see this from the output of the program, which appears in Figure 3.2.


Figure 3.2 The values in the two objects are independent of each other.

In Figure 3.2, the program stores and prints two different sets of values. This shows that the two objects contain different information; they are independent of each other.

Member Functions

As mentioned earlier, programmers define more than just the data for the objects they create. When a game programmer creates a type, she also writes the set of operations that can be performed on the type. Those operations are functions, which we introduced in chapter 2. The functions that form the set of valid operations that can be performed on a type are all members of the class. Just as we can define member data in classes, we can also define member functions. Member functions form the set of valid operations a program can perform on an object. Again, it's easier to show what I mean rather than launching into a long explanation. So let's examine the program in Listing 3.3.

Example 3.3. Creating and using member functions

1 #include
2 #include
3
4 using namespace std;
5
6 class my_object
7 {
8 public:
9 void SetI(int iValue);
10 int GetI();
11
12 void SetF(float fValue);
13 float GetF();
14
15 private:
16 int i;
17 float f;
18 };
19
20
21 void my_object::SetI(int iValue)
22 {
23 i = iValue;
24 }
25
26
27 int my_object::GetI()
28 {
29 return (i);
30 }
31
32
33 void my_object::SetF(float fValue)
34 {
35 f = fValue;
36 }
37
38
39 float my_object::GetF()
40 {
41 return (f);
42 }
43
44
45 int main(int argc, char *argv[])
46 {
47 my_object anObject, anotherObject;
48
49 anObject.SetI(10);
50 anObject.SetF(3.14159);
51
52 anotherObject.SetI(-10);
53 anotherObject.SetF(0.123456);
54
55 cout << "anObject.i = " << anObject.GetI() << endl;
56 cout << "anObject.f = " << anObject.GetF() << endl;
57
58 cout << "anotherObject.i = " << anotherObject.GetI() << endl;
59 cout << "anotherObject.f = " << anotherObject.GetF() << endl;
60
61 system("PAUSE");
62 return (EXIT_SUCCESS);
63 }

The program in Listing 3.3 is a modification of the program in Listing 3.2. If you compare the two programs, you'll see some very important changes.

First, the program in Listing 3.3 changed the member data from public to private. You'll see this on lines 15–17. Recall that any function in a program can access public member data. The same is true for public member functions. However, private members are different. The private member data on lines 16 and 17 can only be accessed by member functions. It's as if the class is a very exclusive club; only members can use the club's private goodies.

The list of member functions is shown on lines 9–13. Notice that all of these functions are public. This is typically how programmers define classes. You should only rarely make member data publicly accessible. Normally, the member functions should be public and the member data private.

The reason programmers make class data private and functions public is simple: control. If a class's member data can only be accessed by the member functions, then we're controlling how the data can be changed. If the member functions never let the data get into an invalid state, then the data will always be valid.

Remember our dragon example we used when we first started talking about classes? Recall that the dragon in the game could eat fireberries to spit fire farther. Suppose the number of fireberries the dragon has eaten is stored in the dragon class as a data member. What would happen in the game if that number somehow got to be negative? Oops. Now the dragon swallows fire rather than spits it. The game will probably crash. That's why it's vital to keep all data valid at all times.

When we define objects, controlling the access to member data helps keep the data in a valid state. No functions outside the class can change private data. As long as the member functions never let the data become invalid, the data is always in a valid state. This is such an important point that I'm going to emphasize it with a special Tip.

If you look back at Listing 3.3, you'll see the prototypes for the member functions on lines 9–13. Putting the prototypes in the class tells the compiler which functions are members. The member functions for this class are very simple: All they do is set or get the values of the member data. We'll see in later chapters that member functions can also check the values before they set the member data.

The actual functions are given on lines 21–42. Let's focus on the first line of the SetI() function, which appears on line 21. Notice that the first line of the function includes the name of the class followed by two colons. Two colons used together like that—known as the C++ scope resolution operator—is a way to tell the compiler, "This is the function SetI() that I said was a member in the my_object class definition." The compiler replies, "Aha. So every time I see a call to the my_object class's SetI() function, this is the function I'll use." All member functions must be written in this way.

The SetI() function sets the value of i. You might be thinking, "Duh. I could tell that from the name of the function." That's exactly why the function is named SetI(). When you write your member functions, name them in a way that anyone looking at your code can tell what the function does by its name. If you try to explain what the function does, "Duh" is a good response to get—it means you named your function well.

As I mentioned, the SetI() function sets the value of i to the value in the iValue parameter. In most programs, the SetI() function would check the value of iValue before saving it into i. I'll show how to do that sort of data validation when we discuss if-else statements in "The if-else Statement" section later in this chapter.

Now take a look at the main() function in Listing 3.3. On lines 49–50, you'll see calls to the SetI() and SetF() functions. Notice that the program calls member functions by stating the name of the object, followed by a period, and then the name of the function. There is an important reason for doing it this way: If you look on lines 52–53, you'll see two more calls to the SetI() and SetF() functions. The difference between these two pairs of function calls is that the first pair sets the member data in the anObject variable, while the second pair sets the member data of the anotherObject variable. So any time you call a member function, you call that function on an object. The function uses the member data of that particular object and no other. This enables you to specifically state which object you're changing.

Lines 55–59 demonstrate calls to the GetI() and GetF() functions. Again, they follow the same pattern as calls to the SetI() and SetF() functions. Specifically, they state the variable name, then a period, and then the function name. Your programs always call member functions in this way.

Constructors and Destructors

Classes can have special member functions called constructors and destructors. Constructors are called automatically when the object is created. Constructors are used to initialize the object's member data into known states before any other function can possibly use the object. Destructors are called automatically when the object is destroyed. They perform any cleanup tasks on the object that may be required. Listing 3.4 demonstrates the use of constructors and destructors.

Example 3.4. Constructors and destructors in classes

1 #include
2 #include
3
4 using namespace std;
5
6 class my_object
7 {
8 public:
9 my_object();
10 ~my_object();
11
12 void SetI(int iValue);
13 int GetI();
14
15 void SetF(float fValue);
16 float GetF();
17
18 private:
19 int i;
20 float f;
21 };
22
23
24 my_object::my_object()
25 {
26 cout << "Entering constructor." << endl;
27 i = 0;
28 f = 0.0;
29 }
30
31
32 my_object::~my_object()
33 {
34 cout << "Entering destructor." << endl;
35 }
36
37
38 void my_object::SetI(int iValue)
39 {
40 i = iValue;
41 }
42
43
44 int my_object::GetI()
45 {
46 return (i);
47 }
48
49
50 void my_object::SetF(float fValue)
51 {
52 f = fValue;
53 }
54
55
56 float my_object::GetF()
57 {
58 return (f);
59 }
60
61
62 int main(int argc, char *argv[])
63 {
64 my_object anObject, anotherObject;
65
66 anObject.SetI(10);
67 anObject.SetF(3.14159);
68
69 anotherObject.SetI(-10);
70 anotherObject.SetF(0.123456);
71
72 cout << "anObject.i = " << anObject.GetI() << endl;
73 cout << "anObject.f = " << anObject.GetF() << endl;
74
75 cout << "anotherObject.i = " << anotherObject.GetI() << endl;
76 cout << "anotherObject.f = " << anotherObject.GetF() << endl;
77
78 system("PAUSE");
79 return (EXIT_SUCCESS);
80 }

Looking at lines 9–10 of Listing 3.4, you'll see that the my_object class now contains prototypes for two additional member functions: the constructor and the destructor, respectively. These prototypes are very different from any that we have introduced so far. Unlike other member functions, the names of constructors exactly match the name of the class. The only difference between the names of a constructor and a destructor is that the destructor name is preceded by the tilde (pronounced TILL-duh) character, which looks like this: "~".

The code for the constructor is on lines 24–29. Like all member functions, the constructor must state the class that it's a member of by using the class name and then the scope resolution operator. That's why line 24 has the name of the class, then the two colons followed by the name of the constructor (which is also the name of the class).

Notice that neither the constructor nor the destructor has a return type. The constructor doesn't need a return type because it creates an object of the class that it's a member of. In Listing 3.4, the constructor creates an object of type my_object. In "programmer-speak," we say that the constructor's return type is implicit in its name.

Destructors, on the other hand, delete an object of their class type. There is no return type because there's nothing to return. The object is gone when the destructor ends.

When the constructor starts, it prints the message Entering constructor to the screen. Most constructors don't do this; I did it here just for demonstration purposes. The primary purpose of a constructor is to set the member data into a known state when the program creates an object of that type. That's exactly what happens on lines 27–28 of Listing 3.4. The constructor sets the member data to 0.

The destructor appears on lines 32–35. This class has nothing to clean up, so the destructor doesn't do anything significant. For demonstration purposes, it prints the message Entering destructor when it starts.

A look at the main() function on lines 64–79 shows that it is no different than the one in Listing 3.3. That probably seems strange. The logical question to ask is, "Why aren't the constructor and destructor called?" In fact, they are. The program output in Figure 3.3 proves it.


Figure 3.3
Calling the constructor and destructor.

When I ran the program in Listing 3.4, it began with the main() function, as all C++ programs do. The first thing main() did was to declare two variables of type my_object. At that time, the program automatically called the constructor once for each object that main() created. The first two lines of the output in Figure 3.3 show that that's exactly what happened; the constructor was called twice. Each time the constructor executed, it printed the message Entering constructor.

On lines 66–70, main() set the member data in both of its my_object variables. It then printed the values in the member data. We can see that in the output in Figure 3.3. The program paused and waited for me to press a key. When I did (just in case you're curious, I pressed the letter Z), the program executed the return statement on line 79. This caused the main() function to end. When main() ended, the two variables it declared on line 64 were no longer being used. Programmers say that the variables went "out of scope." When variables go out of scope, C++ programs automatically calls the appropriate destructors once for each class variable. In this case, it called the my_object destructor twice, as you can see from the output in Figure 3.3.

Inline Member Functions

As you begin to read professional C++ programming literature, you'll see that many programmers put the code for their member functions into the class definitions. This approach is allowed in C++ programming. Functions defined this way are called inline member functions. Listing 3.5 demonstrates the use of inline member functions.

Example 3.5. A class with inline member functions

1 #include
2 #include
3
4 using namespace std;
5
6 class point
7 {
8 public:
9 point()
10 {
11 x = y = 0;
12 }
13
14 void SetX(int xValue)
15 {
16 x = xValue;
17 }
18
19 int GetX(void)
20 {
21 return (x);
22 }
23
24 void SetY(int yValue)
25 {
26 y = yValue;
27 }
28
29 int GetY(void)
30 {
31 return (y);
32 }
33
34 private:
35 int x, y;
36 };
37
38 int main(int argc, char *argv[])
39 {
40 point rightHere;
41
42 rightHere.SetX(10);
43 rightHere.SetY(20);
44
45 cout << "(x,y)=(" << rightHere.GetX();
46 cout << "," << rightHere.GetY() << ")";
47 cout << endl;
48
49 rightHere.SetX(20);
50 rightHere.SetY(10);
51
52 cout << "(x,y)=(" << rightHere.GetX();
53 cout << "," << rightHere.GetY() << ")";
54 cout << endl;
55
56 system("PAUSE");
57 return (EXIT_SUCCESS);
58 }

Rather than just prototypes for member functions, the class in Listing 3.5 contains the member functions themselves. All of the code for each function appears in the class. The main() function on lines 38–58 demonstrates that your programs can use inline member functions in exactly the same way they use member functions defined outside of a class.

You're probably quite logically wondering what, if any, differences exist between inline and out-of-line member functions. There's really only one difference: When you define a member function inline, the compiler substitutes the code from the inline member function into your program. With out-of-line functions, that doesn't happen. For instance, if you look back at Listing 3.5, you'll see the statement 
rightHere.SetX(10); on line 42. When this statement gets compiled, the compiler substitutes the code for the SetX() function right into the statement. That is, it puts the equivalent of
rightHere.x=10; into the compiled program. Of course, it doesn't change the source code in the .cpp file. It performs the substitution in the object code that it generates. It does this everywhere in every function that calls the inline function. The program in Listing 3.5 makes one call to the constructor, and two calls each to SetX() and SetY(). That means it substitutes code from the member functions into the main() function five times.

When I was teaching college-level C++ programming classes, students would often ask, "Why not just make all functions inline?"

The first reason is that the compiler won't allow it. If the compiler determines that the function is too long or too complex, it won't compile the member function as an inline function. It still compiles the function without a problem. However, it converts the member function into an out-of-line function in the compiled code. And it does this without telling you. It is completely up to whoever writes the compiler whether or not a function can be made inline. You and I can't control it. Writing functions inline is more of a suggestion than a command.

Usually functions remain inline if all they do is set or get values from members in a class. They generally also remain inline if they perform simple calculations. However, member functions that contain loops or call other functions are not likely to remain inline.

Another reason why it might not be a good idea to make all member functions inline is that inline member functions can make programs very large. Because the C++ compiler performs code substitution with inline member functions, there are fewer function calls in programs so they run faster. However, it also makes them bigger because the code for the inline functions gets inserted repeatedly. Using inline functions means that there are lots of copies of the inline function in the compiled program. With out-of-line functions, the program jumps to the one and only copy of the member function. That's a tiny bit slower, but it makes the program smaller. So when you're writing programs, you have to decide which is more important—speed or size.

The last reason why you might not make all of your member functions inline is that it makes your classes hard to read. Complex classes with lots of member functions become huge when you use inline member functions. Other programmers tend to find them difficult to deal with. Plenty of programmers disagree with this point of view—you have to decide for yourself.

One way to have the advantages of inline member functions without cluttering up your class definitions is to use out-of-line inline functions.

Say what?

Amazingly enough, C++ lets you define out-of-line member functions that are inline. It sounds kooky, but it's actually a nice feature. listing 3.6 illustrates how to create out-of-line inline functions.

Example 3.6. Making out-of-line functions inline

1 #include
2 #include
3
4 using namespace std;
5
6 class point
7 {
8 public:
9 point();
10 void SetX(int xValue);
11 int GetX(void);
12 void SetY(int yValue);
13 int GetY(void);
14
15 private:
16 int x,y;
17 };
18
19
20 inline point::point()
21 {
22 x = y = 0;
23 }
24
25 inline void point::SetX(int xValue)
26 {
27 x = xValue;
28 }
29
30 inline int point::GetX(void)
31 {
32 return (x);
33 }
34
35 inline void point::SetY(int yValue)
36 {
37 y = yValue;
38 }
39
40 inline int point::GetY(void)
41 {
42 return (y);
43 }
44
45
46
47 int main(int argc, char *argv[])
48 {
49 point rightHere;
50
51 rightHere.SetX(10);
52 rightHere.SetY(20);
53
54 cout << "(x,y)=(" << rightHere.GetX();
55 cout << "," << rightHere.GetY() << ")";
56 cout << endl;
57
58 rightHere.SetX(20);
59 rightHere.SetY(10);
60
61 cout << "(x,y)=(" << rightHere.GetX();
62 cout << "," << rightHere.GetY() << ")";
63 cout << endl;
64
65 system("PAUSE");
66 return (EXIT_SUCCESS);
67 }

In Listing 3.6, all of the functions from Listing 3.5 are now moved out of line. Only the function prototypes remain in the class definition. However, if you look at the member functions, you'll see that each one begins with the C++ keyword inline. An example is the SetX() function on line 25. Putting the keyword inline at the beginning of the function definition makes SetX() an inline function even though the code appears out of line. Using this style gives you shorter, more readable class definitions and still provides the advantages of inline functions. When we examine the source code for the LlamaWorks2D game engine in the next chapter (the source code is also provided on the CD), you'll find that this is the style it uses for nearly all of its inline functions.


Logical Operators

The sample programs presented so far generally execute one statement after another. They usually do exactly the same thing each time they run. Although this is a good way to learn C++ programming, that's not how games operate. Games need to present players with different experiences each time they play. The most important tool for doing this is the C++ if-else statement, which we will discuss shortly. However, to use the if-else statement, programmers have to understand logical operators. Therefore, we'll take a brief look at them now.

Logical operators are pretty straightforward. In fact, you have seen them before in your math classes in school. Table 3.1 shows the C++ logical operators.

Table 3.1. The C++ Logical Operators

Operator

Description

<

Less than

<=

Less than or equal to

==

Equal to

!=

Not equal

!

Not

>=

Greater than or equal to

>

Greater than

&&

And

||

Or

Although you may have used different symbols for these in school, you're probably familiar with what they do. You use these operators in comparisons, which programmers call logical expressions. All logical expressions evaluate to true or false. Recall from chapter 2 that true and false are actual values in C++, just like numbers.

You've actually used logical operators in your programs already. chapter 2 showed their use with while and do-while loops. Table 3.2 gives some more examples of how to use logical operators.

Table 3.2. Using Logical Operators In Expressions

Expression

Meaning

thisValue <= thatValue

Evaluates to true if the variable thisValue contains a value that is less than or equal to the value in the variable thatValue. Otherwise, it evaluates to false.

thisValue != 100

Evaluates to true if thisValue is not equal to 100. Otherwise, it evaluates to false.

thisValue == 100

Evaluates to true if thisValue is equal to 100. Otherwise, it evaluates to false.

!(thisValue == 100)

Evaluates to true if thisValue is not equal to 100. Otherwise, it evaluates to false.

(thisValue < 100) && (thatValue != 10)

Evaluates to true if thisValue is less than 100 and that Value is not equal to 10. Otherwise, it evaluates to false.

(thisValue < 100) || (thatValue != 10)

Evaluates to true if thisValue is less than 100 or that Value is not equal to 10. Otherwise, it evaluates to false.

Some of the expressions in Table 3.2 deserve particular attention. The expression !(thisValue == 100) says the same thing as thisValue != 100.

To understand this expression, you need to know that in C++, everything in parentheses is done first. To evaluate !(thisValue == 100) you first evaluate thisValue == 100 because it's in the parentheses.

This expression evaluates to true when the variable thisValue contains 100. If it contains any other value, the expression is false. If you then add the Not operator, which is the exclamation point, it negates the expression.

That means if thisValue == 100 evaluates to true, then !(thisValue == 100) evaluates to false.

And any time thisValue == 100 evaluates to false, it makes !(thisValue == 100) evaluate to true. The ! operator negates a logical condition.

The last two expressions in Table 3.2 are also of particular interest. These are often called compound logical expressions because they put together more than one comparison or condition. When you're writing your programs, you make compound logical expressions with the And operator. You can also create them with the Or operator. How is this done?

The expression (thisValue<100) && (thatValue!=10) has two conditions: thisValue<100 and thatValue!=10

In order for the entire expression to evaluate to true, both the first and the second conditions must evaluate to true. If either of them evaluates to false, then the whole expression is false. As a result, expressions with an And operator tend to evaluate to false more than they evaluate to true.

If we rewrite the expression to make this (thisValue<100) || (thatValue!=10) then we're using the Or operator. This says that the entire expression is true if either the first or the second condition evaluates to true. The only time it's false is if both conditions are false.


The If-Else Statement

The if-else statement gives C++ programs a way to make decisions based on the information the program has as it's running. Let's use listing 3.7 to see how it works.

Example 3.7. A simple if-else statement

1 #include
2 #include
3
4 using namespace std;
5
6 int main(int argc, char *argv[])
7 {
8 cout << "Please input a number greater than or equal to 0: ";
9
10 int aNumber;
11
12 cin >> aNumber;
13
14 if (aNumber >= 0)
15 {
16 cout << "Very good." << endl;
17 }
18 else
19 {
20 cout << "Not very good." << endl;
21 }
22
23 system("PAUSE");
24 return (EXIT_SUCCESS);
25 }

You create an if-else statement to make a decision, as Listing 3.7 illustrates. In this example, the program asks the user to enter a number. It makes the decision on lines 14–21. If the user entered a number greater than or equal to 0, the program prints a compliment (line 16). If the number the user entered is less than 0, the program prints another message (line 20).

All if-else statements follow the format of the one in Listing 3.7 To be specific, they all start with the C++ keyword if, which is always followed by a logical expression in parentheses. Like a loop, if-else statements can have bodies. Of course, the bodies are enclosed in opening and closing braces. The if-else statement is actually composed of two parts, or clauses. They all have a required if clause and an optional else clause. The if clause for the program in Listing 3.7 appears on lines 14–17. As you can see, the if clause has a code body on lines 15–17. The body of the else clause spans lines 19–21.

Because the if and else clauses in an if-else statement each have their own code bodies, the program executes different blocks of code based on the condition. In other words, if the condition on line 14 is true, the program executes the body of the if clause on lines 15–17. On the other hand, when the condition on line 14 is false, the program executes the code body of the else clause. As a result, the program prints the string on line 16 if the condition is true. If the condition is false, it prints the string on line 20.

With all if-else statements, the else clause is optional. You don't have to put it in. Listing 3.8 shows the same if-else statement without an else clause.

Example 3.8. An if statement without an else clause

1 #include
2 #include
3
4 using namespace std;
5
6 int main(int argc, char *argv[])
7 {
8 cout << "Please input a number greater than or equal to 0: ";
9
10 int aNumber;
11
12 cin >> aNumber;
13
14 if (aNumber >= 0)
15 {
16 cout << "Very good." << endl;
17 }
18
19 system("PAUSE");
20 return (EXIT_SUCCESS);
21 }

If you run this program and the condition is true, it executes the body of the if statement. However, when you run this program and the condition evaluates to false, the program skips to the next statement after the if, which is on line 19. Essentially, this if does nothing when its condition is false.


Namespaces and Scope Resolution

In C++, the name of every class in a program has to be unique; there can't be two classes with the same name. This presents a potential problem. Suppose you write a game that uses the LlamaWorks2D library. LlamaWorks2D contains a class called vector, which I'll present in chapter 5, "Function and Operator Overloading." Games contain a lot of math. Imagine that you decide that you will also use a math library written by someone else in your game. It's very likely that the math library also contains a class called vector. When your game declares a variable of type vector, which class should the compiler use, the one in LlamaWorks2D or the one in the math library?

C++ provides a solution to the problem of conflicting class names: namespaces. A namespace is a way of grouping related classes together. In fact, it can be used for more than classes. Namespaces group related types, functions, and other kinds of C++ constructs that I'll discuss later. You create a namespace with the namespace keyword, as shown in Listing 3.9.

Example 3.9. Defining a namespace

1 #include
2 #include
3
4 using namespace std;
5
6 // Beginning of first namespace.
7 namespace anamespace
8 {
9
10 class point
11 {
12 public:
13 point()
14 {
15 x = y = 0;
16 }
17
18 void SetX(int xValue)
19 {
20 x = xValue;
21 }
22
23 int GetX(void)
24 {
25 return (x);
26 }
27
28 void SetY(int yValue)
29 {
30 y = yValue;
31 }
32
33 int GetY(void)
34 {
35 return (y);
36 }
37
38 private:
39 int x,y;
40 };
41
42 // End of first namespace.
43 };
44
45
46
47 // Beginning of second namespace.
48
49 namespace anothernamespace
50 {
51 class point
52 {
53 public:
54 point()
55 {
56 x = y = 0;
57 }
58
59 void SetX(int xValue)
60 {
61 x = xValue;
62 }
63
64 int GetX(void)
65 {
66 return (x);
67 }
68
69 void SetY(int yValue)
70 {
71 y = yValue;
72 }
73
74 int GetY(void)
75 {
76 return (y);
77 }
78
79 void Reset()
80 {
81 x = y = 0;
82 }
83
84 private:
85 int x,y;
86 };
87
88 // End of second namespace.
89
90 };
91
92
93
94 int main(int argc, char *argv[])
95 {
96 anamespace::point rightHere;
97
98 rightHere.SetX(10);
99 rightHere.SetY(20);
100
101 cout << "(x,y)=(" << rightHere.GetX();
102 cout << "," << rightHere.GetY() << ")";
103 cout << endl;
104
105 anothernamespace::point rightThere;
106 rightThere.SetX(20);
107 rightThere.SetY(10);
108
109 cout << "(x,y)=(" << rightThere.GetX();
110 cout << "," << rightThere.GetY() << ")";
111 cout << endl;
112
113 rightThere.Reset();
114
115 cout << "(x,y)=(" << rightThere.GetX();
116 cout << "," << rightThere.GetY() << ")";
117 cout << endl;
118
119 system("PAUSE");
120 return (EXIT_SUCCESS);
121 }

This sample program defines two namespaces: one called anamespace and the other called anothernamespace. Both namespaces contain a class called point. If you examine the two classes, you'll find that they're nearly identical. The difference is that the point class in anothernamespace has a member function called Reset(). The point class in anamespace doesn't have that member function.

The program in Listing 3.9 uses both point classes without a problem. The compiler can tell which point class is being referred to because the main() function uses the scope resolution operator, which is the double colon (::). You see it on lines 96 and 105. On line 96, the main() function states that it is declaring a variable of type point and using the point class in the namespace called anamespace. Line 105 also declares a variable of type point, but it is the point class in the namespace called anothernamespace.

Every time you declare a variable that uses a type from a namespace, you must use the scope resolution operator. This makes your code really wordy. Most programmers don't care for this. The way you get around it is to put the using keyword into your program. On line 4 of Listing 3.9, you can see an example of the using statement. This line specifies that the program is using a namespace called std. The std namespace is defined by the C++ Standard Libraries in the file iostream, which is included on line 2. Pretty much all of the programs presented so far have used this namespace; most C++ programs do.

When you start using the LlamaWorks2D game engine, you'll see that all of its types are defined in a namespace called llamaworks2d. As a result, you'll put the statement

using namespace llamaworks2d;

at the beginning of your source code for your games. Having the using statement at the beginning of your files means that you can use types that LlamaWorks2D provides, such as vector, without having to specify llamaworks2d::vector throughout your code. If you use a vector class from another library, that's okay. Any time there's a conflict, you can resolve it by using the scope resolution operator to specify exactly which vector class you mean.

A Brief Word About Structures

Before we dive into writing games with LlamaWorks2D, which is the subject of the next chapter, there's one more topic I'd like to touch on briefly. That topic is structures.

Occasionally you will want to create a class that is so simple that it doesn't seem worth it to write member functions to get and set its values. If that's the case, you should not make it a class. Instead, you should make it a structure.

A structure is almost exactly the same as a class. A structure can have member functions, constructors, destructors, and member data just like classes. It can have public data, private data, public functions, and private functions just the same as a class. The only difference between classes and structures is the default for all class members is private while the default for structures is public.

For example, suppose I define a class like this:

class bitmap_region{ int top,left,bottom,right;};

This class does not contain the keywords public or private. By default, the four data members are set to private access. That means only member functions can access them. However, there are no member functions. If I change this to a structure, the scope changes as well. Here's the same thing as a structure:

struct bitmap_region{ int top,left,bottom,right;};

As shown here, all structures begin with the keyword struct. This structure does not contain the keywords public or private. By default, its members are public. They can be accessed by any function in the program that has a bitmap_ region variable.

In general, you should not use many structures in your program because their members are public by default. Whenever you can, it's better to use classes that keep their data private and their functions public.

We've covered a lot of territory in one chapter. You've had a whirlwind introduction to object-oriented programming. Don't expect to master everything presented here right away; we'll practice everything you learned in this chapter throughout the rest of the book. Along the way, I'll explain in detail some of the C++ features I just touched on in this chapter. Before the end of this book, you'll have used all of these concepts and techniques repeatedly so that when you start writing your own games, you'll have a strong background to draw from.

With the tools that object oriented programming provides, you'll be able to make software objects that you can use and reuse in your games. You'll also be able to use the LlamaWorks2D game engine, which is based on software objects.

 

Excerpted from Creating Games in C++: A Step-by-Step Guide by David Conger with Ron Little. Copyright © 2006. Used with permission of Pearson Education, Inc. and New Riders.

Return to the web version of this article
Copyright © UBM TechWeb