Tải bản đầy đủ (.pdf) (26 trang)

C++ Programming for Games Module I phần 6 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (557.63 KB, 26 trang )

int main()
{
// Our main array pointer and a variable to keep track
// of the array size.
in rray = 0; t* a
int arraySize = 0;

// Boolean variable to let us know when the user wants
// qu so that we can termin to it, ate the loop.
bool done = false;
while( !done )
{
// Every loop cycle print the array.
PrintArray(array, arraySize);

// Print a menu giving the user a list of options.
cout <<
"1) Set Element "
"2) size Array " Re
"3) Quit ";

// Input the users selection.
int selection = 1;
ci s tion; n >> elec

// Some variables that will receive additional input
// depending on the users selection.
int index = -1;
int value = 0;
int newSize = 0;


// d out what menu opti Fin on the user selected.
switch( selection )
{
// Case 1: Set Element
ca : se 1
// Ask for the index of the element the user wants
// to set.
cout << "Index = ";
cin >> index;

// Make sure index is "in array bounds."
if( index < 0 || index >= arraySize )
{
cout << "Bad Index!" << endl;
}
else
{
// Ask the user to input the value the user
// wants to assign to element 'index'.
cout << "[" << index << "] = ";
cin >> value;

// Set the value the user entered to the index
// the user specified.
array[index] = value;
}

130
break;
// Case 2: Resize Array

case 2:
// Ask the user to enter the size of the new array.
cout << "Size = ";
cin >> newSize;

// Call the resize function. Recall that this
// function returns a pointer to the newly resized
// array.
array = ResizeArray(array, arraySize, newSize);

// Update the array size.
arraySize = newSize;
break;
// Quit
default:
// Cause the loop to terminate.
done = true;
break;
}
}

delete [] array;
array = 0;
}

Program 4.9 Output
NULL Array
1) Set Element 2) Resize Array 3) Quit 2
Size = 4
{-842150451 -842150451 -842150451 -842150451 }

1) Set Element 2) Resize Array 3) Quit 1
Index = 3
[3] = 4
{-842150451 -842150451 -842150451 4 }
1) Set Element 2) Resize Array 3) Quit 1
Index = 2
[2] = 3
{-842150451 -842150451 3 4 }
1) Set size Array 3) Quit 1 Element 2) Re
Index = 0
[0] = 1
{1 -842150451 3 4 }
1) Set Element 2) Resize Array 3) Quit 1
Index = 1
[1] = 2
{1 2 3 4 }
1) Set Element 2) Resize Array 3) Quit 2
Size = 7
{1 2 3 4 -842150451 -842150451 -842150451 }
1) Set Element 2) Resize Array 3) Quit 1

131
Index = 4
[4] = 5
{1 2 3 4 5 -842150451 -842150451 }
1) Set Element 2) Resize Array 3) Quit 2
Size = 5
{1 2 3 4 5 }
1) Set Element 2) Resize Array 3) Quit 3
Press any key to continue



Note: Notice the trick in this code:

cout <<
"1) Set Element "
"2) Resize Array "
"3) Quit ";

“Adjacent” strings like this will be put into one string. That is, the above is equal to:

cout << "1) Set Element 2) Resize Array 3) Quit ";

er several lines to make the code more readable.
owerful tool, the risk of memory leaks requires extreme caution. In
ct, so guages, such as Java, have deemed pointers to be too dangerous and
mer. Although not having pointers
d low ming simpler, the loss of this memory control can lead
g an array, and it turns out that this is a common operation in
e worthwhile if
re were a way to “wrap up” the code that resizes an array into a package of code. Once this resizing
de was verified to work and that it contained no memory leaks, this code “package” could be used
roughout our programs with the confidence that all the memory management was being done correctly
ortunately for us, such a package exists and is part of the standard library.
a special type called
std::vector (include <vector>). A vector in this context
hich can dynamically resize itself. The following program shows a simple example:
d::vector as a resizable array.
In this way, we can break long strings up ov


4.6 std::vector
Although dynamic memory is a p
me other programming lanfa
error prone, and therefore they do not expose pointers to the program
an -level memory access can make program
to inefficiencies.

The key theme of Program 4.9 was resizin
non-trivial programs. In order to resize the array, dynamic memory was used. It would b
the
oc
th
behind the scenes. F

The code package is
is simply an array w

Program 4.10: Using st
#include <iostream>
#include <vector>
using namespace std;

132
int main()
{
vector<float> floatVec;

floatVec.resize(12);

cout << "My size = " << floatVec.size() << endl;


for(int i = 0; i < 12; ++i)
floatVec[i] = i;

for(int i = 0; i < 12; ++i)
cout << "floatVec[" << i << "] = " << floatVec[i] << endl;

}

rogram 4.10 OutputP
My size = 12
floatVec[0] = 0
floatVec[1] = 1
floatVec[2] = 2
floatVec[3] = 3
floatVec[4] = 4
floatVec[5] = 5
floatVec[6] = 6
floatVec[7] = 7
floatVec[8] = 8
floatVec[9] = 9
floatVec[10] = 10
floatVec[11] = 11
Press any key to continue

Program 4.10 demonstrates some syntax not yet discussed. First a variable is declared called
floatVec
angle brackets <> a type is specified, which indicates what type of
tore
floats in the vector.

ething called
resize(12).
operator, called
resize, particular to vector that instructs the vector to resize
vector keeps track of its size—its size can be accessed using its
ize
e accessed in the same way elements in an array are accessed,
vector has many more operators than resize, but they will
r in this course. For now, just think of
vector as a resizable array.
now rewritten using vector. Note that by using vector, less code needs to be written.
t memory management does not need to be done as all the memory
anage side vector.
of type
vector, and inside the
tor stores. We selements the vec

ent, the vector variable name is followed by a dot and somIn the next statem
This syntax calls an
itself to size 12. Also observe that a
operator. .s

Finally, the elements in
floatVec can b
cket operator. Note that by using the bra
discussed latebe

Program 4.9 is
More importantly, observe tha
ment is “wrapped up” inm



133
Pr 4.11: Dynamic memory “wogram rapped up” in std::vector.
#include <iostream>
# d ctor> inclu e <ve
using namespace std;

v ri ctor(vector<int>& v) oid P ntVe
{
if( v.size() == 0 )
{
cout << "NULL Array" << endl;
}
else
{
cout << "{";
for(int i = 0; i < v.size(); ++i)
cout << v[i] << " ";

cout << "}" << endl;
}
}

int main()
{
vector<int> array;

// Boolean variable to let us know when the user wants
// to quit, so that we can terminate the loop.

bool done = false;
w ) hile( !done
{
// Every loop cycle print the array.
PrintVector(array);

// Print a menu giving the user a list of options.
c <out <
"1) Set Element "
"2) Resize Array "
"3) Quit ";

// Input the users selection.
i l n = 1; nt se ectio
cin >> selection;

// Some variables that will receive additional input
// depending on the users selection.
int index = -1;
int value = 0;
int newSize = 0;

// Find out what menu option the user selected.
s ( selection ) witch
{
// Case 1: Set Element
case 1:
// Ask for the index of the element the user wants
// to set.


134
cout << "Index = ";
cin >> index;

// Make sure index is "in array bounds."
if( index < 0 || index >= array.size() )
{
cout << "Bad Index!" << endl;
}
else
{
// Ask the user to input the value the user
// wants to assign to element 'index'.
cout << "[" << index << "] = ";
cin >> value;

// Set the value the user entered to the index
// the user specified.
array[index] = value;
}
break;
// Case 2: Resize Array
case 2:
// Ask the user to enter the size of the new array.
cout << "Size = ";
cin >> newSize;

// Call the resize operator. Recall that this
// function returns a pointer to the newly resized
// array.

array.resize(newSize);
break;
// Quit
default:
// Cause the loop to terminate.
done = true;
break;
}
}
}


s of objects that live in computer memory. A program’s instructions
achine code) are also loaded into memory. This is necessary because these instructions will need to
be loaded in and out of the CPU’s instruction register (a register that stores the current instruction
structions. What happens when a function is
nt, to start executing the code of a function,
the execution flow needs to jump from the current execution path to the beginning of the execution path
of the function we wish to call. Since a function has a memory address, this is not a difficult task.
4.7 Function Pointers
Variables are not the only type
(m
being executed), in order for the computer to execute the in
called? Ignoring arguments and parameters for the mome

135
Simply set the instruction pointer (a CPU register that points to the memory address of the next
machine instruction to execute) to point to the address of the first instruction of the function. Figure 4.9
shows an example of this concept.



Figure 4.9: CPU instructions in memory. The question marks simply indicate that we do not know what the actual
bits of these instructions look like, but we assume they are instructions as marked in the figure. We see that a few
instructions, after the first instruction of main, we come to a function call instruction, which modifies the instruction
pointer to point to the first instruction of some function also in memory. The flow of execution thus flows into this
function, and the function code is executed. The last instruction of the function modifies the instruction pointer again,
this time setting it back to the instruction that followed the original function call instruction. Thus we observe the
flow of execution into a function and then back out of a function.

The r, a
ore elaborate explanation is best left to a course on computer architecture and machine language. The
y point to remember is that a function lives in memory and thus has a memory address. Therefore, we
can have pointers to functions.
ike re al level of indirection instead of
alling the function directly, so let us look at an example.
preceding paragraph gave a simplified explanation of what occurs “behind the scenes.” Howeve
m
ke

4.7.1 The Uses of Function Pointers
L
c
gular pointers, it may not be obvious why we need an addition

136
Later in this course, we will introduce Windows programming; that is, creating programs with menus,
dialog boxes, etc—no more console window! One of the things that we will learn is that Windows
rogramming is fundamentally different from how we currently write our programs. In particular,
Window en. A Windows program constantly scans for events such as mouse
clicks, tions, and so on. When an event occurs, the program needs to respond to

it. Eac ogram generally responds differently to a specific event. This is one of the
ature hat makes the programs different from each other. For example, a game responds to keyboard
than a word processor does.

Window (the ccurs, Windows
needs to call a An event handler is a function that
contain the c event. Because each program generally
handles an eve enerally be different for each program. Therefore,
each W n event handler function, and then registers a pointer to this
functio ith , via the function
ointer hen an event occurs. Figure 4.10 demonstrates this concept.
p
s programs are event driv
key presses, menu selec
ifferent Windows prh d
s tfe
input much differently
s operating system) is constantly checking for events. When an event o
function to handle the event, called an event ha
ode that should be executed in response to an
ndler.
s
nt differently, the event handler will g
indows program defines its ow
n w
, w
Windows. Consequently, Windows can call this event handler function
p



Figure 4.10: Defining an event handler and registering a pointer to it with Windows. In this way, the internal

4.7.2
To dec


The re
address
Windows code can call this event handling function, via the pointer, when an event occurs.
Function Pointer Syntax
lare a function pointer variable, the following syntax is used:

returnType (*PointerName)(paramType paramName, )
turn type and parameter listing of the function pointer must match that of the function whose
is being assigned to the function pointer. Consider this example:


137
float Square(float x)
{
return x * x;
}


pointer to Square. Note address of operator (&)
// is not necessary for function pointers.
float x) = Square;


Note that a function pointer can be invoked without dereferencing.


4.8 Summary
1. A reference is essentially an alias for a variable. Given a reference R to a variable A, we can
dire ince R refers to A. Using references we can, for example, return
multiple return values from a function.

2. A pointer type that can store the memory address of another variable. Given
a p actual variable to which it points can be accessed and modified by
deref multiple return values can be returned from a
function, arrays can be efficiently passed to functions, and dynamic memory can be used.
converted to a pointer to the first element in the array. Given a
etic.
4. Dynamic memory allows the creation and destruction of memory at runtime (while the program
cate memory, use the C++

oid memory leaks, every new
delete/delete[] operation.
tor instead of dynamic memory. std::vector
eventing accidental memory leaks. Moreover, by
nage and
ode needs to call a section of your
code. For example, Windows may need to call your event handler function.

int main()
{
// Get a
float (*squarePtr)(

// Call Square via pointer:
cout << "squarePtr(2.0f) = " << squarePtr(2.0f) << endl;

}

ctly access A with R s
is a special variable
ointer to a variable, the
erencing the pointer. By using pointers

3. In C++, the array name can be
pointer to its first element, an array can be navigated by using pointer arithm


is running) in order to meet the current needs of the program. To allo
new operator and to destroy it, use either the delete operator for non-array pointers or the
delete[] operator for array pointers. Remember that to av
operation should eventually have a corresponding

5. If a resizable array is required, use
std::vec
handles the dynamic memory for you, thus pr
using
std::vector, less code is required and the program becomes easier to ma
maintain.

6. Function pointers are useful when a third party section of c

138
4.9 Exercises
s



e) Pointer arithmetic:
ory:

3. oes it do?

eaks) and easier alternative to dynamic memory?
4.9.1 Essay Question
1. Explain in your own words and using complete sentences what the following terms are:

a) References:
b) Constant References:

c) Pointers:

d) Constant Pointers:

f) Static Mem
g) Dynamic Memory

h) Runtime

2. State three benefits of pointers.

What is the symbol for the “address of” operator and what d

4. What is the symbol of the “indirection operator” and when is it used?

5. How are references probably implemented “behind the scenes?”

6. Why is dynamic memory useful?


7. Explain how arrays are passed into functions.

emory l8. What is a safer (i.e., avoids m

9. Explain a situation where function pointers might be useful.



139
4.9.2 Dice Function
ction that returns two random numbers, both in the range [1,
6]. Implement the function two times: once using references, and a second time using pointers. Your
function declarations should like this:
After you have implemented and tested this function, write a small craps-like gambling game that allows
e user to place bets on the dice roll outcomes. You are free to make up your own game rules.
rray Fill
Write a function called RandomArrayFill that inputs (i.e., takes a parameter) an integer array, and
also es a parameter) is
then to write a rando array. The function
eclaration of this function should look like so:
ow wri a program that asks the user to input the size of an integer array. The program then needs to
reate an array of exactly this size. Next, the program must pass this created array to the
of the array. After
output
create: 6
with random numbers
rray = {57, 23, 34, 66, 2, 96}
Press any key to continue
r you h plementing the above function, rewrite it again, but this time using

std::vector.
he function declaration of this new function should look like so:
Write a dice rolling function; that is, a fun

void Dice(int& die1, int& die2);
void Dice(int* die1, int* die2);


th

4.9.3 A
that inputs (i.e., tak an integer, which contains the size of the array. The function
m number, in the range [0, 100], to each element of the
d


void RandomArrayFill(int* array, int size);

N te
c
RandomArrayFill function, so that a random number is assigned to each element
hich, the program must output every array element to the console window. Your programw
should look similar to this:

Enter the size of an array to
Creating array and filling it
A

Afte finis im
T


andomArrayFill(std::vector& vec); void R






140
4.9.4 Quadratic Equation
Background Info

Recall that the standard form of a quadratic equation is given by:
Here, a, b, and c are called the coefficients of the quadratic equation. Geometrically, this describes a
arabola—Figure 4.11. Often we want to find the values of x where the parabola intersects the x-axis;
at is, find x such that
. These values of x are called the roots of the quadratic

cbxaxy ++=
2
.

p
0
2
=++= cbxaxy
th
equation.



Figure 4.11: Parabola with a 2-unit scale, a horizontal translation of 3 units, and a vertical
translation of –4 units. The roots are approximately 1.58 and 4.41.

()
432
2
−−= xy



141
Conveniently, a mechanical formula exists that allows
formula, called the quadratic formula, is as follows:

us to find the roots of a quadratic equation. This
a
acbb 4
2
−±−
x
2
=


To be sure that you understand how this equation works we will do some examples.

xample 1E : Find the roots of the following quadratic equation: .
6
2
−− xx


Using the quadratic formula we have:

()( )
2
51
2
251
12
61411
2
±
=
±
=

−−±
=
−±− c

2
4
=
a
abb
x

So, and .
xample 2
3

1
=x 2
2
−=x

E : Find the roots of the following quadratic equation: .

Using the quadratic formula we have:
12
2
+− xx

()()
1
2
02
12
11442
2
4
2
=
±
=

−±
=
−±−
=
a

acbb
x


So,
.
xample 3
1
21
== xx

E : Find the roots of the following quadratic equation: .

sing the quadratic formula we have:
52
2
++ xx
U

()
(
)
2
162
12
51442
2
4
2
−±−

=

−±−
=
−±−
=
a
acbb
x


Recalling that
we obtain:

1
2
−=i

i
ii
21
2
42
2
162
2
162
2
±−=
±−

=
±−
=
−±−


So, and .

dratic equation can contain imaginary numbers; a
a real and an imaginary component is termed a complex number.

ix 21
1
+−= ix 21
2
−−=
Observe in example three that the roots to the qua
number that includes both

142
Exercise

Write a function that inputs (i.e., takes as parameters) the coefficients of a quadratic equation, and
outputs the result. The function should return two solutions with two parts: 1) A real part and 2) an
imaginary part. Of course, if a solution does not have an imaginary part (or real part, for that matter)
then the corresponding component will just be zero (e.g.,
i20
+
,
i03

+
). The function should be
prototyped as follows:

bool QuadraticFormula(float a, float b, float c,
float& r1, float& i1, float& r2, float& i2);

ts of the quadratic equation. And where r1 denotes the real part of
solution 1 and where
i1 denotes the imaginary part of solution 1. Likewise, r2 denotes the real part of
solu n

Not h
ermined as follows: If the function contains
n i aginary part th
false true. We do this because some applications
ight not want to work with non-real results (i.e., results that have imaginary parts), and such
application can easily test for a non-real result by examining the return value of this function.
above three examples.
Your output should be formatted similar to this:

Where a, b, and c are the coefficien
tio 2 and i2 denotes the imaginary part of solution 2.
e t at the return type is a
bool. The return v
m en return
, otherwise return
alue is det
a
m


Test your function with the coefficients given in the quadratic equations from the

Coefficients a=1, b=2, c=5 yield S1 = -1 + 2i, and S2 = -1 – 2i






143

Chapter 5


Classes and Object Oriented
Programming








Introduction

144

Thus far we have been using intrinsic C++ variable types such as

bool, char, int, float, arrays, etc,
rary
std::string type, to represent simple quantities. These types work well to
s names and numbers. However, many real-world objects which need to be
as an aggregate set of various intrinsic types (e.g., a game player has many
atistic s a name, health, armor, etc). Moreover, many real-world objects can
orm actions. A game player can run and fire his/her weapon, for example. The primary theme of
is chapter is to learn how to create complex types called classes, which will enable the creation of
er Objectives
ct oriented programming attempts to solve.
members of that class.
ss design strategies.
ted Programming Concepts
std::string name;


float positionX;
armor;

logically group the variables associated
represented by variables, objects
ns. These actions are usually specific to a particular type of object. For
ample, a player in a role-playing game might be able to perform actions such as
fight, talk,
ample, a function called
oreover, these functions
ould have access to the player’s data properties so that, for example, hit points could be decreased whn
e player was injured, magic points increased when a new level is attained, and so on.
of data (properties) and perform actions (functions). The key idea of object-
ervation in code so that new variable types can be defined

ich c at behave like real-world objects. These new variable types
or this style of programming is four-fold.
and the standard lib
describe trivial things such a
escribed in code are built d
st al properties such a
perf
th
variables which consist of several components and can perform actions.
Chapt
• Understand the problems obje
nd instantiate• Define a class a
• Learn some basic cla

5.1 Object Orien
Many real-world objects are described by a multitude of data members. For example, a player in a game
might have several properties:

int hitPoints;
int magicPoints;

float positionY;
std::string weaponName;
int weaponDamage;
int

Because there may be many players in a game, we would like to
h each player via one name. In addition to an object’s properties,wit
can also perform certain actio
ex

castSpell, etc. These verbs can be represented in code with functions; for ex
ght would execute the code necessary to perform a combat simulation. Mfi
w
th

Real-world objects consist
oriented programming is to model this obs
an be used to instantiate variables thwh
are called classes. The motivation f

145


Fir
in
st, i ral for humans to think in terms of real objects than it is to
k in terms of computer instructions and memory. This is especially true in large and complex
natural organizational system. When creating these
sses, all the related code associated with the class is organized in a tight code unit. Instead of having
Finally
facilities lik tes. These topics are covered towards the end of
this bo

5.2 C
A class ew variable type. For example, we can define a class called Wizard. We
can the
the same way we instantiate variables of the
intrinsi

Wizard type Wizard.


Note th the class. The class definition states to
the com oes not create an object. In the above
exampl
is the object (i.e., an instance of class Wizard).

A conc used to explain the difference between a class and an object is a
blueprint. A house blueprint, for example, specifies how a house would be made, but it is not a house
itself. The actual houses built based on that blueprint would be called objects (i.e., instances) of that
bluepri variable itself. The
actual variables built based on that class would be called objects (i.e., instances) of that class.
5.2.1 Syntax
In general, a class is defined with the following syntax:
t is intuitive. It is much more natu
th
systems.

Second, object-oriented programming provides a
cla
a lot of data variables scattered throughout the code and manipulating that data through functions, we
obtain a much more self-contained code unit of related code through classes.

Third, data can be encapsulated with object-oriented programming. The class writer can enforce which
parts of the class are visible to outside code, and which parts should be kept purely internal. In this way,
the class writer prevents users of the class from accidentally making destructive modifications to internal
class data.

, object-oriented programming enables a higher level of code reuse and generalizations through
e inheritance, polymorphism, and templa
ok.

lasses
allows us to define a n
n instantiate
Wizard objects (i.e., instances) in
r example, we will be able to write: c types. Fo
wiz0; //Instantiate a variable called wiz0 of
at the definition of a class is different than an instance of
ject of the class has, but it dpiler what properties an ob
e,
Wizard is the class and wiz0
eptual analogy which is often
nt. Similarly, a class specifies how a variable would be made, but it is not a


146


class ClassName
{
// Methods

// Data members
};


For example, we might define our aforementioned
Wizard class like so:
d fight(
d talk();
ethods (also called member functions) are the class functions, which specify the actions which

izard class, we define a Wizard to have a name, hit
oints, magic points, and armor. Thus we have built a complex
Wizard type out of simpler components.
Note: We prefix the data members of a class with ‘m’ (for member) so that we can readily distinguish
d not required.
fter nted. Method
utside the class definition scope and follow this general
eturnType ClassName::MethodName(ParameterList )
oid Wizard::fight()


}

void W

lass Wizard c
{
public:
// Methods
voi );
voi
void castSpell();

// Data members
std::string mName;
int mHitPoints;
int mMagicPoints;
int mArmor;
};



M
objects of this class can perform. Data members are the variable components that together form a more
complex type, such as a
Wizard. In the above W
p

them from non-class member variables; this is purely for notation an

class methods still need to be implemeA our class has been defined, the
implementations (i.e., definitions) occur o
syntax:

r
{
Body Code
}


To illustrate a simple example, we implement the functions of
Wizard, like so:




v
{
cout << "Fighting." << endl;
izard::talk()


147

{

}

void Wizard::castSpell()
{
< endl;
}


The on
method
The sco in the same sense in which it was used with namespaces. Recall
that the prefix told the compiler that the standard library code we need belongs to the standard
namesp e use the scope resolution operator to specify the class to which
e met
Note: It is possible to define methods inside the class instead of outside. For example, we could
class Wizard
{
}
ot Operator
cout << "Talking." << endl;
cout << "Casting Spell." <
ly syntactic difference between a method definition and a “regular” function definition is that the
name must be prefixed with the class name followed by the scope resolution operator (
::).
pe resolution operator is used
std::

ace. Similarly with classes, w
hod belongs. th

implement the
Wizard methods like so:

public:
// Methods
void fight()
{
cout << "Fighting." << endl;
void talk()
{
cout << "Talking." << endl;
}
void castSpell()
{
cout << "Casting Spell." << endl;
}

// Data members
std::string mName;
int mHitPoints;
int mMagicPoints;
int mArmor;
};

However, for reasons Section 5.2.3 will give, this is usually considered bad form.

5.2.2 Class Access: The D


148

In Section 5.2.1 we defined and implemented a
Wizard class. This class is now ready to be used. Let
ct:
iz0;
we have a
object created, we want it to perform actions. So let us call some of its
thod the dot operator (.) is used as follows:
that was used to resize a vector. This is because std::vector is
executes the necessary code to resize the
e methods are associated with a particular object—that is, we invoke them through an object with
ay ask whether a method can access the data members of the calling object. For
xample, when
wiz0.fight() is called, and a fight simulation begins, can fight() access the armor
wiz0? It would seem reasonable that it could, since
a part of the object as the data members are. Indeed, a member function can access
lling object. We will see many examples of how this is done throughout this
w it actually works in the next chapter.
s to be read or modified. To access a data member, the dot
rato ws:
0;
wiz0.mName << endl;
// Test to see if player has enough magic points to cast a spell
s > 4 )
l();

members. For example, if we have several Wizard objects
e, number of hit points, and magic points. In other words,

cts are completely independent from each other.
e identify different
at array using the bracket operator ([]). With objects, we identify different
ata members of that object with the dot operator, followed by the method or data member
e.
te pointers to class types in the same way that we create pointers to intrinsic types.
r to an object? For example, suppose we create a wizard instance like so:
us first instantiate an obje

Wizard w

Now that
Wizard
methods. To invoke a class me

wiz0.fight();
wiz0.talk();

Note that this is the same dot operator
method of that class, whichactually a class and resize is a
vector.

incS
the dot operator—we m
e
property
mArmor or the mHitPoints properties of
fight() is as much
the data members of the ca
apter, and we will see hoch


Suppose now a data member of
wiz0 need
r is also used, as this next snippet shoope


wiz0.mArmor = 1

cout << "Player's name = " <<


if( wiz0.mMagicPoint
wiz0.castSpel
else
cout << "Not enough magic points!" << endl;

Note that each object has its “own” data
wiz0, wiz1,…, they each have their own nam
jethe data members of ob

of the dot operator as a subscripting symbol. In an array wIt may be helpful to think
elements that belong to th
thods and dme
amn

Note: We can crea
What if we have a pointe

Wizard* wiz0 = new Wizard;


149


rence the pointer to get the variable,
must come before the dot operation.

rship operator (->):

5.2.3 Header Files; Class Definitions; Class Implementations
One of the goals of C++ is to separate class definitions from implementation. The motivation behind
this is that a programmer should be able to use a class without knowing exactly how it works (i.e.,
you may be using classes you did not
rite yourself—for example, when you learn DirectX, you will use classes that Microsoft wrote.
lass definitions from implementation, two separate files are used: header files (.h)
les (.cpp). Header files include the class definition, and implementation files
entation (i.e., method definitions). Eventually, the source code file is compiled
compiled class implementation, which
To invoke a method or access a data member we must first derefe
and then we can use the dot operator as normal:

(*wiz0).fight();
agicPoints = 5;

(*wiz0).mM

We use the parentheses to specify that the dereference operation
However, this syntax is considered cumbersome, so a shorthand syntax was developed. When using a
pointer to an object, we can invoke a member function or access a data member using the indirect
membe
wiz0->fight();

wiz0->mMagicPoints = 5;

without knowing the implementation details). This occurs when
w

In order to separate c
and implementation fi
ontain the class implemc
to either an object file (.obj) or a library file (.lib). Once that is done, you can distribute the class header
file along with the object file or library file to other programmers. With the header file containing the
lass definition, and with the object or library file containing the c
is linked into the project with the linker, the programmer has all the necessary components to use the
class without ever having to see the implementation file (that is, the .cpp file). For example, the DirectX
Software Development Kit includes only the DirectX header files and the DirectX library files—you
never see the implementation files (.cpp).

In summary, given the header file and object file or library file, other programmers are able to use your
class without ever seeing how it was implemented in the source code file. This does two things: first,
others will not be able to modify the implementation, and second, your implementation (intellectual
property) is protected.

To illustrate, let us rewrite the
Wizard class and its implementation, but this time using header files and
source code files.

// Wiz.h (Wizard header file.)

#ifndef WIZARD_H
#define WIZARD_H


150


#include <iostream>
#include <string>

class Wizard
{
public:
// Methods
void fight();
void talk();
void castSpell();

// Data members
std::string mName;
int mHitPoints;
int mMagicPoints;
int mArmor;
};

// WIZARD_H #endif

// Wiz.cpp (Wizard implementation file.)

#include "wiz.h"
using namespace std;

void Wizard::fight()
{

cout << "Fighting." << endl;
}

void Wizard::talk()
{
cout << "Talking." << endl;
}

void Wizard::castSpell()
{
Casting Spell." << endl; cout << "
}

// Main.cpp (The file with main.)

#include <iostream>
#include <string>
#include can declare Wizard objects. "wi so we z.h" //< Include
using namespace std;

int main()
{
Wizard wiz0; // Declare a variable called wiz0 of type Wizard.

wiz0.fight();
wiz0.talk();

wiz0.mArmor = 10;

151



cout << "Player's name = " << wiz0.mName << endl;

// Test to see if player has enough magic points to cast a spell
if( wiz0.mMagicPoints > 4 )
wiz0.castSpell();
else
cout << "Not enough magic points!" << endl;
}

Throughout the rest of this course we will separate our class definition from its implementation into two
separate files.

Note: A segment of code that uses a class is called a client of the class. For example,
Main.cpp
is a
client file of
Wizard because it uses objects of that class.

Note: When including header files in the project which you have written, such as
Wiz.h
, use quotation
marks in the include directive instead of the angle brackets (e.g., “Wiz.h”)
5.2.2.1 Inclusion Guards
Observe that in Wiz.h our class is surrounded with the following:

#ifndef WIZARD_H
#define WIZARD_H



#endif // WIZARD_H

This is called an inclusion guard. These statements are called preprocessor directives and they direct
the compiler on how to compile the code.

The first line,
#ifndef WIZARD_H, has the compiler ask if a symbol called WIZARD_H has not been
already defined. If it has not been defined then the code between the #ifndef and the #endif will be
compiled. Here the code contained between this “compiler if statement” is the class and another
preprocessor directive #define WIZARD_H. The directive #define WIZARD_H tells the compiler to
define a symbol called
WIZARD_H. Thus, the first time the compiler sees Wiz.h in a translation unit,
WIZARD_H is not defined, so the class will be compiled, and WIZARD_H will also be defined. Any other
time the compiler sees Wiz.h in a translation unit,
WIZARD_H will already be defined and thus the class
will not be recompiled again. This prevents a redefinition of the class, which is desirable because it only
needs to be compiled once per translation unit.

But why would the same class be included more than once in a translation unit? Consider the
Wizard
class file breakdown Wiz.h, Wiz.cpp, and Main.cpp. We include iostream and string in Main.cpp, but
Main.cpp also includes Wiz.h, which in turn includes iostream and string as well. Thus iostream and
string would be included twice in the translation unit Main.cpp, which would cause a class redefinition
error. However, iostream and string contain the above mentioned inclusion guards, thereby preventing

152

them from being compiled more than once, and thus preventing a redefinition error. This same scenario
can occur with our own classes as well, so we too need inclusion guards.

5.2.4 Data Hiding: Private versus Public
C++ class writers can enforce which parts of the class are visible to outside code, and which parts should
be kept purely internal. To do this, C++ provides two keywords: public and private. All code in a
class definition following a public keyword and up to a private keyword is considered public, and
all code in a class definition following a
private keyword up to a public keyword is considered
private. Note that classes are private by default.

Note: The public and private keywords are used to enforce which parts of the class are visible to
outside
code. A class’s own member functions can access all parts of the class since they are
inside
the
class, so to speak.

The public portion of a class is referred to as the class-interface because that is the interface exposed to
other code units. The private portion of a class is considered to be part of the class implementation,
because only the internal implementation has access to it.

The general rule is that data members (class variables) should be kept internal (private) and only
methods should be exposed publicly. By making the programmer go through member functions, the
class can safely regulate how the internal class data can be accessed and manipulated. Hence, the class
can maintain the integrity of its internal data. Why does the integrity need to be maintained? Because
the class methods most likely have some expectations about the data, and by going through member
functions it can enforce these expectations.

Using this general rule, our Wizard class can be rewritten like so:

class Wizard
{

public:
// Methods
void fight();
void talk();
void castSpell();

private:
// Data members
std::string mName;
int mHitPoints;
int mMagicPoints;
int mArmor;
};


However, now the data members cannot be accessed and the code fails to compile:


wiz0.mArmor = 10;


153

cout << "Player's name = " << wiz0.mName << endl;

// Test to see if player has enough magic points to cast a spell
if( wiz0.mMagicPoints > 4 )
wiz0.castSpell();
else
cout << "Not enough magic points!" << endl;



The errors generated are:
“error C2248: 'Wizard::mArmor' : cannot access private member declared in class 'Wizard',”
“error C2248: 'Wizard::mName' : cannot access private member declared in class 'Wizard',”
“error C : cannot access private member declared in class 'Wizard'.”

This should not be surprising as these variables were just made private and cannot be accessed directly.

To solve this problem, a similar, but safer, functionality must be provided via the class interface. First,
we will add a
setArmor method, which allows a client to set the armor. It is implemented
like so:

void Wizard::setArmor(int armor)
{
if( armor >= 0 )
mArmor = armor;
}


Here th
t we said member functions can
access
wiz0.setArmor(10);

We are setting the
mArmor member of wiz0 to ten.

Why is

Wizard::setArmor “better” than making mArmor public? By forcing the client to go through
Wizard::setArmor, we can add our own safety checks to maintain data integrity. For example, in our
Wizard armor was nonnegative.

Next, a way for the client to get t
irect access to the name must be
provided. This is done by making a function that returns a copy of the name. Thus the client cannot
modify the internal name, but only a copy. This function is implemented like so:

std::string Wizard::getName()
{
return mName;

Finally, the
mMagicPoints data member must be considered. In actuality, the client should not be
getting access to this value. The class itself can test whether or not the wizard has enough magic points
internally. We rewrite
Wizard::castSpell like this:

2248: 'Wizard::mMagicPoints'
Wizard::
e member function sets the data member
mArmor (remember tha
the class data). Thus if we write:

::setArmor implementation, we added a check to make sure
he name of a
Wizard without giving d
}



154

×