+ All Categories
Home > Documents > C++ Módulo I e II

C++ Módulo I e II

Date post: 30-Aug-2014
Category:
Upload: neemias-gabriel
View: 29 times
Download: 0 times
Share this document with a friend
Popular Tags:
629
C++ Programming for Games Module I e-Institute Publishing, Inc. 1
Transcript
Page 1: C++ Módulo I e II

C++ Programming for Games

Module I

e-Institute Publishing, Inc.

1

Page 2: C++ Módulo I e II

©Copyright 2004 e-Institute, Inc. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system without prior written permission from e-Institute Inc., except for the inclusion of brief quotations in a review. Editor: Susan Nguyen Cover Design: Adam Hoult E-INSTITUTE PUBLISHING INC www.gameinstitute.com Frank Luna, Game Institute Faculty, C++ Programming for Games All brand names and product names mentioned in this book are trademarks or service marks of their respective companies. Any omission or misuse of any kind of service marks or trademarks should not be regarded as intent to infringe on the property of others. The publisher recognizes and respects all marks used by companies, manufacturers, and developers as a means to distinguish their products. E-INSTITUTE PUBLISHING titles are available for site license or bulk purchase by institutions, user groups, corporations, etc. For additional information, please contact the Sales Department at [email protected]

2

Page 3: C++ Módulo I e II

Table of Contents CHAPTER 1: INTRODUCING C++.......................................................................................................................................9

INTRODUCTION ......................................................................................................................................................................10 CHAPTER OBJECTIVES ...........................................................................................................................................................10 1.1 GETTING STARTED—YOUR FIRST C++ PROGRAM ..........................................................................................................10

1.1.1 Creating the Project................................................................................................................................................10 1.1.2 Adding A .CPP File to the Project ..........................................................................................................................12 1.1.3 Writing the Code .....................................................................................................................................................13 1.1.4 Compiling, Linking, and Executing.........................................................................................................................14

1.2 THE “PRINT STRING” PROGRAM EXPLAINED ...................................................................................................................16 1.2.1 Comments................................................................................................................................................................16 1.2.2 White Space.............................................................................................................................................................17 1.2.2 Include Directives ...................................................................................................................................................18 1.2.3 Namespaces.............................................................................................................................................................18 1.2.4 The main... Function.....................................................................................................................................20 1.2.5 std::string ................................................................................................................................................................20 1.2.6 Input and Output with std::cin and std::cout ............................................................................................20

1.3 VARIABLES ......................................................................................................................................................................21 1.3.1 Variable Declarations and Definitions ...................................................................................................................24 1.3.2 Variable Names.......................................................................................................................................................25 1.3.3 The sizeof Operator ...........................................................................................................................................25 1.3.4 The unsigned Keyword .......................................................................................................................................26 1.3.5 Literal Assignments.................................................................................................................................................27 1.3.6 Type Conversions ....................................................................................................................................................27 1.3.7 Typedefs ..............................................................................................................................................................30 1.3.8 Const Variables.......................................................................................................................................................30 1.3.9 Macros ....................................................................................................................................................................30

1.4 ARITHMETIC OPERATIONS ...............................................................................................................................................31 1.4.1 Unary Arithmetic Operations..................................................................................................................................32 1.4.2 Binary Arithmetic Operations .................................................................................................................................33 1.4.3 The Modulus Operator............................................................................................................................................34 1.4.4 Compound Arithmetic Operations ..........................................................................................................................35 1.4.5 Operator Precedence ..............................................................................................................................................36

1.5 SUMMARY........................................................................................................................................................................37 1.6 EXERCISES .......................................................................................................................................................................38

1.6.1 Arithmetic Operators ..............................................................................................................................................38 1.6.2 Cin/Cout ..................................................................................................................................................................38 1.6.3 Cube ........................................................................................................................................................................38 1.6.4 Area/Circumference ................................................................................................................................................39 1.6.5 Average ...................................................................................................................................................................39 1.6.6 Bug Fixing...............................................................................................................................................................39

CHAPTER 2: LOGIC, CONDITIONALS, LOOPS AND ARRAYS .................................................................................41 INTRODUCTION ......................................................................................................................................................................41 CHAPTER OBJECTIVES: ..........................................................................................................................................................42 2.1 THE RELATIONAL OPERATORS ........................................................................................................................................42 2.2 THE LOGICAL OPERATORS...............................................................................................................................................44 2.3 CONDITIONAL STATEMENTS: IF, IF…ELSE ......................................................................................................................48

2.3.1 The If Statement ......................................................................................................................................................49 2.3.2 The Else Clause.......................................................................................................................................................50 2.3.3 Nested If…Else Statements......................................................................................................................................51 2.3.4 The Switch Statement ..............................................................................................................................................53 2.3.5 The Ternary Operator .............................................................................................................................................55

3

Page 4: C++ Módulo I e II

2.4 REPETITION......................................................................................................................................................................56 2.4.1 The for-loop.............................................................................................................................................................56 2.4.2 The while Loop.....................................................................................................................................................58 2.4.3 The do…while Loop..............................................................................................................................................60 2.4.4 Nesting Loops..........................................................................................................................................................61 2.4.5 Break and Continue Keywords................................................................................................................................62

2.5 ARRAYS ...........................................................................................................................................................................63 2.5.1 Array Initialization ..................................................................................................................................................64 2.5.2 Iterating Over an Array...........................................................................................................................................64 2.5.3 Multidimensional Arrays .........................................................................................................................................65

2.6 SUMMARY........................................................................................................................................................................67 2.7 EXERCISES .......................................................................................................................................................................68

2.7.1 Logical Operator Evaluation ..................................................................................................................................68 2.7.2 Navigator.................................................................................................................................................................68 2.7.3 Average....................................................................................................................................................................69 2.7.4 Factorial..................................................................................................................................................................69 2.7.5 Matrix Addition .......................................................................................................................................................70 2.7.6 ASCII .......................................................................................................................................................................71 2.7.7 Linear Search ..........................................................................................................................................................71 2.7.8 Selection Sort...........................................................................................................................................................73

CHAPTER 3: FUNCTIONS ...................................................................................................................................................75 INTRODUCTION ......................................................................................................................................................................75 3.1 USER DEFINED FUNCTIONS ..............................................................................................................................................78

3.1.2 Functions with One Parameter ...............................................................................................................................80 3.1.3 Functions with Several Parameters.........................................................................................................................82

3.2 VARIABLE SCOPE .............................................................................................................................................................83 3.2.1 Example 1................................................................................................................................................................83 3.2.2 Example 2................................................................................................................................................................85 3.2.3 Example 3................................................................................................................................................................86

3.3 MATH LIBRARY FUNCTIONS ............................................................................................................................................87 3.4 RANDOM NUMBER LIBRARY FUNCTIONS.........................................................................................................................88

3.4.1 Specifying the Range ...............................................................................................................................................91 3.5 FUNCTION OVERLOADING................................................................................................................................................92

3.5.1 Default Parameters .................................................................................................................................................94 3.6 SUMMARY........................................................................................................................................................................96 3.7 EXERCISES .......................................................................................................................................................................97

3.7.1 Factorial..................................................................................................................................................................97 3.7.2 ToUpper; ToLower..................................................................................................................................................97 3.7.3 3D Distance.............................................................................................................................................................98 3.7.4 Arc Tangent 2 ..........................................................................................................................................................99 3.7.5 Calculator Program ..............................................................................................................................................100 3.7.6 Slot Machine..........................................................................................................................................................101 3.7.7 Binary Search ........................................................................................................................................................102 3.7.8 Bubble Sort ............................................................................................................................................................103

CHAPTER 4: REFERENCES AND POINTERS...............................................................................................................107 INTRODUCTION ....................................................................................................................................................................108 CHAPTER OBJECTIVES .........................................................................................................................................................108 4.1 REFERENCES ..................................................................................................................................................................108

4.1.1 Constant References ..............................................................................................................................................110 4.2 POINTERS .......................................................................................................................................................................111

4.2.1 Computer Memory Primer ....................................................................................................................................111 4.4.2 Pointer Initialization .............................................................................................................................................112 4.4.3 Dereferencing........................................................................................................................................................114

4.3 ARRAYS REVISITED........................................................................................................................................................117

4

Page 5: C++ Módulo I e II

4.3.1 Pointer to the Beginning of an Array ....................................................................................................................117 4.3.2 Pointer Arithmetic.................................................................................................................................................118 4.3.1 Passing Arrays into Functions ..............................................................................................................................120

4.4 RETURNING MULTIPLE RETURN VALUES ......................................................................................................................122 4.4.1 Returning Multiple Return Values with Pointers ..................................................................................................122 4.4.2 Returning Multiple Return Values with References ..............................................................................................124

4.5 DYNAMIC MEMORY.......................................................................................................................................................125 4.5.1 Allocating Memory................................................................................................................................................126 4.5.2 Deleting Memory...................................................................................................................................................127 4.5.3 Memory Leaks .......................................................................................................................................................127 4.5.4 Sample Program ...................................................................................................................................................128

4.6 STD::VECTOR..................................................................................................................................................................132 4.7 FUNCTION POINTERS......................................................................................................................................................135

4.7.1 The Uses of Function Pointers ..............................................................................................................................136 4.7.2 Function Pointer Syntax........................................................................................................................................137

4.8 SUMMARY......................................................................................................................................................................138 4.9 EXERCISES .....................................................................................................................................................................139

4.9.1 Essay Questions ....................................................................................................................................................139 4.9.2 Dice Function........................................................................................................................................................140 4.9.3 Array Fill...............................................................................................................................................................140 4.9.4 Quadratic Equation...............................................................................................................................................141

CHAPTER 5: CLASSES AND OBJECT ORIENTED PROGRAMMING.....................................................................144 INTRODUCTION ....................................................................................................................................................................144 CHAPTER OBJECTIVES .........................................................................................................................................................145 5.1 OBJECT ORIENTED PROGRAMMING CONCEPTS ..............................................................................................................145 5.2 CLASSES ........................................................................................................................................................................146

5.2.1 Syntax....................................................................................................................................................................146 5.2.2 Class Access: The Dot Operator...........................................................................................................................148 5.2.3 Header Files; Class Definitions; Class Implementations .....................................................................................150 5.2.2.1 Inclusion Guards................................................................................................................................................152

5.2.4 DATA HIDING: PRIVATE VERSUS PUBLIC ....................................................................................................................153 5.2.5 Constructors and Destructors ...............................................................................................................................155 5.2.6 Copy Constructors and the Assignment Operator.................................................................................................157

5.3 RPG GAME: CLASS EXAMPLES......................................................................................................................................158 5.3.1 The Range Structure..............................................................................................................................................158 5.3.2 Random Functions ................................................................................................................................................159 5.3.3 Weapon Class........................................................................................................................................................159 5.3.4 Monster Class .......................................................................................................................................................160 5.3.5 Player Class ..........................................................................................................................................................165 5.3.6 Map Class .............................................................................................................................................................173

5.4 THE GAME .....................................................................................................................................................................177 5.4.1 Segment 1 ..............................................................................................................................................................179 5.4.2 Segment 2 ..............................................................................................................................................................180 5.4.3 Segment 3 ..............................................................................................................................................................180 5.4.4 Segment 4 ..............................................................................................................................................................182

5.5 SUMMARY......................................................................................................................................................................183 5.6 EXERCISES .....................................................................................................................................................................184

5.6.1 Gold Modification .................................................................................................................................................185 5.6.2 Character Races....................................................................................................................................................185 5.6.3 Leveling Up...........................................................................................................................................................185 5.6.4 Magic Points .........................................................................................................................................................185 5.6.5 Random Encounters During Rest ..........................................................................................................................186 5.6.6 A Store...................................................................................................................................................................186 5.6.7 Items......................................................................................................................................................................187 5.6.8 Multiple Enemies...................................................................................................................................................187

5

Page 6: C++ Módulo I e II

CHAPTER 6: STRINGS AND OTHER TOPICS ..............................................................................................................188 INTRODUCTION ....................................................................................................................................................................189 CHAPTER OBJECTIVES .........................................................................................................................................................189 6.1 CHAR STRINGS.................................................................................................................................................................189 6.1 STRING LITERALS...........................................................................................................................................................191 6.2 ESCAPE CHARACTERS ....................................................................................................................................................192 6.2 C-STRING FUNCTIONS....................................................................................................................................................193

6.2.1 Length....................................................................................................................................................................193 6.2.2 Equality .................................................................................................................................................................194 6.2.3 Copying .................................................................................................................................................................195 6.2.4 Addition .................................................................................................................................................................195 6.2.7 Formatting.............................................................................................................................................................196

6.3 STD::STRING ...................................................................................................................................................................199 6.3.1 Length....................................................................................................................................................................199 6.3.2 Relational Operators .............................................................................................................................................200 6.3.3 Addition .................................................................................................................................................................201 6.3.4 Empty Strings ........................................................................................................................................................201 6.3.5 Substrings ..............................................................................................................................................................202 6.3.6 Insert......................................................................................................................................................................203 6.3.7 Find .......................................................................................................................................................................204 6.3.8 Replace ..................................................................................................................................................................204 6.3.9 Bracket Operator...................................................................................................................................................205 6.3.10 C-String Equivalent .............................................................................................................................................205 6.3.11 getline ............................................................................................................................................................206

6.4 THE THIS POINTER .........................................................................................................................................................208 6.5 FRIENDS .........................................................................................................................................................................211

6.5.1 Friend Functions ...................................................................................................................................................211 6.5.2 Friend Classes.......................................................................................................................................................212

6.6 THE STATIC KEYWORD ..................................................................................................................................................212 6.6.1 Static Variables in Functions ................................................................................................................................212 6.6.2 Static Data Members .............................................................................................................................................213 6.6.3 Static Methods .......................................................................................................................................................214

6.7 NAMESPACES .................................................................................................................................................................215 6.7.1 Variations of the “using” Clause ..........................................................................................................................217

6.8 ENUMERATED TYPES .....................................................................................................................................................218 6.9 SUMMARY......................................................................................................................................................................219 6.10 EXERCISES ...................................................................................................................................................................220

6.10.1 String Reverse......................................................................................................................................................220 6.10.2 To-Upper .............................................................................................................................................................220 6.10.3 To-Lower .............................................................................................................................................................220 6.10.4 Palindrome ..........................................................................................................................................................221

CHAPTER 7: OPERATOR OVERLOADING...................................................................................................................222 INTRODUCTION ....................................................................................................................................................................223 CHAPTER OBJECTIVES .........................................................................................................................................................224 7.1 VECTOR MATHEMATICS.................................................................................................................................................224 7.2 A VECTOR CLASS...........................................................................................................................................................232

7.2.1 Constructors ..........................................................................................................................................................233 7.2.2 Equality .................................................................................................................................................................233 7.2.3 Addition and Subtraction.......................................................................................................................................234 7.2.4 Scalar Multiplication.............................................................................................................................................234 7.2.5 Length....................................................................................................................................................................235 7.2.6 Normalization........................................................................................................................................................235 7.2.7 The Dot Product ....................................................................................................................................................235 7.2.8 Conversion to float Array .................................................................................................................................236

6

Page 7: C++ Módulo I e II

7.2.9 Printing .................................................................................................................................................................237 7.2.10 Inputting ..............................................................................................................................................................237 7.2.11 Example: Vector3 in Action ............................................................................................................................237

7.3 OVERLOADING ARITHMETIC OPERATORS......................................................................................................................240 7.3.1 Operator Overloading Syntax ...............................................................................................................................241 7.3.2 Overloading the Other Arithmetic Operators .......................................................................................................242 7.3.3 Example using our Overloaded Operators ...........................................................................................................243

7.4 OVERLOADING RELATIONAL OPERATORS .....................................................................................................................244 7.5 OVERLOADING CONVERSION OPERATORS .....................................................................................................................246 7.6 OVERLOADING THE EXTRACTION AND INSERTION OPERATORS.....................................................................................247 7.7 A STRING CLASS; OVERLOADING THE ASSIGNMENT OPERATOR, COPY CONSTRUCTOR, AND BRACKET OPERATOR.....250

7.7.1 Construction and Destruction ...............................................................................................................................250 7.7.2 Assignment Operator ............................................................................................................................................251 7.7.3 Copy Constructor ..................................................................................................................................................253 7.7.4 Overloading the Bracket Operator........................................................................................................................254

7.8 SUMMARY......................................................................................................................................................................254 7.9 EXERCISES .....................................................................................................................................................................255

7.9.1 Fraction Class..................................................................................................................................................255 7.9.2 Simple float Array Class...................................................................................................................................256

CHAPTER 8: FILE INPUT AND OUTPUT.......................................................................................................................259 INTRODUCTION ....................................................................................................................................................................260 CHAPTER OBJECTIVES .........................................................................................................................................................260 8.1 STREAMS .......................................................................................................................................................................260 8.2 TEXT FILE I/O................................................................................................................................................................261

8.2.1 Saving Data...........................................................................................................................................................261 8.2.2 Loading Data ........................................................................................................................................................262 8.2.3 File I/O Example...................................................................................................................................................263

8.3 BINARY FILE I/O............................................................................................................................................................268 8.3.1 Saving Data...........................................................................................................................................................268 8.3.2 Loading Data ........................................................................................................................................................269 8.3.3 Examples ...............................................................................................................................................................270

8.4 SUMMARY......................................................................................................................................................................273 8.5 EXERCISES .....................................................................................................................................................................274

8.5.1 Line Count.............................................................................................................................................................274 8.5.2 REWRITE.....................................................................................................................................................................274

CHAPTER 9: INHERITANCE AND POLYMORPHISM ...............................................................................................275 INTRODUCTION ....................................................................................................................................................................276 CHAPTER OBJECTIVES .........................................................................................................................................................276 9.1 INHERITANCE BASICS ....................................................................................................................................................277 9.2 INHERITANCE DETAILS ..................................................................................................................................................284

9.2.1 Repeated Inheritance ............................................................................................................................................284 9.2.2 isa versus hasa ......................................................................................................................................................284 9.2.3 Moving Between the Base Class and Derived Class .............................................................................................285 9.2.4 Public versus Private Inheritance .........................................................................................................................287 9.2.5 Method Overriding................................................................................................................................................288

9.3 CONSTRUCTORS AND DESTRUCTORS WITH INHERITANCE..............................................................................................290 9.4 MULTIPLE INHERITANCE................................................................................................................................................292 9.5 POLYMORPHISM.............................................................................................................................................................292

9.5.1 First Attempt (Incorrect Solution).........................................................................................................................293 9.5.2 Second Attempt (Correct Solution) .......................................................................................................................296

9.6 HOW VIRTUAL FUNCTIONS WORK.................................................................................................................................300 9.7 THE COST OF VIRTUAL FUNCTIONS ...............................................................................................................................302 9.8 ABSTRACT CLASSES ......................................................................................................................................................303 9.9 INTERFACES...................................................................................................................................................................305

7

Page 8: C++ Módulo I e II

9.10 SUMMARY....................................................................................................................................................................307 9.11 EXERCISES ...................................................................................................................................................................308

9.11 Employee Database................................................................................................................................................308 C++ MODULE I CONCLUSION.........................................................................................................................................313

8

Page 9: C++ Módulo I e II

Chapter 1

Introducing C++

9

Page 10: C++ Módulo I e II

Introduction

C++ is a powerful language that unifies high-level programming paradigms, such as object oriented programming, with low-level efficiencies, such as the ability to directly manipulate memory. For these reasons, C++ has been embraced as the language of choice among game developers. C++ fulfills the need for high-level language constructs which aid in the organization of building complex virtual worlds, but is also able to perform low-level optimizations in order to squeeze out extra performance for such things as sophisticated special effects, realistic physics, and complex artificial intelligence.

Chapter Objectives

• Create, compile, link and execute C++ programs. • Find out how C++ code is transformed into machine code. • Learn some of the basic C++ features necessary for every C++ program. • Discover how to output and input text information to and from the user. • Understand the concept of variables. • Perform simple arithmetic operations in C++.

1.1 Getting Started—Your First C++ Program

A program is a list of instructions that directs the computer to perform a series of operations. An operation could be adding two numbers together or outputting some data to the screen. In this section you will learn, step-by-step, how to create, compile, link and execute a C++ program using Visual C++ .NET (either the 2002 or 2003 edition). We recommend that you actually perform these steps as you read them in order to fully understand and absorb all that you are learning. If you are not using Visual C++ .NET, consult your particular C++ development tool’s documentation for information on how to create, compile, link and execute a C++ program.

1.1.1 Creating the Project

After you launch Visual C++ .NET, go to the menu and select File->New->Project. The following dialog box appears:

10

Page 11: C++ Módulo I e II

Figure 1.1: The “New Project” dialog box.

Enter a name of your choosing for the project and the location to which you wish to save it on your hard drive. Then press the OK button. A new Overview dialog box now appears, as seen in Figure 1.2. Selecting th Application Settings

e button displays the dialog box shown in Figure 1.3

Figure 1.2: The “Overview” dialog box. Select the "Application Settings" button from the blue column on the left.

11

Page 12: C++ Módulo I e II

Figure 1.3: The “Application Settings” dialog box. Be sure to select “Console application” and check “Empty

rward, press the “Finish” project.” Afte button.

lication for the Application type setting, and have checked Empty

project for the Additional options setting, press the Finish button. At this point, we have successfully created a C++ project. The next step is to add a C++ source code file (.CPP) to the project; that is, a file

.CPP File to the Project

dd New (.cpp).

n select Open. A blank .CPP file should automatically be opened in

Once you have selected Console app

in which we will actually write our C++ code.

1.1.2 Adding A

To add a .CPP file to your project, go to the menu and select Project->Add New Item… An Aem dialog box appears (Figure 1.4). From the right category, Templates, select a C++ FileIt

Give the .CPP file a name, and theVisual C++ .NET.

12

Page 13: C++ Módulo I e II

gure 1.4: The “Add New Item” dialog box. Select the file type you wish to add to the project. In this caseFi we want

.1.3 riting the Code

Program 1.1: Print String.

to add a C++ File (.cpp).

W1

You should now see a blank .CPP file where we can begin to write our code. Type or copy the llowing C++ code, exactly as it is, into your .CPP file. fo

//===================================================================// print_string.cpp By Frank Luna //=================================================================== #include <iostream> #include <string> int main() std::string firstName = ""; std::cout << "Enter your first name and press Enter: "; std::cin >> firstName; std::cout << std::endl; std::cout << "Hello, " << firstName << std::endl << std::endl;

13

Page 14: C++ Módulo I e II

1.1.4 Compiling, Linking, and Executing

After the C++ code is completed, it must be translated into a language the computer understands—that is, machine language—in order that it can be executed. There are two steps to this translation process: 1) Compilation 2) Linking In the compilation step, the compiler co ore complex projects will contain more than and generates an object file (.OBJ) for ach one. An object file is said to contain object code.

le. It is the executable file which will run on your platform.

he fir rogram. To compile the program, go to the enu a NET, the results of your compilation should

mpiles each source code file (.CPP) in your project (m one source code file)

e In the next step, called linking, the linker combines all the object files, as well as any library files (.LIB), to produce an executable fi

Note: A library file is a set of object code that usually stores the object code of many object files in one compact file. In this way, users do not have to link numerous object files but can merely link one library file.

T st step towards generating an .EXE is to compile the p

nd select Build->Compile. At the bottom of VC++ .mbe displayed in the Output window—see Figure 1.5.

Figure 1.5: Compilation Output.

Observe that we have zero errors and zero warnings; this means we have written legal C++ code, and

laying various errors r warnings. For example, if you removed one of the ending semicolons from Program 1.1 and tried to

compilidentifirevent ul compile.

Once we have fixed any compiler errors and there are zero errors or warnings, we can proceed to the next step—the build step (also called linking). To build the program, select the Build->Build Solution

therefore our program has compiled successfully. If we had written any illegal C++ code (e.g., we made typos or used incorrect C++ punctuation), the compiler would let us know by dispo

e it, you would get the following error message: “error C2146: syntax error: missing ';' before er 'cout'”. You can use these error messages to help pinpoint the problems in your code that are ing a successfp

14

Page 15: C++ Módulo I e II

item from the menu. Similarly to the compilation process, the results of youin the Output window—see Figure 1.6.

r build should be displayed

Figure 1.6: Build Output.

Observe that we have zero errors and zero warnings; this means we have written legal C++ code, and therefore our program has linked successfully. As with compilation, there would be error messages if we had written any illegal C++ code. At post-build we have an executable file generated for a program. We can now execute the program

V going to the menu and selecting Debug->Start Without Debugging. Doing so unche

rst name and press enter. (Note that we bold is output.) The

fromla

C++ .NET bys our console application, which outputs the following:

Enter your first name and press Enter: Doing as the application instructs, you will input your fiinput text in this book so that it is clear which text is entered as input and which text rogram then displays the following: p

Program 1.1: Output.

Enter your first name and press Enter: Frank Hello, Frank Press any key to continue

Note that by choosing Start without Debugging, the compiler automatically adds the “Press any key to continue” functionality. Before continuing with this chapter, spend some time studying the ouput of Program 1.1 and the code used to create it. Based on the output, can you guess what each line of code does?

Note: If your program has not been compiled or built, you can still go directly to the “Start without Debugging” menu command, and VC++ .NET will automatically compile, build, and execute the program in one step.

15

Page 16: C++ Módulo I e II

1.2 The “Print String” Program Explained

The following subsections explain Program 1.1 line-by-line.

1.2.1 Comments

//========================================================= // print_string.cpp By Frank Luna //========================================================= The first three lines in Program 1.1 are comments. A single lined comment is designated in C++ with

e double forward slashes ‘//’—everything on the same line that follows the ‘//’ is part of the comment. ent, where everything between a ‘/*…*/’ pair is part of the

he ‘/*…*/’ style comment, you can comment out parts of a line, whereas ‘//’ mment the entire line. For example,

rint firstName*/ firstName << endl;

mment (/*print firstName*/) in the middle of a code line. In general, ‘/*…*/’ mments are highlighted in

omments are strings that are ignored by the compiler. That is, when the compiler compiles the code, it . For example, at some ts ou can write a clear

coworker from using your code incorrectly and asting , the code that was obvious to you three months ago might not be so obvious day. ul comments can be beneficial to you as well.

writing “clear” and “useful” omments, as opposed to “bad” comments. “Good” comments are clear, concise, and easy to

thC++ also supports a multi-line commcomment. For example: /* This is

a multi-

line comment */

lso that by using tNote acomments always co cout << "Hello, " << /*p

ere we have inserted a coHstyle comments comment out only what is between them. Observe that cogreen in VC++ .NET. Cskips over any comments. Their main purpose is to make notes in a programoint you may write some tricky code that is difficult to follow. With commen , yp

English (or some other natural language) explanation of the code. Writing clear comments becomes especially important when working in teams, where other programmers will need to read and modify your code. For instance, you might have a piece of code that expects a certain kind of input, which may not be obvious to others. By writing a comment that explains he kind of input that is expected, you may prevent yourt

w time. FurthermoreSo maintaining usefto

ote that throughout the above discussion, we state that the goal is N

c

16

Page 17: C++ Módulo I e II

understand. “Bad” comments are inconsistent, out of date, ambiguous, vague, or superfluous and are of no use to anyone. In fact, bad comments can even increase the confusion of the reader. As Bjarne Stroustrup, the inventor of the C++ language, states in his text, The C++ Programming Language: Special Edition: “Writing good comments can be as difficult as writing the program itself. It is an art

ts and the #include lines, we have a blank line. Blank lines and spaces are called hite space, for obvious reasons. The important fact about white space is that the compiler ignores it

(alt u spaces, multiple spaces bet e piler’s point of view, the following code is equivalent to Program 1.1 from Section 1.1.3:

ro m ace.

well worth cultivating.”

1.2.2 White Space

Between the commenw

ho gh there are some exceptions). We very well could have multiple linewe n words, and multiple C++ statements all on the same line. From the com

P gra 1.2: Playing with the white sp

//=================================================================== // print_string.cpp By Frank Luna //=================================================================== #include <iostream> #include <string> int main ) ( std::string firstName = ""; std::cout << "Enter your first name and press Enter: "; std::cin >> firstName; std::cout << std::endl; std::cout<<"Hello, "<<firstName<<std::endl<<std::endl; You should try to compile the above program to verify that it still works correctly.

(e.g., “s p a c e s”) are not ignored, as they are actually considered space keywords cannot simply be broken up and expected to mean the same

thing. For example, you cannot write “firstName” as

Note that spaces inside a string characters. Additionally, C++

fir st Name

Symbols that have actual meaning must be kept intact as they are defined.

17

Page 18: C++ Módulo I e II

Finally, the #include directives, which we discuss in the next section, are special kinds of statements and they must be listed on their own line.

Code for outputting and inputting data to and from the console window s such as sine and cosine

more functionality to the standard library than just described, and we will ith it as we progress through this course. (Note that there are entire volumes

we will be using code from the C++ standard library, but in order to do so, we must our code. To do this, we use an include directive (#include

>) rogram 1, we invoked two include directives:

#

e fir instructs the compiler to take all the code in the specified file (the file in twee s), “iostream,” and include it (i.e., copy and paste it) into our .CPP file. milar e code in “string” and include into der files, which contain C++

andard library code. Note that in addition to including C++ standard library header files, your program

.2.3

lashes, namespaces are used to organize groups of related code and prevent code name clashes. For instance, the entire C++ standard library is organized in the standard (std) namespace. That is why we had to prefix most of our code in Program 1.1. Essentially, the

1.2.2 Include Directives

In the programs we write, we will need to use some code that we did not write ourselves. For this course, most of this “other” code will be found in the C++ standard library, which is a set of C++ utility code that ships with your C++ compiler. Some examples of useful code which the C++ standard library includes are:

Functions for computing various math operation Random number generators

from the hard drive Code for saving and loading files to and Code to work with strings

There is, of course, much become more familiar wdedicated to just the C++ standard library functionality.) We know that

ude some standard library code into incl<file . In our first example program, P include <iostream> #include <string> Th st include directive

n the angle bracketbeSi ly, the second include directive instructs the compiler to take all th

our .CPP file. iostream and string are C++ standard library heait ts

links with standard library files as well; however, this is done automatically—the C++ standard libraries are linked by default when you create a new C++ project.

1 Namespaces

Namespaces are to code as folders are to files. That is to say, as folders are used to organize groups of related files and prevent file name c

18

Page 19: C++ Módulo I e II

std:: prefix tells the compiler to search the standard code we need.

namespace (folder) to look for the standard library

espace. The following revision of Program 1.1 includes a using namespace std clause, hich moves the code in the std namespace to the global namespace, and as such, we no longer need to

Of course, prefixing all of your code library with std::, or some other namespace can become cumbersome. With the using namespace X clause, you can move code in some particular namespace to the global namespace, where X is some namespace. Think of the global namespace as the “working folder”. You do not need to specify the folder path of a file that exists in the folder you are currently working in, and likewise, you do not need to specify a namespace when accessing code in the global namwprefix our standard library code with the std:: prefix: Program 1.3: Print String with “using namespace std” clause.

//===================================================================// print_string.cpp By Frank Luna //=================================================================== #include <iostream> #include <string> using namespace std; int main() string firstName = ""; cout << "Enter your first name and press Enter: "; cin >> firstName; cout << endl; cout << "Hello, " << firstName << endl << endl;

he namespace/folder analogy, pref using namespace X clause

re, such as creating your own namespaces, but we will defer a more detailed discussion until Chapter 5.

Note that you can “use” more than one namespace. For example, if you had defined another namespace called math, you could write:

using namespace math; using namespace std;

ixing Standard Library code, and the T

are all you need to know about namespaces for now. Note, however, that there is much more to namespaces than we covered he

19

Page 20: C++ Módulo I e II

1.2.4

ust have a main function.

or now, e ecuted by the

main main ’ enotes

Braces m

.2.5 std::string

std::scharactstring” library, rite the include directive #include <string>. In our example program, we declare a std::string variable called firstName (the variable name

e want with a few exceptions—see Section 1.3.2) and define it to be an empty string (i.e. “”). This std::string variable firstName will be used to store (i.e., save) the first name the user enters into the program. std::string is a powerful variable type, and we will return to it in

t with std::cin and std::cout

e might guess that “std::cout <<” outputs data to the ole window and “

o use them we

std::cout << "Enter your first name and press Enter: ";

We prompt the user to enter his/her name with the following line:

The main... Function

The main... function is special because it is the entry point for every C++ program; that is, every ++ program starts executing at main. Consequently, every C++ program mC

The code inside a function’s corresponding curly braces is called the function body or function efinition. We will discuss in more detail what we mean by the term function in Chapter 3. Fd

just understand that the first instruction in main’s function body is the first instruction xrogram. p

In C++, the curly braces are used to define the beginning and the end of a logical code unit. For xample, the code inside the braces of defines the unit (function) . The opening brace ‘e

d the beginning of a code block, and the closing brace ‘’ denotes the end of the code block. ust always be paired together; a beginning brace must always have an ending brace.

1

tring is a C++ variable type (more on variables in Section 1.3) that represents a string of ers (i.e., text used for constructing words and sentences). For example, “hello,” and “this is a are both strings. As you can tell by the std:: prefix, std::string is part of the C++ standard and in order to use it we must w

can be almost anything w

detail in Chapter 6, but for now it suffices to know that it is used to store text strings.

1.2.6 Input and Outpu

Program 1.1 is able to output and input data to and from the console window. By examining the code of Program 1.1 and its corresponding output, wcons std::cin >>” inputs information from the console window. This guess is, in fact, correct. Observe that std::cout and std::cin exist in the std namespace, and tmust write the include directive #include <iostream>. For example, in Program 1.1 we display the text “Enter your first name and press Enter:” to the console window with the following line:

20

Page 21: C++ Módulo I e II

firstName; The sta ut << std::endl;” instruc s a result, the pro e cursor to the next line for Finally, we can chain outputs together with separate i std::cout << "Hello, " << firstName Here we output “Hello, ” followed by the string val wed by two “new line” commands. We can also chain inputs

Note: The symbol ‘<<’ is called the insertion o extraction operator. These names make sense when we ut into an outbound stream of data, and ‘>>’ is used to extr

most every line of code in Program 1.1 ples of ts. A statement in C++ instructs th reate a

perform some arithmetic, or output so a statement h a semicolon (not a new line). A semic iod ends an

English sentence. We could very well have st readability this usually is not done.

1.3 Variables

In Prog we ask the user to enter his/her name. e. The computer knows what name to say “Hello” to becaus the line: std::cin >> firstName; The command std::cin >> prompted the user to e it, the program

ved what was entered in a string variable called firstName has been saved, we

arizes the

std::cin >>

tement “std::cogram will move th

ts the computer to output a new line. Aeither input or output.

l: nsertions per line, as we do with this cal

<< std::endl << std::endl;

ue stored in the variable firstName, follo together, but this will be discussed later.

perator. And the symbol ‘>>’ is called the consider that ‘<<’ is used to insert outpact input from an inbound stream of data.

Important Note!So far, alstatemen

has ended with a semicolon. These are exame computer to do a specific action, such as cme text. variable,

ends witOf particular importance is that

olon ends a C++ statement much like a peratements that span multiple lines, but for

purposes,

ram 1.1 The program then says “Hello” to that name we saved the name the user entered with

nter some text and when he entered. Because the stringsa

can output the string with the following line: std::cout << "Hello, " << firstName << std::endl << std::endl; A variable occupies a region of physical system memory and stores a value of some type. There are several built-in C++ variable types which allow you to store different types of values, and as you will earn in later chapters, you can even make your own variable types. The following table summl

C++ variable types:

21

Page 22: C++ Módulo I e II

Table1.1: C++ Types.

Variable Type Description std::string Used to store string variables. Note that

std::string is not part of the core language, but part of the standard library.

char Used to store single character variables such as ‘a’, ‘b’, ‘c’, etc. Usually this is an 8-bit value so that it can store up to 256 values—enough to represent the Extended

A for a table of the Extended ASCII Character Set. Observe from the ASCII table, that characters are actually represented with

, thus a char type is really an interpreted as a character.

ASCII Character Set. See Appendix

integersinteger, but

int The primary type used to store an integer value.

short Typically stores a shorter range of integers than int.

long A signed integer tystore a longer range

pe that typically can of integers than int.

float Used to stois, number

re floating point numbers; that s with decimals like 1.2345 and

–32.985. double Similar to a float, but typically stores

oint numbers with greater than float.

floating pprecision

bool Used to store truth-values; that is, true or false. Note that true and false are C++ keywords. Also note that, in C++, zero is also considered false, and any non-zero value, negative or positive, is considered to be true.

The exact range of values each type can hold or the amount of memory that each type occupies is not

ecause these values are largely platform-dependent. A char may be 8-bits (1 byte) on ay be 32-bits on another platform. Thus, you would usually like to avoid making

n your code, in order that your code remain portable (i.e., works on ther platforms). For example, if you assume in your code that chars are 8-bits, and then move

development to a platform with 32-bit chars, you will have errors which will need to be fixed.

ote that you can use std::cout and std::cin to output and input these other types as well. For example, consider the following program.

noted in the table bsome platforms, but massumptions about the C++ types io

N

22

Page 23: C++ Módulo I e II

Program 1.4: Type Input/Output.

// Program asks user to enter different types of values, the // program then echoes these values back to the console window. #include <iostream> #include <string> using namespace std; int main() // Declare variables and define them to some default value. char letter = 'A'; int integer = 0; float dec = 0.0f; cout << "Enter a letter: "; // Output cin >> letter; // Get input cout << "Enter an integer: ";// Output cin >> integer; // Get input Output cout << "Enter a float number: "; // cin >> dec; // Get input cout << endl; // Output another new line // Now output back the values the user entered. cout << "Letter: " << letter << endl; cout << "Integer: " << integer << endl; cout << "Float: " << dec << endl; Program 1.4: Output.

Enter a letter: F Enter an integer: 100 Enter a float number: -5.987123

Letter: F Integer: 100 Float: -5.98712 Press any key to continue

23

Page 24: C++ Módulo I e II

1.3.1 Variable Declarations and Definitions

W e write the following code, w

hen w e say that we are declaring a variable:

int myVar; // Variable Declaration.

alled myVar of type int. Although myVar has been declared, it is undefined; that is, the value it stores is unknown. Consequently, it is common to say that the variable

In particular, we declare a variable c

contains garbage. Try compiling and executing this small program to see what value myVar contains: #include <iostream> using namespace std; int main() int myVar; cout << myVar << endl;

hen we assign a value to a variable we are defining or initializing the variable:

he r. Note that the assignment operator assigns alu mparative sense. This is often onfusi

We can d le at the same time:

eclaration and Definition.

Because variables that contain garbage are useless and prone to introducing errors, it is advisable to

comma as this next code snippet shows:

j = 2.0f, k = 3.0f;

vised for readability purposes, to make one declaration or definition line

W myVar = 10; // Variable Definition.

T ‘=’ symbol, in C++, is called the assignment operato

es to variables—it says nothing about equality in a purely covc ng to new C++ students so be sure to try to remember this.

also declare an define a variab

int myVar = 10; // Variable D

always define your variables at the time of declaration to some default value. Typically, zero is a good default value for numeric values, and an empty string, “”, is a good default value for strings. t is possible to make multiple declarations and/or definitions in one statement using the I

operator int x, y, z;

x = 1, y = 2, z = 3; float i = 1.0f,

ore compact, it is adDespite being mper .

24

Page 25: C++ Módulo I e II

Finally, we can also chain assignments together (assigned values flow from right to left):

float a, b, c, d; a = b = c = d = num;

mes

when we declare/define a variable we must give it a name (identifier) so that le within the program. Variable names can be almost anything, with some

with a letter. The variable name 2VarName is illegal. However, the

t use symbols like ‘!’, ‘@’, ‘#’, ‘$’, ‘%’ in your variable names.

Variable names cannot be C++ keywords (Appendix B). For instance, you cannot name a variable “float” since t the C++ type float.

Variable names cannot

ote: C++ is case sensiti . For example, these identifiers are all unique because they differ by case: llo, Hello, HELLO, heLLo.

C++ types and the amount of memory they occupy is platform-dependent. In order to get the size of a type, in bytes, on the current platform, you use the

float num = 0.0f;

1.3.2 Variable Na

As shown in Program 1.3, we can refer to that variabexceptions:

1. Variable names must beginunderscore is considered a letter, and therefore, the identifier _myVar is legal.

2. Variable names can include the underscore (‘_’), letters, and numbers, but no other symbols. For

instance, you canno

3. hat is a C++ keyword that specifies

4. have spaces between them.

N vehe

1.3.3 The sizeof Operator

As stated in Section 1.3, the range of values of the

sizeof operator. Consider the following program, written on a 32-bit Windows platform: Program 1.5: The “sizeof” Operator.

// Program outputs the size of various types. #include <iostream> using namespace std; int main() cout << "sizeof(b sizeof(bool) << endl; ool) = " << cout << "sizeof(c sizeof(char) << endl; har) = " << cout << "sizeof(s sizeof(short) << endl; hort) = " << cout << "sizeof(int) = " << sizeof(int) << endl;

25

Page 26: C++ Módulo I e II

cout << "sizeof(long) = " << sizeof(long) << endl; cout << "sizeof(float) = " << sizeof(float) << endl; cout << "sizeof(double) = " << sizeof(double) << endl; Program 1.5: Output.

sizeof(bool) = 1 sizeof(char) = 1 sizeof(short) = 2 sizeof(int) = 4 sizeof(long) = 4 sizeof(float) = 4 sizeof(double) = 8 Press any key to continue

Note that these results will be specific to the platform on which the program is being e

ired for these Cxecuted. In fact, ++ types on the

ed

from these results we can infer the following value ranges and bytes requ32-bit Windows platform: Type Range Bytes RequirChar [–128, 127] 1 Short [–32768, 32767] 2 Int [–2147483648, 2147483647] 4 Long [–2147483648, 2147483647] 4 Float ± [ 38102.1 −× , 38104.3 × ] 4 Double ] 8 ± [ 308102.2 −× , 308108.1 × Note that there is no difference in range or memory requirements between an int and a long.

1.3.4 The unsigned Keyword

An int supports the range [–2147483648, 2147483647]. Howepositive values, then it is possible to ta

ver, if you only need to work with ke the memory reserved for representing negative numbers and

use it to represent additional positive numbers so that our range of positive values increases. This would be at the cost of sacrificing the ability to represent negative integers. We can do this by prefixing our

Range Bytes Required

integer types with the unsigned keyword. On 32-bit Windows we have the following: Type unsigned char [0, 255] 1 unsigned short [0, 65535] 2 unsigned int [0, 4294967295] 4 unsigned long [0, 4294967295] 4

26

Page 27: C++ Módulo I e II

Only integer types can be unsigned. By using unsigned types we have not gained a larger range, we type holds 4294967296 unique values (counting

nments

rals ally the number ten. Likewise, the ring owing code illustrates some literal signm

have merely transformed our range. That is, an intro) whether we use the range [–2147483648, 2147483647] or the range [0, 4294967295]. ze

1.3.5 Literal Assig

L are values that are not variables. For example, 10 is literello world.” The foll

itest hello world is literally the string “h

ents: as bool b = true; // boolean literal. har letter = 'Z'; // charact c er literal. std::string str = "Hello"; // string literal. int num = 5; // integer literal. unsigned int uint = 5U; // unsigned integer literal lo // long literal. ng longNum = 10L; unsigned long ulong = 50UL; // unsigned long literal. float floatNum = 123.987f; // float literal. double dblNum = 567.432; // double literal. / Note that the literal type suffixes are not case / // e following also works: sensitive. Thus, th unsigned long ulong2 = 50ul; // unsigned long literal. float floatNum2 = 123.987F; // float literal.

There example, it is unclear whether 123.987

double. To avoid this ambiguity, C++ allows us to attach a type ffix t then it is treated as a float; otherwise is tre double s and unsigned types. Observe uint, ongNum, and ulong, in the above code, where we use the U, L, or combination UL to denote the literal

ersions

nments between various types. For instance, we can assign an int ariable to a float variable and vice versa. However, there are some caveats. Program 1.6 illustrates is.

is some ambiguity with floating point numbers. Forshould be treated as a float or asu o the literal. If the literal has an ‘f’ suffix as in 123.987f

ated as a . A similar problem arises with longit ltype.

1.3.6 Type Conv

It is possible to make variable assigvth

27

Page 28: C++ Módulo I e II

Pro mgra 1.6: Type Conversions.

// Demonstrates some type conversions. #include <iostream> using namespace std; int main() ert from a less precise type // Case 1: Conv // to a more precise type: char c = 10; short s = c; cout << “char to short: “ << s << endl; // Case 2: Convert from a more precise integer // to a less precise integer: unsigned char uc = 256; cout << “int to uchar: “ << (int)uc << endl; // Case 3: Convert from a float to an int, // assuming the int can store the float’s value. int i = 496512.546f; cout << “float to int: “ << i << endl; // Case 4: Convert from a float to a short, this // time the int can’t store the float: s = 496512.987123f; cout << “float to short: “ << s << endl;

P Output. rogram 1.6:

char to short: 10 int to uchar: 0 float to int: 496512 float to short: -27776 Press any key to continue

• Case 1: Here we convert from a less precise type to a more precise type. Since the more precise type can fully represent the less precise type, there is no conversion problem and everything works out as expected.

28

Page 29: C++ Módulo I e II

• Case 2: Here we convert from a more precise integer to a less precise integer. However, an unsigned char cannot represent the value 256. What happens is called wrapping. The unsigned char cannot store values over 255 so it wraps back to zero. Thus 256 becomes

• an int. Observe that the float is truncated—the float

• a short, but the short cannot store the whole number part

ping scenario.

a serious problem that can lead to hard-to-find bugs. Thus, you should orking with values that your types can correctly store.

es these type conversions implicitly; that is, automatically. However, sometimes ou nee ll the compiler to treat a type as a different type. We actually see this in Case 2

of Program

“ << (int)uc << endl;

The (int)uc syntax tells the compiler to treat uc as an int, not as a char. This is because cout will

e general, this type of casting can be done with either of the

llowing syntaxes:

result = static_cast<typeToConvertTo>(valueToConvert); vert;

int y = (int)result;

zero, 257 would become 1, 258 would become 2, and so on. The values “wrap” back around. Wrapping also occurs in the opposite direction. For instance, if we assigned –1 to an unsigned char, the value would wrap around in the other direction and become 255.

Case 3: Here we assign a float toloses its decimal.

Case 4: Here we assign a float toof the float. Thus we observe a wrap

Note: Integer wrapping is always ensure that you are w

he C++ compiler doT

y d to explicitly te 1.6. Specifically, the line:

cout << “int to uchar:

output the character representation of uc since it is a char. But we want it to output the integer representation of the char. Thus we must perform a type cast (conversion between types) and xplicitly tell the compiler to treat uc as an int. Info result = (typeToConvertTo) valueToCon Examples: int x = 5; float result = static_cast<float>(x);

29

Page 30: C++ Módulo I e II

.3.7 Typedefs 1

At some point, you might find a C++ type that is too long. For example, unsigned int is a lot to allows you to define an alternate name (synonym) via the typedef keyword. ine the following shorter names for the unsigned types:

f unsigned long ulong;

f having to write:

19;

e can simply write:

Variables

es we will want to define a variable that cannot change. Such variables are constant. For , we may want to define a constant variable pi to represent the mathematical constant

write out repeatedly. C++For example, we might def

typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; typede

Thus, for example, instead o unsigned int x = W uint x = 19;

1.3.8 Const

Sometimexample 14.3≈π .

ifying keyword:

// error, cannot redefine constant pi.

notational convenience. It is clearer to read the symbolic name a programmer may not immediately connect 3.14 to

To do this we use the const mod const float pi = 3.14f; If the programmer tries to change pi, an error will result: pi = 12.53f; Constant variables are often used for

π“pi” than it is to read the number 3.14; .

1.3.9 Macros

Sometimes we want to create a symbol name (identifier) that stands for some set of code. We can do this by defining a macro. For example, the following line of code defines a macro PRINTHELLO, which

hen written, executes the statement: cout << "Hello" << endl;. // Define a macro PRINTHELLO that means

< endl;

w

// "cout << 'Hello' << endl;" #define PRINTHELLO cout << "Hello" <

30

Page 31: C++ Módulo I e II

Using this macro we could write a program that outputs “Hello” several times like so: Program 1.7: Macros

#include <iostream> using namespace std; // Define a macro PRINTHELLO that means // "cout << 'Hello' << endl;" #define PRINTHELLO cout << "Hello" << endl; int main() PRINTHELLO PRINTHELLO PRINTHELLO PRINTHELLO Note that the semi-colon is inc ition a to place one at the end of each line.

Program 1.7 Output

luded in the macro defin nd thus we did not need

Hello Hello Hello Hello Press any key to continue

When the compiler compiles the source code and encounters a macro, it internally replaces the macro

mbol with the code for which it stands. Program 1.7 is expanded internally as:

endl;

"Hello" << endl; "Hello" << endl;

thmetic Operations

sy int main() cout << "Hello" <<

cout << "Hello" << endl; cout <<

cout <<

1.4 Ari

31

Page 32: C++ Módulo I e II

In addition to declaring, defining, inputting, and outputting variables of various types, we can perform them.

Unary Operator Description Example

basic arithmetic operations between

1.4.1 Unary Arithmetic Operations

A unary operation is an operation that acts on one variable (operand). Table 1.2 summarizes three unary operators: Table 1.2: Unary Operations.

Negation Operator: - Negates the operand. int x = 5; int y = -x; // y = -5.

Increment Operator: ++ Increments the operand by e

increment operator can prefix or postfix the operator.

int x = 5; ++x; // x = 6 (prefix) x++; // x = 7 (postfix)

one. Note that th

Decrement Operator: -- Decrements the operand by one. Note that the decrement operator can prefix or postfix the

int x = 5; --x; // x = 4 (prefix) x--; // x = 3 (postfix)

operator. Observe that the increment and decrement operators can be written as a prefix or postfix. The technical ifference between prefix and postfix is determined by where the increment/decrement occurs in a

illustrates: dstatement. The following program

rogram 1.8: Prefix versus Postfix. P

#include <iostream> using namespace std; int main() int k = 7; cout << "++k = " << ++k << endl; cout << "k++ = " << k++ << endl; cout << k << endl;

1.8: Output. Program

++k = 8 k++ = 8

32

Page 33: C++ Módulo I e II

9 Press any key to continue

D see the difference betwo you een the prefix and postfix increment/decrement operator? In the first call

cou layed. Hence the number 8 is displayed. owev <, k is first displayed, and then is incremented. Hence the mbe at it was indeed incremented,

expression, when using the efix decremented first, before it is used. Conversely, when using the

, after it is used.

4.2 Operations

++ co

Multiplication operator (*), (/) (%).

n, multiplication and division are defined for all numeric types. The modulus nteger operation only. The only arithmetic operation defined for std::string is the

addition operator. The following program illustrates the arithmetic operations:

to t <<, k is incremented first, before being disper, in the second call to cout <H

nu r 8 is displayed again. Finally, we output k a third time to show thcond time. Therefore, in a complexbut only after it was displayed the se

form, the value is incremented/prpostfix form, the value is incremented/decremented last

1. Binary Arithmetic

C ntains five binary arithmetic operators: 1) Addition operator (+),

Subtraction operator (-), 2))3

4) Division operator5) Modulus operator Addition, subtractiooperator is an i

Program 1.9: Arithmetic Operations.

// Program demonstrates some arithmetic operations. #include <iostream> #include <string> using namespace std; int main() //========================= // Do some math operations: float f1 = 10.0f * 10.0f; float f2 = f / 10.0f; 1 float fDif = f1 - f2; cout << f1 << " - " << f2 << " = " << fDif; cout << endl << endl;

33

Page 34: C++ Módulo I e II

//============================ // Do some integer operations: int i1 = 19 + 4; int i2 = 10 - 3; int remainder = i1 % i2; cout << i1 << " % " << i2 << " = " << remainder; cout << endl << endl; //=========================== // Do some string operations: string s1 = "Hello, "; string s2 = "World!"; string stringSum = s1 + s2; cout << s1 << " + " << s2 << " = " << stringSum; cout << endl << endl; Program 1.9: Output.

100 - 10 = 90

23 % 7 = 2

Hello, + World! = Hello, World!

Press continue any key to

1.4.3

e modulus operator returns the remainder of an integer division. For example,

The Modulus Operator

Th

73

7+= . 223

H e call the nere w umerator 2, in 72 , the remainder—it is the remaining part that cannot be divided

enly We will say two integers divide evenly if and only if the division results in an integer; ., no

ple,

ev by seven. (i.e t a fraction.)Consider the exam 135 . In this case, the remainder is five; that is, 13 divides into 5 zero times, and

the r rt that cannot be evenly divided by 13 is 5. so emaining pa

34

Page 35: C++ Módulo I e II

1. Compound Arithmetic Oper4.4 ations

+ de lly perform two operations, namely an arithmetic eration and an assignment operation. The following table summarizes:

ithmetic Operations.

tion Equivalent Meaning

C+p

fines the following “shortcut” operators that reao Table 1.3: Compound Ar

Compound Arithmetic Operax += y x = x + y x -= y x = x – y x *= y x = x * y x /= y x = x / y x %= y x = x % y The following program illustrates how they are used in a C++ program: Program 1.10: Compound Arithmetic Operators.

#include <iostream> using namespace std; int main() int x = 0; int y = 0; cout << "Enter an integer: "; cin >> x; cout << "Enter an integer: "; cin >> y; // Save to separate variables so each operation is // independent of each other. int a = x; int b = x; int c = x; int d = x; int e = x; a += y; b -= y; c *= y; d /= y; e %= y; cout << "x += y = " << a << endl;

35

Page 36: C++ Módulo I e II

out << "x -= y = " << b << endl; c cout << "x *= y = " << c << endl; cout << "x /= y = " << d << endl; cout << "x %= y = " << e << endl; Program 1.10: Output.

Enter an integer: 50 Enter an integer: 12 x += y = 62 x -= y = 38 x *= y = 600 x /= y = 4 x %= y = 2 Press any key to continue

Note: The output of Program 1.9 brings up an important point. Namely, 50 / 12 is not 4, but approximately 4.1667. What happened? The decimal portion of the answer is lost because integers are being used, and they are unable to represent decimals. Bear this truncation in mind when doing division with integers and ask yourself whether or not this is a problem for your particular circumstances.

Con in

n wprece e r of greatest precedence to least precedence.

ultiplication, division and the modulus operations have the same precedence level. Similarly, addition anddivi ndivisionabove e

ot Someti

e add cedence to an operation by rrounding it with a pair of parentheses, much like you do in mathematical notation. We can force the

addition to come before multiplication by using the following parentheses:

1.4.5 Operator Precedence

sider the following statement:

t x = 5 + 3 * 8; I hich order will the compiler perform the various arithmetic operations? Each operator has a defined

d nce level, and operators are evaluated in the ordeM

subtraction have the same precedence level. However, the precedence level of multiplication, sio , and modulation is greater than that of addition and subtraction. Therefore, multiplication,

, and modulation operations always occur before addition and subtraction operations. Thus the xpression is evaluated like so:

int x = 5 + 3 * 8; = 5 + 24

= 29 N e that operators with the same precedence level are evaluated left to right.

mes you want to force an operation to occur first. For example, you may have actually wanted ition to take place before the multiplication. You can give greatest preth

su

36

Page 37: C++ Módulo I e II

nt x = (5 + 3) * 8; i

= 8 * 8 = 64

Par hshould

e inne ple illustrates:

int x = (((5 + 3) - 2) * 6) + 5; ) * 6) + 5 + 5

= 36 + 5 = 41

ns evaluate to a numeric value. The terminology for something that evaluates to a number is called a numeric expression. More generally, something that evaluates

xists lo

C++ code to object code. The linker then combines the object code, produce an executable program that can be run on the operating

age code).

2. White space consists of blank lines and spaces, which are ignored by the compiler. Use white ur code in a more readable way.

3. The C++ standard library includes code for outputting and inputting data to and from the console

4. Every C++ program must have a main function, which defines the program entry point.

5. Namespaces are used to organize code into logical groupings and to prevent name clashes.

6. A variable occupies a region of physical system memory and stores a value of some type. Variable names can consist of letters and numbers, but cannot begin with a number (the

is considered a letter). Recall that C++ is case sensitive.

ent eses can also be nested so that you can explicitly specify the second, third, etc. operation that occur. In an expression with nested parentheses, the operations are evaluated in the order from

rmost parentheses to the outermost parentheses. The following examth

= ((8 – 2 = (6 * 6)

Note: Observe how arithmetic operatio

to something else is considered an expression. As you will see in the next chapter, there e gical expressions (expressions with logical operators), which evaluate to truth-values.

1.5 Summary

1. A C++ compiler translates

from several object files, tosystem (i.e., machine langu

space to format yo

window, functions for computing various math operations such as sine and cosine, random number generators, code for saving and loading files to and from the hard drive, code to work with strings, and much, much more.

underscore

37

Page 38: C++ Módulo I e II

7. C++ can implicitly convert between its intrinsic types; however, one must be alert for decimal truncation and integer wrapping. It is good practice to try and avoid type conversions when practical.

ence define the order in which the compiler performs a series of parentheses to explicitly define the order in which the operations should be

is also makes your code clearer.

sks the user to input two real numbers, and . Compute the sum

8. The rules of operator preced

operations. Useperformed. Consequently, th

1.6 Exercises

1.6.1 Arithmetic Operators

rite a program that aW 1n 2n 21 nn + , the ifference , the product , and the quotient (assume21 nn − 21 nn ⋅ 21 / nn 02 ≠nd ), and output the results.

ollows:

4.67 + -14.2 = 50.47

64.67 - -14.2 = 78.87 64.67 * -14.2 = -918.314

4.55423 continue

m example given in Section 1.1.3. This time, ask the user to enter his first and last y a space, on one line (i.e., use one “cin >>” operation to read both the first and last Does a problem occur? If so, describe it in complete sentences. Then try and find a

round” to the problem, by any means possible.

Your program output should be formatted as f Enter a real number n1: 64.67

4.2Enter a real number n2: -16

64.67 / -14.2 = -Press any key to

1.6.2 Cin/Cout

Rewrite the progranames, separated bname in one pass).

atemporary “work

1.6.3 Cube

38

Page 39: C++ Módulo I e II

Write a program that asks the user to input a real number n. Compute 3n and output the result. Your program output should be formatted as follows: Enter a real number: 7.12 7.12^3 = 36Press any ke

0.944 y to continue

6.4 nce

rite the user to input the radius r of a circle. Compute the area A and rcumference C of the circle using the formulas and

1. Area/Circumfere

W a program that asks ci 2rA ⋅= π rC ⋅⋅= π2 respectively, and output the

sults. pproximationre Note that for this exercise you can make the a 14.3≈π . Your program output uld be formatted as follows:

Enter the radius of a circle: 10 The area A of a circle with radius 10 = 314 The circumference C of a circle with radius 10 = 62.8 Press any key to continue

1.6.5 Average

Write a program that asks the user to input five real numbers. Compute the average of these numbers and output the results to the user. Your program output should be formatted as follows: Enter a0: 5 Enter a1: 10 Enter a2: -2 Enter a3: 2.7 Enter a4: 0 The average of the five inputs a0...a4 = 3.14 Press any key to continue

1.6.6 Bug Fixing

sho

39

Page 40: C++ Módulo I e II

The following program contains several bugs. Entecompile it. What error/warning messages do you get? pleteach error/warning message means. Afterwards, fix the th

r the program into your C++ editor and try to In com e sentences, describe what you think errors so at it compiles correctly.

#include <iostream> #include <string> int mian() string str = "Hello World!" cout << str << endl; cout << float x = 5.0f * str << end; int 65Num = 65; cout << "65Num = " < 65Num << endl;

40

Page 41: C++ Módulo I e II

Chapter 2

Logic, Conditionals, Loops and

Introduction

Arrays

41

Page 42: C++ Módulo I e II

The previous chapterdeas

limited in terms of the i we can er we expand our C++ “vocabulary” and “ to oints are less than zero then player is to execute blocks of cod This ut and upd worl a logical container. In a casino gam

o nt in the container

C bj

• Discover how to execute a block of code repeatedly using the various kinds of loop statements

ate the individual elements in s.

nal Operators

robably already familiar with their operators allow us to determine some

emen ample, given the following two variables:

hat c e can determine is that they are not equal. Moreover, we can y var1 is greater than , or is less than var1. The C++ relational operators allow us to press ational operations are a type of boolean expression. A olea e that evaluates to a truth-value—true or false. For example, when relating two

equality is true) or not equal (equality is false).

ble 2

ble 2.

elatio

covered the creation of trivial C++ programs, but we are still very express using C++. In this chapt

grammar” in order the

program more interesting actions such as, “If the player’s hitp dead and the game is over.” In addition, we will learn how

e repeatedly. ate the game

is particularly useful in games where we need to repeatedly check user inpd accordingly. Finally, we will learn how to store a collection of variables in

e example, this would be useful to represent a deck of cardsprogrammatically; yrepresents a card in th

u could keep a container of 52 ints, where each elemee deck.

hapter O ectives:

• Understand and evaluate logical expressions. • Form and apply conditional, if…then, statements.

C++ exposes. • Learn how to create containers of variables and how to manipul

those container

2.1 The Relatio

The relational operators are fairly straightforward because you ar classes. The relational

e pconcepts from your high school algebrael tary relationships between variables. For ex int var1 = 15;

int var2 = 7;

them? One thing wW an we say about sa var2 var2

eas in code. Note that relex these types of idn expression is onbo

objects, it can be said that they are either equal ( Ta .1 summarizes the C++ relational operators:

Ta 1: The Relational Operators.

R nal Operator DescriptionEqua tor. ls operators Recall that the single equal sign ‘=’ is the assignment opera

== Consequently, C++ uses a double equal sign ‘==’ to test

42

Page 43: C++ Módulo I e II

equality. This operator returns if the two operands artrue e equal, otherwise it returns false.

Not equals operator The not equals operator returns true if the two operands aequal, otherwise it returns . !=

re not false

Less than operator

The less than operator returns true if the left hand operand is less than the right hand operator, otherwise it returns false. <

Greater than Theoperator

> is greater thafalse.

greater than operator returns true if the left hand operand n the right hand operator, otherwise it returns

Less than or equals The less operator operand<= it return

than or equals operator returns true if the left hand is less than or equals the right hand operator, otherwise false.

s

Greater than or The greequals operator operand i

>= otherwise

ater than or equals operator returns true if the left hand s greater than or equals the right hand operator, it returns false.

To see how these operators can be used in a C++ program, consider the following program: Program 2.1: Relational operations.

// Program demonstrates how the relational operators // are evaluated. #include <iostream> using namespace std; int main() // Set output flag so that 'bool' variables are // output as 'true' and 'false' instead of '1' // and '0', respectively. cout.setf( ios_base::boolalpha ); float num1 = 0.0f; float num2 = 0.0f; cout << "Enter a number: "; cin >> num1; cout << "Enter another number: "; cin >> num2; bool isEqual = num1 == num2; bool isNotEqual = num1 != num2; bool isNum1Greater = num1 > num2; bool isNum1Less = num1 < num2; bool isNum2GrterOrEql = num2 >= num1; bool isNum2LessOrEql = num2 <= num1; cout << endl;

43

Page 44: C++ Módulo I e II

cout << "isEqual = " << isEqual << endl; cout << "isNotEqual = " << isNotEqual << endl; ut um1Greater = " << isNum1Greater << endl; co << "isN ut um1Less = " << isNum1Less << endl; co << "isN ut << isNum2GrterOrEql = " << isNum2GrterOrEql << endl; co " ut << isNum2LessOrEql = " << isNum2LessOrEql << endl; co "

utput 2.1 O

Enter a number: -5.2 Enter another number: 12.84 isEqual = false isNotEqual = true isNum1Greater = false isNum1Less = true isNum2GrterOrEql = true isNum2 ssOrE = false Le qlPress any key to continue

Observe that we store the result of a relational expression in a bool value. Recall that a bool variable type is used to store truth-values—true or false.

te: In Program 2.1 we use a line that we have not seen before; that is,

cout.setf( ios_base::boolalpha );

This line is used to set formatting flags which control the way cout outputs information. In this case we , which tells put values as “tru f

.2 The Log

), there are three other elementary types of ings we would like to say. Consider two bool variables, A and B. Eventually, we will need to make

tatements such as these: “If A and B are both true then this,” or “While either A or B is true then this,” r “If A and not B are both true then this.” To express these ideas in code, C++ provides a logical and

e or operator (||), and a logical not operator (!). he ‘&&’ and ‘||’ operators are binary operators; that is, they act on two operands. Conversely, the ‘!’

-value of the operand(s). The following truth tables, where ‘T’ denotes ue and ‘F’ denotes false, show the truth-values of these logical operators for different truth

of A and B.

No

pass a flag ios_base::boolalpha cout to out bool e” or “false.” Iwe did not include this line, cout would output true values as “1” and false values as “0.” We will discuss other various formatting flags as we across them in this course.

2 ical Operators

When working with boolean values (true or false valuesthsooperator (&&), a logical inclusivToperator is a unary operator which acts on one operand. Note that the truth-value of these logical operations depends on the truthtrcombinations

44

Page 45: C++ Módulo I e II

Table 2.1: Thf and onl

e logical AND (&&) truth table. Observe from the four possible combinations that an AND operation is y if both of its operands are true. true i

A B A && B T T T T F F F T F F F F

Table 2.2: Logand o ly if at l

ical OR (||) truth table. Observe from the four possible combinations that an OR operation is true if east one of its operands is true.

B A || B n

A T T T T F T F T T F F F

Table 2.3: Logical NOT (!) truth ta

hen negated and fable. The not operator simply negates the truth-value of a boolean operand—true lse becomes true when negated. becomes false w

A !A T F F T --- --- --- ---

Note: An inclusive or, A || B, is true if A is true, or B is true, or both are true. An exclusive or is r B is true, but not both. Observe that we do not need an exclusive or operator because

n exclusive or out of our three existing operators. For instance, the following logical to true if either A is true or B is true, but not A and B (not both):

ive OR: (A || B) && !(A && B)

e some boolean expressions with the logical operators ue, B is false, and C is true.

xamp

true if A is true owe can formulate aexpression evaluates Exclus

Before looking at a sample program, let us evaluatby hand first. Suppose A is tr E le 1: Evaluate A && !B.

ting the knownSubstitu truth-values (using ‘T’ for true and ‘F’ for false) into the expression and evaluating step-by-step yields: A && !B = T && !F

45

Page 46: C++ Módulo I e II

= T && T = T

xamp E le 2: Evaluate !B || C. Substituting our known truth-values (using ‘T

ing step-by-step yields: ’ for true and ‘F’ for false) into the expression and

aluat

B |

amp

ev ! | C = !F || T = T || T = T

xE le 3: Evaluate (A || C) && !(A && C). Substituting our known truth-values (using ‘T’ for true and ‘F’ for false) into the expression and

F

thoug 2.2 shows how the gical expressions are evaluated.

evaluating step-by-step yields: (A || C) && !(A && C) = (T || T) && !(T && T) = T && !T = T && F =

h logical operators are mostly used in conditional statements, ProgramAllo Program 2.2: Logical expressions evaluated.

// Program demonstrates how the logical operators // are evaluated. #include <iostream> using namespace std; int main() cout.setf( ios_base::boolalpha ); bool B0 = false; bool B1 = false; bool B2 = false; cout << "Enter 0 for false or 1 for true: "; cin >> B0; cout << "Enter 0 for false or 1 for true: "; cin >> B1; cout << "Enter 0 for false or 1 for true: "; cin >> B2; bool notB0 = !B0; bool notB1 = !B1;

46

Page 47: C++ Módulo I e II

bool notB2 = !B2; bool isB0AndB1 = B0 && B1; bool isB0AndB1AndB2 = B0 && B1 && B2; bool isB0OrB1 = B0 || B1; bool isB1OrB2 = B1 || B2; // Exclusive OR... bool isB0ExclOrB1 = (B0 || B1) && !(B0 && B1); // Complex logical expression... bool isComplex = (B0 && (B1 || B2)) && !((B0 && B1) || (B0 && B2)); cout << "B0 = " << B0 << endl; cout << "B1 = " << B1 << endl; cout << "B2 = " << B2 << endl; cout << "notB0 = " << notB0 << endl; cout << "notB1 = " << notB1 << endl; cout << "notB2 = " << notB2 << endl; cout << "isB0AndB1 = " << isB0AndB1 << endl; cout << "isB0AndB1AndB2 = " << isB0AndB1AndB2 << endl; cout << "isB0OrB1 = " << isB0OrB1 << endl; cout << "isB1OrB2 = " << isB1OrB2 << endl; cout << "isB0ExclOrB1 = " << isB0ExclOrB1 << endl; cout << "isComplex = " << isComplex << endl; Output 2.2

Enter 0 for false or 1 for true: 0 Enter 0 for false or 1 for true: 1 Enter 0 for false or 1 for tr 0ue: B0 = false B1 = true B2 = false notB0 = true notB1 = false notB2 = true isB0AndB1 = false isB0AndB1AndB2 = false isB0OrB1 = true isB1OrB2 = true isB0ExclOrB1 = true isComplex = false Press any key to continue

For the given program, input B0 = 0 = false, B1 = 1 = true, B2 = 0 = false1, it is important that each logical expression from this program be evaluated manually (as was done in the preceding three examples) and the results verified with the program’s output. Do your results match the program’s utput? o

1 Recall that C++ treats zero as false and any non-zero number, positive or negative as true.

47

Page 48: C++ Módulo I e II

Also try the program using different inputs, and again verify the resulting program output by first evaluating the logical operations yourself, either mentally or on paper. It is important that you are able to evaluate logical expressions mentally and quickly, and some of the exercises of this chapter will help you in building this skill. The “hardest” logical expression Program 2.2 performs is the “complex” one. Therefore, we will walk through the evaluation of this expression step-by-step for the input given in the sample run. For this

, 0, respectively, and therefore: B0 = false

ecall that the logical expression was:

|| B2)) && !((B0 && B1) || (B0 && B2));

’ to denote false.

omp B2)) && !((B0 && B1) || (B0 && B2)); && T ) || (F && F )) (F) || (F) ) ) && !(F || F) && !(F)

ot co is the same result which Program 2.2 calculated.

ators, we can use parentheses to control the order in which e log

.3 Conditional Statements: If, If…Else

the relational and logical operators, we can begin n general, a conditional statement takes on the form: “If A is true then olean expression (i.e., evaluates to either true or false) and P is a

llow on the condition that A is true (i.e., A implies P). The statement en” is called the antecedent and the statement(s) which follow(s) the

lled the consequent. For example, in the statement “If the player’s hitpoints are less than ntecedent would be “the player’s hitpoints are less than zero” and the

are the key to any computer program, as the program must be able to ake various decisions based on current conditions and react to user input. For example, in a game we

particular sample run, we entered 0, 1

B1 = true B2 = false R isComplex = (B0 &&(B1

e will use ‘T’ to denote true and ‘FW i lex = (B0 && (B1 ||sC = (F && (T || F )) && !((F

(T) ) && !( = (F && = (F && T

= (F) = F && T

= F

incidentally, this N Finally, observe that like the arithmetic operth ic rators are evaluated. al ope

2

Now that we can form boolean expressions with both to form conditional statements. I

,” where A is some boP followsstatement (or statements) that foafter the “if” and before the “th“then” is cazero then he is dead,” the aconsequent would be “he is dead.” Clearly, conditional statements m

48

Page 49: C++ Módulo I e II

need to be able to test whether game objects have collided (e.g., if collided then execute collision physics), or whether the player still has enough magic points to cast a spell (e.g., if magic points are greater than zero then cast magic missile), or whether the user has pressed a key or mouse button (e.g., if

ft arrow key pressed then strafe left; if right mouse button pressed then fire secondary weapon).

tatement

ditional statements to control program flow. If the antecedent is true nt C++ statement(s). For example, consider the following short program:

ogram demonstrates the ‘if’ statement.

le

2.3.1 The If S

In the context of C++, we use conthen execute the conseque Program 2.3: Pr

#include <iostream> us namespace std; ing int main() float num = 0.0f; cout << "Enter a real number: "; cin >> num; cout << endl; char cube = 0; c < "Cube " << num << " ?out < ?? (y)es / (n)o: "; cin >> cube; // Did the user enter a 'y' or 'Y' for yes--test both uppercase // and lowercase version. if( cube == 'y' || cube == 'Y' ) num = num * num * num; cout << endl; cout << "num = " << num << endl; Output 2.3a.

Enter a real number: 2 Cube 2 ??? (y)es / (n)o: Y num = 8 Press any key to continue

Output 2.3b.

Enter a real number: 2 Cube 2 ??? (y)es / (n)o: n num = 2 Press any key to continue

49

Page 50: C++ Módulo I e II

If (cube == 'y' || cube == 'Y') evaluates to true then the code “num = num * num * num;” is executed, otherwise it is not executed. Thus an “if” statement can be used to control which code is to be executed based on the condition. Program 2.3 executes one statement if the condition,(cube == 'y' || cube == 'Y'), is true.

ust use a compound rly braces. Program 2.4 illustrates this (new lines

mpound Statements.

More than one statement can be executed in consequence but to do so we matement, which is a set of statements enclosed in cust

have been bolded). Program 2.4: Co

#include <iostream> us namespace std;ing in in() t ma float num = 0.0f; cout << "Enter a real number: "; cin >> num; ch ube = 0; ar c cout << "Cube " << num << " ??? (y)es / (n)o: "; cin >> cube; if( cube == 'y' || cube == 'Y' ) cout << "Cubing num..." << endl; num = num * num * num; cout << "Done cubing..." << endl; cout << "num = " << num << endl; Output 2.4.

Enter a real number: 4 Cube 4 ??? (y)es / (n)o: y Cubing num... Done cubing... num = 64 Press any key to continue

Note: The indentation of the line that follows the if( boolExpression ) line is made purely for readability purposes in order to identify the code that is to be executed if is true. Remember, white space is ignored.

boolExpression

2.3.2 The Else Clause

50

Page 51: C++ Módulo I e II

To give more control over the program execution flow, C++ provides an else clause. The else clause allows us to say things like “If A is true then P follows, else Q follows.” Program 2.5 demonstrates how the else clause can be used. Program 2.5: The else clause.

// Program demonstrates the 'else' clause. #include <iostream> using namespace std; int main() int num = 0; cout << "Enter an integer number: "; cin >> num; // Test whether the entered number is >= to zero. if( num >= 0 ) // If num >= 0 is true, then execute this code: cout << num << " is greater than or equal to zero."; cout << endl; else // num < 0 // If num >= 0 is *not* true then execute this code: cout << num << " is less than zero." << endl; Output 2.5a.

Enter an integer number: -45 -45 is less than zero. Press any key to continue

Output 2.5b.

Enter an integer number: 255 255 is greater than or equal to zero. Press any key to continue

Program 2.5 asks the user to enter an integer number n and then outputs some information based on the ero. As you can see, due to the conditional statements, the code

relationship between n and the number zthat is executed varies depending on the input.

2.3.3 Nested If…Else Statements

51

Page 52: C++ Módulo I e II

An if…else statement can branch execution along two separate paths—one path if the condition is true ever, what if you need more than two execution paths?

at if you wanted to say: “if A then B else, if C then D else, if E then F else G.” This can achi eral nested if…else statements. Program 2.6 shows how this might be used to

g-type game based on the character the user

ogram …else statements.

or a second path if the condition is not true. HowFeor instance, wh

b eved by using sevinitialize a player’s character class in a fantasy role-playinselected. Pr 2.6: Program illustrates creating multiple execution paths using nested if

#include <iostream> #include <string> using namespace std; int main() // Output some text asking the user to make a selection. cout << "Welcome to Text-RPG 1.0" << endl; cout << "Please select a character class number..."<< endl; cout << "1)Fighter 2)Wizard 3)Cleric 4)Thief : "; // Prompt the user to make a selection. int characterNum = 1; cin >> characterNum; // Initialize character variables to default value. int numHitpoints = 0; int numMagicPoints = 0; string weaponName = ""; string className = ""; if( characterNum == 1 ) // Fighter selected? numHitpoints = 10; numMagicPoints = 4; weaponName = "Sword"; className = "Fighter"; else if( characterNum == 2 ) // Wizard selected? numHitpoints = 4; numMagicPoints = 10; weaponName = "Magic Staff"; className = "Wizard"; else if( characterNum == 3 ) // Cleric selected? numHitpoints = 7; numMagicPoints = 7; weaponName = "Magic Staff"; className = "Cleric"; else // Not 1, 2, or 3, so select thief. numHitpoints = 8; numMagicPoints = 6;

52

Page 53: C++ Módulo I e II

weaponName = "Short Sword"; className = "Thief"; cout << endl; cout << "Character properties:" << endl; co < "Class name = " << className ut < << endl; cout << "Hitpoints = " << numHitpoints << endl; cout << "Magicpoints = " << numMagicPoints << endl; co < "Weapon = " << weaponName ut < << endl; Output 2.6.

Welcome to Text-RPG 1.0 Please select a character class number... 1)Fighter 2)Wizard 3)Cleric 4)Thief : 2 Character properties: Class name = Wizard Hitpoints = 4 Magicpoints = 10 Weapon = Magic Staff

Press any key to continue

Here we provide four different execution paths based on whether the user chose a “Fighter,” “Wizard,” “Cleric,” or “Thief.” Adding more execution paths is trivial. You need only add more “else if” statements in the pattern shown.

2.3.4 The Switch Statement

The switch statement is essentially a cleaner alternative to nested if…else statements. It is best explained by example, so consider the following:

rogram 2.7: Program illustrates creating multiple execution paths using the switch statement. P

#include <iostream> #include <string> using namespace std; int main() int num = 0; cout << "Enter an even integer in the range [2, 8]: "; cin >> num; switch( num ) case 2:

53

Page 54: C++ Módulo I e II

cout << "Case 2 executed!" << endl; break; ca : se 4 cout << "Case 4 executed!" << endl; break; case 6: cout << "Case 6 executed!" << endl; break; ca : se 8 cout << "Case 8 executed!" << endl; break; default: cout << "Default case executed implies you do not "; cout << "enter a 2, 4, 6, or 8." << endl; break; Output 2.7.

Enter an even integer in the range [2, 8]: 4 Case 4 executed! Press any key to continue

Here num is the value to test against several possible cases. The code first compares num against the first case 2. If num equals 2 then the code following case 2 is executed; otherwise, the code jumps to the next case—case 4. If num equals 4 then the code following case 4 is executed; otherwise, the code jumps to the next case, and so on. The case is used to handle any other case not specifically handled equals 5, there is no case statement to handle the case wh se handles it.

p break statement is necessary following your case a case handler and there is not an ending break o the next case handler, and then the next and so on

ncountered. The break statement essentia ly exi the switch statement, which is typically desired after a particular case was andled. To illustrate, Program 2.8 shows what happens if you do not include a break statement.

ent with no “breaks.”

default in the switch statement. For example, if num

, so therefore the default caere num is 5

An im ortant f tatement is that a act about the switch shandling code. When the execution flows into

flow automatically falls tstatement, the programtil th end un e of the default case handler, or until a break is e

l ts out ofh

Program 2.8: Program demonstrates a switch statem

#include <iostream> #include <string> using namespace std; int main() int num = 0; cout << "Enter an even integer in the range [2, 8]: "; cin >> num; switch( num )

54

Page 55: C++ Módulo I e II

case 2: cout << "Case 2 executed!" << endl; case 4: cout << "Case 4 executed!" << endl; case 6: cout << "Case 6 executed!" << endl; case 8: cout << "Case 8 executed!" << endl; default: cout << "Default case executed implies you do not "; cout << "enter a 2, 4, 6, or 8." << endl; Output 2.8.

Enter an even integer in the range [2, 8]: 4 Case 4 executed! Case 6 executed! Case 8 executed! Default case executed implies you do not enter a 2, 4, 6, or 8. Press any key to continue

On the other hand, this execution fall-through may be used for your own purposes. For example, you

break; case 3: // Fall through to case 4 case 4: // Fall through to case 5

e same code for 3, 4, and 5. break;

.3.5 The Ternary Operator

ompact notation to represent a basic if…else statement. It is the only operator rands. The general syntax is this:

ator:

might have a situation where you want to execute the same code for several cases. This can be implemented like so:

case 0: // Fall through to case 1 case 1: // Fall through to case 2 case 2: ... // Execute same code for 0, 1, and 2.

case 5: ... // Execut

2

The ternary operator is a c C++ that takes three opein

nary OperTer (boolExpression ? value1 : value2)

55

Page 56: C++ Módulo I e II

The ternary operator may be read as follows. If boolExpression is true then the ternary operation valuates to value1, else it evaluates to value2. Consider this specific example, where B is of type

10 : -5;

xpression evaluates to 10, which is then assigned to x. However, if B is not true aluates to –5, which is then assigned to x. Notice that this is equivalent to:

;

ause of its cryptic

2.4 Repetition

The abwill neenemy projectile” or “While the player is not dead, continue game play.” C++ facilitates the need for

petition via loops. C++ provides three different loop styles; these variations are for convenience onlthree d

2. 1

The foThe fol

Pro m

ebool:

int x = B ? If B is true then the ethen the expression ev int x; if( B ) x = 10 else x = -5; Finally, it is worth mentioning that many programmers dislike the ternary operator bec

ntax. sy

ility to repeat C++ statements is an important one. For instance, to make nontrivial programs we ed to be able to say things like “For each game character, test whether or not any were hit by an

rey—you could use only one of these styles and forever ignore the other two. However, by providing

ifferent styles, you can pick the style that is most natural to the type of repetition needed.

4. The for-loop

r-loop is commonly used when you need to repeat some statement(s) a known number of times. lowing program executes an output statement ten times.

gra 2.9: Program demonstrates the for-loop.

#include <iostream> using namespace std; int main() for(int cnt = 0; cnt < 10; ++cnt) cout << cnt << ": Hello, World!" << endl;

56

Page 57: C++ Módulo I e II

Output 2.9.

0: Hello, World! 1: Hello, World! 2: Hello, World! 3: Hello, World! 4: Hello, World! 5: Hello, World! 6: Hello, World! 7: Hello, World! 8: Hello, World! 9: Hello, World! Press any key to continue

The syntax of the for-loop is simple. There are essentially four parts to a “for loop.”

art 1; Part 2; Part 3)

for(P

art 4; P

• Part 1: This can be any C++ statement(s). However, it is usually used to initialize a counting

variable; that is, a variable that counts the loop cycles. The code of Part 1 is executed first and executed once. Program 2.9 declares and initializes a counting variable called cnt to zero;

int cnt = 0.” only that is, “

• Part 2: This is the conditional part; that is, the loop continues to loop only so long as this

is condition is tested in every loop cycle. Program 2.9 makes the condition uld continue to loop as long as the counting variable is less than ten; that is,

condition is true. Ththat the program sho“cnt < 10.”

• Part 3: This can be any C++ statement(s). However, it is usually used to modify the counting

y. The statement(s) of Part 3 are executed for every loop cycle. In Program ounter variable so that cnt is increased by one for every loop cycle.

Because cnt is initialized to zero and it is incremented by one for every loop cycle, it follows

variable in some wa2.9, we increment the c

that Program 2.9’s for-loop will repeat exactly ten times.

• Part 4: This part contains the statement(s) which you want to execute for every cycle of the loop. Just as in an “if” statement, the curly braces are optional for one statement. However, if you need to execute several statements per cycle then you need the curly braces to form a compound statement.

, the following code is functionally equivalent to the for-loop of

int cnt = 0;

To show the flexibility of the for-loopProgram 2.9:

57

Page 58: C++ Módulo I e II

for( ; cnt < 10; ) cout << cnt << ": Hello, World!" << endl; ++cnt; What we have done here is moved the counter initialization outside the loop and replaced Part 1 with an mpty statement, which is perfectly legal since Part 1 can be “any C++ statement(s)”. Second, we have

rt 4, and we replaced Part 3 with an empty statement. gal since Part 3 can be “any C++ statement(s)”. Convince yourself that this

2.9.

and Part 3 of the for-loop can contain multiple statements. For example:

emoved the counter increment from Part 3 to PaAgain, this is perfectly lealternate for-loop is functionally equivalent to the for-loop of Program

Finally, Part 1 Program 2.10: Program demonstrates the for-loop.

#include <iostream> us namespace std; ing int main() fo t cnt1 = 0, int cnt2 =r(in -cnt2) 9; cnt1 < 10; ++cnt1, - cout << cnt1 << "---Hello, World!---" << cnt2 << endl; Output 2.10.

0---Hello, World!---9 1---Hello, World!---8 2---Hello, World!---7 3---Hello, World!---6 4---Hello, World!---5 5---Hello, World!---4 6---Hello, World!---3 7---Hello, World!---2 8---Hello, World!---1 9---Hello, World!---0 Press any key to continue

This time there are two counter variables (separat

cremented and the othed by commas), which are initialized to 0 and 9.

er is decremented. Consequently, as shown from the output, ards. Part 2—the condition—remains the same; that is, it still

Moreover, one is inone counts forward and one counts backw

e loop ten times. specifies that w

2.4.2 The while Loop

58

Page 59: C++ Módulo I e II

The while-loop is commonly used when you need to repeat some statements an unknown number of mes. For example, in a poker game, after every hand, the program might ask the user if he wants to

ser input, the program will decide whether to “repeat” and play again, or to tiplay again. Based on this uexit the loop. The following program illustrates a while-loop that terminates based on user input. Program 2.11: Program demonstrates the while-loop.

#include <iostream> using namespace std; int main() // Boolean value, true if we want to quit, false otherwise. bool quit = false; // Keep looping so long as quit is not true. while( !quit ) // Ask the user if they want to quit or not. char inputChar = 'n'; cout << "Continue to play? (y)es/(n)o..."; cin >> inputChar; // Test for both uppercase or lowercase. if( inputChar == 'n' || inputChar == 'N') cout << "Exiting..." << endl; quit = true; else cout << "Playing game..." << endl; O .11. utput 2

Continue to play? (y)es/(n)o...y Playing game... Continue to play? (y)es/(n)o...Y Playing game... Continue to play? (y)es/(n)o...y Playing game... Continue to play? (y)es/(n)o...n Exiting... Press any key to continue

Program 2.11 is a useful example because, in addition to the while-loop, it demonstrates many of the other topics of this chapter; namely, relational operators (e.g., inputChar == 'n'), logical operators (e.g., !quit) and conditional statements (if…else).

59

Page 60: C++ Módulo I e II

As Program 2.11 implies, the while-loop takes on the following general syntax:

Execute this C++ statement(s); The condition used in Program 2.11 is the boolean expression !quit (not quit), which instructs the

s !quit is true. If the condition is true, we execute the statements in the loop body. Inside the loop body, the program asks the user if he wishes to continue. If the player chooses “no” then the program assigns true to quit, thereby making false. This will cause the

depends on user input.

ile Loop

o the while-loop. However, a do…while is guaranteed to execute at least ce. these statements at least once regardless of the condition, then while the nditi o do these statements.” Program 2.12 shows the do…while syntax.

ogram 2.12: P

while( condition is true )

program to keep looping so long a

!quitwhile-loop to terminate on the next cycle when the condition !quit is tested again. Observe that this loop will repeat an unknown amount of times and its termination

2.4.3 The do…wh

The do…while loop is similar tEssentially it says: “Do on

co on holds, continue t Pr ro emonstrates the do…while lgram d oop.

#include <iostream> using namespace std; int main() bool condition = false; do c < "Enter a 0 to quit or 1 to coout < ntinue: "; cin >> condition; while( condition );

Output 2.12.

Enter a 0 to quit or 1 to continue: 1 Enter a 0 to quit or 1 to continue: 1 Enter a 0 to quit or 1 to continue: 0 Press any key to continue

Ab

s you can see from Program 2.12, despite condition being initialized to false, we still enter the loop ody. Inside the body, the program assigns the truth-value the user entered to condition. Then at the

end, the condition is tested to see if we will loop again. By moving the loop condition to the end, we are guaranteed the loop body statements will be executed at least once.

60

Page 61: C++ Módulo I e II

Note that it does not take too much imagination to see how a do…while loop could be rewritten using a while-loop. Consequently, in practice, the do…while loop is not encountered very often.

2.4.4 Nesting Loops

Just as if…else statements can be nested, loops can be nested; that is, a loop inside a loop. Consider the following program, which nests a for-loop inside a while-loop: Program 2.13: Program demonstrates nested loops; that is, loops inside loops.

#include <iostream> using namespace std; int main() bool quit = false; while( !quit ) for(int cnt = 0; cnt < 10; ++cnt) cout << cnt << " "; cout << endl; char inputChar = 'n'; cout << "Print next ten integers (y)es/(n)o? "; cin >> inputChar; if(inputChar == 'n' || inputChar == 'N') cout << "Exiting..." << endl; quit = true;

Output 2.13.

0 1 2 3 4 5 6 7 8 9 Print next ten integers (y)es/(n)o? y 0 1 2 3 4 5 6 7 8 9 Print next ten integers (y)es/(n)o? y 0 1 2 3 4 5 6 7 8 9 Print next ten integers (y)es/(n)o? n Exiting... Press any key to continue

61

Page 62: C++ Módulo I e II

Here the outermost while-loop executes the innermost for-loop. The inner for-loop outputs the integers , and -loop continues to loop as long as the user specifies to continue. Thus, rows of

user answers “yes” to the question.

p need to be handled which terminate the loop early. For example, ) over a set of game items until you find a specific one. However, once you find it,

ou can stop your search and exit the loop. To facilitate this case, C++ provides the keyword, which ban infithe play

gram 2.14: Program demonstrates the ‘break’ keyword. (Note that Program 2.14 has the same functionality as Program 2.11 and therefore will have same output for the same input.)

0-9 the outer whileintegers 0-9 will continue to be output as long as the

2.4.5 Break and Continue Keywords

Sometimes specu may iterate (loop

ial cases inside a looyoy break

reaks out of the current loop. To illustrate, Program 2.14 rewrites Program 2.11, this time using nite loop; that is, a loop that is always true (and as such, will loop infinite times). This time, if er chooses not to continue, a break statement is executed to exit the infinite loop.

Pro

#include <iostream> using namespace std; int main() // Loop forever. while( true ) // Ask the user if they want to quit or not. char inputChar = 'n'; cout << "Continue to play? (y)es/(n)o..."; cin >> inputChar; // Test for both uppercase or lowercase. if( inputChar == 'n' || inputChar == 'N') cout << "Exiting..." << endl; break; //<--This will exit out of the infinite loop. else cout << "Playing game..." << endl; In addition to breaking out of a loop early, there may be special cases that allow us to decide early on

hether we can jump straight to the next loop cycle. The following program sums the numbers from s this by testing for

o the next loop

wzero to fifteen. However, it ignores the numbers three, seven, and thirteen. It doethese numbers, and if it finds one of them, then the program simply continues (jumps) tcycle, thereby ignoring them (i.e., not including them in the sum). Program 2.15: Program demonstrates the ‘continue’ keyword. (Note that Program 2.15 does not output anything.)

#include <iostream> using namespace std;

62

Page 63: C++ Módulo I e II

in in() t ma int sum = 0; for(int cnt = 0; cnt <= 15; ++cnt) nt == 3 || cnt == 7 || cnt == 13) if( c continue; // <--jump straight to next loop cycle. // Otherwise, add the number to the sum. sum += cnt;

statements. However, in some cases, the break and continue keywords provide a much cleaner, shorter, and intuitive way of expressing the idea than a more complex if…else statement would.

2.5 Arrays

hus fa e needed, we simply declared them like so:

“”;

owev 00 variables or an even greater amount? It is not practical to actually d give them all unique names. We can, however, declare a set of variables

the act way using an array. An array is a contiguous block of memory that vidual variables in an array the elements of the array, olds the array size. To declare an array we use the

tions:

at elements. int n[200]; // Array with 200 integer elements.

specific element in an array can be accessed using the bracket operator and supplying an array index, which identifies the element:

0f to element zero.

Note: It does not take too much effort to realize that the functionality of the break and continue statements can be implemented with if…else

T r, when variables wer

=std::string str float f = 0.0f; int n = 0; H er, what if you need 1declare so many variables an

f same type in a compocontains n number of variables. We call the indind we call the number of elements an array ha

following general syntax: type identifier[n]; Where n is an integer constant that specifies the number of elements the array contains. Here are some specific examples of array declara float fArray[16]; // Array with 16 flo std::string str[5000]; // Array with 5000 string elements. A

fArray[0] = 1.0f; // Assign 1.

63

Page 64: C++ Módulo I e II

fArray[1] = 2.0f; // Assign 2.0f to element one. fArray[12] = 13.0f;// Assign 13.0f to element twelve.

//fArray[20] = 21.0f;// !! BAD, OUT OF BOUNDS INDEX !!

Important: Observe that arrays are zero-based; that is, the first element in the array is identified with an index of zero. And therefore, the last element is identified with an index of n-1, where n is the total number of elements. Supplying an out of bounds index results in code that compiles—the compiler will not force you to correct

ou are essentially accessing memory that does not fArray was declared to store 16 elements. Thus, an

t of bounds index.

cessed one by one and assigned an initial

t in

intArray[5] = 78; intArray[6] = 0;

rray[7] = 4;

+ provides an alternative syntax:

Array[8] = -4, 6, -2, 0, 33, 78, 0, 4;

entry in the curly brace list corresponds to element [0], the second entry to [1], and so on. ent is explicitly initialized, the compiler can deduce

eds, and therefore, the array size, 8, is not explicitly required. That is, is next statement is equivalent:

In actuality, all the preceding array initialization syntaxes are only practical for small arrays. Manually initializing an array with a thousand elements, for example, is clearly impractical.

2.5.2 Iterating Over an Array

it. However, it is strongly advised not to do so as ybelong to you. In the former example, the array index of 20 is an ou

2.5.1 Array Initialization

To initialize the elements of an array, each element could be acvalue like so: in tArray[8];

intArray[0] = -4; intArray[1] = 6; intArray[2] = -2;

[3] = 0; intArrayintArray[4] = 33;

intA In addition, C+ int int Here the first By using this curly brace notation, where each elemhow many elements the array neth int intArray[] = -4, 6, -2, 0, 33, 78, 0, 4;

64

Page 65: C++ Módulo I e II

Typically, when a large array of items are stored, the elements are somewhat related to a larger whole. For example, in 3D computer graphics we usually represent a 3D object with an array of polygons.

hese polygons form a larger whole, namely the 3D object. If all of the polygon parts of the 3D object 3D object itself moves in that direction. As such, we normally do

ot modify the elements of arrays individually. Rather, we usually want to apply the same operation to every element (e.g., initialize all elements to zero, add all corresponding elements of two arrays into a third array). A loop is perfect for doing this. Program 2.16 iterates through every element in two arrays of the same size and adds their corresponding elements together and stores the sum in a third array, which is also of the same size. Program 2.16: Program demonstrates iterating over arrays.

Tmove in the same direction then then

#include <iostream> using namespace std; int main() int array0[7] = 1, 2, 3, 4, 5, 6, 7; int array1[7] = -9, -8, -7, -6, -5, -4, -3; int sum[7];//<--Stores the addition result. for(int i = 0; i < 7; ++i) // Add the corresponding i-th elements and store in sum. sum[i] = array0[i] + array1[i]; cout << array0[i] << " + " << array1[i] << " = "; cout << sum[i] << endl; Output 2.16.

1 + -9 = -8 2 + -8 = -6 3 + -7 = -4 4 + -6 = -2 5 + -5 = 0 6 + -4 = 2 7 + -3 = 4 Press any key to continue

Loops and arrays go hand-in-hand and many problems are solved using by them together. Note how the ounter variable of the c for-loop naturally becomes an index into the i-th element of the array.

rrays

2 ltidimensional A.5.3 Mu

65

Page 66: C++ Módulo I e II

It is possible to have an array of arrays; that is, an array where each individual element is also an array.

One can be declared using the following double bracket syntax: type identifier[m][n];

Figure 2.1: An array or arrays, where each element contains an array. If we say the array of arrays goes from top to bottom and each element of this array of arrays contains an array going left to right then we have a table layout.

As Figure 2.1 shows, an array of arrays forms a rectangular table or matrix. The constant m specifies the number of rows and the constant n specifies the number of columns. Such a matrix is of size (or

imension) . Here are some specific examples of matrix declarations:

float fMatrix[16][12]; // Matrix with 16x12 elements. int n[20][20]; // Matrix with 20x20 elements. std::string str[500][100]; // Matrix with 500x100 elements.

We can access a specific element in a matrix using the double bracket syntax and supplying two array dices, which identifies the row and column of the element:

fMatrix [0][0] = 1.0f; // Assign 1.0f.

= 2.0f; // Assign 2.0f. ] = 13.0f;// Assign 13.0f.

//fMatrix[16][3] = 21.0f;// !! BAD, OUT OF BOUNDS INDEX !! In asyntax

three columns.

;

d

nm×

in

fMatrix [1][2] fMatrix [12][10

ddition to performing individual element initializations, you can initialize matrices using curly brace as shown here:

// Matrix with four rows of

int matrix[4][3] =

1, 2, 3, // Row 1 4, 5, 6, // Row 2 7, 8, 9, // Row 3 10, 11, 12// Row 4

66

Page 67: C++ Módulo I e II

Becaus ix is done with a double nested loop—one that

erates over the rows and one that iterates over the columns. The following double for-loop iterates ove

for(int i = 0; i < 4; ++i)

New line for each row.

arrays and so on by following the same general pattern. That is, a 3D

s: type identifier[m][n][p];

ultidimensional array greater than a 3D array to avoid overly complex situations.

2.6 Summary

1. The relational operators allow us to determine certain elementary relationships between

a. The logical AND operator (&&)

e of the extra dimension, iteration over a matrit

r the preceding matrix and outputs its elements to the console window: // Loop over rows.

// Loop over columns. for(int j = 0; j < 3; ++j)

cout << matrix[i][j] << " "; cout << endl; //

You can create arrays of arrays ofarray would be declared a

But, as a rule of thumb, it is advised that you not use a m

variables, such as equality and inequality.

2. We use three logical operators:

b. The logical OR operator (||) operator (!). c. The logical NOT

67

Page 68: C++ Módulo I e II

These logical operators allow us to form more complex boolean expressions and therefore enable

make decisions at runtime. They allow us ecuted if a particular condition is satisfied

ue then execute this code).

4. Loops enable us to repeat a block of code a variable number of times.

ontiguous block of memory that contains n amount of variables. We call the les in an array the elements of the array, and we call the number of elements an

array holds, the array size.

2.7.1 Logical Operator Evaluation

hat Boolean value do the following expressions

|| B || C || D

&& B && C && D

d. !((A && B) || (B && !C)) || !(C && D)

e. !(!((A && !D) && (B || C)))

ne unit north, east, south or west. ach time the player enters a selection, update the coordinates of the user and output the current

position. Start the player at the origin of the coordinate system. Your program’s output should look like e followin :

ast, (S)outh, (W)est (Q)uit? n urren P 0, 1) Move (N)orth, (E)ast, (S)outh, (W)est (Q)uit? e

us to make more useful conditional statements.

to any program that needs to3. Conditionals are the key to make certain that a block of code will only be ex(i.e., if this condition is tr

5. An array is a cindividual variab

2.7 Exercises

Assume A is true, B is true, C is false and D is false. Wevaluate to?

a. A b. A c. !C && !D

2.7.2 Navigator

Write a program that displays a menu which allows the user to move oE

th g Current Position = (0, 0) ove (N)orth, (E)MC t osition = (

68

Page 69: C++ Módulo I e II

Curren osiove (N)orth, (E)ast, (S)outh, (W)est (Q)uit? s

w

ove (N)orth, (E)ast, (S)outh, (W)est (Q)uit? q

Write an averaging program. The program must prompt the user to enter a positive integer that specifies es to average. The program must then ask the user to input these n

values. The program should then compute the average of the values inputted and output it to the user—use the following average formula:

t P tion = (1, 1) MCurrent Position = (1, 0) Move (N)orth, (E)ast, (S)outh, (W)est (Q)uit? Current Position = (0, 0) MExiting... Press any key to continue

2.7.3 Average

the number of values n the user wish

naaa

avg n 110 ... −+++= .

Your program’s output should look like the following:

1] = 2 2] = 3 3] = 4 [4] = 5 verage = 3

Factorial

orial of a positive integer n, denoted n!, is defined as follows:

)

ent, 0! = 1. To ensure that you are comfortable with the factorial operation, let us write out a uple of examples.

• Evaluate 4!.

Enter the number of values to average: 5 0] = 1 [[[[

APress any key to continue

2.7.4

The fact

( )( ) ( )( )(123...21 −−= nnn . n!

mBy agreeco

69

Page 70: C++ Módulo I e II

4! = 4

(3)(2)(1) = 24.

• Evaluate 6!.

trices. Consider

⎣ −⎦⎣ −++⎦− 63160300163

Write a program that creates three matrix variables of dimension

6! = 6(5)(4)(3)(2)(1) = 720. Write a program that asks the user to enter in a positive integer n. Compute the factorial of n and output the result to the user. Your program’s output should look like the following: Enter a positive integer to compute the factorial of: 5 5! = 120 Press any key to continue

2.7.5 Matrix Addition

The sum of two matrices is found by adding the corresponding elements of the two mathe following two matrices and A B.

⎥⎢=A ⎥⎢=B ⎤⎡− 825 ⎤⎡ 201

⎦⎣ 001 ⎦⎣ − 630 The sum, found by adding corresponding elements, is:

⎢=+BA ⎥⎤

⎢⎡−

=⎥⎤

⎢⎡ +++−

=⎥⎤

⎢⎡

+⎥⎤⎡− 1024280215201825

⎣⎦⎣ 0001 ⎦

32× , two of which are to be initialized

in such a way that they are identical to A and B; the third will be used to store the sum of A and B. sing a double for-loop to iterate over the matrix elements, compute the sum of each component and ore the result in the third matrix (e.g., C[i][j] = A[i][j] + B[i][j]). Finally, output the matrix sum. Your

program’s output should look like the following:

-5 2 8 1 0 0

=

y key to continue

Ust

A =

B1 0 2 0 3 -6 A + B = -4 2 10 1 3 -6 ress anP

70

Page 71: C++ Módulo I e II

2.7.6 ASCII

Write a program that outputs every character in the extended ASCII character set in the range [33, 255]. Note that we omit characters from [0, 32] since they are special command characters. (Hint: Recall that characters are represented by the char and unsigned char types, so simply loop through each

: 59: ; 60: < 61: = 62: > D 69: E 70: F 71: G 72: H

97: a 98: b 99: c 100: d 101: e 102: f 03: g 104: h 105: i 106: j 107: k 108: l 109: m 110: n 111: o 112: p

: ô 148: ö 149: ò 150: û 151: ù 152: ÿ 155: ¢ 156: £ 157: ¥ 158: ₧ 159: ƒ 160: á 161: í 162: ó

170: ¬ 171: ½ 172: ¼ 73: ¡ 174: « 175: » 176: 177: 178: 179: 180: 181: 182: 83: 184: 185: 186: 187: 188: 189: 190: 191: 192:

195: 196: 197: 198: 199: 200: 201: 202: 211: 212:

Φ

8: ε 239: ∩ 240: ≡ 241: ± 242: ≥ √ 252: ⁿ

inear Search

t of data for values that have some e and you want to

s; that is, a subset of n order to do this, you will need to search the

rs of each class and copy those members into their corresponding class subset.

possible value—33-255—and output it.) Your program’s output should look like the following: 33: ! 34: " 35: # 36: $ 37: % 38: & 39: ' 40: ( 41: ) 42: * 43: + 44: , 45: - 46: . 47: / 48: 0 49: 1 50: 2 51: 3 52: 4 53: 5 54: 6 55: 7 56: 8 57: 9 58:3: ? 64: @ 65: A 66: B 67: C 68:673: I 74: J 75: K 76: L 77: M 78: N 79: O 80: P 81: Q 82: R 83: S 84: T 85: U 86: V 87: W 88: X 89: Y 90: Z 91: [ 92: \ 93: ] 94: ^ 95: _ 96: `1113: q 114: r 115: s 116: t 117: u 118: v 119: w 120: x 121: y 122: z 123: 124: | 125: 126: ~ 127: 128: Ç 129: ü 130: é 131: â 132: ä 133: à 134: å 135: ç 136: ê 137: ë 138: è 139: ï 140: î 141: ì 142: Ä 143: Å 144: É 145: æ 146: Æ 147153: Ö 154: Ü163: ú 164: ñ 165: Ñ 166: ª 167: º 168: ¿ 169: 11193: 194: 203: 204: 205: 206: 207: 208: 209: 210:213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: α 225: ß 226: Γ 227: π 228: Σ 229: σ 230: µ 231: τ 232:233: Θ 234: Ω 235: δ 236: ∞ 237: φ 2343: ≤ 244: ⌠ 245: ⌡ 246: ÷ 247: ≈ 248: ° 249: · 250: · 251: 2253: ² 254: 255: Press any key to continue

2.7.7 L

Background Information In writing computer programs you will often need to search a separticular properties. For example, imagine you have some fantasy role-playing gam

on the character classeorganize a global dataset of “players” into subsets based a subset of clerics, and etc. Iwarriors, a subset of wizards,

global player list for the membe

71

Page 72: C++ Módulo I e II

As another example, suppose aparticular identification number,

business issues identification numbers to its customers. Given a the business would like to search its customer database for the

customer information (e.g., name, address, order history, etc.) that corresponds with the given that the value used for the search, which in this example is an identification

umber, is called a If the business searched for a customer’s information using the

xample

identification number. Notesearch key.n

customer’s last name, then the last name would be the search key. One method of searching a set of data is called a linear search. The linear search simply scans the dataset element-by-element until it finds the elements it is looking for. Clearly, this search can be fast if the particular element is found early on (not guaranteed), or can be slow if the particular element is one of the last few elements to be scanned. E : Find the array position of the value ‘9’ in the following dataset: 7, 3, 32, 2, 55, 34, 6, 13, 29, 22, 11, 9, 1, 5, 42, 39, 8. Solution: We use the linear search mwe find that the value ‘9’ is

ethod; that is, reading the array left to right and element-by-element located in the eleventh position (e.g., index [11]) of the array (recall that

general, we have the following linear search algorithm:

ine S

array positions start at 0 and not 1). In L ar earch.

Let x[n] = x[0],…,x[n-1] be an array of given integers to search. Let store the array index of the item we are searching for. Position be an integer toLet are searching for. Value be the value we For 0=i to 1−n , do the following: If( x[i] = Value ) Position = i; Break; Else Continue;

eExercis

rogram search the array for the integer the user entered and output its array position (i.e., s array index). Your output should look similar to the following:

tem f dex [4] Press any key to continue

Hardcode the following integer array: 7, 3, 32, 2, 55, 34, 6, 13, 29, 22, 11, 9, 1, 5, 42, 39, 8 into your

rogram. Display this array to the user. Then ask the user to input an integer to search for. Yourpp should thenit List = 7, 3, 32, 2, 55, 34, 6, 13, 29, 22, 11, 9, 1, 5, 42, 39, 8 nter an integer in the list to search for: 55 EI ound at in

72

Page 73: C++ Módulo I e II

2.7.8 Selection Sort

Background Information

you may want to sort records f customers by age, zip code, and/or gender; or in 3D computer graphics you might want to sort 3D

geometry (e.g., polygons) based on their distance from the viewer. Because sorting is so important, we ill end up spending several lab projects working on the different sorting techniques that have been

also

A frequent task that occurs in writing computer programs is the need to sort a set of data. For example, you might want a list of integers sorted in ascending or descending order;o

wdevised. However, to begin with, we will start with one of the simplest (and one of the least efficient) sorting methods, called the selection sort. The selection sort algorithm is as follows: Ascending order Selection Sort.

Let x[n] = x[0],...,x[n-1] be an array of given integers to sort. Let p be an integer to store an array index. For 0=i to 2−n , do the following:

1. Find the array index of the smallest value in the subarray x[i],…,x[n-1] and store it in p.

2. Swap x[i] and x[p]. 3. Increment i.

To be sure that you understand what this algorithm specifies, consider the following example: Example: Consider the following integer array:

= 7, 4, 1, 8, 3 ; we scan through the subarray x[0],…,x[n-1] and search for the smallest number. We find

t integer 1 is located at index p = 2. The algorithm then tells us to swap x[i] and x[p], tly means to swap x[0] and x[2]. Doing so yields the new array configuration:

barray x[1],…,x[n-1] and searching for the = 4. Swapping x[1] and x[4] yields:

1, 3, 7, 8, 4

x[5]

When 0=iallesthat the sm

which curren

1, 4, 7, 8, 3

1 . Scanning through the suIncrementing i, we now have =ismallest integer we find 3 at index p

73

Page 74: C++ Módulo I e II

Incrementing i, we now have 2=i . Scanning throughsmallest integer we find 4 at index p = 4. Swapping x[2

1, 3, 4, 8, 7

the subarray x[2],…,x[n-1] and searching for the ] and x[4] yields:

Incrementing i, we now have . Scanning through the subarray x[3],…,x[n-1] and searching for the smallest integer we find 7 at index p = 4. Swapping x[3] and x[4] yields:

1, 3, 4, 7, 8

. Observe that we make passes, since the ts.

As you can see, this algorithm moves one element into its correct sorted position per pass. That is, for each cycle it finds the smallest element in the subarray x[i],…,x[n-1] and moves it to its correct sorted

osition, which is the front of the subarray: x[i]. This makes sense because, if we are sorting in cending order then the smallest element in the subarray x[i],…,x[n-1] should be first. Because each

pass correctly sorts one element, we do not need to consider that element anymore. We effectively nore that element by shortening the subarray by one element (incrementing i). We then repeat the ocess with our new shortened subarray by searching for the next smallest element and placing it in its

sorted position, and so on, until we have sorted the entire array.

xercise

3=i

We have successfully sorted the array in ascending orderlast pass correctly sorts both the x[n-2] and x[n-1] elemen

2−n

pas

igpr

E

sk the user to input ten random (non-sorted) integers and store them in an array. Sort these integers in cending order using the selection sort and output the sorted array to the user. Your output should

look similar to the following: ter ten unsorted integers... ] = 5

[1] = -3 [2] = 2 ] = 1 ] = 7

[5] = -9 [6] = 4 ] = -5 ] = 6

[9] = -12 sorted List = 5, -3, 2, 1, 7, -9, 4, -5, 6, -12, rting...

Sorted List = -12, -9, -5, -3, 1, 2, 4, 5, 6, 7, Press any key to continue

Aas

En[0

[3[4

[7[8

UnSo

74

Page 75: C++ Módulo I e II

Chapter 3

Functions

Introduction

75

Page 76: C++ Módulo I e II

A function is a unit of code designed to perform a certain task. In order to perform its task, a function me information and/or returns some information. The concept is somewhat similar to

e trigonometric function typically inputs somathematical functions. Consider th ( )xsin , which takes a parameter x and

) some value, namely the sine of x. For example, the sine of 45° is approximately , given 45° as a parameter, the function works to compute

hen returns or evaluates to the result 0.707.

he utility of functions can be be easily demonstrated if we first consider a program without them. To lustrate, suppose we have a program that must input the radius of a circle from the user and compute

evaluates (returns0.707, ( ) 707.045sin =° ; that is to say ( )xsinthe sine of the given angle, and t Tilthe area throughout the program. (Recall the area of a circle is given by 2rA ⋅= π , where r is the radius of the given circle.) As a first attempt we might do the following: Program 3.1: Program without Functions.

#include <iostream> using namespace std; int main() float PI = 3.14f; // Input a radius and output the circle area. float radius = 0.0f; cout << "Enter a radius of a circle: "; cin >> radius; float area = PI*radius*radius; cout << "Area = " << area << endl; // Do some other work... cout << "Other work..." << endl; // Input another radius and output the circle area. cout << "Enter a radius of a circle: "; cin >> radius; area = PI*radius*radius; cout << "Area = " << area << endl; // Do some other work... cout << "Other work..." << endl; // Input another radius and output the circle area. cout << "Enter a radius of a circle: "; cin >> radius; area = PI*radius*radius; cout << "Area = " << area << endl; // and so on... Output 3.1.

Enter a radius of a circle: 2

76

Page 77: C++ Módulo I e II

Area = 12.56 Other work... Enter a radius of a circle: 3 Area = 28.26 Other work... Enter a radius of a circle: 1 Area = 3.14 Press any key to continue

ate code which esse a rams with duplicated code are hard to m to be made, it would be necessary to make the change or c e ogram, going through each source cod l rror.

compute the area and return the result. For the sake of discussion, let us assume such a unction exists and call it Area; moreover, assume the task of Area—that is inputting the radius from

the user, computing the area and returning the result—is executed by simply writing “Area()” in a C++ program. (When we execute a function we say that we call it or invoke it.) Program 3.1 can now be rewritten like so: Program 3.2: Revision of Program 3.1 using an Area function. Note that this program will not compile yet because the function Area is not actually defined. Note that we have bolded the calls to the area function.

The main problem which Program 3.1 suffers is code duplication—there is duplic

nti lly performs the same task. Besides bloating the code, progaintain because if changes or corrections need orr ction in every duplicated instance. In a large real world pre fi e and making changes is not only a waste of time, but it is prone to e

The problems of Program 3.1 could be resolved using a function whose sole task is to input the radius from the user, f

#include <iostream> using namespace std; int main() // Input a radius and output the circle area. cout << "Area = " << Area() << endl; // Do some other work... cout << "Other work..." << endl; // Input another radius and output the circle area. cout << "Area = " << Area() << endl; // Do some other work... cout << "Other work..." << endl; // Input another radius and output the circle area. cout << "Area = " << Area() << endl; // and so on... Program 3.2 is much cleaner and more compact. There is no longer duplicate code for the input code

pute a and calculation code—we simply write “Area()” wherever necessary to input a radius and com

77

Page 78: C++ Módulo I e II

circle area. Moreover, if a change or correction needs to be made to the code Area executes, it would modification of the Area function. With that, let us see how Area works.

ons, let us examine the actual syntactic details of making unction (i.e., create) the following needs to be specified: pe of value the function evaluates to)

name (i.e., what you want to refer to it as) eter list (i.e., what values does it take as input, if any)

gure ction, from the preceding discussion, would be plem

only necessitate

3.1 User Defined Functions

Now that we understand the benefits of functiand using them. To define a f

return type (i.e., the ty

parambody (i.e., the code to be executed when the function is invoked)

3.1 shows the syntax of how the Area funFi

im ented.

Figure 3.1: Function definition.

l compile and run.

on of Program 3.2, this time with the Area function defined.

lProgram 3.3 rewrites Program 3.2, this time defining Area so that the program wi

Program 3.3: Revisi

#include <iostream>

78

Page 79: C++ Módulo I e II

using namespace std; fl Area() oat float PI = 3.14f; float radius = 0.0f; cout << "Enter a radius of a circle: "; cin >> radius; float area = PI*radius*radius; return area; int main() // Input a radius and output the circle area. cout << "Area = " << Area() << endl; // Do some other work... cout << "Other work..." << endl; // Input another radius and output the circle area. cout << "Area = " << Area() << endl; // Do some other work... cout << "Other work..." << endl; // Input another radius and output the circle area. cout << "Area = " << Area() << endl; // and so on... Observfunctio lared or defined before it is called, as the compiler must recognize the

nction before you call it. A function declaration (also called a function prototype) consists of the return type, function name, and parameter list followed by a semicolon—there is no body in a function

. However, once call the function. Program 3.4 rewrites

Program 3.3 using a function declaration.

e from Program 3.3 that the function Area is defined before any calls to that function. A n must be either dec

fu

declaration. Once a function is declared, it can be defined elsewhere in the programdeclared, the function definition can come even after you

Program 3.4: Revision of Program 3.3, this time using a function declaration.

#include <iostream> using namespace std; // Function declaration. The function declaration just tells the // compiler the function exists (it will be defined later), and // its name, return type and parameters. float Area(); int main()

79

Page 80: C++ Módulo I e II

// Input a radius and output the circle area. cout << "Area = " << Area() << endl; // Do some other work... cout << "Other work..." << endl; // Input another radius and output the circle area. cout << "Area = " << Area() << endl; // Do some other work... cout << "Other work..." << endl; // Input another radius and output the circle area. cout << "Area = " << Area() << endl; // and so on... // Function definition. The function definition contains the // ction body and consists of the code that specif fun ies what // the function actually does. float Area() float PI = 3.14f; float radius = 0.0f; cout << "Enter a radius of a circle: "; cin >> radius; float area = PI*radius*radius; return area;

Note: It is illegal syntax to define a function inside another function. This includes main because main is a function, itself.

3.1.2 Functions with One r

The Area function did not have a parameter. But let us look at an example which does have a parameter. A useful function might be one that cubes ( 3x ) the given input, as follows:

rogram 3.5: Function with a parameter. We have bolded the function calls to .

Paramete

CubeP

#include <iostream> using namespace std; // Declare a function called 'Cube' which has a parameter // of type 'float' called 'x', and which returns a value // of type 'float'.

80

Page 81: C++ Módulo I e II

float Cube(float x); int main() float input0 = 0.0f; cout << "Enter a real number: "; cin >> input0; cout << input0 << "^3 = " << Cube(input0) << endl; float input1 = 0.0f; cout << "Enter another real number: "; cin >> input1; cout << input1 << "^3 = " << Cube(input1) << endl; // Provide the definition of Cube--it computes x^3. float Cube(float x) float result = x * x * x; // x^3 = x * x * x. return result; Program 3.5 Output

Enter a real number: 2 2^3 = 8 Enter another real number: 3 3^3 = 27 Press any key to continue

The Cube function is similar to the function except that it takes a parameter (i.e., its parameter list

side the parentheses of the fArea

unction declaration/definition is not empty). A parameter is not a value in riable). That is, the function caller will “pass in” or

le for the function to use. The actual value passed into a alled an argument.

0). Here input0 is the argument—it stores a Cube hat is, the value stored in input0 is copied into

meter x. The word “copied” is important, as input0 and x are not the same variables but the function is called. This copying of argument value to

rame he argument is copied to the parameter, the code inside the e value that was passed into it.

gure code relative to the calling program code. Think of a eed data into it (copy arguments into the parameters), it

s so n body), and it outputs a result (returns something back to u). Again, functions are useful primarily because they prevent code duplication and provide a level of

inand of itself, but rather a value placeholder (va“input” a value into this placeholder variabparticular function call is c For example, in program 3.5, we write Cube(input

ecific value which is input into the function; tspthe Cube parawill contain copies of the same value when

ter is called passing by value. Once tpabody of Cube can execute, where x contains th Fi 3.2 shows how you can think of functionfunction as a separate “machine” where you f

mething with those parameters (functiodoeyo

81

Page 82: C++ Módulo I e II

code organization; that is, breaking up programs into more manageable parts, where each part does a specific task.

Figure 3.2: Calling functions with parameters and returning results.

3.1.3 Functions with Several Parameters

Functions are not limited to zero or one parameter, but can have several parameters. The following program uses a function named PrintPoint, which takes three parameters: one each for the x-coordinate, y-coordinate and z-coordinate of a point. The function then outputs the coordinate data in a convenient point format. Program 3.6: Functions with several parameters.

#include <iostream> using namespace std; void PrintPoint(float x, float y, float z); int main() PrintPoint(1.0f, 2.0f, 3.0f); PrintPoint(-5.0f, 3.5f, 1.2f); PrintPoint(-12.0f, 2.3f, -4.0f); PrintPoint(9.0f, 8.0f, -7.0f); void PrintPoint(float x, float y, float z)

82

Page 83: C++ Módulo I e II

cout << "<" << x << ", " << y << ", " << z << ">" << endl; Pr 3.6 Output ogram

<1, 2, 3> <-5, 3.5, 1.2> <-12, 2.3, -4> <9, 8, -7> Press any key to continue

As Program 3.6 shows, additional parameters could be added and

is called, an argument for each parameter is provided. separated with the comma operator.

other thing to notice about the PrintPoint function is that because its sole task is to output a point the console window, it does not need to return a value. It is said that the function returns void, and

specified for the return type. Observe that you do not need to write a return a function that returns void.

nctions themselves can contain other code units such as if statements and loops. Furthermore, these

s now to see how this vocabulary is used.

When a function Ato

n

as such, void is statement for

3.2 Variable Scope

As Figure 3.2 implies, by using functions our programs are broken up into code parts. Moreover, fucode units can be nested. This brings up the topic of variable scope. Variable scope refers to what variables a code unit can “see” or “know about”. A variable defined inside a particular code unit is said to be a local variable relative to that code unit. Additionally, a variable defined outside a particular code unit is a global variable relative to that code unit. (A subunit of code is not considered to be “outside” the unit of code that contains the subunit.) A unit of code can “see” variables that are global and local, relative to it. Let us look at a couple of example

3.2.1 Example 1

Program 3.7: Variable scope example 1.

#include<iostream> using namespace std; float gPI = 3.14f; float SphereVolume(float radius); int main()

83

Page 84: C++ Módulo I e II

cout << "PI = " << gPI << endl; cout << endl; float input0 = 0.0f; cout << "Enter a sphere radius: "; cin >> input0; float V = SphereVolume(input0); cout << "V = " << V << endl; float SphereVolume(float radius) float V = (4.0f/3.0f)*gPI*radius*radius*radius; return V; Program 3.7 Output

PI = 3.14 Enter a sphere radius: 3 V = 113.04 Press any key to continue

The first variable defined is gPI. Because gPI is outside the code units of main and SphereVolume, it is global relative to both of them, and both functions can “see”, use, and modify gPI. The next set of variables occurs inside the main function unit of code. These variables, input0 and V, re local to main and global to no code unit. Therefore, main is the only code unit that can “see” them.

ot “see” input0, and if you try to use input0 in SphereVolume, error. Note, however, that you can define a variable with the same variable

er code unit, as we do with V. Because the variables V are defined in separate are completely independent of each other.

ned, SphereVolume defines its own separate version of V. This V is local to lume is the only code unit that can “see” this V. Additionally,

e. When the argument input0 is passed into variable radius. It is important to understand

t input0 and radius are separate variables in memory.

t are destroyed when the program exits that code unit. For example, when SphereVolume is invoked, the program will create memory for the variable V. After

aFor example, SphereVolume cannyou will get a compilername as a variable in anothcode units they Finally, as already mentioSphereVolume and therefore SphereVoth m variable radius is local to SphereVolum

in input0 is copied to thee para eter

the function, the value stored es place and thathat this copy tak

Important: Variables declared in a code uni

the function ends (after V is returned) the memory for V is deleted.

84

Page 85: C++ Módulo I e II

3.2.2 Example 2

Program 3.8: Variable scope example 2.

#include<iostream> using namespace std; int main() for(int i = 0; i < 5; ++i) int cnt; cout << "Hello, World!" << endl; ++cnt; cout << "cnt = " << cnt << endl; This short program fails to compile. In particular, we get the error “error C2065: 'cnt' : undeclared

entifier.” This error is caused because cnt is local to the for-loop code unit. The variable cnt is bal to main, and as such, main cannot “see” it. Thus, the program reports that it is

d” when main attempts to access it.

rogram 3.8 also has a logic error. Namely, the counting variable cnt is er of loop cycles. Recall the “important” note from the Section 3.2.1.

idneither local nor glo“undeclare Besides the compilation error, P

ot keeping track of the numbnVariables declared in a code unit are destroyed when the program exits that code unit. Every time the for-loop repeats, cnt is re-created and re-destroyed after that loop cycle, and therefore, the value does not persist. The program needs to be rewritten as follows: Program 3.9: Revision of Program 3.8.

#include<iostream> using namespace std; int main() int cnt; for(int i = 0; i < 5; ++i) cout << "Hello, World!" << endl; ++cnt; cout << "cnt = " << cnt << endl;

cnt is global to the for-loop, and will not be created and destroyed every time the loop peats. In this way, it will be able to actually count the loop cycles. Furthermore, cnt is now local to

main, and so fixes the “'cnt' : undeclared identifier” error.

Note that re

85

Page 86: C++ Módulo I e II

3.2.3 Example 3

Program 3.10: Variable scope example 3.

#include<iostream> using namespace std; int main() float var = 5.0f; if( var > 0.0f ) float var = 2.0f; cout << "var = " << va dl; r << en cout << "var = " << var << endl; Program 3.10 Output

var = 2 var = 5 Press any key to continue

Recall that we can create varia

ubles of t t in ifferent code units. This is nctions b f op statements. Program 3.10

eclares a variable called var local to main and assigns 5.0 to it. The program then asks if var is greater

ut. However, this presents a dilemma: Which ar is used in the cout statement: the one local to the ‘if’ statement or the one local to main? As a

he same name if they exis dstraightforward with separate f ut can be tricky when using i /lodthan zero. It is, and the program executes the ‘if’ statement consequent. The program declares a new variable also called var (which is legal since the ‘if’ statement is a separate code unit) and assigns 2.0 to it. The program then proceeds to output var using covrule, C++ always uses the variable “closest” to the working code unit. In this example, the program chooses to output the variable var that is local to the ‘if’ statement because it is “closer” to the ‘if’ statement than the variable var that is global to the ‘if’ statement (local to main). The program output verifies this—the cout statement inside the ‘if’ statement printed out the local version of var, which contained the value 2.

86

Page 87: C++ Módulo I e II

3.3 Math Library Functions

s for many of the elementary math functions and operations, oot and absolute value ust be included. The

ble 3.

The C++ standard library provides functionsuch as trigonometric, logarithmic, and exponential functions, as well as square rfunctions. To use the standard math functions, the <cmath> header file mfollowing table summarizes some of the most commonly used math functions: Ta 1: Some Standard Library Math Functions.

Function Declaration Description

( )xcos .float cosf(float x); Returns ( )xsinfloat sinf(float x); Returns . ( )xtan . float tanf(float x); Returns ( )x1cos− . float acosf(float x); Returns ( )x1sin − . float asinf(float x); Returns ( )x1tan − . float atanf(float x); Returns

float sqrtf(float x); Returns x . ( )xln . float logf(float x); Returns

float expf(float x); xe . Returns float powf(float x, float y); Returns yx . float fabsf(float x); Returns x . float floorf(float x); Returns the largest integer x≤ . float ceilf(float x); Returns the smallest integer x≥ . Remark 1: The trigonometric functions work in radians and not degrees. A number x can be converted from radians to degrees by multiplying it by π°180 . For example:

°=°⋅=°⋅== 360180218022 πππx . Likewise, a number x can be converted from degrees to radians y multiplying it by °180π . For example: ππ 2180360360 =°⋅°=° . b

Remark 2: The functions above work with floats, hence the ‘f’ suffixes. The standard math library

t work with doubles. The double versions are the same except that the ‘f’ example, the double version of the cosine function would be double

x). In real-time 3D computer game graphics floats are typically used, which is the e float versions were given in the above table.

s how to call some of these “calculator” functions. The results can be mputations on a calculator.

ogram library functions.

also provides versions thaffix is omitted. For su

cos(double son why threa

The following program showverified by performing the co P 3.11: Examples of using the standard mathr

#include <iostream>

87

Page 88: C++ Módulo I e II

#include <cmath> us namespace std; ing int main() float PI = 3.14f; float quarterPI = PI / 4.0f; cout << "cosf(0.0f) = " << cosf(0.0f) << endl; cout << "sinf(quarterPI) = " << sinf(quarterPI) << endl; cout << "sqrtf(2.0f) = " << sqrtf(2.0f) << endl; cout << "logf(expf(1.0f)) = " << logf(expf(1.0f)) << endl; cout << "powf(2.0f, 3.0f) = " << powf(2.0f, 3.0f) << endl; cout << "fabsf(-5.0f) = " << fabsf(-5.0f) << endl; cout << "floorf(2.3f) = " << floorf(2.3f) << endl; cout << "ceilf(2.3f) = " << ceilf(2.3f) << endl; Program 3.11 Output

cosf(0.0f) = 1 sinf(quarterPI) = 0.706825 sqrtf(2.0f) = 1.41421 logf(expf(1.0f)) = 1 powf(2.0f, 3.0f) = 8 fabsf(-5.0f) = 5 floorf(2.3f) = 2 ceilf(2.3f) = 3 Press any key to continue

3.4 Random Number Library Functions

The C++ standard library provides a function called rand (include <cstdlib>), which can be used to generate a pseudorandom number. This function returns a random integer in the range [0, RAND_MAX], where RAND_MAX is some predefined constant. Consider the following example: Program 3.12: Random numbers without seeding.

#include <iostream> #include <cstdlib> using namespace std; int main() int r0 = rand(); int r1 = rand(); int r2 = rand(); int r3 = rand(); int r4 = rand();

88

Page 89: C++ Módulo I e II

cout << "r0 = " << r0 << endl; cout << "r1 = " << r1 << endl; out << "r2c = " << r2 << endl; r3 << endl; cout << "r3 = " << r4 << endl; cout << "r4 = " << Program 3.12 Output after first run

r0 = 41 r1 = 18467 r2 = 6334 r3 = 26500 r4 = 19169 Press any key to continue

Program 3.12 Output after second run

r0 = 41 r1 = 18467 r2 = 6334 r3 = 26500 r4 = 19169 Pr any key to continuess e

Output after third run Program 3.12

r0 = 41 r1 = 18467 r2 = 6334 r3 = 26500 r4 = 19169 Press any key to continue

This program is executed several times with the same output every time—that is not very random.

This is because pseudorandom numbers are not really random but generated using a complex algorithm. The algorithm has a starting point which it uses to start generating numbers. If the starting point is always the same then the sequence of generated numbers will also always be the same. The solution to this problem is to set the rting point at the beginning of the application to a value that is different than the previous

ay to achieve this is to use the current system time as a starting point, nce the system time will be different every time the program runs. Because a different starting point is

ns, the random numbers generated will be different each time the

ystem time we use the time function (include <ctime>). The MSDN (Microsoft etwork) library states: “The time function returns the number of seconds elapsed since

mathematical pseudorandompseudorandomalgorithm’s sta starting points used. An easy wsiused every time the application ruapplication runs. To get the sDevelopers N

89

Page 90: C++ Módulo I e II

midnight (00:00:00), January 1, 1970, coordinated universal time (UTC), according to the system

he pseudorandom number generator’s starting point is set using the srand function. (This is called r.) The following code snippet sets the starting point to that

time(0) );

rned from time is passed to srand. Note that the pseudorandom number generator is re making any calls to rand.

et us now rewrite Program 3.12 with seeding:

clock.” Tseeding the pseudorandom number generatoof the current time:

srand(

The value retuseeded once per application befo L

Program 3.13: Random numbers with seeding.

#include <iostream> #include <cstdlib> #include <ctime> using namespace std; int main() srand( time(0) ); int r0 = rand(); int r1 = rand(); int r2 = rand(); int r3 = rand(); int r4 = rand(); " <cout << "r0 = < r0 << endl; cout << "r1 = " << r1 << endl; cout << "r2 = " << r2 << endl; cout << "r3 = " << r3 << endl; cout << "r4 = " << r4 << endl; As the output shows, we get different pseudorandom numbers every time the program runs. Program 3.13 Output after first run

r0 = 16879 r1 = 22773 r2 = 31609 r3 = 31002 r4 = 15582 Press any key to continue

90

Page 91: C++ Módulo I e II

Program 3.14 Output after second run

r0 = 16928 r1 = 20159 r2 = 4659 r3 = 31504 r4 = 6460 Press any key to continue

Program 3.14 Output after third run

r0 = 16996 r1 = 16499 r2 = 19359 r3 = 12545 r4 = 26458 Press any key to continue

3.4.1 Specifying the Range

Usually [0, RAND_MAX] is not desired; rather, we would like to ecify ber in the range [0, n-1] can be computed, where n is an integer we

operator. For example, to compute a random integer in the range [0,

t nut va

as:

his works when you consider the fact that the remainder of a number ided by n must be in the range [0, n-1]. This is because if the remainder was greater than or equal to

then idend again. Remember, a remainder is the remaining part t can t divisible by n).

2 + rand() % 11;

, a random number in the range the range. A random numsp

specify, by using the modulus (%)24] we would write: i m = rand();

l = num % 25; nin

is is more compactly writtenTh int num = rand() % 25;

It is not difficult to see how tdivn, the divisor could divide into the div

not be evenly divided by n (i.e., is notha We also want ranges that do not start at zero. For example, we may want a range like [2, 10]. This is easily formulated. First, we start with rand() % 11; This gives a random number in the range [0, 10]. To get the two, we can shift our random range over by adding two like so:

91

Page 92: C++ Módulo I e II

But now our shifted range is [2, 12]. This is remedied by subtracting two from the right side modulus

s:

2);

alized and generated in range [a, b] using the formula:

ading

ometimes, two or more versions of some function are needed. For example, suppose that we want an o

eturn the computed area, but, in addition, we also want an Area function, which simply returns the parameter. Here are the two versions:

ea Version 1

operand. That i 2 + rand() % (11 – This yields a random number in the range [2, 10]. Using the same method of the preceding example, a random number can be generthe a + rand() % ((b + 1) – a )

3.5 Function Overlo

SArea function, like the one in Section 3.1, which prompts the user to enter a radius and then proceeds trcomputed area given the radius as a Ar

float Area() float PI = 3.14f; float radius = 0.0f; cout << "Enter a radius of a circle: "; cin >> radius; float area = PI*radius*radius; return area; Area Version 2

float Area(float radius) float PI = 3.14f; return PI * radius * radius;

e both versions of our area function were named Becaus “Area”, the compiler would be expected to it is assumed that the compiler would not be

e to distinguish between them. This is not the case, however, because these two versions of Area are ture. The signature of a function includes the

object to the redefinition of Area, and even if it did not, ablnot ambiguous due to their differing function signa

92

Page 93: C++ Módulo I e II

function name and parameter listings. If either the function name or the parameter listings between ed. Parameter listings

y by quantity, type or both. Observe that the return type is not part of the function signature. The act ons—which differ in signature—of a function is called function

t us look at another example. Recall that the PrintPoint function from Section 3.1.3 was plemented like so:

x, float y, float z)

<< "<" << x << ", " << y << ", " << z << ">" << endl;

ng the coordinates x, y, and z, a programmer may want to pass in a 3-element array, o the x-coordinate, [1] to the y-coordinate, and [2] to the z-coordinate.

o facilitate this, we overload PrintPoint like so:

A better way to implement our new PrintPoint is in terms of the other PrintPoint function. That is:

PrintPoint(p[0], p[1], p[2]);

rogram 3.15: Function Overloading.

several functions varies, then the compiler can deduce which function was callvarof defining several different versi

rloading. ove Lim

e

void PrintPoint(float cout

passiIn addition towhere element [0] corresponds tT void PrintPoint(float p[3]) cout << "<" << p[0] << ", " << p[1] << ", " << p[2] << ">" << endl;

void PrintPoint(float p[3])

Program 3.15 revises Program 3.6, this time using both versions of PrintPoint. P

#include <iostream> using namespace std; void PrintPoint(float x, float y, float z); void PrintPoint(float p[3]); int main() PrintPoint(1.0f, 2.0f, 3.0f); PrintPoint(-5.0f, 3.5f, 1.2f); float point1[3] = -12.0f, 2.3f, -4.0f; float point2[3] = 9.0f, 8.0f, -7.0f; PrintPoint(point1); // use array version

93

Page 94: C++ Módulo I e II

PrintPoint(point2); // use array version void PrintPoint(float x, float y, float z) cout << "<" << x << ", " << y << ", " << z << ">" << endl; void PrintPoint( p[3]) float PrintPoint(p[0], p[1], p[2]);

Program 3.15 Output

<1, 2, 3> <-5, 3.5, 1.2> <-12, 2.3, -4> <9, 8, -7> Press any key to continue

The client (user of the functions) is now provided with two options. The client can either pass in the

coordinates of the point, or pass in a 3-element array that represents the point. Providing uivalent functions with different parameters is convenient because, depending on the data tion with which the client is working, the client can call the most suitable function version.

individual several eqrepresenta

3.5.1 Default Parameters

A default parameter is a parameter where the caller has the option of specifying an argument for it. If the function caller chooses not to specify an argument for it then the function uses a specified default value called the “default” parameter. To illustrate, consider the following:

Program 3.16: Default Parameters.

#include <iostream> #include <string> using namespace std; // Function declaration with default parameters. Default parameters // take the syntax '= value' following the parameter name. void PrintSomethingLoop(string text = "Default", int n = 5); int main() // Specify an argument for both parameters. PrintSomethingLoop("Hello, World", 2);

94

Page 95: C++ Módulo I e II

// Specify an argument for the first parameter. PrintSomethingLoop("Hello, C++"); // Use defaults for both parameters. PrintSomethingLoop(); void PrintSomethingLoop(string text, int n) for(int i = 0; i < n; ++i) cout << text << endl; cout << endl;

ram Prog 3.16 Output

Hello, World Hello, World Hello, C++ Hello, C++ Hello, C++ Hello, C++ Hello, C++ Default Default Default Default Default Press any key to continue

Admittparamethe par

e fun nition. The function PrintSomethingLoop simply outputs e passed-in text parameter n number of times. The first time this function is called, we specify

arguments for both parameters. The second time this function is called, we specify the text parameter nly and leave n to the default value—5. Note that it would be impossible to do the opposite (to specify but leave text to its default value) because if we did, we would have something like

PrintSomethingLoop(10); and the compiler would think we were trying to pass in 10 for text. Thus, efault parameters must be the right-most parameters. For example, if you had parameters listed left to ght, a through d, d would be considered the default parameter because it is the right-most parameter. If

d was specified, then you could also include c as a default parameter (the next right-most parameter). However, you could not set a as a default parameter unless b, c, and d were also default parameters. You

lt parameter unless c and d were also default parameters, and so on. The third default values for both and specify no arguments. The

corresponding program output verifies that, indeed, the default values were used.

edly, this is a contrived and impractical function. Still, it illustrates the concept of default ters. First of all, to specify a default value for a parameter, the syntax “= value” is appended to ameter name. Also observe that the default parameter syntax (“= value”) is only specified in ction declaration and not in the defith

th

on

dri

could not set b as a defautime this function is called we use the

95

Page 96: C++ Módulo I e II

3.6 Summary

return type, its name, its parameter list, and a body (i.e., the code to be executed when the function is invoked).

2. Variable scope refers to what variables a part of the program can “see” or “know” about. A

to that code unit. A unit of code can “see” variables that are global and local, relative to it.

+ standard library provides functions for many of the elementary math functions and ns, such as trigonometric, logarithmic, exponential functions, as well as square root and

To use the standard math functions, the <cmath> header file must be included.

numbers. Remember to first seed the random number

nd only once per program) using the srand function, so that your programs generate different random numbers every time they run. To use the random

ral versions of a function with the same n includes the

everal versions of a function different versions so that the both. Note that a function

an argument for function uses a

eter a default parameter, we append the syntax “= value” to the parameter name (e.g.,

1. A function is a unit of code designed to perform a certain task. By dividing our program into several different functions, we organize our code into more easily manageable parts. Moreover, functions help avoid code duplication. To define a function you must specify its

variable defined in a particular code unit is said to be local relative to that code unit. A variable defined outside a particular code unit is said to be global relative

3. The C+

peratiooabsolute value functions.

4. Use the rand function to generate randomgenerator with the system time (a

number functions the <cstdlib> header file must be included.

5. Function overloading allows implementation of sevename as long as the function signature is still different. The signature of a functiofunction name and parameter listings. Therefore, to implement swith the same name the parameter listings must vary between the signatures differ. Vary parameter listings in quantity, in type, orsignature does not include the return type.

6. A default parameter is a parameter where the caller has the option of specifying

it. If the function caller chooses not to specify an argument for it then thed the “default” parameter. To make a paramspecified default value which is calle

void Func(int x = 5)). Note that the default parameter syntax (“= value”) is only specified in the function declaration and not in the definition.

96

Page 97: C++ Módulo I e II

3.7 Exercises

3.7.1 Factorial

ewrite the factorial proR gram (Section 2.8.4) using a function. That is, implement a function called r) a positive integer n. The function should then compute

e fact n unction is to have the following prototype:

atted like so:

3! = 6 Press any key to continue

ecall that characters (i.e., char types) are represented internally with an integer value. The following chart shows an abridged listing of an abridged ASCII table.

7: 9 58: : 59: ; 60: < 61: = 62: > 63: ? 64: @ 65: A 66: B 67: C 68: D 69: E 70: F 71: G 72: H 73: I 74: J 75: K 76: L 77: M 78: N 79: O 80: P 81: Q 82: R 83: S 84: T 85: U 86: V 87: W 88: X 89: Y 90: Z 91: [ 92: \ 3: ] 4: ^ 5: _ 101: e 102: f

o 112: p y 122: z

24 125: 12 ~ 127:

e ential orde g of the letters (e.g., integers 65, 66, 67, correspond with letters ‘A’, ‘B’, s ely). This s uential ordering makes it easy to setup a loop that iterates through each

e bet.

CII table, implement a function called ToUpperCase, which inputs a character. For example, if the letter ‘a’ is already in uppercase form, then the e input, then return ‘A’). If the input

Factorial that inputs (i.e., has a parameteorial of , and return the result. The fth

int Factorial(int n); After you have implemented this function, test your function by calculating 5!, 0!, 9!, and 3!, and output

e results to the console window. The output should be formth 5! = 120 0! = 1 9! = 362880

3.7.2 ToUpper; ToLower

R

33: ! 34: " 35: # 36: $ 37: % 38: & 39: ' 40: ( 41: ) 42: * 43: + 44: , 45: - 46: . 47: / 48: 0 49: 1 50: 2 51: 3 52: 4 53: 5 54: 6 55: 7 56: 8 5

9 9 9 96: ` 97: a 98: b 99: c 100: d103: g 104: h 105: i 106: j 107: k 108: l 109: m 110: n 111: 13: q 114: r 115: s 116: t 117: u 118: v 119: w 120: x 121: 1123: 1 : | 6: Observe th sequ rinand ‘C’, re pectiv eq

tter in th alphale

sing the preceding abridged ASUsingle character variable and returns the uppercase form of that

haracteris input, the function should return ‘A’. If the input cfunction should return that uppercase form (e.g., if ‘A’ is th

97

Page 98: C++ Módulo I e II

character is not a letter character (i.e., not a member of the alphabet) then return a null character (‘/0’).

char ToUpperCase(char input);

Additionally, implement a complementary function called ToLowerCase, which inputs a single char e form of that character. For example, if the letter ‘A’ is input, the

eady in lower case form, then the function should return that lowercase form (e.g., if ‘a’ is the input then return ‘a’). If the input character is not a letter

ber of the alphabet) then return a null character (‘/0’). The function is to have e following prototype:

char T

ave imop that

exercise should be the ppercase letterforms of every letter in the alphabet.

alphabet.

Freque n d to know the distances between objects in the game. The distanc e

The function is to have the following prototype:

variable and returns the lowercasfunction should return ‘a’. If the input character is alr

character (i.e., not a memth

oLowerCase(char input); After you h plemented these functions and verfied them to work, then you are to do the following: Create a lo iterates over every lowercase letter in the alphabet. For each letter, call ToUpperCase and output the result to the console screen. The output for this part of the u Additionally, create a loop that iterates over every uppercase letter in the alphabet. For each letter, call ToLowerCase and output the result to the console screen. The output for this part of the exercise should be the lowercase letterforms of every letter in the

3.7.3 3D Distance

ntly in a 3D game, you will eee betw en two points ( )zyx uu ,,=u u and ( )zyx vvv ,,=v in 3-space is given by the

formula:

( ) ( ) ( )222zzyyxx uvuvd +−+−= uv − .

plem s the distance between them. The function is to

est this function by using it to compute the distance between these points:

.

c. and .

Im ent a function that inputs two points and returnhave the following prototype:

float dist3(float ux, float uy, float uz, float vx, float vy, float vz);

T a. ( )3,2,1 and ( )0,0,0 b. ( )3,2,1 and ( )3,2,1 .

( )3,2,1 ( )5,4,7 −

98

Page 99: C++ Módulo I e II

The output should look like this:

istance between (1, 2, 3) and (7, -4, 5) = 8.7178

ay want to write a separate function to print 3D points to make the output of points less cumbersome.)

Distance between (1, 2, 3) and (0, 0, 0) = 3.74166 Distance between (1, 2, 3) and (1, 2, 3) = 0 DPress any key to continue (Tip: You m

3.7.4 Arc Tangent 2

Background Information

( ) xy=θtan , where y and x are coordinates of some point in the 2D plane. Solving for theta Considerwe obtain ( )xy1tan −=θ . But, recall from your studies of trigonometry that the inverse tangent function has some problems; in particular, its range is [ ]°°− 90,90 , which means we cannot get angles outside quadrants 1 and 4. However, ( )yx, can be in any quadrant. Clearly we have a problem, but making some observations easily solves it. Let us work with a concrete example to make this a bit asier.

° angle with the positive paper). he point lies in quadrant 2, we know the inverse tangent will

re we stuck? Not

e Let 4−=x 4=y . Clearly ( )4,4− lives in quadrant 22 and makes a 135 and x-axis (sketch it out on Because tnot return the correct angle since quadrant 2 is not in the inverse tangent’s range. Ayet. Let us calculate the inverse tangent just to see what happens: ( ) ( ) °−=−= − 454/4tan 1 .

ve that if we add 180° to the inverse tangent result then we obtain the correct angle 135°; = −tan 1 xyθ

Here we obserthat is, -45° + 180° = 135°. In fact, if the angleθ falls in quadrant 2 or 3 (which we can determine by

gle by adding 180°. In examining the signs of x and y), we will always be able to get the correct ansummary:

If θ is in quadrants 1 or 4 then ( )xy1tan −=θ .

Else if θ is in quadrant 2 or 3 then ( )xy1tan −=θ + 180°.

Exercise Using atanf, write a function with the following prototype: float MyArcTangent(f

loat y, float x);

2 We know this by examining the signs of x and y: Since x is negative it has to be to the left of the y-axis, and since y is positive it must be above the x-axis. Therefore, the point lies in quadrant 2.

99

Page 100: C++ Módulo I e II

( )yx,This function should examine the signs of the coordinates of the point and return the correct angle based on what quadrant the point lies in as described in the background readings for this lab project. Test your function with the following points: (2, 4), (-1, 5), (-6, -4), (4, -6). You should get the

yArcTangent( 4, 2) = 63.4671

yArcT

unction does. Its prototype is:

t , float x);

ld prompt the user to make a selection from the menu. After the user has made their lection the program should ask for the input values; note that some functions only need one input

ser enters the input values, the program should perform the . The program should then loop back and again prompt the user to make

ue this process until the user quits:

3) tan(x), 4) atan2(y, x), 5) sqrt(x), 6) x^y 8) e^x, 9) |x|, 10) floor(x), 11) ceil(x), 12) Exit.1

an(x), 4) atan2(y, x), 5) sqrt(x), 6) x^y , 8) e^x, 9) |x|, 10) floor(x), 11) ceil(x), 12) Exit.4 2

Enter y: 4

n(x), 4) atan2(y, x), 5) sqrt(x), 6) x^y 7) ln(x), 8) e^x, 9) |x|, 10) floor(x), 11) ceil(x), 12) Exit.7 Enter x: 2

) tan(x), 4) atan2(y, x), 5) sqrt(x), 6) x^y

following results: MMyArcTangent( 5, -1) = 101.27

angent(-4, -6) = 213.707 MMyArcTangent(-6, 4) = -56.3385 Press any key to continue Now that you have written the function yourself, you should know that the C++ standard math library already includes a function that does exactly what your f float atanf2(floa y

3.7.5 Calculator Program

To get some practice using the standard math functions you will write a simple calculator program. The program should display the following menu: 1) cos(x), 2) sin(x), 3) tan(x), 4) atan2(y, x), 5) sqrt(x), 6) x^y, 7) ln(x), 8) e^x, 9) |x|, 10) floor(x), 11) ceil(x), 12) Exit. The program shousevalue, whilst others need two. After the ucalculation and outp e resultut tha selection. The program should contin

x), 2) sin(x), 1) cos(7) ln(x), Enter x: 3.14 cos(x) = -0.999999

, 2) sin(x), 3) t1) cos(x)) ln(x)7Enter x:

atan2(y, x) = 1.10715 1) cos(x), 2) sin(x), 3) ta

ln(x) = 0.693147 ) cos(x), 2) sin(x), 317) ln(x), 8) e^x, 9) |x|, 10) floor(x), 11) ceil(x), 12) Exit.9 Enter x: -5 |x| = 5 1) cos(x), 2) sin(x), 3) tan(x), 4) atan2(y, x), 5) sqrt(x), 6) x^y

100

Page 101: C++ Módulo I e II

7) ln(x), 8) e^x, 9) |x|, 10) floor(x), 11) ceil(x), 12) Exit.11 Enter x: 11.2 eil(x) = 12 c1) cos(x), 2) sin(x), 3) tan(x), 4) atan2(y, x), 5) sqrt(x), 6) x^y 7) ln(x), 8) e^x, 9) |x|, 10) floor(x), 11) ceil(x), 12) Exit.12 Exiting... Press any key to continue

);

) Pla

ulate three random

ld look like:

layer’s chips: $1000 ) Play slot. 2) Exit. 1 nter your bet: 1500

not enter a valid bet. ur bet: 1000

slot. 2) Exit.

3.7.6 Slot Machine

Implement a function that returns a random integer in the range [low, high], where low and high are input parameters. The function prototype should look like this: int Random(int low, int high Be sure to verify that your function implementation works by testing it. Using your Random function, write a virtual slot machine program. The program should start the player off with $1000.00, and should display a menu like this: layer’s chips: $1000 P1 y slot. 2) Exit.

If the player enters “1”, the program should ask the user to enter in his or her bet. The program needs to verify that a legal bet was placed; that is, a bet greater than zero and less than or equal to the amount of money the player has. After the player has input his or her bet, the program must calcnumbers in the range [2, 7] and output them neatly to the screen. If all three numbers are sevens, then the player wins ten times their betting money; else if, the three numbers are all the same, but not sevens, then the player wins five times their betting money; else if, two out of the three numbers are the same then the player wins three times their betting money; else, the player loses his or her bet. At this point, calculate the player’s new chip amount and redisplay the menu. If at any point the player loses all of his or her chips, a message should be displayed to the player and the program should exit. Also, if the player enters “2” from the menu then the program should exit. Here is an example of what the output shou P1EYou did nter yoE3 3 7 You win! Player’s chips: $3000 1) Play 2xiting…E

101

Page 102: C++ Módulo I e II

3.7.7 Binary Search

Background Information

n the exercises of the previous chapter we examined the linear search. In the worst cast scenario, the

er search method is the binary search. However, the sorted in some way.

concrete example. Consider the following array of ending order:

Suppose that you want to find integer 21. Instead of starting the search at the beginning of the array, in the binary search we start at the middle. The value stored in the middle array element is 14. Since 21 is

is sorted in ascending order, we are guaranteed that the item we are searching for lies in the upper half of the array. Thus, we do not need to check any elements in the lower half of the array. We have, with one test, eliminated half of the elements we would potentially

der the upper half of the previous working dataset, which we have bolded:

ubarray. We now consider the lower half of the previous orking dataset, which we have bolded:

, 5, 6, 9, 14, 21, 23, 28, 31, 35

In this arbitrarily choose the lower element as the “middle.” nd that element is the value we are searching for, located at position 6 in the array.

he key idea of the binary search is this: Because the data is sorted, we can quickly eliminate half of our data set with each scan. This is the beauty of the binary search. To make this result more rofound, imagine that you had a sorted array of ten thousand integers. Using a linear search you very

the one you want. Conversely, the binary search liminates half of its data set after each scan. After just one test, the binary search has narrowed the

Ilinear search must scan every single element in the given array. For large datasets this can be problematic (i.e., too slow/inefficient). A fast

the dataset be alreadybinary search requires that To illustrate the binary search, let us examine a

ave already been sorted in ascintegers, which h

, 5, 6, 9, 14, 21, 23, 28, 31, 35 1, 4

greater than 14 and the array

have to scan. We now consi

1, 4, 5, 6, 9, 14, 21, 23, 28, 31, 35 Again we start at the middle of our new dataset. The value stored in the middle is 28. Since the value 21 is less than 28 and the array is sorted in ascending order, we are guaranteed that the item we are searching for lies in the lower half of our working subarray. Thus, we do not need to check any elements in the upper half of our working sw

1, 4

case there is not an exact middle, so we willA Typwell might have to scan all 10,000 integers to find esearch down to 5, 000 integers, after the second test the binary search is down to 2,500 integers, and so on.

102

Page 103: C++ Módulo I e II

Exercise Write a function that searches, using the binary search algorithm, an integer array and returns the array

osition of the “found” integer that matches the search key. The function should be prototyped as follows

int numElements, int searchKey);

Use the te 1, 4, 5, 6, 9, 14, 21, 23, 28, 31, 35, 42, 46, 50, 53, 57, 62, 63, 65, 74, 79, 89, 95

our output should be similar to the following: 1, , 89,

t): 50 0 is in position 13.

earch key (or ‘x’ to exit): 0 ound.

ackground Information

p:

int BinSearch(int data[],

following array for st purposes:

Y

4 5, 6, 9, 14, 21, 23, 28, 31, 35, 42, 46, 50, 53, 57, 62, 63, 65, 74, 79, 95 Enter search key (or ‘x’ to exit): 21 21 is in position 6. Enter search key (or ‘x’ to exi5Enter s not f0Enter search key (or ‘x’ to exit): x Exiting…

3.7.8 Bubble Sort

B

he bubble sort algorithm is similar to the selection sort in that it also makes several passes over the array, a th er, the bubble sort has a couple he t to its sorted position per

ass, the other elements “bubble” closer to their correct positions. Second, we can “skip” a pass if it is

The uinteger

x[6] = x[0],…,[5] = 12, 5, 21, 1, 15, 17.

nd suppose that we wish to sort this array in ascending order.

T

nd after each pass e array becomes more and more sorted. Howevof advantages over t selection sort. First, besides moving one elemen

punnecessary; that is, if the next element is already in its sorted position then we do not need to do any work and we can skip to the next element.

b bble sort algorithm is best explained by looking at an example. Consider the following array of s:

A

103

Page 104: C++ Módulo I e II

Pass 1:

n the first pass, our working subarray is x[0],…,x[5] (the entire array). We start at x[0] and compare it

ext, we compare x[1] and x[2]. Because 12 is less than 21, we do not swap the two values. Thus the ring x[2] and x[3], we have 21 is greater

an 1, so we swap the two values:

5, 12, 1, 15, 21, 17 // comparing x[3] and x[4] omparing x[4] and x[5]

g rule:

ighbor x[i+1] then swap x[i] and x[i+1]. In consequ ranteed, for each pass, that the greatest value in the working subarray will be placed in its sorted position. And indeed, observe that the greatest value, 21, is in its sorted posit ay).

Oto its right-next-door neighbor x[1]. Because 12 is greater than 5, we swap the two values. This yields:

5, 12, 21, 1, 15, 17 Narray remains unchanged. Continuing the pattern by compath

5, 12, 1, 21, 15, 17 Repeating this process yields:

5, 12, 1, 15, 17, 21 // c From this first pass we observe the followin

• Rule 1: If x[i] is greater than its right-next-door ne

ence to this rule, we are gua

ion (the top of the arr Pass 2: We are guworking su

aranteed from the previous pass that the greatest value is correctly positioned at the top of the barray. Hence, for the second pass we need only consider the subarray x[0],…,x[4]. We start

5, 1, 12, 15, 17, 21 // comparing x[2] and x[3] results in no change.

volved in a swap operation is x[2]. Moreover, observe that the no coincidence and brings us to a new rule:

at x[0] and compare it to its right-next-door neighbor x[1]. Because 5 is less than 12 we do nothing. Comparing x[1] and x[2] we have 12 is greater than 1, which indicates a swap operation must take place. Swapping x[1] and x[2] yields:

5, 1, 12, 15, 17, 21 Continuing this pattern results in the following:

5, 1, 12, 15, 17, 21 // comparing x[3] and x[4] results in no change. Observe that the last element to be in

[2],…,x[5] is sorted. This issubarray x

104

Page 105: C++ Módulo I e II

• ule 2: Suppose we are working with a zero-based array of n elements. For a particular bubble pass, if the last element to be involved in a swap operation is x[k] then we can conclude that ubarray x[k],…,x[n-1] is sorted.

Rsort

sthe

ss 3Pa : The previous pass told us that the last swap occurred at x[2].

We start at x[0] and comp Thus, for this pass we are only concerned are it to its right-next-door neighbor x[1].

ap the values. This yields:

1, 5, 12, 15, 17, 21 We are now done with the third pass. Because the last index involved in the last swap was x[1], we can conclude, from Rule 2, that the subarray x[1],…,x[5] is sorted. But if x[1],…,x[5] is sorted then the last element x[0] must also be in its sorted position, and from inspection we observe it is. Thus the entire array has been sorted in ascending order. The following algorithm outline summarizes the bubble sort: Table 3: Ascending order Bubble Sort.

with the subarray x[0],…,x[1]. nce 5 is greater than 1 we swSi

Let x[n] = x[0],...,x[n-1] be an array of given integers to sort. Let SubArrayEnd be an integer to store the last index of the working subarray. Let nextEnd be an integer used to help compute the end of the next pass’ subarray. Initialize SubArrayEnd = n – 1. While SubArrayEnd > 0, do the following:

1. Initialize nextEnd = 0; 2. For to SubArrayEnd - 1, do the following: 0=j

a. If x[j] > x[j+1] then i. swap x[j] and x[j+1]. ii. nextEnd = j;

b. Increment j.

3. SubArrayEnd = nextEnd. Exercise Ask the user to input ten random (non-sorted) integers and store them in an array. Sort these integers in ascending order using a bubble sort function and output the sorted array to the user. Your function should have the following prototype:

void BubbleSort(int data[], int n);

105

Page 106: C++ Módulo I e II

where data is the array parameter, and n is the number of elements in the array (i.e., the size of the array). Your output should look similar to the following: Enter ten unsorted integers... [0] = 5 [1] = -3 [2] = 2 [3] = 1 [4] = 7 [5] = -9 [6] = 4 [7] = -5 [8] = 6 [9] = -12 Unsorted List = 5, -3, 2, 1, 7, -9, 4, -5, 6, -12, Sorting... Sorted List = -12, -9, -5, -3, 1, 2, 4, 5, 6, 7, Press any key to continue

106

Page 107: C++ Módulo I e II

Chapter 4

References and Pointers

107

Page 108: C++ Módulo I e II

Introduction

We are at the point now where we can write some useful programs. Our programs can make decisions based on input and the program’s status using conditional statements. We can execute blocks of code repeatedly with loop statements. We can organize our programs into multiple parts with functions, each designed for a specific task. However, there are still some outstanding problems. First, recall that when passing arguments into functions, the argument is copied into the parameter. But what if you are passing in an array? An array can potentially be very large and copying every element value from the argument to the parameter would be very inefficient. Second, we learned in the last chapter that a function could return or evaluate to some value. But what if we want to return more than one value? Finally, so far in every program we have written, when we needed memory (variables) we declared them in the program code. But declaring the variables in the program implies that we know, ahead of time, all the memory the program will need. But what if the amount of memory needed is variable? For example, in a massive multiplayer online game, you may use an array to store all of the game players. Because players are constantly entering and leaving online play, the array may need to resize accordingly. All of these problems can be solved with references or pointers.

Chapter Objectives

• Become familiar with reference and pointer syntax. • Understand how C++ passes array arguments into functions. • Discover how to return multiple return values from a function. • Learn how to create and destroy memory at runtime (i.e., while the program is running).

4.1 References

A reference is essentially an alias for a variable. Given a reference R to a variable A, we can directly access A with R since R refers to A. Do not worry about why this is useful, as we will discuss that further in coming sections. For now, just focus on learning the syntax of references. To create a reference you must:

specify the type of variable the reference will refer to follow with the unary address of operator (&)

108

Page 109: C++ Módulo I e II

follow with the name of the reference follow with an initialization, which specifies the variable the reference refers to

For example, to create a reference, called valueRef, to an integer called value we would write:

int value = 0; //<-- Create a variable called 'value'. int& valueRef = value; //<--Create a reference to 'value'.

Here are some other examples using various types:

// Variables: float pi = 3.14f; char letter = 'B'; bool truth = false; double e = exp(1.0); // References to those variables: float& piRef = pi; char& letterRef = letter; bool& truthRef = truth;

double& eRef = e; We can access the variable a reference refers to through the reference. This is because a reference is just an alias to that variable. Program 4.1 verifies this: Program 4.1: Accessing a variable via a reference to it.

#include <iostream> using namespace std; int main() // Create variable. int value = 10; // Create reference to 'value'. int& valueRef = value; // Print the number stored in 'value'. cout << "value = " << value << endl; // Also print the value referenced by 'valueRef'. // Because 'valueRef' is an alias for 'value' it // should print the same number stored in 'value'. cout << "valueRef = " << valueRef << endl; // Modify the reference. However since the reference is // just an alias for 'value', modifying 'valueRef' modifies // the number stored in 'value'. valueRef = 500; // Print the number stored in 'value' to prove that modifying // the reference modifies the variable it refers to. cout << "value = " << value << endl;

109

Page 110: C++ Módulo I e II

// And print 'valueRef' again. cout << "valueRef = " << valueRef << endl;

Program 4.1 Output

value = 10 valueRef = 10 value = 500 valueRef = 500 Press any key to continue

We have two different names (value and valueRef), which both refer to the same variable—that is, the same unit of memory—and as such, they both can access and modify that variable.

Important: References must be initialized when they are declared. You cannot have a reference that does not refer to anything. This is illegal: int& valueRef; //<--Error uninitialized reference.

4.1.1 Constant References

Suppose we try and write the following: int& valueRef = 1; If we try and compile this we will get an error; namely, “error C2440: 'initializing' : cannot convert from 'int' to 'int &' .” This should not be surprising since a reference is an alias to a variable and a literal is not a variable. Still, sometimes we will want to be able to assign literals to references. We note, however, that such an alias to a literal should not be able to change the literal through that alias; this restriction simply follows from the fact that it does not make sense to change a literal—a literal is literally that value. To facilitate literal assignments to references we must use constant references: const int& valueRef = 1; If we try to change a constant reference like so: valueRef = 20; we get the error “error C2166: l-value specifies const object.” A constant reference is actually implemented by the compiler as follows: const int temp = 1; const int& valueRef = temp;

110

Page 111: C++ Módulo I e II

It creates a temporary const variable to store the literal, and then has the reference refer to this temporary variable. The temporary will stay alive in memory as long as the reference to it stays alive in memory.

4.2 Pointers

4.2.1 Computer Memory Primer

In a computer, each byte3 of memory has a unique memory address. Figure 4.1 shows a conceptual example of a segment of computer memory.

Figure 4.1: A segment of memory. Each upper square represents a byte of memory; the question marks denote that

indows, a short variable is two bytes and a float variable is four bytes. Figure 4.2 shows how a variable varShort of type short and a variable varFloat of type float would be stored in memory.

we do not know what value is stored in these bytes. Each bottom rectangle represents a unique memory address, which corresponds to a byte in memory.

We learned in the first chapter that the various C++ intrinsic types require different amounts of memory; ecall that in 32-bit Wr

Figure 4.2: Variables stored in memory spanning multiple bytes.

Since these two variables require more than one byte apiece, they span over several byte

yout. That being the case, it is natural to ask what the address of varShort and s in the memory

varFloat is. C++ s the address of multi-byte types to be the address of the “lowest” byte the variable spans. Thus,

from Figure 4.2, the address of varShort is 507 and the address of varFloat is 512.

laconsider

3 A byte is the smallest addressable piece of memory.

111

Page 112: C++ Módulo I e II

4.4.2 Pointer Initialization

A pointer is a special variable type that can store the memory address of another variable. For example, suppose that a pointer exists called varFloatPtr, which stores the address of varFloat. Figure 4.3 ill

ustrates this relation.

Figure 4.3: Here we have added a pointer variable varFloatPtr, which stores the address of another variable,

namely varFloat. Observe that the pointer occupies four bytes; this is because we are assuming a 32-bit system

riable. Furthermore, given the address of a variable (a pointer), the actual riable which is being pointed at can be accessed and modifed; this process is called dereferencing.

Thus, like references, the same variable can be accessed via several different pointers that point to it. owever, as it turns out, pointers can do more than references. In fact, the C++ reference mechanism is

general

nlike t have to be initialized, but they should always be initialized for the me reason all variables should always be initialized to something—the program is easier to debug hen y default value. When a variable is filled with garbage, it is not so easy recognize that it contains a bad value, and therefore, you may think it contains valid information.

Moreov ter that points to nothing. So a good default value for pointers, if you wish to postpone initialization, is null. The null value in C++ is simply zero. Rewriting the preceding pointer declarations with initialization to null yie ool* boolPtr = 0; t* intPtr = 0; oat* floatPtr = 0;

where pointers are 32-bits.

ou can see why they call these types of variables “pointers”—by storing the address of a variable they Y

essentially ‘point to’ that vava

Hly understood to be implemented using pointers “underneath the hood.”

To declare a pointer, the type of variable the pointer will point to must be specified, followed by the unary indirection operator (*), followed by the name of the pointer. Example declarations: bool* boolPtr; int* intPtr;

float* floatPtr;

Note: The operator (*) is not ambiguous because the compiler can determine through context whether to interpret it as the unary indirection operator or as the binary multiplication operator.

U references, pointers do nosaw ou are able to recognize ato

er, unlike references, pointers can be assigned a null value. A null pointer is a poin

lds:

binfl

112

Page 113: C++ Módulo I e II

Note: Some programmers like to define a macro called NULL, which is equal to zero. That is, #define NULL 0

float* floatPtr = NULL;

se you may see this NULL used in other code,

is not very interesting. Pointers are variables that store the addresses of other hat we want to be doing is assigning variable addresses to our pointers. To assign the

ariable to a pointer, we need a way of getting the address of a variable. We can do that (&)—the same one used with references. The following examples

ustra

This is so that they can nullify pointers by writing: bool* boolPtr = NULL; int* intPtr = NULL;

We do not use NULL in this book, but we bring it up becausuch as Microsoft Windows code.

Initializing pointers to nullvariables, so w

dress of a vadwith the unary address of operator ill te: Program 4.2: Initializing pointers and displaying memory addresses.

#include <iostream> us namespace std; ing int main() bool boolVar = true; int intVar = 50; float floatVar = 3.14f; // Initialize p ointers to the addresses of the // corresponding variables. bool* boolPtr = &boolVar; int* intPtr = &intVar; float* floatPtr = &floatVar; // Print normal variable values. cout << "boolVar = " << boolVar << endl; cout << "intVar = " << intVar << endl; cout << "floatVar = " << floatVar << endl; cout << endl; // Print the addresses the pointers store. cout << "boolPtr = Address of boolVar = " << boolPtr << endl; cout << "intPtr = Address of intVar = " << intPtr << endl; cout << "floatPtr = Address of floatVar = " << floatPtr << endl; Program 4.2 Output

boolVar = 1 intVar = 50

113

Page 114: C++ Módulo I e II

floatVar = 3.14 boolPtr = Address of boolVar = 0012FED7 intPtr = Address of intVar = 0012FEC8 floatPtr = Address of floatVar = 0012FEBC Press any key to continue

The syntax ‘&’ followed by the variable name evaluates to the memory address of the variable name. So

re a pointer to a ar.

e.g., 0012FED7) is a 32-bit hexadecimal number, which is how cout ply another numbering system that is useful when exadecimal until Chapter 12 in the next module. If n then you can cast the pointer to an int before

utputting it:

cout <cout << "intPtr = Address of intVar = " << (int)intPtr << endl; out << "floatPtr = Address of floatVar = " << (int)floatPtr << endl;

You will get output similar to this:

intPtr = Address of intVar = 1244872

4.4.3 Dereferencing

Given the address of a variable (i.e. a pointer) we can access and modify the actual variable pointed to by dereferencing the pointer. To dereference a pointer, we prefix the pointer name ith the indirection o

the following, for example, float* floatPtr = &floatVar, reads like so: Declafloat called floatPtr and assign to it the address of a float variable called floatV

he strange output for the pointers (Toutputs pointers by default. Hexadecimal is simanalyzing memory; we will postpone discussing hyou want to see the integer address representatioo

< "boolPtr = Address of boolVar = " << (int)boolPtr << endl;

c

boolPtr = Address of boolVar = 1244887

floatPtr = Address of floatVar = 1244860

Note: Pointers can only store variable addresses; if we try to assign a value to a pointer like this: float* floatPtr = floatVar, we get the error: “error C2440: 'initializing' : cannot convert from 'float' to 'float *'.”

wperator (*). For example, given the initialized pointer float* floatPtr = &floatVar, we can

dereference floatPtr with the syntax *floatPtr, which evaluates to the variable being pointed to; that is, floatVar. Figure 4.4 shows the relationship between a pointer and the address, and a dereferenced pointer and the variable.

114

Page 115: C++ Módulo I e II

F 4.4: A pointer stores an address. By dereferencing a pointer we obigure tain the variable at the address pointed to.

ith th it follows that the variable whose address is stored in the pointer modifed by dereferencing the pointer. This is similar to references; that is,

r to a variable, and similarly, with inter Th following program illustrates:

ogram

W is pointer-variable relationship can be accessed, read, andwith references, a variable can be accessed via references that refe

e po s, a variable can be accessed via pointers that point to it. Pr 4.3: Accessing a variable via a pointer to it.

#include <iostream> u namespace std; sing in in() t ma // Create variable. int value = 10; // Create a pointer to the address of 'value'. int* valuePtr = &value; // Print: cout << "value = " << value << endl; cout << "valuePtr = " << valuePtr << endl; cout << "*valuePtr = " << *valuePtr << endl; // Modify 'value' via the pointer by dereferencing it. *valuePtr = 500; // Print again to show changes: cout << "value = " << value << endl; out << "valuePtr = " << valuePtr << endl; c out << "*valuePtr = " << *valuePtr << endl; c Program 4.3 Output

value = 10 valuePtr = 0012FED4 *valuePtr = 10 value = 500 valuePtr = 0012FED4 *valuePtr = 500 Press any key to continue

Admittedly, the following paragraph is hard to follow since the use of a pointer introduces a confusing layer of indirection. Read the paragraph slowly and refer to Figure 4.5.

115

Page 116: C++ Módulo I e II

Figure 4.5: valuePtr stores the address of value. We can get the variable (value) at that address through the

pointer valuePtr by dereferencing it (*valuePtr). Thus we can read and write to the variable value indirectly via the pointer .

Program 4.3 is similar to Program 4.1, but instead of references we use pointers to indirectly modify the value s gram 4.3 does is create a pointer to value: int* valueP e value stored in value and the address stored in valueP variable valuePtr currently points to (rem *valuePtr). Since vbased o Next th*value

to mod lly, we print all the va t again to verify that massignment, *valuePtr = 500 odified the variable that is pointed to (i.e value). And indeed it was

tr.

access to a variable. Given the address of a variable, we can get to that variable in the same way a letter can get to a house given the house address. By going through

g the pointer you get access to the take an indirect step when you can

just access the variable directly? The following sections of this chapter show the benefits and real-world

constant pointers. There are four syntaxes we need to consider:

(iv) const float* const constFloatConstFloatPtr;

stant; that is, the pointer variable itself cannot change; however,

and the variable pointed to is also constant.

valuePtr

tored in a variable. The first key operation Protr = &value. The program then prints thtr (address of value). Then the program prints the value of the

ember that it stores the address of this variable) by dereferencing the pointer (aluePtr points to value, this output should be the same as just printing value directly, and n the program output, it is; that is, value == 10 == *valuePtr.

e program makes an assignment to the dereferenced pointer: *valuePtr = 500. Because Ptr refers to the variable and valuePtr stores the address of (value), we expect this assignment ify value as well. So fina lues ou aking the

mmodified: value == 500 == *valueP In summary, a pointer gives us indirect

the pointer, you get the address of a variable, and then by dereferencinvariable. Of course, it is still not clear why this is even useful. Why

uses of references and pointers.

Note: Just as we can have constants for non-pointer variables and constant references, we can have

(i) float* const constFloatPtr; (ii) float const* constFloat0; (iii) const float* constFloat1;

Form (i) means that the pointer is conthe variable pointed to can change. Form (ii) and (iii) are different syntaxes for the same idea; that being, the pointer is not constant but the variable pointed to is constant. Finally, form (iv) combines both; it says the pointer is constant

116

Page 117: C++ Módulo I e II

Stroustrup suggests to read these declarations right-to-left; for example, (i) would read “constFloatPtr is a constant pointer to type float” and (iii) would read “constFloat1 is a pointer to type const float.”

4.3 Arrays Revisited

4.3.1 Pointer to the Beginning of an Array

With our new unde at them. In C++, n array name can be converted to a pointer to the first element in the array. Consider the following

= arrayName;

that firstPtr is a pointer to the first element in the array, which would be

rstanding of the idea of pointers, it is now time to take a closer lookaarray:

short arrayName[8] = 1, 2, 3, 4, 5, 6, 7, 8; A pointer to the first element can be acquired by writing: short* firstPtr The claim isarrayName[0]—Figure 4.6.

Figure 4.6: An array name gives a pointer to the first element of the array.

that this is, in fact, the case.

nter to the first element in an array via the array’s name.

If this is true then dereferencing arrayName ought to yield the value of element [0]. Program 4.4 shows

Program 4.4: Verification that we can get a poi

#include <iostream> using namespace std; int main() short arrayName[8] = 1, 2, 3, 4, 5, 6, 7, 8;

117

Page 118: C++ Módulo I e II

// Use array name to get pointer to the first element. short * firstPtr = arrayName; cout << "arrayName[0] = " << arrayName[0] << endl; cout << "*firstPtr = " << *firstPtr << endl; Program 4.4 Output

arrayName[0] = 1 *firstPtr = 1 Press any key to continue

The output of Program 4.4 verifies the claim; namely, arrayName is a pointer to the first element in the array.

4.3.2 Pointer Arithmetic

Given a pointer to the first element in an array, how do we access the other elemompiler knows the variable type of each element in the array

ents of the array? The ++ c (it knows because the type is

d an integer to the pointer. For example, we may write:

+ 1;

he integer added indicates how many elements to offset. Figure 4.7 illustrates this.

Cspecified when you declare the array) and it knows how many bytes need to be offset to get to the next element. Thus, C++ provides a way to navigate the elements of an array by offsetting the array pointer.

o perform the actual offset operation we adT

firstPtr firstPtr + 5; firstPtr + 4; T

Figure 4.7: Pointer arithmetic.

er like this simply evaluates to a new pointer, which points to the offset he value at that new address the pointer needs to be dereferenced.

Note that offsetting the pointelement. In order to get t

118

Page 119: C++ Módulo I e II

In addition to adding with the binary addition operator (+) a pointer can be “incremented”, and also, pound a

ent 4 array slots away

e assignments to the pointer and thus ss it points to. In contrast:

is ex ually change the address to which it o a new pointer to the next element.

s wel s add long the array using decrements are seldom encountered. All of

re referred to as pointer arithmetic. Note that multiplication and division is t defined for pointers.

ust be careful not to use pointer arithmetic to offset into a memory address that is not part instance, the array in the preceding example contained ten elements. The offset

goes outside the array boundary and into memory we do not “own.” used for something else, can be destructive.

over a small array using two different styles of pointer arithmetic and

com ssignments can be performed on it. Examples:

firstPtr++; // Point to next element. t element after that. ++firstPtr; // Point to nex

firstPtr += 4;// Point to elem

ssignment operators makNote that the increment and compound achange the addre firstPtr + 1;

pression makes no assignment to firstPtr and does not actThpoints. Rather firstPtr + 1 evaluates t A l a ition type operations, it is possible to move backwards a

operations. However, these types of pointer arithmeticand subtraction ese pth ointer operations a

no

Note: You mof the array. ForfirstElementPtr + 12

memory, which may be Accessing this

The following program iterates prints each element: Program 4.5: Pointer arithmetic.

#include <iostream> using namespace std; int main() short arrayName[8] = 1, 2, 3, 4, 5, 6, 7, 8; // Use array name to get pointer to the first element. short* firstPtr = arrayName; cout << "Style 1: Addition operator." << endl; for(int i = 0; i < 8; ++i) cout << *(firstPtr + i) << " "; cout << endl; cout << "Style 2: Increment operator." << endl; for(int i = 0; i < 8; ++i) cout << *firstPtr << " "; ++firstPtr; // Move pointer to next element.

119

Page 120: C++ Módulo I e II

cout << endl; Program 4.5 Output

Style 1: Addition operator. 1 2 3 4 5 6 7 8 Style 2: Increment operator. 1 2 3 4 5 6 7 8 Press any key to continue

In the first style the ptr + intege

remented for each loop cycle, each r style is used, in particular: firstPtr + i. Because ‘i’ is element is iterated over using this style. In the second style, the

iterates over each element. Observe e pointed to when we want to

tput i

the p erators [ ] to navigate the elements of an array. In reality e bra et op r arithmetic just discussed, plus a dereference; that is, the llowi perations:

1) == firstPtr[0] 2) == firstPtr[1]

.

n array are accessible and navigable given a pointer to the first

.3.1 ns

what if you are tially be very large and copying every element value from the

ume uld be very inefficient. C++ handles this problem by copying ointer argument to the first element of the array into the parameter. The following example program

incpointer for each loop cycle is incremented, which again effectively that in both cases the pointer must be dereferenced to get the actual valuou t.

st we In a have always used the bracket opck erator is shorthand for the pointeth

fo ng are equivalent o *(firstPtr +firstPtr +*(

*(firstPtr + 3) == firstPtr[2] .. To summarize, all of the elements of aelement in the array.

4 ing Arrays into FunctioPass

When passing arguments into functions, the argument is copied into the parameter. But passing in an array? An array can poten

nt array to the parameter array woarg pa

verifies this: Program 4.6: Array parameters.

#include <iostream> #include <cstdlib> #include <ctime> using namespace std; void PrintArray(int array[20]) // Output the size, in bytes, of the parameter.

120

Page 121: C++ Módulo I e II

cout << "sizeof(array) = " << sizeof(array) << endl; // Print the array. for(int i = 0; i < 20; ++i) cout << array[i] << " "; cout << endl; int main() e random number generator. // Seed th rand( time(0) ); s // Array of 20 integers. int randomArray[20]; // Fill each element with a random number in the range [0, 100]. for(int i = 0; i < 20; ++i) randomArray[i] = rand() % 101; // Output the size, in bytes, of the array. cout << "sizeof(randomArray) = " << sizeof(randomArray) << endl; PrintArray( randomArray ); Program 4.6 Output

sizeof(randomArray) = 80 sizeof(array) = 4 23 5 20 41 5 32 90 13 49 8 98 39 39 80 1 6 79 60 98 66 Press any key to continue

From the output, we observe that the size of the array parameter is 4 bytes. Thus 20*4 bytes are not

ecause it is clearer to read, but we could have used pointer arithmetic plus a dereference.

rst element, we can also write the function signature

void PrintArray(int* array);

rray(int array[]); the first advantage of pointers.

ater, mplex types built out of several

copied into the function, but rather only 4 bytes are—the size of a 32-bit pointer—thus showing a pointer was copied and not the entire array. From the preceding sections, we know that we can navigate over all the elements of an array given a pointer to the first element in the array. Hence, by passing a pointer for efficiency, we are not limited in what we can do with that array. The function PrintArray shows this as it proceeds to print every element in the array. Note that we use the bracket [] notationb

Note: Since we pass arrays with a pointer to the filike this: Or like this: void PrintA

The efficiency gained by passing pointers to arrays into functions islop larger variable types (think of coL when we learn how to deve

121

Page 122: C++ Módulo I e II

intrinsic types), we will see that we can gain the same efficiency by passing pointers to these larger of copies.

.4 R

ppos function that needs to return multiple values back to the function caller. How ould y

r references.

le Return Values with Pointers

tMousePos, which needs to return the x- and y-coordinates the mouse position relative to the screen. Such a function is useful when you need a game (or any gram) to react to mouse input. The following program illustrates how to implement this function so

o parameters. (Note that, for illustration purposes, we return random numbers for the ince we do not know yet how to actually get the current mouse position.)

4.7: Returning multiple return values with pointers.

types instead

4 eturning Multiple Return Values

Su e you have a w ou do that? The usual return keyword approach restricts you to only one return value, so we

relies on pointers omust look for an alternative method. This alternative method

4.4.1 Returning Multip

Suppose that we require a function called Geof prothat it can return twmouse’s x- and y-coordinates s Program

#include <iostream> #include <cstdlib> #include <ctime> using namespace std; void GetMousePos(int* outX, int* outY) // Pretend to return the mouse's current position. *outX = rand() % 801; *outY = rand() % 601; int main() // Seed the random number generator. srand ime(0( t ) ); // Initialize two variables that will receive the // mouse position. int x = 0; int y = 0; // Output before x and y before receiving mouse position. cout << "Before GetMousePos(...)" << endl; cout << "x = " << x << endl; cout << "y = " << y << endl;

122

Page 123: C++ Módulo I e II

GetMousePos( &x, &y ); cout << "After GetMousePos(...)" << endl; cout << "x = " << x << endl; cout << "y = " << y << endl;

Program 4.7 Output

Before GetMousePos(...) x = 0 y = 0 After GetMousePos(...) x = 597 y = 353 Press any key to continue

From the output we verify that GetMousePos did indeed modify both x and y, thereby “returning” more than one value. How does it work? When we call a function with parameters, C++ does its normal processing; that is, it copies the argument value to the parameter. But in this case, the argument value is a memory address (&x and &y)—so it copies the address of x and the address of y into outX and , respectively. This means that the parameters and now point to the variables x

ariable. Thus, we can modify x and y from inside the function. Figure 4.8 shows the relationship between x and

, and and , visually.

outY outX outYand y. From what we studied previously, given a pointer to a variable, we can access that v

y outX outY

Figure 4.8: Observe that the parameters and outY point to the variables x and y, respectively.

” a function, thereby “returning” se pointer parameters. The ability to return multiple values through

nter vantage of pointers.

outX

In this way, we can modify multiple “outside” variables from “insidemultiple return values through thepoi parameters is the second ad

123

Page 124: C++ Módulo I e II

4 Returning Multiple Return .4.2 Values with References

e ca ction follows the same line or n, with the distinction being that it replaces pointer syntax with reference

the x- and y-coordinates n is useful when you need a game (or any

wing program illustrates how to implement this function so at it c , for illustration purposes, we return random numbers for the

e we do not know yet how to actually get the current mouse position.)

W n also return multiple return values with references. This sereasoning as the previous sectiosyntax. Suppose that we require a function called GetMousePos, which needs to return of the mouse position relative to the screen. Such a functioprogram) to react to mouse input. The follo

an return two parameters. (Note thatthmouse’s x- and y-coordinates sinc Program 4.8: Returning multiple return values with references.

#include <iostream> #include <cstdlib> #include <ctime> using namespace std; void GetMousePos(int& outX, int& outY) // Pretend to return the mouse's current position. outX = rand() % 801; outY = rand() % 601; int main() // Seed the random number generator. srand( time(0) ); // Initialize two variables that will receive the // mouse position. int x = 0; int y = 0; // Output before x and y before receiving mouse position. cout << "Before GetMousePos(...)" << endl; cout << "x = " << x << endl; cout << "y = " << y << endl; GetMousePos( x, y ); cout << "After GetMousePos(...)" << endl; cout << "x = " << x << endl; cout << "y = " << y << endl;

124

Page 125: C++ Módulo I e II

Program 4.8 Output

Before GetMousePos(...) x = 0 y = 0 After GetMousePos(...) x = 265 y = 24 Press any key to continue

From the output, we verify that GetMousePos did indeed modify both x and y, thereby “returning” more than one value. So how does it work? When we call a function with parameters, C++ does its normal processing; that is, it copies the argument value to the parameter. But in this case, the parameter is a reference, so the parameters become references to the arguments. Thus the parameters, outX and

.5 Dynamic Memory

ered thus far is that their size is fixed and their size must be specified in advance (i.e., at compile time). For instance, this is not legal:

cin >> n;

e ca e this. This presents a problem because it is not hard to agin ive multiplayer online me, re constantly entering

define an array with axim aximum amount of players. However, it is unlikely that the array

at all times, and therefore, memory will be wasted.

o rem d-size arrays, C++ provides the concept of dynamic memory. The word dynam can create and destroy memory at runtime (while the program is

all, we must use pointers. This is beca rator which we will use to allocate additional memory returns a pointer to that

ory is a ird important function of pointers.

outY, now refer to the variables x and y. From what we studied previously, given a pointer to a variable we can access that variable. Thus, we can modify x and y from inside the function.

4

One of the drawbacks of the arrays cov

int n = 0;

cout << "Enter an array size: "; float array[n]; W nnot create a variable-sized array likim e a case where the number of array items will vary. For example, in a massga you may use an array to store all of the game players. Because players aand leaving online play, the array may need to resize. One possibility is to “m um size” that can handle a m

valuewill be filled to the maximum T edy the problem of fixe

ic” is used in the sense that we“running). It is used in contrast to static memory, which is memory fixed at compile time. How do we incorporate dynamic memory into our applications? First of

use the C++ opememory. Therefore, we are forced into using pointers with dynamic memory. Dynamic memth

125

Page 126: C++ Módulo I e II

4.5.1

turns a pointer to the ry. Here are some examples:

ool = new bool;

le[5000]; // dynamic array

.

ents of dynamic arrays is done in the same way as with regular arrays—using the bracket operator [] or pointer arithmetic.

e initialized during allocation using parentheses syntax:

t* dynFloat = new float(6.28f);

array size: ";

t[n];

this is legal. Thus, we can allocate (and destroy) memory to meet the current r with dynamic memory, we can allocate memory only if it is

0];

Allocating Memory

To create memory dynamically we use the new operator. The new operator renewly allocated memo

e memory. // Allocatbool* dynB

int* dynInt = new int; float* dynFloat = new float;

char* dynCharArray = new char[80]; // dynamic array long* dynLongArray = new long[256]; // dynamic array double* dynDoubleArray = new doub // Initialize values *dynBool = true;

*dynInt = 5; *dynFloat = 6.28f; Note that when using the new operator to allocate arrays, the operator returns a pointer to the first element in the array. Therefore, accessing the elem

Additionally, the non-array values can b

// Allocation and value initialization. bool* dynBool = new bool(true); int* dynInt = new int(5); floa The key benefit of dynamic memory is that variable amounts of memory can be allocated at runtime. For example, we can now rewrite the earlier problem with a code snippet like so: int n = 0;

r an cout << "Ente cin >> n; float* array = new floa

sing dynamic memory, Uneeds of the program at runtime. Moreoveneeded. For example, we can do the following: if( memoryNeeded )

w float[500 dynMemory = ne In this way, the array of 5000 floats will only be allocated if the program actually needs it. Clearly dynamic memory gives us a great deal of control over memory and this turns out to be one of the more powerful features of pointers (and C++ in general).

126

Page 127: C++ Módulo I e II

4.5.2 Deleting Memory

One of the key differences between static and dynamic memory is that the creation and deletion of static emory is handled automatically, whereas the programmer must create and delete dynamic memory

manual

o delete memory that was allocated with new, the delete operator must be invoked on the pointer to the memory. Here are some examples to match the previous section’s new operations:

dynBool = 0; delete dynInt;

ray; Cha lete nLon ete dynDoubleArray = 0; Observe that when you delete an array, you need to use a special delete operator; the delete[] operator. Also ll after deletion. This is good programming practice becaus point to any memory after it is deleted. Consequently,

rogram are easier to debug because it will be clear if the program is trying to use a null pointer.

float* arrayOfFloats = new float[100];

Leaker() called!" << endl;

mly.

T

delete dynBool;

dynInt = 0; delete dynFloat; dynFloat = 0;

deynlete[] dynCharAr

d rArray = 0; [] dynLongArray; de

yd gArray = 0; [] dynDoubleArray; del

note that the pointers are set to nue t ensures that the pointer does noti

s pConversely, if the pointer was deleted but not nullified, then it would be harder to determine if the pointer was actually valid or not. By nullifying the pointer after its deletion, we explicitly state that it is now an invalid (null) pointer, and there is no ambiguity about its validity.

Rule: For every new operation invoked, a corresponding delete operator must be invoked when you are done with the memory. If deleting an array, be sure to use the delete[] syntax.

4.5.3 Memory Leaks

Memory leaks refer to the case where a pointer to memory which was allocated with new is lost before that memory was deleted. The following snippet illustrates a common example:

void MemLeaker()

cout << "Mem

127

Page 128: C++ Módulo I e II

int main()

for(int i = 0; i < 500; ++i)

ere an array of 100 floats is allocated in the function MemLeaker. When the function returns, all local

variables are deleted automatically, which includes the pointer arrayOfFloats. Remember that a to other memory. What this amounts to is that we have lost our ve allocated. Because the corresponding delete operator for that

dynamic memory was never called, the memory will not be deleted—thereby causing a memory leak.

eaker leaks a certain amount of memory each time it is called, if this function is alled often enough, it is possible that it will drain all of the available memory, which will be roblematic as your machine may eventually run out of available memory in which to perform

ariables. It is imperative to always delete any memory which you have you are done using that memory. For reference, the function should be written

Program

largest we have seen so far in this course. Each part has been ents carefully. The program allows the user

resiz an arr various elements in the array via a menu interface. this way w e. Memory will be allocated and destroyed while the

MemLeaker();

H

pointer is a variable too, it just pointspointer to the dynamic memory we ha

Moreover, since the pointer to that memory was lost, it is impossible to ever delete it since we have no idea where that memory lives anymore—access to it was lost when the pointer (i.e. the address) was lost. The memory loss problem is compounded by the fact that MemLeaker is called hundreds of times inside ain. Because MemLm

cpoperations or allocate new vallocated with new when like this:

void MemLeaker() float* arrayOfFloats = new float[100]; cout << "MemLeaker() called!" << endl; delete[] arrayOfFloats; arrayOfFloats = 0;

4.5.4 Sample

The following sample program is thecommented with explanations, so be sure to read the comm

at ruto e ay ntime and set the values of the In e can see dynamic memory in us

ng. program is runni Program 4.9: Dynamic memory.

#include <iostream> using namespace std;

128

Page 129: C++ Módulo I e II

//============================================================== // Desc: Function iterates through each element in the given // array. // ay - pointer to the first element of an arr integer array. // e - the number of elements in the siz array. //==== ======================== ==================================vo rintArray(int* array, int size) id P // If the array is of size zero than call it a null array. if ze == 0 ) ( si cout << "NULL Array" << endl; else // size not zero so loop through each element and // print it. cout << ""; for(int i = 0; i < size; ++i) cout << array[i] << " "; cout << "" << endl; //============================================================== // Desc: Function returns a new array given the new size. // array - pointer to the first element of an integer array. // oldSize - the number of elements currently in 'array'. // Size - the number of elements we want in the new array. new// =========================== =================================== in esizeArray(int* arrayt* R , int oldSize, int newSize) // Create an array with the new size. int* newArray = new int[newSize]; // New array is a greater size than old array. if( newSize >= oldSize ) // Copy old elements to new array. for(int i = 0; i < oldSize; ++i) newArray[i] = array[i]; // New array is a lesser size than old array. else // newSize < oldSize // Copy as many old elements to new array as can fit. for(int i = 0; i < newSize; ++i) newArray[i] = array[i]; // Delete the old array. de [] array; lete // urn a pointer to th Ret e new array. re newArray; turn

129

Page 130: C++ Módulo I e II

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

Page 131: C++ Módulo I e II

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) ReIndex = 0 [0] = 11 -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

Page 132: C++ Módulo I e II

Index = 4 [4] = 51 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 pme other programming lanfa

error prone, and therefore they do not expose pointers to the programan -level memory access can make programto inefficiencies. The key theme of Program 4.9 was resizinnon-trivial programs. In order to resize the array, dynamic memory was used. It would btheoc

thbehind the scenes. F The code package isis simply an array w Program 4.10: Using st

#include <iostream> #include <vector> using namespace std;

132

Page 133: C++ Módulo I e II

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 thetor 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 isMore importantly, observe tha

ment is “wrapped up” inm

133

Page 134: C++ Módulo I e II

Pr 4.11: Dynamic memory “wogram rapped up” in std::vector.

#include <iostream> # d ctor> inclu e <veusing 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

Page 135: C++ Módulo I e II

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 incalled? Ignoring arguments and parameters for the mome

135

Page 136: C++ Módulo I e II

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.” Howevemke

4.7.1 The Uses of Function Pointers

Lc

gular pointers, it may not be obvious why we need an addition

136

Page 137: C++ Módulo I e II

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.

ps programs are event driv

key presses, menu selecifferent Windows prh d

s tfeinput much differently

s operating system) is constantly checking for events. When an event o function to handle the event, called an event haode that should be executed in response to an

ndler.s

nt differently, the event handler will gindows program defines its ow

n w, w

Windows. Consequently, Windows can call this event handler functionp

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 readdress

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

Page 138: C++ Módulo I e II

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 variableointer 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 allonew operator and to destroy it, use either the delete operator for non-array pointers or thedelete[] operator for array pointers. Remember that to avoperation should eventually have a corresponding

5. If a resizable array is required, use std::vec

handles the dynamic memory for you, thus prusing std::vector, less code is required and the program becomes easier to mamaintain.

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

138

Page 139: C++ Módulo I e II

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

Page 140: C++ Módulo I e II

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 functionm number, in the range [0, 100], to each element of the

d void RandomArrayFill(int* array, int size); N te

cRandomArrayFill function, so that a random number is assigned to each element

hich, the program must output every array element to the console window. Your programwshould look similar to this: Enter the size of an array toCreating array and filling itA

Afte finis imT

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

140

Page 141: C++ Módulo I e II

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 .

p02 =++= cbxaxyth

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

Page 142: C++ Módulo I e II

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

aacbb 42 −±−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: . 62 −− xx Using the quadratic formula we have:

( )( )2

512

25112

614112 ±=

±=

⋅−−±

=−±− c

24

=a

abbx

So, and .

xample 2

31 =x 22 −=x E : Find the roots of the following quadratic equation: . Using the quadratic formula we have:

122 +− xx

( )( )

12

0212

114422

42

=⋅−±

=−±−

=a

acbbx

So, .

xample 3

121 == xx E : Find the roots of the following quadratic equation: .

sing the quadratic formula we have:

522 ++ xx

U

( )( )2

16212

514422

42 −±−=

⋅−±−

=−±−

=a

acbbx

Recalling that we obtain:

12 −=i

iii 212

422

1622

162 2

±−=±−

=±−

=−±−

So, and .

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

ix 211 +−= ix 212 −−=

Observe in example three that the roots to the quanumber that includes both

142

Page 143: C++ Módulo I e II

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 vm en return , otherwise return

alue is detam

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

Page 144: C++ Módulo I e II

Chapter 5

Classes and Object Oriented Programming

Introduction

144

Page 145: C++ Módulo I e II

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 libdescribe trivial things such aescribed in code are built d

st al properties such aperfthvariables which consist of several components and can perform actions.

Chapt

• Understand the problems objend 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,witcan also perform certain actioexcastSpell, etc. These verbs can be represented in code with functions; for ex

ght would execute the code necessary to perform a combat simulation. Mfiwth Real-world objects consistoriented programming is to model this obs

an be used to instantiate variables thwhare called classes. The motivation f

145

Page 146: C++ Módulo I e II

Fir

inst, 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

Finallyfacilities 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 natuthsystems. Second, object-oriented programming provides aclaa 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 nn 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 ofject 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

Page 147: C++ Módulo I e II

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; ; Mobjects 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 Wp

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

class methods still need to be implemeA our class has been defined, theimplementations (i.e., definitions) occur osyntax: r ...Body Code To illustrate a simple example, we implement the functions of Wizard, like so: v

cout << "Fighting." << endl;

izard::talk()

147

Page 148: C++ Módulo I e II

void Wizard::castSpell() < endl; The onmethodThe 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, whod 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

Page 149: C++ Módulo I e II

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 Wizardmethods. 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.

incSthe dot operator—we meproperty mArmor or the mHitPoints properties offight() 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 dmeamn

Note: We can creaWhat if we have a pointe Wizard* wiz0 = new Wizard;

149

Page 150: C++ Módulo I e II

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 derefeand 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 cand implementation fiontain 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

Page 151: C++ Módulo I e II

#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" //<--Includeusing namespace std; int main() Wizard wiz0; // Declare a variable called wiz0 of type Wizard. wiz0.fight(); wiz0.talk(); wiz0.mArmor = 10;

151

Page 152: C++ Módulo I e II

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

Page 153: C++ Módulo I e II

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

Page 154: C++ Módulo I e II

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 thathe class data). Thus if we write:

::setArmor implementation, we added a check to make sure

he name of a Wizard without giving d

154

Page 155: C++ Módulo I e II

void Wizard::CastSpell() if( mMagicPoints > 4) cout << "Casting Spell." << endl; else cout << "Not enough magic points!" << endl;

The client code can now be rewritten with our new interface as follows:

int main()

wiz0.Fight(); wiz0.Talk(); wiz0.set cout << "Player's name = " << wiz0.getName() << endl; wiz0.CastSpell();

Note: In addition to defining a class, you can define what is called a structure. In C++, a structure is exactly the same as a class except that it is public by default, whereas a class is private by default. For example, we can write a Point2D structure like so: struct Point2D float x; float y; ; We do not need to explicitly designate it as public because structures are public by default. Structures can have member functions as well, but in practice, structures are usually used for types that only have data members. Because structures are essentially the same as classes, we use the two terms interchangeably.

5.2.5 Constructors and Destructors

Every class has a constructor special kinds of methods. If you do not explicitly define these method default versions automatically. In short, a constructor is a method that is automatically executed when an object is instantiated and a destructor is a method that is automatically executed when an object is deleted. A constructor is usually used to initialize data members to some default value or to allocate any dynamic memory the class uses or to execute any initialization code that you want executed as the object is being created. Conversely, the destructor is usually used to free any dynamic memory which the class has allocated because if it does not delete it when the object is being destroyed, it will result in a memory

Wizard wiz0;

Armor(10);

and destructor, which ares, the compiler will generate

155

Page 156: C++ Módulo I e II

leak. Note that you never invoke a destructor yourself; rather, the destructor will automatically be called when an object is being deleted from memory. Constructors and destructors are special methods and they require a specific syntax. In particular, a constructor has no return type and its name is also the name of the class. Likewise, a destructor has no return type, no parameters, and its name is the name of the class but prefixed with the tilde (~). This next snippet shows how the constructor and destructor would be declared in the Wizard class definition: class Wizard public: // Constructor. Wizard(); // Overloaded constructor. Wizard(std::string name, int hp, int mp, int armor); // Destructor ~Wizard(); ... The implementations of these function is done just as any other method, except there is no return type. The following snippet gives a sample implementation: Wizard::Wizard() // Client called constructor with zero parameters, // so construct a "wizard" with default values. // We call this a “default” constructor. mName = "DefaultName"; mHitPoints = 0; mMagicPoints = 0; mArmor = 0; Wizard::Wizard(std::string name, int hp, int mp, int armor)

ent called constructor with parameters, so // construct a "wizard" with the specified values. mName = name; mHitPoints = hp;

m Wizard::~Wizard() // No dynamic memory to delete--nothing to cleanup.

N e that we have overloaded the constructor function. Recall that the act of defining several different versions—which differ in signature—of a function is called function overloading. We can overload methods in the same way we overload functions.

// Cli

mMagicPoints = mp; Armor = armor;

ote: Observ

156

Page 157: C++ Módulo I e II

Constructors are called when an object is created. Thus instead of writing: Wizard wiz0; We now write:

wiz0();// Use “default” constructor.

or: Wizard wiz0(“Gandalf”, 20, 100, 5);// Use constructor with // parameters. Note that the following are actually equivalent; that is, they both use the default constructor: Wizard wiz0; // Use “default” constructor. W ;// Use “default” constructor.

Constructors and the Assignment Operator

and an assignment operator. If you do not explicitly define these methods, the comp e default ones automatically. A copy constructor is a method that constructs an object via another object of the same type. For example, we should be able to construct a new Wizard object from another Wizard object—somewhat like a copy:

... Wizard wiz1(wiz0);// Construct wiz1 from wiz0.

If you use the default copy constructor, the object will be constructed by copying the parameter’s bytes, byte-by-byte, into the object being constructed, thereby performing a basic copy. However, there are times when this default behavior is undesirable and you need to implement your own copy constructor code. Similarly, an assignment operator is a method that specifies how an object can be assigned to another object. For example, how should a Wizard object be assigned to another Wizard object?

Wizard wiz0; ... Wizard wiz1 = wiz0;// Assign wiz0 to wiz1.

If you use the default assignment operator, a simple byte-by-byte copy from the right hand operand’s memory into the left hand operand’s memory will take place, thereby performing a basic copy. However, there are times when this default behavior is undesirable and you need to override it with your own assignment code.

Wizard

izard wiz0()

5.2.6 Copy

Every class also has a copy constructoriler will generat

Wizard wiz0;

157

Page 158: C++ Módulo I e II

We discuss the details of cases where you would need to implement your own copy constructor and assignment operator in Chapter 7. For now, just be aware that these methods exist.

Game: Class Examples

To help reinforce the concepts of classes, we will create several classes over the following subsections. We will then use these classes to make a small text-based role-playing game (RPG).

When utilizing the object oriented programming paradigm, the first thing we ask when designing a new program is: “What objects does the program attempt to model?” The answer to this question depends on the program. For example, a paint program might utilize objects such as Brushes, Canvases, Pens, Lines, Circles, Curves, and so on. In the case of our RPG, we require various types of weapon objects, monster objects, player objects, and map objects. After we d on what objects our program will use, we need to design corresponding classes which define the properties of these kinds of objects and the actions they perform. For example, what aggregate set of data members represents a Player in the game? What kind of actions can a Player perform in the game? In addition to the data and methods of a class, the class design will also need to consider the relationships between the objects of one class and the objects of other classes—for example, how they will interact with each other. The following subsections provide examples for how these class design questions can be answered.

5.3.1 The Range Structure

Our game will rely on “dice rolls” as is common in many role-playing games. We implement random dice rolls with a random number generator. To facilitate random number generation, let us define a range structure, which can be used to define a range in between which we compute random numbers:

5.3 RPG

have decide

// Range.h #ifndef RANGE_H #define RANGE_H // Defines a range [mLow, mHigh]. struct Range int mLow; int mHigh; ; #endif //RANGE_H This class is simple, and it contains zero methods. The data members are the interface to this class. Therefore, there is no reason to make the data private and so we leave them public. (Note as well that we actually used the struct type rather than the class type as discussed in section 5.2.4).

158

Page 159: C++ Módulo I e II

5.3.2 Random Functions

Our game will require a couple of utility functions as well. These functions do not belong to an object, l implement them using “regular” functions.

per se, so we wil

// Random.h #ifndef RANDOM_H #define RANDOM_H #include "Range.h" int Random(Range r); int Random(int a, int b); #endif // RANDOM_H // Random.cpp #include "Random.h" #include <cstdlib> // Returns a random number in r. int Random(Range r) return r.mLow + rand() % ((r.mHigh + 1) - r.mLow); // Returns a random number in [low, high]. int Random(int low, int high) return low + rand() % ((high + 1) - low);

• Random: This function returns a random number in the specified range. We overload this function to work with the Range structure, and also to work with two integer parameters that specify a range. Section 3.4.1 describes how this calculation works.

5.3.3 Weapon Class

159

Page 160: C++ Módulo I e II

Typically in an RPG game there will be many different kinds of weapons players can utilize. Therefore, it makes sense to define a Weapon class, from which different kinds of weapon objects can be instantiated. In our RPG game the Weapon class is defined like so: // Weapon.h #ifndef WEAPON_H #define WEAPON_H #include "Range.h" #include <string> struct Weapon std::string mName; Range mDamageRange; ; #endif //WEAPON_H An object of class Weapon has a name (i.e., the name of the weapon), and a damage range, which specifies the range of damage the weapon inflicts against an enemy. To describe the damage range, we use our Range class. Again, note that this class has no methods because it performs no actions. You might argue that a weapon attacks things, but instead of this approach, we decide that game characters (players and monsters) attack things and weapons do not; this is simply a design decision. Because there are no methods, there is no reason to protect the data integrity. In fact, the data members are the class interface. The following code snippet gives some examples of how we might use this class to instantiate different kinds of weapons: Weapon dagger;

dagger.mName = "Dagger"; dagger.mDamageRange.mLow = 1; dagger.mDamageRange.mHigh = 4; Weapon sword; sword.mName = "Sword"; sword.mDamageRange.mLow = 2; sword.mDamageRange.mHigh = 6;

5.3.4 Monster Class

160

Page 161: C++ Módulo I e II

In addition to weapons, an RPG game will have many different types of monsters. Thus, it makes sense to define a Monster class from which different kinds of monster objects can be instantiated. In our RPG game the Monster class is defined like so: // Monster.h #ifndef MONSTER_H #define MONSTER_H #include "Weapon.h" #include <string> class Player; class Monster public: Monster(const std::string& name, int hp, int acc, int xpReward, int armor, const std::string& weaponName, int lowDamage, int highDamage); bool isDead(); int getXPReward(); std::string getName(); int getArmor(); void attack(Player& player); void takeDamage(int damage); void displayHitPoints(); private: std::string mName; int mHitPoints; int mAccuracy; int mExpReward; int mArmor; Weapon mWeapon; ; #endif //MONSTER_H The first thing of interest is the first line after the include directives; specifically, the statement class Player;. What does this do? This is called a forward class declaration, and it is needed in order to use the Player class without having yet defined it. The idea is similar to function declarations, where a function is declared first, in order that it can be used, and then defined later. Monster Class Data:

• mName: The name of the monster. For example, we would name an Orc monster “Orc.”

161

Page 162: C++ Módulo I e II

• mHitPoints: An integer that describes the number of hit points the monster has.

• mAccuracy: An integer value used to determine the probability of a monster hitting or missing a

game player.

• mExpReward: An integer value that describes how many experience points the player receives upon defeating this monster.

• mArmor: An integer value that describes the armor strength of the monster.

• mWeapon: The monster’s weapon. A Weapon value describes the name of a weapon and its

range of damage. Note how objects of this class will contain a Weapon object, which in turn contains a Range object. We can observe this propagation of complexity as we build classes on top of other classes.

Monster Class Methods

:

nsterMo :

e constructor simply takes a parameter list, which is used to initialize the data members of a Monster object at the time of construction. It is implemented like so: Monster::Monster(const std::string& name, int hp, int acc, int xpReward, int armor, const std::string& weaponName, int lowDamage, int highDamage) mName = name; mHitPoints = hp;

mAccuracy = acc; mExpReward = xpReward;

mArmor = armor; mWeapon.mName = weaponName; mWeapon.mDamageRange.mLow = lowDamage; mWeapon.mDamageRange.mHigh = highDamage; As you can see, this function copies the parameters to the data members, thereby initializing the data members. In this way, the property values of a monster object can be specified during construction. isDead

Th

: This simple method returns true if a monster is dead, otherwise it returns false. A monster is defined to be dead if its hit points are less than or equal to zero. bool Monster::isDead() return mHitPoints <= 0;

162

Page 163: C++ Módulo I e II

This method is important because during combat, we will need to be able to test whether a monster has been killed. getXPReward: This method is a simple accessor method, which returns a copy of the mExpReward data member: int Monster::getXPReward() return mExpReward; getName: This is another accessor method, which returns a copy of the mName data member: std::string Monster::getName()

getArmor

return mName;

: This is another accessor method; this one returns a copy of the mArmor data member: int Monster::getArmor() return mArmor; attack: This m nly nontrivial method of Monster. This method executes the code which has a monster attack a game Player (a class we will soon define). Because a monster attacks a Player, we pass a reference to a Player into the function. We pass by reference for efficiency; that is, just as we do not want to copy an entire array into a parameter, we do not want to copy an entire Player object. By passing a reference, we merely copy a reference variable (a 32-bit address). The attack method is responsible for determining if the monster’s attack hits or misses the player. We use the following criteria to determine whether a monster hits a player: If the monster’s accuracy is greater than a random number in the range [0, 20] then the monster hits the player, else the monster misses the player: if( Random(0, 20) < mAccuracy ) If the monster hits the player, then the next step is to compute the damage the monster inflicts on the player. W dom number in the range of damage determined by the monster’s weapon Range:

ethod is the o

e start by computing a ran—mWeapon.mDamage

163

Page 164: C++ Módulo I e II

int damage = Random(mWeapon.mDamageRange);

However, armor must be brought into the equation. In particular, we say that armor absorbs some of the damage. Mathematically we describe this by subtracting the player’s armor value from the random damage value: int totalDamage = damage - player.getArmor(); It is possible that damage is a low value in which case totalDamage might be less than or equal to zero. In damage is actually inflicted—we say that the attack failed to penetrate the armor. Conversely, if totalDamage is greater than zero then the player loses hit points. Here is the attack function in its entirety: void Monster::attack(Player& player) cout << "A " << mName << " attacks you " << "with a " << mWeapon.mName << endl; if( Random(0, 20) < mAccuracy ) int damage = Random(mWeapon.mDamageRange); int totalDamage = damage - player.getArmor(); if( totalDamage <= 0 ) cout << "The monster's attack failed to " << "penetrate your armor." << endl; else cout << "You are hit for " << totalDamage << " damage!" << endl; player.takeDamage(totalDamage); else cout << "The " << mName << " missed!" << endl; cout << endl; takeDamage

this case, no

: This method is called when a player hits a monster. The parameter specifies the amount of damage for which the monster was hit, which indicates how many hit points should be subtracted from the monster: void Monster::takeDamage(int damage) mHitPoints -= damage;

164

Page 165: C++ Módulo I e II

displayHitPoints: This method outputs the monster’s hit points to the console window. This is used in the game during battles so that the player can see how many hit points the monster has remaining. void Monster::displayHitPoints() cout << mName << "'s hitpoints = " << mHitPoints << endl;

5.3.5 Player Class

The Player class describes a game character. In our game there is only one player object (single player), however you could extend the game to support multiple players, or let the user control a party of several characters. The Player class is defined as follows: // Player.h #ifndef PLAYER_H #define PLAYER_H #include "Weapon.h" #include "Monster.h" #include <string> class Player public: // Constructor. Player(); // Methods bool isDead(); std::string getName(); int getArmor(); void takeDamage(int damage); void createClass(); bool attack(Monster& monster); void levelUp(); void rest(); void viewStats(); void victory(int xp); void gameover(); void displayHitPoints(); private: // Data members.

165

Page 166: C++ Módulo I e II

std::string mName; std::string mClassName; int mAccuracy; int mHitPoints; int mMaxHitPoints; int mExpPoints; int mNextLevelExp; int mLevel; int mArmor; Weapon mWeapon; ; #endif //PLAYER_H The Player class is similar in many ways to the Monster class, as can be seen by the similar data members and functions. However, there are some additional data and methods the Player class contains which the Monster class does not. Player Class Data:

• mName: The name of the character the player controls. You name the character during character

• mClassName: A string that denotes the player class type. For example, if you play as a wizard

“Wizard.”

• mAccuracy: An integer value used to determine the probability of a player hitting or missing a monster.

• mHitPoints: An integer that describes the current number of hit points the player has.

• mMaxHitPo ger that describes the maximum number of hit points the player can

currently have.

• mExpPoints: An integer that describes the number of experience points the player has currently earned.

• mNextLevelExp: An integer that describes the number of experience points the player needs to

reach the next level. We define the amount of experience needed to reach the next level in terms of the player’s current level; that is, mNextLevelExp = mLevel * mLevel * 1000;

• mLevel: An integer that describes the current level of the player.

• mArmor: An integer value that describes the armor strength of the player.

• mWeapon: The player’s weapon. A Weapon value describes the name of a weapon and its range

of damage.

creation.

then your class name would be

ints: An inte

166

Page 167: C++ Módulo I e II

Player Class Methods: Player: The constructor of our Player class is a default one—it simply initializes the data members to default values. This is no because these values will be changed during character creation. Player::Player() mName = "Default"; mClassName = "Default"; mAccuracy = 0; mHitPoints = 0; mMaxHitPoints = 0; mExpPoints = 0; mNextLevelExp = 0; mLevel = 0; mArmor mWeapon.mName = "Default Weapon Name"; mWeapon.mDamageRange.mLow = 0; mWeapon.mDamageRange.mHigh = 0; isDead

t problematic

= 0;

: This simple method returns true if a player is dead, otherwise it returns false. A player is defined to be dead if its hit points are less than or equal to zero. bool Player::isDead() return mHitPoints <= 0; This method is important because during combat, we will need to be able to test whether a player has been killed. getArmor: An accessor method; this one returns a copy of the mArmor data member: int Player::getArmor() return mArmor; takeDamage:

167

Page 168: C++ Módulo I e II

This method is called when a monster hits a player. The parameter specifies the amount of damage for which the player was hit, which indicates how many hit points should be subtracted from the player: void Player::takeDamage(int damage) mHitPoints -= damage; createClass: This method is used to execute the code that performs the character generation process. First, it asks the user to enter in the name of the player. Next, it asks the user to select a character class. Then, based on the character class chosen, the properties of the Player object are filled out accordingly. For example, a “fighter” is given more hit points than a “wizard.” Similarly, different classes start the game with different weapons. void Player::createClass() cout << "CHARACTER CLASS GENERATION" << endl; cout << "==========================" << endl; // Input character's name. cout << "Enter your character's name: "; getline(cin, mName); // Character selection. cout << "Please select a character class number..."<< endl; cout << "1)Fighter 2)Wizard 3)Cleric 4)Thief : "; int characterNum = 1; cin >> characterNum; switch( characterNum ) case 1: // Fighter mClassName = "Fighter"; mAccuracy = 10; mHitPoints = 20; mMaxHitPoints = 20; mExpPoints mNextLevelEx mLevel = 1; mArmor = 4; mWeapon.mName = "Long Sword"; mWeapon.mDamageRange.mLow = 1; mWeapon.mDamageRange.mHigh = 8; break; case 2: // Wizard mClassName = "Wizard"; mAccuracy = 5; mHitPoints = 10; mMaxHitPoints = 10; mExpPoints = 0; mNextLevelExp = 1000;

= 0; p = 1000;

168

Page 169: C++ Módulo I e II

mLevel = 1; mArmor = 1; mWeapon.mName = "Staff"; mWeapon.mDamageRange.mLow = 1; mWeapon.mDamageRange.mHigh = 4; break; case 3: // Cleric mClassName = "Cleric"; mAccuracy = 8; mHitPoints = 15; mMaxHitPoints mNextLevelExp = 1000; mWeapon.mName = "Flail"; mWeapon.mDamageRange.mHigh 6; break; default: // Thief mClassName = "Thief"; mMaxHitPoints = 12; mExpPoints = 0; mNextLevelExp = 1000; mLevel = 1; mArmor = 2; mWeapon.mName = "Short Sword"; mWeapon.mDamageRange.mLow = 1; mWeapon.mDamageRange.mHigh = 6; break; attack

= 15; mExpPoints = 0;

mLevel = 1; mArmor = 3;

mWeapon.mDamageRange.mLow = 1; =

mAccuracy = 7; mHitPoints = 12;

: The attack method is essentially the sam s Monster::attack. However, one important difference is that we give the player an option of what to do on his attack turn. For example, in our game, the player can choose to fight or run: int selection = 1; cout << "1) Attack, 2) Run: "; cin >> selection; cout << endl; This can be extended to give the player more options such as using an item or casting a spell. If the player chooses to attack, then the execute code is very similar to Monster::attack, except that the roles are reversed; that is, here a player attacks a monster, whereas in Monster::attack, a monster attacks a player. switch( selection )

e a

169

Page 170: C++ Módulo I e II

case 1: cout << "You attack an " << monster.getName() << " with a " << mWeapon.mName << endl; if( Random(0, 20) < mAccuracy ) int damage = Random(mWeapon.mDamageRange); int totalDamage = damage - monster.getArmor(); if( totalDamage <= 0 ) cout << "Your attack failed to penetrate " << "the armor." << endl; else cout << "You attack for " << totalDamage << " damage!" << endl; // Subtract from monster's hitpoints. monster.takeDamage(totalDamage); else cout << "You miss!" << endl; cout << endl; break; On the other hand, if the player chooses to run, then the code computes a random number, where there is a 25% chance that the player can escape. case 2:

// 25 % chance of being able to run. int roll = Random(1, 4); if( roll == 1 ) cout << "You run away!" << endl; return true;//<--Return out of the function. else cout << "You could not escape!" << endl; break; Observe that this function returns true if the player runs away, otherwise it returns false. levelUp:

170

Page 171: C++ Módulo I e II

This method tests whether or not the player has acquired enough experience points to level up. It is called after every battle. If the player does have enough experience points then some of the player’s statistics such as hit points and accuracy are randomly increased. void Player::levelUp() if( mExpPoints >= mNextLevelExp ) cout << "You gained a level!" << endl; // Increment level. mLevel++; // Set experience points required for next level. mNextLevelExp = mLevel * mLevel * 1000; // Increase stats randomly. mAccuracy += Random(1, 3); mMaxHitPoints += Random(2, 6); mArmor += Random(1, 2); // Give player full hitpoints when they level up. mLevel = mMaxHitPoints; rest: This method is called when the player chooses to rest. Currently, resting simply increases the player’s hit points to the maximum. Later you may wish to add the possibility of random enemy encounters during resting or other events.

r::rest() cout << "Resting..." << endl; mHitPoints = mMaxHitPoints; viewStats

void Playe

: Often, a player in an RPG likes to view his player’s statistics, so that he knows what items are in his inventory, how many hit points he has, or how many experience points are required to reach the next level. f information with the viewStats method: void Player::viewStats() cout << "PLAYER STATS" << endl; cout << "============" << endl; cout << endl; cout << "Name = " << mName << endl; cout << "Class = " << mClassName << endl;

We output this type o

171

Page 172: C++ Módulo I e II

cout << "Accuracy = " << mAccuracy << endl; = " << mHitPoints << endl; cout << "MaxHitpoints = " << mMaxHitPoints << endl; cout << "XP = " << mExpPoints << endl; cout << "XP for Next Lvl = " << mNextLevelExp << endl; cout << "Level = " << mLevel << endl; cout << "Armor = " << mArmor << endl; cout << "Weapon Name = " << mWeapon.mName << endl; cout << "Weapon Damage = " << mWeapon.mDamageRange.mLow

<< "-" << mWeapon.mDamageRange.mHigh << endl; cout << endl; cout << "END PLAYER STATS" << endl; cout << "================" << endl; cout << endl; victory

cout << "Hitpoints

: This method is called after a player is victorious in battle. It displays a victory message and gives the player an experience point award. void Player::victory(int xp) cout << "You won the battle!" << endl; cout << "You win " << xp ndl << endl; mExpPoints += xp; gameover

<< " experience points!" << e

: This method is called if the player dies in battle. It displays a “game over” string and asks the user to press ‘q’ to quit: void Player::gameover() cout << "You died in battle..." << endl; cout << endl; cout << "================================" << endl; cout << "GAME OVER!" << endl; cout << "================================" << endl; cout << "Press 'q' to quit: "; char q = 'q'; cin >> q; cout << endl; displayHitPoints:

172

Page 173: C++ Módulo I e II

This method simply outputs the player’s hit points to the console window. This is used in the game during battles so that the player can see how many hit points he has left. void Monster::displayHitPoints() cout << mName << "'s hitpoints = " << mHitPoints << endl;

5.3.6 Map Class

The final class we implement is called Map. An object of this class is used to represent the game board of either the gam art of the game world, or a dungeon in the game world. In our small game we use a single Map object for our limited 2D game world. One responsibility of a Map object is to keep track of the player’s world position; that is, its coordinates. In doing so, we make the Map class responsible for i user’s movement input. Additionally, since a Map should know where objects are on the map, it should know where the monsters are. Therefore, we also make the Map class responsible for handling enemy encounters. A further extension to the Map class would be to define “landmarks” on it, or key areas where you want something special to occur. For example, perhaps at coordinates (2, 3) you want to place a dungeon, so that when the player moves to coordinates (2, 3), the game will describe the exterior of the dungeon and ask if the player wants to enter. A town would be another example. Let us now look at the header file that contains the Map class:

e world, p

nputting the

// Map.h #ifndef MAP_H #define MAP_H #include "Weapon.h" #include "Monster.h" #include <string> class Map public: // Constructor. Map(); // Methods int getPlayerXPos(); int getPlayerYPos(); void movePlayer(); Monster* checkRandomEncounter(); void printPlayerPos();

173

Page 174: C++ Módulo I e II

private: // Data members. int mPlayerXPos; int mPlayerYPos; ; #endif //MAP_H Map Class Data:

• mPlayerXPos: The x-coordinate position of the player.

• mPlayerYPos: The y-coordinate position of the player. Map Class Methods: Map: The constructor initializes the player’s position coordinates to the origin; that is, the player starts off at the origin: Map::Map() // Player starts at origin (0, 0) mPlayerXPos = 0; mPlayerYPos = 0; getPlayerXPos: This method is an accessor function that returns the current x-coordinate of the player. int Map::getPlayerXPos() return mPlayerXPos; getPlayerYPos: This method is an accessor function that returns the current y-coordinate of the player. int Map::getPlayerYPos() return mPlayerYPos; movePlayer:

174

Page 175: C++ Módulo I e II

As stated, we make it a Map’s responsibility to keep track of the player’s position. This function is called when the player wants to move. It prompts the user to enter in a direction of movement and then updates the player’s coordinates accordingly: void Map::movePlayer() int selection = 1; cout << "1) North, 2) East, 3) South, 4) West: "; cin >> selection; // Update coordinates based on selection. switch( selection ) case 1: // North mPlayerYPos++; break; case 2: // East mPlayerXPos++; break; case 3: // South mPlayerYPos--; break; default: // West mPlayerXPos--; break; cout << endl; checkRandomEncounter: This function is the key function of the Map class. It generates a random number in the range [0, 20]. Depending upon which sub-range in which the generated number falls, a different encounter takes place:

• Range [0, 5] – The player encounters no enemy. • Range [6, 10] – The player encounters an Orc. • Range [11, 15] – The player encounters a Goblin. • Range [15, 19] – The player encounters an Ogre. • Range [20] – The player encounters an Orc Lord.

The bulk of this method code consists of testing in which range the random number falls and then creating the appropriate kind of monster. Monster* Map::checkRandomEncounter() int roll = Random(0, 20); Monster* monster = 0; if( roll <= 5 )

175

Page 176: C++ Módulo I e II

// No encounter, return a null pointer. return 0; else if(roll >= 6 && roll <= 10) monster = new Monster("Orc", 10, 8, 200, 1, "Short Sword", 2, 7); cout << "You encountered an Orc!" << endl; cout << "Prepare for battle!" << endl; cout << endl; else if(roll >= 11 && roll <= 15) monster = new Monster("Goblin", 6, 6, 100, 0, "Dagger", 1, 5); cout << "You encountered a Goblin!" << endl; cout << "Prepare for battle!" << endl; cout << endl; else if(roll >= 16 && roll <= 19) monster = new Monster("Ogre", 20, 12, 500, 2, "Club", 3, 8); cout << "You encountered an Ogre!" << endl; cout << "Prepare for battle!" << endl; cout << endl; else if(roll == 20) monster = new Monster("Orc Lord", 25, 15, 2000, 5, "Two Handed Sword", 5, 20); cout << "You encountered an Orc Lord!!!" << endl; cout << "Prepare for battle!" << endl; cout << endl; return monster; Observe that the function returns a pointer to the encountered monster. We chose to use a pointer because with pointers we can return a null pointer. A null pointer is useful in the case in which the player encounters no enemy. Also note that when we do create a Monster, dynamic memory must be used so that the system does not automatically destroy the memory when the function returns—remember, once we use dynamic memory it is our responsibility to destroy it. We make it the responsibility of the function caller, which receives the pointer, to delete it. printPlayerPos:

176

Page 177: C++ Módulo I e II

When the player is at the main menu, we would like to display the player’s current coordinate position on the map. This is what this function is used for. void Map::printPlayerPos() cout << "Player Position = (" << mPlayerXPos << ", " << mPlayerYPos << ")" << endl << endl;

5.4 The Game

The classes that the objects of our game are members of have now been defined, and we are ready to instantiate these objects and put them to use. But, before looking at the game code, let us look at a sample output of the game so that we have an idea of the game flow.

Note: You can download the executable and source code for this program from the www.gameinstitute.com website. You may want to run the program yourself a few times, in order to get familiar with its functionality before examining the code.

CHARACTER CLASS GENERATION ========================== Enter your character's name: Frank Please select a character class number... 1)Fighter 2)Wizard 3)Cleric 4)Thief : 1 Player Position = (0, 0) 1) Move, 2) Rest, 3) View Stats, 4) Quit: 1 1) North, 2) East, 3) South, 4) West: 1 Player Position = (0, 1) 1) Move, 2) Rest, 3) View Stats, 4) Quit: 1 1) North, 2) East, 3) South, 4) West: 2 You encountered an Ogre! Prepare for battle! Frank's hitpoints = 20 Ogre's hitpoints = 20 1) Attack, 2) Run: 1 You attack an Ogre with a Long Sword You attack for 3 damage! A Ogre attacks you with a Club You are hit for 4 damage! Frank's hitpoints = 16 Ogre's hitpoints = 17

177

Page 178: C++ Módulo I e II

1) Attack, 2) Run: 1 You attack an Ogre with a Long Sword You attack for 6 damage! A Ogre attacks you with a Club The Ogre missed! Frank's hitpoints = 16 Ogre's hitpoints = 11 1) Attack, 2) Run: 1 You attack an Ogre with a Long Sword You miss! A Ogre attacks you with a Club You are hit for 3 damage! Frank's hitpoints = 13 Ogre's hitpoints = 11 1) Attack, 2) Run: 1 You attack an Ogre with a Long Sword You attack for 5 damage! A Ogre attacks you with a Club You are hit for 4 damage! Frank's hitpoints = 9 Ogre's hitpoints = 6 1) Attack, 2) Run: 1 You attack an Ogre with a Long Sword You miss! A Ogre attacks you with a Club You are hit for 3 damage! Frank's hitpoints = 6 Ogre's hitpoints = 6 1) Attack, 2) Run: 1 You attack an Ogre with a Long Sword You attack for 6 damage! You won the battle! You win 500 experience points! Player Position = (1, 1) 1) Move, 2) Rest, 3) View Stats, 4) Quit: 3

178

Page 179: C++ Módulo I e II

PLAYER STATS ============ Name = Frank Class = Fighter Accuracy = 10 Hitpoints = 6 MaxHitpoints = 20 XP = 500 XP for Next Lvl = 1000 Level = 1 Armor = 4 Weapon Name = Long Sword Weapon Damage = 1-8 END PLAYER STATS ================ Player Position = (1, 1) 1) Move, 2) Rest, 3) View Stats, 4) Quit: 2 Resting... Player Position = (1, 1) 1) Move, 2) Rest, 3) View Stats, 4) Quit: 4 As the game output shows, the game first proceeds to create a game character. The core game then begins which allows the user to move about the map, fight random monsters, rest, view the player stats, or exit. This is one big loop which continues as long as the player does not die, or the player does not quit. To create the m to our project. A “client file” is a file which uses ., Weapon, Monster, Player). Let us now look at the client file one segm e.

5.4.1 Segment 1

ain game logic, we add a client file called game.cpp the classes we created in the previous section (e.g

ent at a tim

// game.cpp #include "Map.h" #include "Player.h" #include <cstdlib> #include <ctime> #include <iostream> using namespace std; int main() srand( time(0) ); Map gameMap;

179

Page 180: C++ Módulo I e II

Player mainPlayer; mainPlayer.createClass(); Code segment 1 is quite trivial. We include the necessary header files and begin the main function. The first thing we do is seed the random number generator (Section 3.4). Next, we instantiate a Map object called gameMap and we instantiate a Player object called mainPlayer. Finally, we execute the character class creation code by calling createClass for mainPlayer.

5.4.2 Segment 2

// Begin adventure. bool done = false; while( !done ) // Each loop cycly we output the player position and // a selection menu. gameMap.printPlayerPos(); int selection = 1; cout << "1) Move, 2) Rest, 3) View Stats, 4) Quit: "; cin >> selection; Monster* monster = 0; switch( selection ) In segment 2, we begin the main “game loop.” This is the loop which will continue to execute until either the player dies or the player quits the game. The key tasks the game loop performs are to display the player’s position every loop cycle and to prompt the user to make a menu selection. We then execute different code paths depending on the chosen selection via a switch statement.

5.4.3 Segment 3

case 1: // Move the player. gameMap.movePlayer(); // Check for a random encounter. This function // returns a null pointer if no monsters are // encountered. monster = gameMap.checkRandomEncounter(); // 'monster' not null, run combat simulation. if( monster != 0 )

180

Page 181: C++ Módulo I e II

reak' statement. // Loop until a 'b while( true ) // Display hitpoints. mainPlayer.displayHitPoints(); monster->displayHitPoints(); cout << endl; // Player's turn to attack first. bool runAway = mainPlayer.attack(*monster); if( runAway ) break; if( monster->isDead() ) mainPlayer.victory(monster->getXPReward()); mainPlayer.levelUp(); break; monster->attack(mainPlayer); if( mainPlayer.isDead() ) mainPlayer.gameover(); done = true; break; // The pointer to a monster returned from // checkRandomEncounter was allocated with // 'new', so we must delete it to avoid // memory leaks. delete monster; monster = 0; break; The case where the player moves is the largest code unit. First we call Map::movePlayer, which prompts the user to enter the direction of movement. Next we check if the player encountered an enemy with the Map::checkRandomEncounter method. Recall that this function returns null if no monster was encountered. This fact allows us to use a simple if statement to determine whether or not a monster was encountered. If the pointer is not null, then a monster was encountered and we proceed to enter a “combat loop;” that is, a loop that continues until the player dies or runs away, or the monster dies. Inside the combat loop we output the player’s hit points and the monster’s hit points for every loop cycle so that the game player can observe the progress of the battle. Afterwards, we have the player attack the monster: bool runAway = mainPlayer.attack(*monster);

181

Page 182: C++ Módulo I e II

Before doing anything else, we check to see whether the player ran away, and if so, we break out of the combat loop: if( runAway ) break; If the player did not run, but rather attacked and killed the monster then we execute our victory message and test to see if the player leveled up: if( monster->isDead() ) mainPlayer.victory(monster->getXPReward()); mainPlayer.levelUp(); break; If the monster did not die from the previous attack then it is the monster’s turn to attack the player: monster->attack(mainPlayer); If the attack kills the player, we execute the “game over” message and exit the game loop:

if( mainPlayer.isDead() ) mainPlayer.gameover(); done = true; break; This cycle of turn-based attacking continues until the combat loop is terminated. Finally, after the battle is over we must delete the pointer to dynamic memory which the Map::checkRandomEncounter method returned: delete monster; monster = 0;

5.4.4 Segment 4

case 2: mainPlayer.rest(); break; case 3: mainPlayer.viewStats(); break; case 4: done = true; break;

182

Page 183: C++ Módulo I e II

// End Switch Statement // End While Statement // End main function. The last several segments are trivial. If the player chose to rest then we call the Player::rest method. If the player chose to view the character statistics then we call the Player::viewStats method. If the player chose to exit then we assign true to done, which will terminate the game loop.

Note: Hopefully, this program walkthrough has provided you with an idea of how to go about breaking up a program into objects. Try your best to understand how the entire program works together, because the exercises of this chapter will ask you to make some modifications to the program.

5.5 Summary

1. In the real world, objects typically have a multitude of properties that define them (e.g., a fighter jet has a quantity describing its ammo count, its fuel, its altitude, and so on). Furthermore, many kinds of objects can perform actions (e.g., a jet can fly, fire a missile, land, and so on). Classes allow us to model real-world objects in code; that is, with classes we can define new variable types which consist of several properties (data members), and which can perform actions (member functions). Consequently, with classes, we can instantiate variables that behave like real-world objects.

2. The public and private keywords are used to enforce which parts of the class are visible to

outside code. A class’ own member functions can access all parts of the class since they are inside the class, so to speak. 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 manipulated. Hence, the class can maintain the integrity of its internal data.

3. In addition to defining a class, you can define what is called a structure. In C++, a structure is

exactly the same as a class except that it is public by default, whereas a class is private by default.

4. 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. This occurs when you realize that you may be using classes you did not write yourself—for example, if you learn DirectX, you will use classes that Microsoft wrote. In order to separate class definitions from implementation, two separate files are used: header files (.h) and implementation files (.cpp). Header files include the class definition, and implementation files contain the class implementation (i.e., method definitions). Eventually, the source code file is compiled 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

183

Page 184: C++ Módulo I e II

header file containing the class definition, and with the object or library file containing the compiled class implementation, which is linked into the project with the linker, the programmer has all the necessary components to use the class without having seen 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). This does two things: first, others cannot modify the implementation, and second your implementation (intellectual property) is protected.

5. Every class has a constructor and destructor, which are special kinds of methods. If you do not

explicitly define these methods, the compiler will generate default ones automatically. A constructor is a method that is automatically executed when an object is instantiated and a destructor is a method that is automatically executed when an object is deleted. A constructor is usually used to initialize data members to some default value or to allocate any dynamic memory the class uses or to execute any initialization code that you want executed as the object is being created. Conversely, the destructor is usually used to free any dynamic memory which the class has allocated. If it does not delete it when the object is being destroyed, it will result in a memory leak. Note that you never invoke a destructor yourself; rather, the destructor will automatically be called when an object is being deleted from memory.

6. When utilizing the object oriented programming paradigm, the first thing we ask when designing

a new program is: “What objects does the program attempt to model?” The answer to this question depends on the program. After we have decided what objects our program will use, we need to design corresponding classes, which define the properties of these kinds of objects and the actions they perform. In addition to the data and methods of a class, the class design will also need to consider the relationships between the objects of one class and the objects of other classes—for example, how they will they interact with each other.

5.6 Exercises

184

Page 185: C++ Módulo I e II

The exercises for this chapter are suggestions for modifications to the role-playing game discussed in Sections 5.3 and 5.4. They are not very specific on the exact details about what must happen—they are kept open-ended on purpose. You are free to implement these solutions any way you choose —add new data and methods to the existing classes, or create new classes.

5.6.1 Gold Modification

Add an integer data member to the Player class that keeps track of the player’s amount of gold. After each battle, generate a random gold reward that the player receives, in addition to the experience point award. It would make sense for harder enemies (ogres, Orc lords) to provide a larger gold reward then orcs and goblins. Be sure to also modify the Player::viewStats() method to display the current amount of gold the player owns.

5.6.2 Character Races

Modify the character creation process so that the user can choose a character race (e.g., dwarf, human, elf, halfling). Additionally, give statistical pros and cons to each race. For example, an elf may start the game with a higher accuracy rating than a dwarf, but lower hit points. Conversely, a dwarf may start out with more hit points than an elf, but less accuracy.

5.6.3 Leveling Up

The leveling up process we implemented in Section 5.3 does not take the player’s class into consideration. Modify the leveling up process to reflect the class of the character. For example, a “fighter” should gain more hit points than a “wizard.”

5.6.4 Magic Points

Add an integer data member to the Player class that describes the number of magic points the player currently has. Additionally, add a “max magic points” data member that describes the maximum number of magic points the player can have at his/her current level. Be sure to also modify the Player::viewStats() method to display the current amount of magic points the player has. Also, be sure to increase the amount of magic points when the character levels up—and a “wizard” should gain more hit points than a “fighter.”

185

Page 186: C++ Módulo I e II

After you have added magic points to the system, create a Spell structure and then instantiate a few types of spell objects, such as “magic missile,” “fireball,” and “shield.” You may want to implement the Spell structure like so: struct Spell std::string mName; Range mDamageRange; int mMagicPointsRequired; ;

• mName: The name of the spell (e.g., “Fireball”). • mDamageRange: The range of damage the spell inflicts. • mMagicPointsRequired: The number of magic points required to cast the spell.

Finally, add a “cast spell” option to the combat menu, which allows the player to select and cast a spell from a list of spells in his/her spell book. Be sure to verify that the player has enough magic points to cast the spell. And also be sure to deduct the magic points required to cast the spell from the player’s magic point count, after a spell is cast.

5.6.5 Random Encounters During Rest

In the current implementation, a player can rest to full hit points after every battle with no risk. Since there is no such thing as a free lunch, add random encounters when resting– maybe a 25% chance of an attack during rest.

5.6.6 A Store

Add a store to the map at some location. That is, in the main game loop do something like: if( gameMap.getPlayerXPos() == 2 && gameMap.getPlayerYPos() == 3 ) Store store; store.enter(gamePlayer); Thus, if the player lands on the point (2, 4), the player enters the store. And Store is a class you define which contains methods for displaying the store’s inventory, and for buying/selling weapons, armor, and items. Note that you may want to write a small armor structure so that you can name armor: struct Armor

186

Page 187: C++ Módulo I e II

std::string mName; int armorValue; ;

5.6.7 Items

Allow the player to buy and carry healing potions, which can be used to heal the player during combat. Moreover, allow the user to carry magic fireball potions, which can be used against enemies in battle. You can add an Item array (you define the Item class) to the Player class for storing these items, or use a std::vector to store them.

So that the player can use an item during combat, add a “use item” option to the combat menu.

5.6.8 Multiple Enemies

Modify the game so that the player can encounter several enemies at once. For example, Map::checkRandomEncounter() can return a pointer to an array of enemies. Then update Player::attack(Monster& monster) to instead take an array of monsters: Player::attack(Monster monsters[]).

187

Page 188: C++ Módulo I e II

Chapter 6

Strings and Other Topics

188

Page 189: C++ Módulo I e II

Introduction

Recall that a string is an ordered set of characters (e.g., ‘h’, ‘e’, ‘l’, ‘l’, ‘o’), which is commonly used to form words and sentences (e.g., “hello”). So far, we have been using the standard library type std::string to represent strings, but we may ask: how does std::string work internally? The first theme of this chapter is to address that question and to discover how strings can be described using only intrinsic C++ data types. The second theme of this chapter is to survey the additional functionality std::string provides via its class method interface. Finally, this chapter closes by discussing some miscellaneous C++ keywords and constructs.

Chapter Objectives

• Understand how C++ natively describes strings. • Learn some important standard library string functions. • Review st become familiar with some of its methods. • Become familiar with this pointer. • Learn about the friend and static keywords. • Discover how to create your own namespaces. • Understand what enumerated types are, how they are defined in C++, and when they would be

used.

6.1 char Strings

We know that the char keyword can represent a character, and we know a string is a set of characters. Thus, it follows that a string can be represented with an array of chars -- which is how C++ natively supports strings. A string represented as an array of chars is called a c-string. For instance, the string “Hello” can be represented as:

char str[6] = 'H', 'e', 'l', 'l', 'o', '\0'; The character ‘\0’ is a special character called the null character, and it is used to mark the end of a c-string. A c-string that ends with a null character is called a null-terminating string. The end of the c-string is marked because it then can be determined when the c-string ends. The ability to figure out when a string ends is important when navigating the elements of the string. For example, if we assume the input c-string is a null-terminating string then we can write a function that returns the number of characters in the c-string (excluding the terminating null character) like so:

d::string andthe

189

Page 190: C++ Módulo I e II

// Assume str is a pointer to a null-terminating string. int StringLength(char* str) int cnt = 0; // Loop through the array until we reach the end. while( str[cnt] != '\0' ) ++cnt; // Count character. // Return the number of characters. return cnt; Without the null character telling us when the c-string ends, we would not know when to exit the loop and how many characters to count. Using StringLength we can write a function to print out a c-string, element-by-element as shown here: int main() char str[6] = 'H', 'e', 'l', 'l', 'o', '\0'; cout << "str = "; for(int i = 0; i < StringLength(str); ++i) cout << str[i]; cout << endl; Again, we need to know how many characters are in the c-string in order to know how many times to loop. Incidentally, you will never write code to print a c-string like this because cout is overloaded to print a c-string: int main() char str[6] = 'H', 'e', 'l', 'l', 'o', '\0'; cout << "str = "; cout << str; cout << endl; Note that even cout needs str to be a null-terminating string so that it too can figure out how many characters are in the c-string. You may observe that we could keep track of the number of elements in a c-string in order to do away with StringLength and the null character completely, and we would know exactly how many characters are in the c-string. This would work, but it is not convenient to have to carry around an extra “size” variable per c-string. By using a null-terminating c-string, the string size can be deduced from the c-string itself, which is much more compact and convenient.

190

Page 191: C++ Módulo I e II

6.1 String Literals

Recall that a literal such as 3.14f is considered to be of type float, but what type is a string literal such as “hello world”? C++ will treat “hello world” as a const char[12]. The const keyword indicates that the string is a literal and a literal cannot be changed. Because all string literals are specified in the program (i.e., they are written in the source code), C++ can know about every literal string the program uses at compile time. Consequently, all string literals are allocated in a special global segment of memory when the program starts. The important property about the memory for string literals is that it exists for the life of the program. Because string literals are stored in char arrays, pointers to the first element can be acquired like so: char* pStr = "hello world"; However, it is important not to modify the elements of the string literal via pStr, because “hello world” is constant. For example, as Stroustrup points out, the following would be undefined: char* pStr = "hello world"; pStr[5] = '-'; // undefined Now consider the following: char* LiteralMsg() char* msg = "hello world"; return msg; int main() cout << "msg = " << LiteralMsg() << endl; In the function LiteralMsg we obtain a pointer to the literal “hello world.” We then return a copy of this pointer back to the caller, which in this case is main. You might suspect that this code is flawed because it returns a pointer to a “local” string literal, which would be destroyed after the function returns. However, this does not happen because memory for string literals is not allocated locally, but is allocated in a global memory pool at the start of the program. Thus, the above code is correct.

191

Page 192: C++ Módulo I e II

6.2 Escape Characters

In addition to characters you are already familiar with, there exist some special characters, called escape characters. An escape character is symbolized with a backslash \ followed by a regular character(s). For instance, the new-line character is symbolized as ‘\n’. The following table shows commonly used escape characters:

Symbol Description \n New-line character: Represents a new line. \t Tab character: Represents a tab space. \a Alert character: Represents an alert. \\ Backslash: Represents a backslash character. \' Single quote mark: \" Double quote mark

Interestingly, because the backslash \ is used to denote an escape character, you may wonder how you would actually express the character ‘\’ in a string. C++ solves this by making the backslash character an escape character itself; that is, a backslash followed by a backslash. Similarly, because the single and double quotation marks are used to denote a character literal and string literal, respectively, you may wonder how you would actually express the characters ‘'’ and ‘"’ in a string. C++ solves this by making the quotation mark characters escape characters: ‘\' ’, and ‘\"’. Program 6.1 demonstrates how the escape characters can be used. Program 6.1: Escape Characters.

#include <iostream> #include <cstring> using namespace std; int main() cout << "\tAfter Tab" << endl; cout << "\nAfter newline" << endl; cout << "\aAfter alert" << endl; cout << "\\Encloses in backslashes\\" << endl; cout << "\'Enclosed in single quotes\'" << endl; cout << "\"Enclosed in double quotes\"" << endl; Program 6.1 Output

After Tab After newline After alert \Encloses in backslashes\

192

Page 193: C++ Módulo I e II

'Enclosed in single quotes' "Enclosed in double quotes" Press any key to continue

The “alert” causes the computer to make a beeping sound. Observe how we can print a new line by outputting a new-line character. Consequently, ‘\n’ can be used as a substitute for std::endl. It is worth emphasizing that escape characters are characters; they fit in a char variable and can be put in strings as such. Even the new-line character, which seems like it occupies a whole line of characters, is just one character.

Note: ‘\n’ and std::endl are not exactly equivalent. std::endl flushes the output stream each time it is encountered, whereas ‘\n’ does not. By “flushing” the output stream, we mean output is kept buffered up so that many characters can be sent (flushed) to the hardware device at once. This is done purely for efficiency—it is more efficient to send lots of data to the device (e.g., console window output) at one time than it is to send many small batches of data. Thus, you may not want to use std::endl frequently since that would mean you are flushing small batches of data frequently, instead of one large batch of data infrequently.

6.2 C-String Functions

The previous section showed how we could represent strings as arrays of chars (c-strings). We now look at some standard library functions that operate on c-strings. To include these functions in your code, you will need to include the <cstring> header file. Note that this header file is different than the <string> header file, which is used for std::string.

6

We already talked about length and we even wrote our own function to compute the length of a null-terminating string. Not surprisingly, the standard library already provides this function for us. The function is called strlen and the function is prototyped as follows: size_t strlen(const char *string); The type size_t is usually defined as a 32-bit unsigned integer. The parameter string is a pointer to a null-terminating c-string, and the function returns the number of characters in this input string. Example

.2.1 Length

: int length = strlen("Hello, world!"); // length = 13

193

Page 194: C++ Módulo I e II

6.2.2 Equality

One of the first things we can ask about two strings is whether or not they are they equal. To answer this question the standard library provides the function strcmp (string compare): int strcmp(const char *string1, const char *string2); The parameters, string1 and string2, are pointers to null-terminating c-strings, which are to be compared. This function returns three possible types of numbers:

• Zero: If the return value is zero then it means the strings, string1 and string2, are equal. • Negative: If the return value is negative then it means string1 is less than string2. What

does “less than” mean in the context of strings? A string A is less than a string B if the difference between the first two unequal characters A[k] – B[k] is less than zero, where k is the array index of the first two unequal characters.

For example, let A = “hella” and B = “hello”; the first two unequal characters are found in element [4]—‘a’ does not equal ‘o’. Since the integer representation of ‘a’ (97) is less than the integer representation of ‘o’ (111), A is less than B.

Consider another example: let A = “abc” and B = “abcd”; the first unequal character is found in element [3] (remember the terminating null!). That is, ‘\0’ does not equal ‘d’. Because the integer representation of ‘\0’ (zero) is less than the integer representation of ‘d’ (100), A is less than B.

• Positive: If the return value is positive then it means string1 is greater than string2. What

in the context of strings? A string A is greater than a string B if the difference between the first two unequal characters A[k] – B[k] is greater than zero, where k

haracters.

For example, let A = “sun” and B = “son”; the first two unequal characters are found in element [1]—‘u’ does not equal ‘o’. Since the integer representation of ‘u’ (117) is greater than the integer representation of ‘o’ (111), A is greater than B. Consider another example: let A = “xyzw” and B = “xyz”; the first unequal character is found in element [3] (remember the terminating null!). That is, ‘w’ does not equal ‘\0’. Because the integer representation of ‘w’ (119) is greater than the integer representation of ‘\0’ (zero), A is greater than B.

Example

does “greater than” mean

is the array index of the first two unequal c

: int ret = strcmp("Hello", "Hello"); // ret = 0 (equal) ret = strcmp("abc", "abcd");

194

Page 195: C++ Módulo I e II

// ret < 0 ("abc" < "abcd") ret = strcmp("hello", "hella"); // ret > 0 ("hello" > "hella")

6.2.3 Copying

Another commonly needed function is one that copies one (source) string to another (destination) string: char *strcpy(char *strDestination, const char *strSource); The second parameter, strSource, is a pointer to a null terminating c-string, which is to be copied to the destination parameter strDestination, which is a pointer to an array of chars. The c-string to which strSource points is made constant to indicate that strcpy does not modify it. On the other hand, strDestination is not made constant because the function does modify the array to which it points. This function returns a pointer to strDestination, which is redundant because we already have a pointer to strDestination, but it does this so that the function could be passed as an argument to another function (e.g., strlen(strcpy(dest, source));). Also, it is important to realize that strDestination must point to a char array that is large enough to store strSource. Example: char dest[256]; char* source = "Hello, world!"; strcpy(dest, source); // dest = "Hello, world!" Note that strcpy does not resize the array dest; rather, dest stores “Hello, world!” at the beginning of the array, and the rest of the characters in the 256 element array are simply unused.

6.2.4 Addition

It would be convenient to be able to add two strings together. For example: “hello ” + “world” = “hello world”

This is called string concatenation (i.e., joining). The standard library provides such a function to do this, called strcat: char *strcat(char *strDestination, const char *strSource);

195

Page 196: C++ Módulo I e II

The two parameters, strDestination and strSource, are both pointers to null-terminating c-strings. The c-string to which strSource points is made constant to indicate that strcat does not modify it. On the other hand, strDestination is not made constant because the function does modify the c-string to which it points. This function appends strSource onto the back of strDestination, thereby joining them. For example, if the source string S = “world” and the destination string D = “hello”, then after the call strcat(D, S), D = “hello world”. The function returns a pointer to strDestination, which is redundant because we already have a pointer to strDestination. It does this so that the function can be passed as an argument to another function (e.g., strlen(strcat(dest, source));). It is important to realize that strDestination must be large enough to store the concatenated string (strDestination is not a string literal, so the compiler will not be automatically allocating memory for it). Returning to the preceding example, the c-string to which D pointed must have had allocated space to store the concatenated string “hello world”. To ensure that the destination string can store the concatenated string, it is common to make D an array with “max size”: const int MAX_STRING SIZE = 256; char dest[MAX_STRING_SIZE]; This means that some memory might be wasted. However, we can be more precise by using dynamic memory to obtain an array size that is exactly large enough to store the concatenated string. Example: char dest[256]; char* source = "Hello, world!"; strcpy(dest, source); // dest = "Hello, world!" strcat(dest, " And hello, C++"); // dest = "Hello, world! And hello, C++"

6.2.7 Formatting

Sometimes we will need to put variable values, such as integers and floating-point numbers, into strings. That is, we must format a numeric value so that it becomes a string (e.g., 3.14 becomes “3.14”). We can do this with the sprintf function: int sprintf( char *buffer, const char *format, [argument] ... );

196

Page 197: C++ Módulo I e II

This function returns the number of characters in the array (excluding the terminating null) to which buffer points after it has received the formatted output.

• buffer: A pointer to a char array, which will receive the formatted output.

• format: A pointer to a null-terminating c-string, which contains a string with some special formatting symbols within. These formatting symbols will be replaced with the arguments specified in the next parameter.

• argument: This is an interesting parameter. The ellipses syntax (…) indicates a variable

amount of arguments. It is here where the variables are specified whose values are to replace the formatting symbols in format. Why a variable number of arguments? Because the format string can contain any number of formatting symbols and we will need values to replace each of those symbols. Since the number of formatting symbols is unknown to the function, the function must take a variable amount of arguments. The following examples illustrate the point.

Suppose that you want to ask the user to enter a number and then put the result into a string. Again, because the number is variable (we do not know what the user will input), we cannot literally specify the number directly into the string—we must use the sprintf function: Program 6.2: The sprintf Function.

#include <iostream> #include <cstring> using namespace std; int main() char buffer[256]; int num0 = 0; cout << "Enter a number: "; cin >> num0; sprintf(buffer, "You entered %d", num0); cout << buffer << endl; The format string contains one formatting symbol called ‘%d’; this symbol will be replaced by the value stored in the argument num0. For example, if we execute this code we get the following output: Program 6.2 Output

Enter a number: 11 You entered 11 Press any key to continue

As you can see, the number entered (11) replaced the %d part of the format string.

197

Page 198: C++ Módulo I e II

Now suppose that you want the user to enter a string, a character, an integer, and a floating-point number. Because these values are variable (we do not know what the user will input), we cannot literally specify the values directly into the string—we must use the sprintf function: Program 6.3: Another example of the sprintf function.

#include <iostream> #include <cstring> using namespace std; int main() char buffer[256]; char s0[256]; cout << "Enter a string with no spaces: "; cin >> s0; int n0 = 0; cout << "Enter a number: "; cin >> n0; char c0 = '\0'; cout << "Enter a character: "; cin >> c0; float f0 = 0.0f; cout << "Enter a floating-point number: "; cin >> f0; sprintf(buffer, "s0=%s, n0=%d, c0=%c, f0=%f",s0,n0,c0,f0); cout << buffer << endl; Program 6.3 Output

Enter a string with no spaces: hello Enter a number: 7 Enter a character: F Enter a floating-point number: 3.14 s0=hello, n0=7, c0=F, f0=3.140000 Press any key to continue

This time the format string contains four formatting symbols called %s, ‘%d’, %c, and %f. These symbols are replaced by the values stored in s0, n0, c0, and f0, respectively—Figure 6.1 illustrates.

198

Page 199: C++ Módulo I e II

Figure 6.1: Argument list replace formatting symbols.

The following table summarizes the different kinds of format symbols: %s Formats a string to a string. %c Formats a character to a string. %n Formats an integer to a string. %f Formats a floating-point number to a string.

ow you argument parameter of sprintf requires a variable number of arguments—one argument is needed for each formatting symbol. In our first example, we used one formatting symbol and thus, had one argument. In our second example, we used four formatting symbols and thus, had four arguments.

6.3 std::string

We now know that at the lowest level, we can represent a string using an array of chars. So how does std::string work? std::string is actually a class that uses char arrays internally. It hides any dynamic memory that might need to be allocated behind the scenes and it provides many methods which turn out to be clearer and more convenient to use than the standard library c-string functions. In this section we survey some of the more commonly used methods.

6.3.1 Length

As with c-strings, we will often want to know how many characters are in a std::string object. To obtain the length of a std::string object we can use either the length or the size method (they are different in name only): string s = "Hello, world!"; int length = s.length(); int size = s.size(); // length = 13 = size

N can see why the

199

Page 200: C++ Módulo I e II

6.3.2 Relational Operators

One of the useful things about std::string is that it defines relational operators which provide a much more natural syntax than using strcmp.

• Equal: We can test if two strings are equal by using the equality operator (==). If two strings A and B are equal, the expression (A == B) evaluates to true, otherwise it evaluates to false.

• Not Equal: We can test if two strings are not equal by using the not equal operator (!=). If two

strings A and B are not equal, the expression (A != B) evaluates to true, otherwise it evaluates to false.

• Less Than: We can test if a string A is less than a string B by using the less than operator (<). If

A is less than B then the expression (A < B) evaluates to true, otherwise it evaluates to false.

• Greater Than: We can test if a string A is greater than a string B by using the greater than operator (>). If A is greater than B then the expression (A > B) evaluates to true, otherwise it evaluates to false.

• Less Than or Equal To: We can test if a string A is less than or equal to a string B by using the

less than or equal to operator (<=). If A is less than or equal to B then the expression (A <= B) evaluates to true, otherwise it evaluates to false.

• Greater Than or Equal To: We can test if a string A is greater than or equal to a string B by using

the greater than or equal to operator (>=). If A is greater than or equal to B then the expression (A >= B) evaluates to true, otherwise it evaluates to false.

See Section 6.2.2 for a description of how “less than” and “greater than” are defined for strings. Examples: string s0 = "Hello"; string s1 = "Hello"; string s2 = "abc"; string s3 = "abcd"; (s0 == s1); // true (s0 != s1); // false (s1 != s2); // true (s2 < s3); // true (s3 > s2); // true

200

Page 201: C++ Módulo I e II

6.3.3 Addition

We can add two strings together to make a third “sum” string using the addition operator (+): string A = "Hello, "; string B = "world!"; string C = A + B; // sum = "Hello, world!" cout << C << endl; This outputs: Hello, world! Furthermore, rather than adding two strings, A and B, to make a third string C, we can directly append a string to another string using the compound addition operator (+=): string A = "Hello, "; string B = "world!"; A += B; // A = "Hello, world!" cout << A << endl; The compound addition operator is essentially the std::string equivalent to the c-string strcat function.

6.3.4 Empty Strings

Sometimes we would like to know if a std::string object is an “empty” string (i.e., contains no characters). For example, the string “” is an empty string. To test whether a std::string object is empty we use the empty() method which returns true if the object is empty and false otherwise. Consider the following short program: Program 6.4: Empty Strings.

#include <iostream> #include <string> using namespace std; int main() string emptyString = ""; string notEmptyStr = "abcdef"; if( emptyString.empty() == true ) cout << "emptyString is empty." << endl; else cout << "emptyString is actually not empty." << endl;

201

Page 202: C++ Módulo I e II

if( notEmptyStr.empty() == true ) cout << "notEmptyStr is empty." << endl; else cout << "notEmptyStr is actually not empty." << endl; Program 6.4 Output

emptyString is empty. notEmptyStr is actually not empty. Press any key to continue

As the program output verifies, emptyString is indeed empty, and so the condition emptyString.empty() == true evaluates to true, thereby executing the corresponding if statement:

On the other hand, notEmptyStr is not empty, so the condition emptyString.empty() == true evaluates to false, therefore the corresponding else statement is executed: cout << "notEmptyStr is actually not empty." << endl;

6.3.5 Substrings

Every so often we will want to extract a smaller string contained within a larger string—we call the smaller string a substring of the larger string. To do this, we use the substr method. Consider the following example: Program 6.5: Substrings.

cout << "emptyString is empty." << endl;

#include <iostream> #include <string> using namespace std; int main() string str = "The quick brown fox jumped over the lazy dog."; string sub = str.substr(10, 9); cout << "str = " << str << endl; cout << "str.substr(10, 9) = " << sub << endl; Program 6.5 Output

str = The quick brown fox jumped over the lazy dog. str.substr(10, 9) = brown fox Press any key to continue

202

Page 203: C++ Módulo I e II

We pass two arguments to the substr method; the first specifies the starting character index of the substring to extract, and the second argument specifies the length of the substring—Figure 6.2 illustrates.

Figure 6.2: Starting index and length.

As the output verifies, the string which starts at index 10 and has a length of 9 is “brown fox”.

6.3.6 Insert

At times we may wish to insert a string somewhere into another string—it could be at the beginning, middle, or end. We can do this with the insert method, as this next example illustrates: Program 6.6: String insertion.

#include <iostream> #include <string> using namespace std; int main() string str = "The fox jumped over the lazy dog."; cout << "Before insert: " << str << endl; string strToInsert = "quick brown"; str.insert(4, strToInsert); cout << "After insert: " << str << endl; Program 6.6 Output

Before insert: The fox jumped over the lazy dog. After insert: The quick brown fox jumped over the lazy dog. Press any key to continue

We pass two arguments to the insert method; the first is the starting character index specifying where we wish to insert the string; the second argument specifies the string we are inserting. In our example, we

203

Page 204: C++ Módulo I e II

specify character [4] as the position to insert the string, which is the character space just before the word “fox.” And as the output verifies, the string “brown fox” is inserted there correctly.

6.3.7 Find

Another string operation that we may need is one that looks for a substring in another string. We can do this with the find method, which returns the index to the first character of the substring if it is found. Consider the following example: Program 6.7: String Finding.

#include <iostream> #include <string> using namespace std; int main() string str = "The quick brown fox jumped over the lazy dog."; // Get the index into the string where "jumped" starts. int index = str.find("jumped"); cout << "\"jumped\" starts at index: " << index << endl; Program 6.7 Output

"jumped" starts at index: 20 Press any key to continue

Here we are searching for “jumped” within the string, “The quick brown fox jumped over the lazy dog.” If we count characters from left-to-right, we note that the substring “jumped” starts at character [20], which is what find returned.

6.3.8 Replace

Sometimes we want to replace a substring in a string with a different substring. We can do this with the replace method. The following example illustrates:

204

Page 205: C++ Módulo I e II

Program 6.8: String replacing.

#include <iostream> #include <string> using namespace std; int main() string str = "The quick brown fox jumped over the lazy dog."; cout << "Before replace: " << str << endl; // Replace "quick brown" with "slow blue" str.replace(4, 11, "slow blue"); cout << "After replace: " << str << endl; Program 6.8 Output

Before replace: The quick brown fox jumped over the lazy dog. After replace: The slow blue fox jumped over the lazy dog. Press any key to continue

Based on the program output, it is not difficult to see how replace works; specifically, it replaced the substring, identified by the character range [4, 11], with “slow blue.” If we count the characters in str before the replace operation occurred, we find that the range [4, 11] identifies the substring “quick brown.” Based on the output, this is exactly the substring that was replaced with “slow blue.”

6.3.9 Bracket Operator

Sometimes we want to access a specific character in a std::string object. We can do this with the bracket operator ([]): string s = "Hello, world!"; char c0 = s[0]; // = 'H' char c1 = s[1]; // = 'e' char c2 = s[4]; // = 'o' char c3 = s[7]; // = 'w' char c4 = s[12]; // = '!'

6.3.10 C-String Equivalent

Some existing C++ libraries do not use std::string, but instead work with c-strings. Thus if we want to work with std::string, and still be able to use a C++ library that does not (for example,

en we need a way to convert between the two. DirectX uses c-strings) th

205

Page 206: C++ Módulo I e II

We can create a std::string object from a c-string since std::string provides a constructor and assignment operator, which both take a c-string parameter. From this c-string representation, an equivalent std::string object can be built, which describes the same string: string s = "Assignment"; string t("Constructor"); So going from a c-string to a std::string object is taken care of. To go in the other direction, that is, to get a c-string representation from a std::string, we use the

method:

string s = "Assignment"; const char* cstring = s.c_str(); // cstring = "Assignment"

6.3.11 getline

Consider the following small program: Program 6.9: Attempting to read a line if input with cin.

c_str

#include <iostream> #include <string> using namespace std; int main() string s = ""; cout << "Enter a multiple word string: "; cin >> s; // Echo the string the user entered back to the console window: cout << "You entered: " << s << endl; Program 6.9 Output

Enter a multiple word string: Hello, world! You entered: Hello, Press any key to continue

What happened? We enter in “Hello, world!” but the program only echoed the string up to a space (“Hello,”). The problem is that cin only reads up to the first whitespace character. To get around this problem we use the getline function, which can read up to a line of input. Program 6.10 rewrites the preceding program using the getline function.

206

Page 207: C++ Módulo I e II

Program 6.10: The getline Function.

#include <iostream> #include <string> using namespace std; int main() string s = ""; cout << "Enter a multiple word string: "; getline(cin, s); // Echo the string the user entered back to the console window: cout << "You entered: " << s << endl; Program 6.10 Output

Enter a multiple word string: Hello, world! You entered: Hello, world! Press any key to continue

The program now behaves as expected. The getline function takes two parameters. First, it takes a reference to a std::istream object, where istream is a class that provides an interface for inputting data from various sources. We note that cin is actually a global instance of this class that is setup to obtain input from the keyboard. The second parameter is a reference to a std::string object through which the function returns the resulting line of string. In reality, there is a third default parameter where we can specify a delimiter (a character indicating where to end the flow of input). By default, getline uses the ‘\n’ character as the delimiter. Thus, it reads up to the first new-line character. Alternatively, we could use another delimiter such as ‘a’, which would instruct getline to read data up to the first ‘a’ character encountered: getline(cin, s, 'a'); We will now turn our attention away from strings, and devote the rest of this chapter to some miscellaneous C++ topics.

207

Page 208: C++ Módulo I e II

6.4 The this Pointer

Consider the following simple class definition and its implementation: // Class definition class Person public: Person(string name, int age); string getName(); int getAge(); void talk(Person& p); private: string mName; int mAge; ; // Class implementation Person::Person(string name, int age) mName = name; age = age; string Person::getName() return mName; int Person::getAge() return mAge; void Person::talk(Person& p) cout << mName << " is talking to "; cout << p.mName << endl; We tend to think of a class as defining the properties (data) and actions (methods) that instances of this class contain. For example, if we instantiate the following objects: Person mike("Mike", 32); Person tim("Tim", 29); Person vanessa("Vanessa", 20); We say each Person object instance has its own name and age, and each Person object has its own constructor, getName(), getAge(), and talk() methods, which can access the corresponding data members.

208

Page 209: C++ Módulo I e II

In general, the data properties of each Person object are unique; that is, no two persons are exactly alike (Mike has his own name and age, Tim has his own name and age, and Vanessa has her own name and age). Therefore, each object must really have its own individual data members—so far, so good. However, what about the member functions? The implementation of a method is the same across all objects. The only difference is with the data members which that method might access. If we call mike.getName(), we expect getName() to return mike’s data member mike.mName, if we call tim.getName(), we expect getName() to return tim’s data member tim.mName, and if we call vanessa.getName(), we expect getName() to return vanessa’s data member vanessa.mName. The same is true for the other member functions as well. It does not seem practical to have a separate function for each object, all of which do the same thing, but with different data members. So what C++ actually does with member functions is to implement them like normal functions, but it creates the function with a “hidden” parameter which the compiler passes as a pointer to the member function’s calling object. For example, the methods that Person defines would really be written like so: Person::Person(Person* this, string name, int age) this->mName = name; this->age = age; string Person::getName(Person* this) return this->mName; int Person::getAge(Person* this) return this->mAge; void Person::talk(Person& p) cout << mName << " is talking to "; cout << p.mName << endl; When we invoke methods like so: Person mike("Mike", 32); mike.getName(); tim.getAge(); vanessa.talk(tim); This really evaluates to the following: Person::Person(&mike, "Mike", 32); Person::getName(&mike); Person::getAge(&tim); Person::talk(&vanessa, tim);

209

Page 210: C++ Módulo I e II

Since these are member functions, they are able to directly access the data members of the passed-in object pointer, whether the data is private or public. A class can always access its entirety within itself. The public and private keywords only indicate how external code can access the class.

By passing a hidden pointer into the member function, which points to the object which called the function, the member function is able to access the data members of the calling object. Although this is all hidden from the programmer, it is not completely hidden. C++ allows you to access the hidden pointer parameter inside your method definition with the this pointer keyword. The name this is just the name of the hidden pointer parameter passed into the member function (which you do not actually see), so that the member function can access the data members of the object which called it. For example, writing the following: Person::Person(string name, int age) mName = name; age = age; string Person::getName() return mName; int Person::getAge() return mAge; void Person::talk(Person& p) cout << mName << " is talking to "; cout << p.mName << endl; This is equivalent to writing: Person::Person(string name, int age) this->mName = name; this->age = age; string Person::getName() return this->mName; int Pe return this->mAge;

rson::getAge()

210

Page 211: C++ Módulo I e II

void Person:

cout << this->mName << " is talking to "; cout < In the latter case, the this pointer is used explicitly, and in the former case it is used implicitly.

6.5 Friends

6.5.1 Friend Functions

Sometimes we will have a non-member function that is closely related to a class. It is so closely related that we would like to give that function the ability to access the class’ private data and methods. To do this, the class must declare the function a friend. A friend of the class can then access the class’ private members directly—it does not need to go through the class’ public interface. Consider the following example:

:talk(Person& p)

< p.mName << endl;

class Point friend void PrintPoint(Point& p); public: Point(float x, float y, float z); private: float mX; float mY; float mZ; ; Point::Point(float x, float y, float z) mX = x; mY = y; mZ = z; void PrintPoint(Point& p) cout << "(" << p.mX << ", "; cout << p.mY << ", "; cout << p.mZ << ")" << endl; int main() Point p(1.0f, 2.0f, 3.0f); PrintPoint(p);

211

Page 212: C++ Módulo I e II

Even though PrintPoint is not a member function of class Point, it is still able to access the private data members of a Point object. This is because we made the function PrintPoint a friend of class Point. To make a function a friend you use the friend keyword, followed by the function prototype in the class definition. The friend statement can be written anywhere in the class definition. Note th function should be a member function of Point.

6.5.2

Friend classes extend the idea of friend functions. Instead of making a function a friend, you make all the methods of another class a friend. The syntax to make all the methods of a class friends with our class is similar to functions. The friend keyword followed by the class prototype in the class definition is used: class A...; class B friend class A; ... ; All the methods of class A would now be able to access the private components of an object of class B.

6.6 The static Keyword

Variable declarations can be prefixed with the static keyword. For example: static int staticVar;

The meaning of static depends on the context of the variable.

6.6.1 Static Variables in Functions

A static variable declared inside a function has an interesting property. It is created and initialized once and not destroyed when the function terminates; that is, its value persists across function calls. A simple

at this example is for illustrative purposes only; in reality, the print

Friend Classes

212

Page 213: C++ Módulo I e II

application of a static variable inside a function is used when you want the function to execute some special code the very first time it is called:

void Func() // Created and initialized once at program start. static bool firstTime = true; if( firstTime ) // Do work the first time the function is called. // ... // Set firstTime to false so that this first-time // work is not executed in subsequent calls. firstTime = false; The static variable firstTime will be initialized once, at the start of the program, to true. Thus, the first time the function is called it will be true and the if-statement body will execute. However, the if-statement body then sets firstTime to false. This value will persist even across all calls to this function. Therefore, firstTime will be false for all subsequent calls to this function, and the if-statement body will not execute. Thus, by using a static variable in a function, we were able to control the execution of some code the first time the function was called.

6.6.2 Static Data Members

Sometimes you will want to have a universal class variable that is not part of the objects of that class, but rather is associated with the class itself. For example, we may want an object counter, which keeps track of how many objects of some class have been instantiated. This is called reference counting. This might be useful if you want to know how many “enemy” opponents are left in a game level, for example. Clearly, each object does not need to “own” a copy of this counter since the count will be the same across all objects. So the variable should not be part of the objects; rather, because we are counting objects of a particular class, and we only need one counter, it makes sense that the counter should be “owned” by the class itself. We can express this kind of class variable by prefixing its declaration with the static keyword: class Enemy public: Enemy(); ~Enemy(); // ... private: static int NUM_ENEMY_OBJECTS; ;

213

Page 214: C++ Módulo I e II

// Special syntax to initialize a class variable. // We have the variable type, followed by the class // name, followed by the identifier name, followed by // assignment. int Enemy::NUM_ENEMY_OBJECTS = 0;

class variable, all object instances of that class can still access this universal variable (but there is only one; that is, each object does not have its “own”). To continue with our reference counting example, every time an object is created we want to increment NUM_ENEMY_OBJECTS, and every time an object is destroyed we want to decrement NUM_ENEMY_OBJECTS. In this way, NUM_ENEMY_OB store how many objects are “alive.” To do this, the following lines are added to the class constructor and destructor:

y() // The constructor was called which implies an object // is being created, so increment the count. ++NUM_ENEMY_OBJECTS; Enemy::~Enemy()

structor was called which implies an object // is being destroyed, so decrement the count. --NUM_ENEMY_OBJECTS; Recall that the constructor function is called automatically when an object is created and the destructor is called automatically when an object is destroyed. Thus, we have implemented the required functionality we seek—the reference count is incremented when objects are created and it is decremented when objects are destroyed.

6.6.3 Static Methods

We now have a static class variable NUM_ENEMY_OBJECTS, which stores the number of objects of class Enemy which currently exist. However, that variable is private and cannot be accessed by anything except instances of that class. To remedy this, a public static accessor method is created called GetEnemyObjectCount. class Enemy public: Enemy(); ~Enemy(); // ... static int GetEnemyObjectCount();

Because this is a

JECTS will

Enemy::Enem

// The de

214

Page 215: C++ Módulo I e II

private: static int NUM_ENEMY_OBJECTS; ; int Enemy::GetEnemyObjectCount() return NUM_ENEMY_OBJECTS; To access a public static class member from outside the class, the class name is used, followed by the scope resolution operator, followed by the static identifier. Continuing with the Enemy class example, we would get a copy of NUM_ENEMY_OBJECTS like so: int cnt = Enemy::GetEnemyObjectCount(); cout << "Num enemies = " << cnt << endl; Observe how we can access the static method without an object—we access it directly through the class.

Note: Becaus is not associated with any particular object instance, but rather the class itself, it does not have the hidden this pointer parameter. Because there is no this pointer, there are no data members which can be accessed. Therefore, static methods can only access static class variables.

6.7 Namespaces

In the first chapter of the analogy that as folders are used to organize groups of related files and prevent file amespaces are used to organize groups of related code and prevent code name clashes. At this point in your programming career, the possibility of name clashes may not be apparent. For instance, you might say that you will ensure that you do not give two things the same name, thereby avoiding name clashes altogether. This ideology is reasonable on a small development scale. However, once you begin large-scale developments where you are working on a team with dozens of programmers and where you are using several third party C++ libraries, the possibility of name clashes increases dramatically. For example, when developing a 3D game (using polygons for the graphics) you may need to define a class called Polygon, but the Win32 API (a C/C++ library used for developing Windows applications) already defines a function called Polygon. Thus the name Polygon becomes ambiguous. Does it refer to the function name or the class name? This is a name clash. The solution to this problem is a namespace. Since our Polygon concerns 3D graphics we might put it in a gfx3D namespace. The following code snippet shows the syntax for creating a namespace and adding a class to it:

e a static method

this text we made name clashes, n

215

Page 216: C++ Módulo I e II

namespace gfx3D class Polygon // ... ; To add something to a namespace, we must define it (for classes) or declare it (variables and functions) inside the namespace block. Note that we can add members to a particular namespace across source code files. For example, the following shows the addition of a class, function, and variable to the gfx3D namespace from three separate files: // Polygon.h namespace gfx3D class Polygon // ... ; // Area.h #include "Polygon.h" namespace gfx3D float Area(Polygon& p); // Variable.h namespace gfx3D const float PI = 3.14f; To use the classes, functions, or variables, which exist in a namespace, the namespace name must be specified, followed by the scope resolution operator, followed by the identifier. Examples: cout << gfx3D::PI << endl; gfx3D::Polygon poly; float f = gfx3D::Area(poly); The class Polygon would no longer clash with the Win32 API function called Polygon, because the class is actually referred to as gfx3D::Polygon, which is a separate name. Note that this is the same idea as when we had to prefix things in the standard library with std::. Just as we used the using clause for the std namespace, we can use it with our namespaces as well, thereby bypassing the need to specify the namespace prefix:

216

Page 217: C++ Módulo I e II

using namespace gfx3D; ... cout << PI << endl; Polygon poly; float f = Area(poly); However, doing this circumvents the whole point of the namespace to begin with. Once we add using namespace gfx3D, the Polygon class becomes ambiguous with the Win32 API function Polygon, because we no longer specify the gfx3D part which enabled the compiler to distinguish between the two. Therefore, even though we specify “using namespace std” in the example programs for convenience, in general practice, it is not a good idea to use the using clause unless you are confident that it will not cause trouble (e.g. you are sure that you will only be using the one namespace).

6.7.1 Variations of the “using” Clause

Sometimes we only want to use some objects in a namespace. For example, instead of “using” the entire std namespace, we can specify only certain objects in it. Consider the following trivial program: #include <iostream> using std::cout; // using cout using std::endl; // using end int main() cout << "Hello, world!" << endl; The only two standard objects we use are std::cout and std::endl. Thus we do not need to “use” the entire std namespace. Consequently, we specify with the using clause that we will only be using these two objects: using std::cout; // using cout using std::endl; // using end

217

Page 218: C++ Módulo I e II

6.8 Enumerated Types

Sometimes we will want to create a variable that can only be assigned a few select values. For example, a variable of type DAY should only be assigned Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, or Saturday—any other value would not make sense for a DAY type. Another example would be a MONTH type. Only a select pool of values can be assigned to a month. To facilitate types such as these naturally in the language, C++ supports enumerated types. An enumerated type is created with the enum keyword. Here is an example using DAY: enum DAY Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday ; To instantiate a DAY variable, we write: DAY day1; We can only assign to any DAY variable a member specified in the DAY enumerated listing; namely, Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday. day1 = Monday; DAY day2 = Friday; DAY day3 = 15; // Error: cannot convert from 'int' to 'DAY' As an implementation detail, an enumerated type is actually represented as an integer internally, and so each enumerated value can be associated with an integer: enum DAY Sunday = 0, Monday = 1, Tuesday = 2, Wednesday = 3, Thursday = 4, Friday = 5, Saturday = 6 ;

218

Page 219: C++ Módulo I e II

Consequently, you can cast an enumerated type to an integer and vice versa. However, you must be careful that the cast is valid. For example, there is no 8th day, so casting 8 to a DAY would be invalid. In the following code snippet, we initialize an array of seven DAYs to the days of the week: int main() DAY days[7]; for(int i = 0; i < 7; ++i) days[i] = (DAY)i;

6.9 Summary

1. A char variable can represent a character, so we can represent a string with an array of chars. A string represented as an array of chars is called a c-string. A c-string that ends with a null character (‘\0’) is called a null-terminating string. The end is marked as such because the ability to figure out when a string ends is important when navigating the elements of the string.

2. std::string is a class that uses char arrays internally. It hides any dynamic memory that

might need to be allocated behind the scenes and it provides many methods, which turn out to be clearer and more convenient to use than the standard library c-string functions.

3. Member functions are similar to non-member functions. The difference is that a hidden pointer

to the object which invoked the method is passed into a hidden parameter of the function. Thus, the hidden pointer parameter, which is given the name this, points to the object which invoked the method. In this way, the member function can access the data members of the object that called it via the this pointer.

4. The static keyword has several meanings, which depend upon context. A static function

variable is initialized once at the program start, persists across function calls, and is not destroyed until the program ends. A static variable declared inside class is a universal class variable that is not part of the objects of that class, but rather is associated with the class name itself. A method can be made static as well. A static method is not associated with any particular object instance, and therefore does not have a hidden this pointer parameter; rather, a static method is associated with the class name. We refer to class (static) variables and method using the scope resolution operator: className::identifier.

5. Namespaces are used to organize groups of related code and prevent code name clashes. You

can create your own namespaces using the namespace keyword. Any class definitions, function declarations or variable declarations made inside the namespace scope become contained in that namespace.

219

Page 220: C++ Módulo I e II

6. Enumerated types are used when we want to create a variable that can only be assigned a few select values. For example, a variable of type DAY should only be assigned Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, or Saturday—any other value would not make sense for a DAY type. An enumerated type can be defined with the enum keyword.

6.10 Exercises

For the following exercises you can use c-strings or std::string or a combination of both.

6.10.1 String Reverse

Write a program that does the following:

1. Ask the user to enter up to a line of text and store it in a string s. 2. Reverse the string s. 3. Output the reversed string to the console window.

Your program output should look like this: Enter a string: Hello, World! Reversed string = !dlroW ,olleH Press any key to continue

6.10.2 To-Upper

Write a program that does the following:

1. Ask the user to enter up to a line of text and store it in a string s. 2. Transform each alphabetical character in s into its uppercase form. If a character is not an

alphabetical character—do not modify it. 3. Output the uppercase string to the console window.

Your program output should look like this: Enter a string: Hello, World! Uppercase string = HELLO, WORLD! Press any key to continue

6.10.3 To-Lower

220

Page 221: C++ Módulo I e II

Write a program that does the following:

1. Ask the user to enter up to a line of text and store it in a string s. 2. Transform each alphabetical character in s into its lowercase form. If a character is not an

alphabetical character—do not modify it. 3. Output the lowercase string to the console window.

Your program output should look like this: Enter a string: Hello, World! Lowercase string = hello, world! Press any key to continue

6.10.4 Palindrome

Dictionary.com defines a palindrome as follows: “A word, phrase, verse, or sentence that reads the same backward or forward. For example: A man, a plan, a canal, Panama!” For our purposes, we will generalize and say that a palindrome can be any string that reads the same backwards or forwards and does not have to form a real word or sentence. Thus, some simpler examples may be: “abcdedcba” “C++C” “ProgrammingnimmargorP” Write a program that does the following:

1. Asks the user to enter up to a line of text and store it in a string s. 2. Tests if the string s is a palindrome. 3. If s is a palindrome then output “s is a palindrome” else output “s is not a palindrome.”

Your program output should look like this: Enter a string: Hello, World! Hello, World! is not a palindrome Press any key to continue Another sample out Enter a string: abcdedcba abcdedcba is a palindrome Press any key to continue

put:

221

Page 222: C++ Módulo I e II

Chapter 7

Operator Overloading

222

Page 223: C++ Módulo I e II

Introduction

In the previous chapter, we saw that we could access a character in a std::string object using the bracket operator ([]). Moreover, we also saw that we could add two std::strings together using the addition operator (+) and that we could use the relational operators (==, !=, <, etc) with std::string objects as well. So it appears that we can use (some) C++ operators with std::string. We may assume that perhaps these operators are defined for every class. However, a quick test verifies that this is not the case: class Fraction public: Fraction(); Fraction(float num, float den); float mNumerator; float mDenominator; ; Fraction::Fraction() mNumerator = 0.0f; mDenominator = 1.0f; Fraction::Fraction(float num, float den) mNumerator = num; mDenominator = den; int main() Fraction f(1.0f, 2.0f); Fraction g(3.0f, 4.0f); Fraction p = f * g; bool b = f > g; The above code yields the errors: C2676: binary '*' : 'Fraction' does not define this operator or a conversion to a type acceptable to the predefined operator C2676: binary '>' : 'Fraction' does not define this operator or a conversion to a type acceptable to the predefined operator. Why can std::string use the C++ operators but we cannot? We actually can, but the functionality is not available by default. We have to define (or overload) these C++ operators in our class definitions. These overloaded operators are defined similarly to regular class methods, and they specify what the operator does in the context of the particular class in which it is being overloaded. For example, what

223

Page 224: C++ Módulo I e II

does it mean to multiply two Fractions? We know from basic math that to multiply two fractions we multiply the numerators and the denominators. Thus, we would overload the multiplication operator for Fraction and give an implementation like so: Fraction Fraction::operator *(const Fraction& rhs) Fraction P; P.mNumerator = mNumerator * rhs.mNumerator; P.mDenominator = mDenominator * rhs.mDenominator; return P; // return the fraction product. With operator overloading we can make our user-defined types behave very similarly to the C++ built-in types (e.g., float, int). Indeed, one of the primary design goals of C++ was for user-defined types to behave similarly to built-in C++ types. The rest of this chapter describes operator overloading in detail by looking at two different class examples. The first class which we build will model a mathematical vector, which is an essential tool for 3D computer graphics and 3D game programming. However, before proceeding, note that operator overloading is not recommended for every class; you should only use operator overloading if it makes the class easier and more natural to work with. Do not overload operators and implement them with non-intuitive and confusing behavior. To illustrate an extreme case: You should not overload the ‘+’ operator such that it performs a subtraction operation, as this kind of behavior would be very confusing.

Chapter Objectives

• Learn how to overload the arithmetic operators. • Discover how to overload the relational operators. • Overload the conversion operators. • Understand the difference between deep copies and shallow copies. • Find out how to overload the assignment operator and copy constructor to perform deep copies.

7.1 Vector Mathematics

In this section we discuss the mathematics of vectors, in order to understand what they are (the data) and what we can do with them (the methods). Clearly we need to know this information if we are to model a vector in C++ with a class. In 3D computer graphics programming (and many other fields for that matter), you will use a vector to model quantities that consist of a magnitude and a direction. Examples of such quantities are physical

224

Page 225: C++ Módulo I e II

forces (forces are applied in a certain direction and have a strength or magnitude associated with them), and velocities (speed and direction). Geometrically, we represent a vector as a directed line segment—Figure 7.1. The direction of the line segment describes the vector direction and the length of the line segment describes the magnitude of the vector.

Figure7.1: Geometric interpretation of a vector.

Note that vectors describe a direction and magnitude, but they say nothing about location. Therefore, we are free to choose a convenient location from which they originate. In particular, for solving problems, it is convenient to define all vectors such that their “tails” originate from the origin of the working coordinate system, as seen in Figure 7.2.

Figure 7.2: A vector with its tail fixed at the origin. Observe that by specifying the coordinates of the vector’s tail we

can control its magnitude and direction.

225

Page 226: C++ Módulo I e II

Thus we can describe a vector analytically by merely specifying the coordinates of its “head.” This motivates the following structural representation of a 3D vector: class Vector3 // Methods... // Data float mX; float mY; float mZ; ; At first glance, it is easy to confuse a vector with a point, as they both are specified via three coordinate components. However, recall that vector coordinates are interpreted differently than point coordinates; specifically, vector coordinates indicate the end point to which a directed line segment (originating from the origin) connects. Conversely, point coordinates specify a location in space and say nothing about directions and/or magnitudes. What follows is a description of important vector operations. For now, we do not need to worry about the detailed understanding of this math or why it is this way; rather, our goal is simply to understand the vector operation descriptions well enough to implement C++ methods which perform these operations.

Note: The Game Mathematics and Graphics Programming with DirectX 9 Part I courses at Game Institute explain vectors in detail.

Throughout this discussion we restrict ourselves to 3D vectors. Let zyx uuuu ,,=

r and

zyx vvvv ,,=r

be any vectors in 3-space, and let 3,2,1=pr and 1,3,5 −=qr . Vector Equality: Two vectors are equal if and only if their corresponding components are equal. That is, vu rr

= if and only if and Vector Addition

,, yyxx vuvu == zz vu = .

: The sum of two vectors is found by adding corresponding components:

zzyyxxzyxzyx vuvuvuvvvuuuvu +++=+=+ ,,,,,,rr.

Example: 4,1,613,32,511,3,53,2,1 −=+−+=−+=+ qp rr .

226

Page 227: C++ Módulo I e II

Geometrically, we add two vectors vu rr+ by parallel translating vr so that its tail is coincident with the

head of ur and then the sum vu rr+ is the vector that originates at the tail of ur and terminates at the head

of vr . Figure 7.3 illustrates.

Figure 7.3: Vector addition. We translate vr so that its tail coincides with ur . Then the sum vu rr

+ is the vector from the tail of ur to the head of translated vr .

A key observation to note regarding parallel translation of a vector is that the length and direction of the vector is preserved; that is, translating a vector parallel to itself does not change its properties and therefore it is legal to parallel transport them around for visualization. Vector Subtraction: The difference between two vectors is found by subtracting corresponding components:

zzyyxxzyxzyx vuvuvuvvvuuuvu −−−=−=− ,,,,,,rr.

Example: ( ) 2,5,413,32,511,3,53,2,1 −=−−−−=−−=− qp rr . Geometrically, we can view the difference vu rr

− as the vector that originates from the head of vr and terminates at the head of ur . Figure 7.4 illustrates.

227

Page 228: C++ Módulo I e II

Figure 7.4: Vector subtraction. We view vector subtraction as the sum ( )vuvu rrrr

−+=− . So first we negate vr and

then translate vr− so that its tail coincides with ur . Then ( )vu rr−+ is the vector originating from the tail of ur and

terminating at the head of vr− .

Observe that subtraction can be viewed as an addition; that is, ( )vuvu rrrr

−+=− , where negating a vector flips its direction. Scalar Multiplication: A vector can be multiplied by a scalar, which modifies the magnitude of the vector but not its direction. To multiply a vector by a scalar we multiply each vector component by the scalar: 321321 ,,,, kvkvkvvvvkvk ==

r Example: ( ) ( ) ( ) 9,6,333,23,133,2,133 ===pr As the name implies, scalar multiplication scales the length of a vector. Figure 7.5 shows some examples.

228

Page 229: C++ Módulo I e II

Figure 7.5: Scalar multiplication. Multiplying a vector by a scalar changes the magnitude of the vector. A negative

scalar flips the direction.

Vector Magnitude: We use a double vertical bar notation to denote the magnitude of a vector; for example, the magnitude of the vector vr is denoted as vr . The magnitude of a vector is found by computing the distance from the tail of a vector to its head:

23

22

21 vvvv ++=

r Example: 14321 222 =++=pr Geometrically, the magnitude of a vector is its length—see Figure 7.6.

Figure 7.6: Vector magnitude. The magnitude of a vector is its length.

Normalizing a Vector:

229

Page 230: C++ Módulo I e II

Normalizing a vector makes its length equal to 1.0. We call this a unit vector and denote it by putting a “hat” on it (e.g., ). We normalize a vector by scalar multiplying the vector by the reciprocal of its magnitude:

vr

vvv rrr=ˆ

Example: 143142,1413,2,1141ˆ === ppp rrr .

The Dot Product: The dot product of two vectors is the sum of the products of corresponding components: zzyyxxzyxzyx vuvuvuvvvuuuvu ++=⋅=⋅ ,,,,rr

It can be proved that θcosvuvu ⋅=⋅

rr , where θ is the angle between ur and vr . Consequently, a dot product can be useful for finding the angle between two vectors. Example: ( ) ( ) ( ) 23651332511,3,53,2,1 =+−=+−+=−⋅=⋅ qp rr Observe that this vector product returns a scalar—not a vector. The dot product of a vector vr with a unit vector nr evaluates to the magnitude of the projection of vr onto , as Figure 7.7 shows.

nr

230

Page 231: C++ Módulo I e II

Figure 7.7: The dot product of a vector vr with a unit vector nr evaluates to the magnitude of the projection of vr onto

. We can get the actual projected vector nr nvr by scaling nr by that magnitude.

Given the magnitude of the projection of vr onto nr , the actual projected vector is: ( )nnvvn

ˆˆ rrrr⋅= , which

makes he projection, andsense: nv ⋅ returns the magnitude of trr nr is the unit vector along which the projection lies. Therefore, to get the projected vector we sim ly scale the unit vector p nr by the magnitude of the projection. Sometimes we will want the vector ⊥vr perpendicular to the projected vector (Figure 7.8). Using our geometric interpretation of vector subtraction we see that

nvr

nvvv rrr−=⊥ . But this then implies ⊥+= vvv n

rrr , which means a vector can be written as a sum of its perpendicular components.

Figure 7.8: Finding the vector perpendicular to the vector projected on nr .

231

Page 232: C++ Módulo I e II

7.2 A Vector Class

Our goal now is to design a class that represents a 3D vector. We know how to describe a 3D vector (with an ordered triplet of coordinates) and we also know what operators are defined for vectors (from the preceding section); that is, what things we can do with vectors (methods). Based on this, we define the following class: // Vector3.h #ifndef VECTOR3_H #define VECTOR3_H #include <iostream> class Vector3 public: Vector3(); Vector3(float coords[3]); Vector3(float x, float y, float z); bool equals(const Vector3& rhs); Vector3 add(const Vector3& rhs); Vector3 sub(const Vector3& rhs); Vector3 mul(float scalar); float length(); void normalize(); float dot(const Vector3& rhs); float* toFloatArray(); void print(); void input(); float mX; float mY; float mZ; ; #endif // VECTOR3_H

Note: Why do we keep the data public even though the general rule is that data should always be private? There are two reasons. The first is practical: typically, vector components need to be accessed quite frequently by outside code, so it would be cumbersome to have to go through accessor functions. Second, there is no real data to protect; that is, a vector can take on any value for its components, so there is nothing we would want to restrict.

The data members are obvious—a coordinate value for each axis, which thereby describes a 3D vector. The methods are equally obvious—most come straight from our discussion of vector operations from the

232

Page 233: C++ Módulo I e II

previous section. But how should we implement these methods? Let us now take a look at the implementation of these methods one-by-one.

7.2.1 Constructors

We provide three constructors. The first one, with no parameters, creates a default vector, which we define to be the null vector. The null vector is defined to have zero for all of its components. The second constructor constructs a vector based on a three-element input array. Element [0] will contain the x-component; element [1] will contain the y-component; and element [2] will contain the z-component. The last constructor directly constructs a vector out of the three passed-in components. The implementations for these constructors are trivial: Vector3::Vector3() mX = 0.0f; mY = 0.0f; mZ = 0.0f; Vector3::Vector3(float coords[3]) mX = coords[0]; mY = coords[1]; mZ = coords[2]; Vector3::Vector3(float x, float y, float z) mX = x; mY = y; mZ = z;

7.2.2 Equality

The next m ethod returns true if the method which calls vector (this vector) is equal to the vector passed into the parameter; otherwise, it returns false. Recall that two vectors are equal if and only if their corresponding components are equal. bool Vector3::equals(const Vector3& rhs) omponents are equal. return mX == rhs.mX && mY == rhs.mY && mZ == rhs.mZ;

ethod we specified was the equals method. The equals m

// Return true if the corresponding c

233

Page 234: C++ Módulo I e II

7.2.3 Addition and Subtraction

We next implement two methods to perform vector addition and subtraction. Recall that we add two vectors by adding corresponding components, and that we subtract two vectors by subtracting corresponding components. The following implementations do exactly that, and return the sum or differe Vector3 Vector3::add(const Vector3& rhs) Vector3 sum; sum.mX = mX + rhs.mX; sum.mY = mY + rhs.mY; return sum; Vector3 Vector3::sub(const Vector3& rhs) dif.mY = mY - rhs.mY; dif.mZ = mZ - rhs.mZ; return dif;

7.2.4 Scalar Multiplication

After the subtraction method we define the mul method, which multiplies a scalar by this vector (the vector object that invoked the method), and returns the resulting vector. Again, it is straightforward to translate the mathematical computations described in Section 7.1 into code. To refresh your memory, to multiply a vector with a scalar we simply multiply each vector component with the scalar: Vector3 Vector3::mul(float scalar) Vector3 p; p.mX = mX * scalar; p.mY = mY * scalar; p.mZ = mZ * scalar; return p;

nce.

sum.mZ = mZ + rhs.mZ;

Vector3 dif; dif.mX = mX - rhs.mX;

234

Page 235: C++ Módulo I e II

7.2.5 Length

The length method is responsible for returning the length (or magnitude) of the calling vector.

Translating the mathematic formula 23

22

21 vvvv ++=

r into code we have: float Vector3::length() return sqrtf(mX*mX + mY*mY + mZ*mZ);

7.2.6 Normalization

Writing a method to normalize the calling vector is as equally easy. Recall that normalizing a vector makes its length equal to 1.0, and that we normalize a vector by scalar multiplying the vector by the reciprocal of its magnitude: vvv rrr

=ˆ Translating this math into code yields: void Vector3::normalize() // Get 'this' vector's length. float len = length(); // Divide each component by the length. mX /= len; mY /= len; mZ /= len;

7.2.7 The Dot Product

The last method, which is mathematical in nature, implements the dot product. Translating the following mathematical formula results in the code seen below:

zzyyxxzyxzyx vuvuvuvvvuuuvu ++=⋅=⋅ ,,,,rr

float Vector3::dot(const Vector3& rhs) float dotP = mX*rhs.mX + mY*rhs.mY + mZ*rhs.mZ; return dotP;

235

Page 236: C++ Módulo I e II

7.2.8 Conversion to float Array

The conversion method toFloatArray does not correspond to a mathematical vector operation. Rather, it returns a pointer to the three-element float representation of the calling object (this). Why would we ever want to convert our Vector3 representation to a three-element float array representation? A good example might be if we were using the OpenGL 3D rendering library. This library has no idea about Vector3, and instead expects vector parameters to be passed in using a three-element float array representation. Providing a method to convert our Vector3 object to a three-element float array would allow us to use the Vector3 class seamlessly with OpenGL. The im float* Vector3::toFloatArray() return &mX; This code returns the address of the first component of this vector. However, we must remember that the memory of class objects is contiguous, just like we saw with arrays. float mX; float mY; float mZ; The memory of mY comes directly after mX, and the memory for mZ comes directly after mY. The above memory layout is equivalent to: float v[3]; Thus, by getting a pointer to mX, we are implicitly getting a pointer to the first element in a three-element array, which represents the vector components. We can now access the x-, y-, and z-components using the array bracket operator: Vector3 w(-5.0f, 2.0f, 0.0f); float* wArray = w.toFloatArray(); // wArray[0] == w.x // wArray[1] == w.y // wArray[2] == w.z

plementation of this function might not be obvious.

== -5.0f == 2.0f == 0.0f

236

Page 237: C++ Módulo I e II

7.2.9 Printing

The print method is responsible for displaying the calling vector to the console window: void Vector3::print() cout << "<" << mX << ", " << mY << ", " << mZ << "> \n";

7.2.10 Inputting

Finally, the last method is used to initialize a vector based on user input from the keyboard. In other words, it prompts the user to enter in the components of a vector one-by-one. void Vector3::input() cout << "Enter x: "; cin >> mX; cout << "Enter y: "; cin >> mY; cout << "Enter z: "; cin >> mZ;

7.2.11 Example: Vector3 in Action

Let us now look at a driver program, which uses our Vector3 class. Program 7.1: Using the Vector3 class.

// main.cpp #include "Vector3.h" #include <iostream> using namespace std; int main() // Part 1: Construct three vectors. float coords[3] = 1.0f, 2.0f, 3.0f; Vector3 u; Vector3 v(coords); Vector3 w(-5.0f, 2.0f, 0.0f); // Part 2: Print the three vectors. cout << "u = "; u.print();

237

Page 238: C++ Módulo I e II

cout << "v = "; v.print(); cout << "w = "; w.print(); cout << endl; // Part3: u = v + w u = v.add(w); cout << "v.add(w) = "; u.print(); cout << endl; // Part 4: v = v / ||v|| v.normalize(); cout << "unit v = "; v.print(); cout << "v.length() = " << v.length() << endl; cout << endl; // Part 5: dotP = u * w float dotP = u.dot(w); cout << "u.dot(w) = " << dotP; // Part 6: Convert to array representation. float* vArray = v.toFloatArray(); // Print out each element and verify it matches the // components of v. cout << "[0] = " << vArray[0] << ", " "[1] = " << vArray[1] << ", " "[2] = " << vArray[2] << endl; cout << endl; // Part 7: Create a new vector and have user specify its // components, then print the vector. cout << "Input vector..." << endl; Vector3 m; m.input(); cout << "m = "; m.print(); Program 7.1 Output

u = <0, 0, 0> v = <1, 2, 3> w = <-5, 2, 0> v.add(w) = <-4, 4, 3> unit v = <0.267261, 0.534522, 0.801784> v.length() = 1 u.dot(w) = 28[0] = 0.267261, [1] = 0.534522, [2] = 0.801784

238

Page 239: C++ Módulo I e II

Input vector... Enter x: 9 Enter y: 8 Enter z: 7 m = <9, 8, 7> Press any key to continue

The code in Program 7.1 is pretty straightforward, so we will only briefly summarize it. In Part 1, we have: float coords[3] = 1.0f, 2.0f, 3.0f; Vector3 u; Vector3 v(coords); Vector3 w(-5.0f, 2.0f, 0.0f); Here, we construct three different vectors using the different constructors we have defined. The vector u takes no parameters and is constructed with the default constructor. The second vector v uses the array constructor; that is, we pass a three-element array where element [0] specifies the x-component, element [1] specifies the y-component, and element [2] specifies the z-component. Lastly, the vector w is constructed using the constructor in which we can directly specify the x-, y-, and z-components. Part 2 of the code simply prints each vector to the console window. In this way, we can check the program output to verify that the vectors were indeed constructed with the values we specified. Part 3 performs an addition operati . u = v.add(w); cout << "v.add(w) = "; u.princout << endl; In particular, Part 3 computes u = v + w. Observe how the method caller v is the “left hand side” of the addition operation, and the argument w is the “right hand side” of the addition operation. Also, notice that the u. The first key statement in Part 4 is the following: v.normalize(); This statement simply makes the vector v a unit vector (length equal to one). The second key statement in Part 4 is: cout << "v.length() = " << v.length() << endl; Here we call the length function for v, which will return the length of v. Because v was just normalized, the length should be equal to 1. A quick check at the resulting output confirms that the length is indeed one.

on, and then prints the sum

t();

add method returns the sum, which we store in

239

Page 240: C++ Módulo I e II

Part 5 performs a dot product: float dotP = u.dot(w); In words, this code reads: Take the dot product of u and w and return the result in dotP, or in mathematical symbols, wudotP rr

⋅= . As with addition, observe how the method caller u is the “left hand side” of the dot product operation, and the argument w is the “right hand side” of the dot product operation. In Part 6 we obtain a float pointer to the first element (component) of the vector v. With this pointer, which is essentially a pointer to a three-element array, we can access all three components of v using the subscript operator. float* vArray = v.toFloatArray(); // Print out each element and verify it matches the // components of v. cout << "[0] = " << vArray[0] << ", " "[1] = " << vArray[1] << ", " "[2] = " << vArray[2] << endl; cout << endl; Finally, Part 7 creates a new vector and prompts the user to specify the components via the input method. Based on the program’s output, we can see the program echoed the values we input, thereby confirming that the input method initialized the components correctly. cout << "Input vector..." << endl; Vector3 m; m.input(); cout << "m = "; m.print();

7.3 Overloading Arithmetic Operators

In the previous section we defined and implemented a Vector3 class. However, the class interface is unnatural. Since a Vector3 object is mathematical in nature and supports mathematical operators, it would be useful if it were possible to add, subtract and multiply vectors using the natural syntax: u = v + w; // Addition v = w - u; // Subtraction w = v * 10.0f; // Scalar multiplication float dotP = u * w; // Dot product

240

Page 241: C++ Módulo I e II

This would be used instead of the currently exposed syntax: u = v.add(w); v = w.sub(u); w = v.float dotP = u.dot(w); This is where operator overloading comes in. Instead of defining method names for Vector3, we will overload C++ operators for Vector3 objects to perform the desired function. For example, using the + operator will be functionally equivalent to using the add method. In this way, we will be able to perform all of our vector operations using a natural mathematical syntax.

7.3.1 Operator Overloading Syntax

Overloading an operator is simple. It is just like defining a method, except that instead of using a method name, the operator keyword is used followed by the operator symbol which we are overloading as the method name. For example, we can overload the + operator like so: Vector We treat the name “operator +” as the method name. When we implement the overloaded operator, we just implement it as we would a regular method with name “operator +”: Vector3 Vector3::operator+(const Vector3& rhs) Vector3 sum;

sum.mZ = return sum; This is quite convenient because we want our overloaded + operator to be functionally equivalent to the add function. We can simply replace add with operator+. Now that we overloaded the + operator between two vectors we can now perform vector addition with this syntax: u = v + w;

mul(10.0f);

3 operator+(const Vector3& rhs);

sum.mX = mX + rhs.mX; sum.mY = mY + rhs.mY;

mZ + rhs.mZ;

241

Page 242: C++ Módulo I e II

7.3.2 Overloading the Other Arithmetic Operators

Overloading the other mathematical operators is equally easy, especially since we already have the desired functionality already written. We already wrote the code which subtracts two vectors, multiplies a scalar and a vector, and takes a dot product. Thus, all we have to do is replace the method “word” names sub, mul, and dot, with the operator symbol equivalent: - and *: Vector3 operator-(const Vector3& rhs); Vector3 operator*(float scalar); float operator*(const Vector3& rhs);

Note: Just as we ca sing a different function signature, we can overload operators hus we can overload the * operator twice, since we use a different signature. The first, which takes a scalar argument, performs a scalar multiplication. The second version, which takes a Vector3 argument, performs a dot product.

The implementations to these operator methods are exactly the same as we had before: Vector3 Vector3::operator-(const Vector3& rhs) Vector3 dif; dif.mX = mX - rhs.mX; dif.mY = mY - rhs.mY; dif.mZ = mZ - rhs.mZ; return dif;

ar) Vector3 p; p.mX = mX * scalar;

return p; float Vector3::operator*(const Vector3& rhs) float dotP = mX*rhs.mX + mY*rhs.mY + mZ*rhs.mZ; return dotP;

n overload function names several times useveral times using different signatures. T

Vector3 Vector3::operator*(float scal

p.mY = mY * scalar; p.mZ = mZ * scalar;

242

Page 243: C++ Módulo I e II

7.3.3 Example using our Overloaded Operators

To show off our new overloaded operators, let us rewrite parts of Program 7.1: Program 7.2: Overloaded Arithmetic Operators.

// main.cpp #include "Vector3.h" #include <iostream> using namespace std; int main() // Part 1: Construct three vectors and print. float coords[3] = 1.0f, 2.0f, 3.0f; Vector3 u; Vector3 v(coords); Vector3 w(-5.0f, 2.0f, 0.0f); cout << "u = "; u.print(); cout << "v = "; v.print(); cout << "w = "; w.print(); cout << endl; // Part 2: u = v + w u = v + w; cout << "u = v + w = "; u.print(); cout << endl; // Part 3: subtraction v = w - u; cout << "v = w - u = "; v.print(); cout << endl; // Part 4: w = v * 10.0f w = v * 10.0f; cout << "w = v * 10.0f = "; w.print(); cout << endl; // Part 5: dotP = u * w float dotP = u * w; cout << "dotP = u * w = " << dotP; cout << endl;

243

Page 244: C++ Módulo I e II

Program 7.2 Output

u = <0, 0, 0> v = <1, 2, 3> w = <-5, 2, 0> u = v + w = <-4, 4, 3> v = w - u = <1, 2, 3> w = v * 10.0f = <10, 20, 30> dotP = u * w = 130 Press any key to continue

There is not much to discuss here. We have omitted some parts of Program 7.1 and added some new parts. The key point of our new version is how we perform arithmetic operations using the C++ arithmetic symbols +, -, and *, instead of the word name add, sub, mul, and dot.

Note 1: We did not overload the division operator (/) in our Vector3 class because we did not need it. You could overload it and define a meaning to it in your classes. The general syntax would be: ReturnType ClassName::operator/(type rhs)

Note 2: We have overloaded the * operator such that we can write v * 10.0f, for example. However, equally correct would be 10.0f * v, but we cannot write this as is because our member function version is setup such that the Vector3 object is the left hand operand and the float scalar is the right hand operand: Vector3 Vector3::operator*(float scalar) The trick is to make a global operator* where the float scalar is the first parameter and the Vector3 object the second parameter: Vector3 operator*(float scalar, Vector3& v); In this way, you can maintain an elegant symmetry, and write, 10.0f * v , as well.

7.4 Overloading Relational Operators

Thus far, for our Vector3 class, we have overloaded the arithmetic operators and have achieved pleasant results. However, what about relational type operators? At present, to determine if two vectors are equal we write: if( u.equals(v) ) // equal else // not equal

244

Page 245: C++ Módulo I e II

We do this using the Vector3::equals method, which, for reference, is implemented like so: bool Vector3::equals(const Vector3& rhs) // Return true if the corresponding components are equal. return mX == rhs.mX && mY == rhs.mY && mZ == rhs.mZ; However, our goal is to make our user-defined types behave more like the built-in C++ types where it makes sense to do so. Therefore, we ask if it is possible to also overload the relational operators (==,

). To be sure, we would not be spending time on a section called “overloading relation not possible!. So indeed we can, and in particular, for Vector3, we are interested

in overloading the equality operator (==) and the not equals operator (!=). Overloading the relational operators is essentially the same as overloading the arithmetic operators; in both cases, we define the method as usual, but replace the method name with the operator keyword followed by the operator’s symbol: bool Vector3:: // Return true if the corresponding components are equal. return mX == rhs.mX && mY == rhs.mY && mZ == rhs.mZ; bool Vector3::operator!=(const Vector3& rhs) // Return true if any one corresponding components // are _not_ equal. return mX != rhs.mX || mY != rhs.mY || mZ != rhs.mZ; Recall from Chapter 2 that the relational operators always return bool values. The relational operators you overload should do the same to preserve consistency and intuition. With our overloaded equals and not equals operators we can write code with Vector3 objects that looks like this: if( u == v ) // equal else // not equal if( u != v ) // not equal else // equal

!=, <, >, <=, >=operators” if it were

operator==(const Vector3& rhs)

245

Page 246: C++ Módulo I e II

What about the other relational operators such as the less than (<) and greater than (>) operators? We could overload these operators (in the same way we overload the other relational operators) and say a vector u is less than a vector v if u’s length is less than v’s length. Likewise, a vector u is greater than a vector v if u’s length is greater than v’s length. However, this is not standard convention.

7.5 Overloading Conversion Operators

Recall the casting operator, which would allow us to, for example, convert a float to an int: float pi = 3.14f; int three = (int)pi; // three = 3 due to truncation. The decimal part of the float is lost because an integer cannot represent it. But can we define casting operators that convert between our user-defined types and other types? Indeed we can, by overloading the casting operators for our class. In our Vector3 example, we can essentially view the method float* toFloatArray(); as a conversion of a Vector3 object to a three-element float array object (we return a pointer to the first element in the array). Our goal now is to overload an operator so that instead of calling toFloatArray to obtain the array representation, we can simply cast our Vector3 object to a float*. That is: // Convert to array representation. Vector3 v(1.0f, 2.0f, 3.0f); float* vArray = (float*)v; // Print out each element and verify it matches the // components of v. cout << "[0] = " << vArray[0] << ", " // 1.0f "[1] = " << vArray[1] << ", " // 2.0f "[2] = " << vArray[2] << endl; // 3.0f Overloading a conversion operator takes on the following syntax: operator type(); Where type is the type of object to which we wish to define a conversion. In our Vector3 example, we wish to convert to type float*. So we replace toFloatArray with our overloaded conversion operator: operator float*();

246

Page 247: C++ Módulo I e II

The implementation of our conversion operator is exactly the same as toFloatArray because they perform the same function. Vector3::operator float*() return &mX; To summarize, we have overloaded the conversion operator which converts our Vector3 to type float e can simply cast our Vector3 object to a float* like float* vArray = ( .

Note: You can define as many conversion operators as you want for a class—just make sure it makes sense for your class to convert to those other types. For example, we could define a conversion from a

an int three-element array: operator double*(); operator int*();

7.6 Overloading the Extraction and Insertion Operators

We have come a long way with our Vector3 class in making it behave very much like the built-in C++ types, but we can go even further. Recall that we can output and input the built-in types to and from the console window with cout <<, and cin >>, respectively. We might wonder whether we could make it so that we could use cout << and cin >> with our user-defined types. Predictably, we can. In particular, we need to overload the insertion operator (<<) and the extraction operator (>>), and specify how our user-defined types will be output and input with these operators. Note that these operators are also sometimes called the output and input operators, respectively. However, one key difference between the insertion and extraction operators that differs from the other operators we have discussed is where we overload these operators. Previously, we always made the operators member functions, but if we look at the syntax: cout << variable; We note that this is translated to cout.operator<<(variable). In other words, cout is the object invoking the operator <<.

Note: For reference, cout and cin are defined in the standard namespace like so:

* so that instead of calling toFloatArray to obtain the array representation, wso:

float*)v;

Vector3 object to a double three-element array or

247

Page 248: C++ Módulo I e II

extern ostream cout; extern istream cin; That is, they are object instances of class ostream and istream.

Obviously, the writers of ostream (the class of which cout is an instance) and of istream (the class of which cin is an instance) did not know about our user-defined class Vector3; that is, there is no method: std::ostream& std::ostream::operator<<(const Vector3& v); nor std::istream& std::istream::operator<<(Vector3& v); Therefore, cout and cin do not work with our objects (by default). Moreover, because we did not write the istream or ostream class, we cannot add such member functions to them. Therefore, the only way to overload << and >> so that they work with cout and cin as we would expect, is to make them n std::istream& operator>>(std::istream& is, Vector3& v); std::ostream& operator<<(std::ostream& os, const Vector3& v); The implem nput and print do: std::i tream& is, Vector3& v) cout << "Enter x: "; cin >> v.mY; cout << "Enter z: "; return is; std::ostream& operator<<(std::ostream& os, const Vector3& v) , " << v.mZ << "> \n"; return os;

Note: Both of these overloaded operators (<< & >>) access the data of a Vector3 object. This is legal since the data members of Vector3 are public in this case. However, this will not always be the case. Therefore, typically you will find the overloaded << and >> operators declared as friends (Section 6.5) of the class they operate on. For example, if Vector3’s data were private, we would write: class Vector3

on-member functions:

entations simply do the same thing we had i

stream& operator>>(std::is

cin >> v.mX; cout << "Enter y: ";

cin >> v.mZ;

cout << "<" << v.mX << ", " << v.mY << "

248

Page 249: C++ Módulo I e II

friend std::istream& operator>>(std::istream& is, Vector3& v); friend std::ostream& operator<<(std::ostream& os, const Vector3& v); ... In this way, the overloaded << and >> operators could access the private data of Vector3 without any problems.

With these overloaded operators, we can now output and input Vector3 objects to and from the console using cout and cin, respectively: Program 7.3: Using the overloaded insertion and extraction operators.

// main.cpp #include "Vector3.h" #include <iostream> using namespace std; int main() // Part 1: Construct three vectors and print. float coords[3] = 1.0f, 2.0f, 3.0f; Vector3 u; Vector3 v(coords); Vector3 w(-5.0f, 2.0f, 0.0f); cout << "u = "; cout << u; cout << "v = "; cout << v; cout << "w = "; cout << w; cout << endl; cin >> u; cout << u; Program 7.3 Output

u = <0, 0, 0> v = <1, 2, 3> w = <-5, 2, 0> Enter x: 9 Enter y: 8 Enter z: 7 <9, 8, 7> Press any key to continue

249

Page 250: C++ Módulo I e II

7.7 A String Class; Overloading the Assignment Operator, Copy Constructor, and Bracket Operator

We now shift focus from the Vector3 class to a custom made String class. Before we begin, understand that this String class will be incomplete and we will use it only to further illustrate some additional operator overloading concepts—it is not meant to replace std::string. Internally, we represent the string data with a c-string. However, because the size of the string is unknown, we use dynamic memory so we can resize the string accordingly: class String public: String(); String(cons hs); String(const String& rhs); String& operator=(const String& rhs); private: char* mData; ;

7.7.1 Construction and Destruction

We would like to be able to construct a String object from a c-string so we added the constructor: String(const char* rhs); (Note: RHS stands for right-hand-side.) At first, you may suspect that all we need to do is this: String::String(const char* rhs) mData = rhs; That is, make our internal char pointer point to the c-string. However, our String objects are not merely pointers to other c-strings; rather, they are completely independent string objects, which use a c-string as a source to copy characters into their own c-string memory destination. Think about it. Suppose we did construct a String object dest using a c-string C, and we let dest.mData point to C. What happens if C changes? If C changes then dest changes as well, since internally, dest’s data representation is a pointer to C. What happens if C is destroyed? If C is destroyed then dest becomes invalid automatically, since dest’s internal data representation is a pointer to C (which was destroyed). This kind of behavior might be surprising to someone using your String class, who would expect

t char* r

250

Page 251: C++ Módulo I e II

String objects to be self-contained and not dependent upon external data. Therefore, when given a c-string in the constructor, we allocate enough memory to store a copy of this c-string, and then copy the c-string into the String’s own internal data: String::String(const char* rhs) // Get the length of the rhs c-string. int len = strlen(rhs); // Allocate enough memory to store a copy of the c-string plus // one more for null-character. mData = new char[len+1]; // Copy characters over to our own data. for(int i = 0; i < len; ++i) mData[i] = rhs[i]; // Set null-character in the last spot. mData[len] = '\0'; Because we have used dynamic memory, we must free the memory when a String object is destroyed. Recall that the destructor is a function that is called when an object gets destroyed. Hence, the destructor is the logical place to put this memory release code: String::~String() delete[] mData; mData = 0;

7.7.2 Assignment Operator

Recall that every class automatically gets a constructor, destructor, copy constructor, and assignment operator, even if it does not manually define one (the compiler will do this for you). What does the default assignment operator do? The default assignment operator will perform a simple byte-by-byte copy from the right hand operand’s memory into the left hand operand’s memory; this is called a shallow copy. Consider the following: String dest("Hello"); String source("C++"); dest = source; A shallow copy implies a direct memory copy, which means the pointer address will by copied from source to dest; that is, dest.mData = source.mData;

251

Page 252: C++ Módulo I e II

This raises two problems:

1. The memory to which dest.mData pointed was never deleted before source.mData was assigned to it. This results in a memory leak.

2. The second problem with a shallow copy in the case of our String class is the same problem

we mentioned in the previous section with the constructor: a shallow copy will simply copy pointers over. That is, dest.mData == source.mData. Consequently, dest is no longer independent (it depends on source now—source.mData, specifically) and we get all the problems we mentioned in the preceding section; namely, if source changes then dest changes, and if source is destroyed then dest contains a pointer to deleted memory.

s, we must overload the assignment operator and perform a deep copy. In a deep

py, we do what we did in the constructor: we allocate enough memory to store a copy of the rhs.mData c-string, and then we copy the c-string into the String’s own internal data: String& String::operator =(const String& rhs) // Prevent self assignment. We say two Strings qual. if( this == &rhs ) return *this; // Get the length of the rhs c-string. int len = strlen(rhs.mData); delete [] mData; to store a copy of the c-string plus // one more for null-character. mData = new char[len+1]; mData[i] = rhs.mData[i]; mData[len] = '\0';

return *this

override the copy constructor and assignment operator and perform copy pointers over and get two objects that point to one thing, thereby making the objects dependent on each other, instead of self-contained, indepen

There are a few other things happening in this assignment operator, which you should understand.

To fix these problemco

// are equal if their memory addresses are e

// Free existing memory.

// Allocate enough memory

// Copy characters over to our own data. for(int i = 0; i < len; ++i)

// Set null-character.

// Return a reference to *this object. ;

Note: As a rule, if your classes contain pointers, you should always a deep copy—otherwise you simply

dent objects.

252

Page 253: C++ Módulo I e II

• We first check for self assignment; for example: String dest("Hello"); dest = dest; For our purposes, we define two String objects to be equal if their addresses are equal. You

tring objects are equal if they represent equivalent strings (e.g., “hello” == nds on the needs of the application.

• Second, we return a reference to the left-hand-side operand; that is, *this. We do this because

it allows us to chain assignments like so: String a; String b; String c; String d("Hello"); a = b = c = d; If we returned, say, void instead of a reference to *this, we would not be able to chain assignments like this, because recall that these operator symbols really look like the following, as far as the compiler is concerned: a.operator=(b.operator=(c.operator=(d))); You have to return something from the “operator function” if you want to be able to pass the function as an argument into another function.

7.7.3 Copy Constructor

A copy constructor is a method that constructs an object via another object of the same type. If you use the default copy constructor, the object will be constructed by copying the parameter’s bytes, byte-by-byte, into the object being constructed, thereby performing a shallow copy. From the previous section, we know this default behavior is unacceptable for our String class (or virtually any class that uses dynamic memory). Because the copy constructor constructs an object via another object of the same type, which is essentially what our String assignment operator does, we can actually implement the copy constructor in terms of the assignment operator like so: String::String(const String& rhs) mData = 0; // Does not point to anything, yet. *this = rhs;

may prefer to say two S “hello”). Which option you choose depe

253

Page 254: C++ Módulo I e II

Our copy constructor now behaves correctly and does a deep copy (because we overloaded the assignment operator to do a deep copy).

7.7.4 Overloading the Bracket Operator

Recall that with a std::string, we could access the individual characters of that string with the bracket operator [] like so: string s = "Hello, world!"; char c0 = s[0]; // = 'H' char c1 = s[1]; // = 'e' char c2 = s[4]; // = 'o' char c3 = s[7]; // = 'w' char c4 = s[12]; // = '!' We can add this exact functionality to our String class by overloading the bracket operator: char& String::operator[](int i) return mData[i]; As you can see, the bracket operator takes one parameter, which is the index into some container (a char array in our case). We use this index to fetch the appropriate character out of our internal char array.

7.8 Summary

1. One of the primary design goals of C++ was for user-defined types to behave similarly to intrinsic C++ types (e.g., float, int). To facilitate this goal, operator overloading was added to C++. Operator overloading enables programmers to overload the C++ operators (e.g., ‘+’, ‘*’, ‘<’, ‘!=”, ‘<<’, ‘>>’, etc) and define new behaviors for them. Consequently, we can overload C++ operators with our user-defined types, which results in behavior similar to the intrinsic C++ types.

2. Operator overloading is not for every class. Only use operator overloading if it makes the class

easier and more natural to work with. Do not overload operators and implement them with non-intuitive and confusing behavior. To illustrate an extreme case: You would not overload the ‘+’ operator such that it performs a subtraction operation, as this kind of behavior would be very confusing.

254

Page 255: C++ Módulo I e II

3. A vector is a mathematical object used to represent magnitudes and directions. Examples of such quantities are physical forces (forces are applied in a certain direction and have a strength and magnitude associated with them), and velocities (speed and direction). Geometrically, we represent a vector as a directed line segment. The direction of the line segment describes the vector direction, and the length of the line segment describes the magnitude of the vector.

4. A shallow copy refers to copying memory byte-by-byte from one segment of memory to another.

A deep copy refers to copying dynamic memory values by allocating enough memory in the destination to store all the values contained in the source, and then copying the source values into the destination memory.

7.9 Exercises

7.9.1 Fraction Class

Define and implement a Fraction class with the following specifications:

• Contains a floating-point data member representing the numerator and contains a floating-point data member representing the denominator.

• Contains a default constructor that takes no parameters and which initializes the fraction to 10 .

• Contains a non-default constructor which takes a floating-point parameter that specifies the

numerator, and takes another floating-point point parameter that specifies the denominator.

• Overloads the arithmetic operators (+, -, *, /) to perform fraction addition, fraction subtraction, fraction multiplication, and fraction division. (You do not need to reduce the fraction, but you can if you are motivated to do so.)

• Overloads the relational operators (==, !=, <, >, <=, >=) so that, for any two Fractions A and B,

we can determine if A == B, A != B, A < B, A > B, A <= B, or A >= B.

• Overloads the float and double conversion operator so that you can convert a Fraction object to a decimal of type double or float; for example, so you can write:

Fraction frac(22, 7); // (22 / 7) float decimal = (float)frac; // convert to decimal; // decimal ≈ 3.142857. Note that the decimal representation of a fraction is computed simply as: decimal = numerator / denominator.

255

Page 256: C++ Módulo I e II

• Overloads the insertion (<<) and extraction (>>) operators so that you can output Fractions with cout and input Fractions with cin.

Be sure to test every operator of your Fraction class thoroughly to verify you have implemented the operators correctly. Also, be sure to watch out for divisions by zero.

7.9.2 Simple float Array Class

Consider the following class definition: // FloatArray.h #ifndef FLOAT_ARRAY_H #define FLOAT_ARRAY_H class FloatArray public: // Create a FloatArray with zero elements. FloatArray(); // Create a FloatArray with 'size' elements. FloatArray(int size); // Create a FloatArray from another FloatArray-- // be sure to prevent memory leaks! FloatArray(const FloatArray& rhs); // Free dynamic memory. ~FloatArray(); // Define how a FloatArray shall be assigned to // another FloatArray--be sure to prevent memory // leaks! FloatArray& operator=(const FloatArray& rhs); // Resize the FloatArray to a new size. void resize(int newSize); // Return the number of elements in the array. int size(); // Overload bracket operator so client can index // into FloatArray objects and access the elements. float& operator[](int i); private: float* mData; // Pointer to array of floats (dynamic memory). int mSize; // The number of elements in the array. ; #endif // FLOAT_ARRAY_H

256

Page 257: C++ Módulo I e II

Your task for this exercise is to provide the implementation for FloatArray. Read the comments above each method for instructions on what the method should do (i.e., how you should implement the method). (Hint: Reread Program 4.9 from Chapter 4 Section 4.5.4 for help on how to implement the resize method.) After you are finished with the implementation, write the following driver program to test it: // FloatArrayDriver.cpp #include "FloatArray.h" #include <iostream> using namespace std; void PrintFloatArray(FloatArray& fa) cout << " "; for(int i = 0; i < fa.size(); ++i) cout << fa[i] << " "; cout << "" << endl << endl; int main() FloatArray A; A.resize(4); A[0] = 1.0f; A[1] = 2.0f; A[2] = 3.0f; A[3] = 4.0f; cout << "Printing A: "; PrintFloatArray(A); FloatArray B(A); cout << "Printing B: "; PrintFloatArray(B); FloatArray C = B = A; cout << "Printing C: "; PrintFloatArray(C); A = A = A = A; cout << "Printing A: "; PrintFloatArray(A); If you implemented the FloatArray class correctly, this driver should compile and display the following expected output without errors:

257

Page 258: C++ Módulo I e II

Printing A: 1 2 3 4 Printing B: 1 2 3 4 Printing C: 1 2 3 4 Printing A: 1 2 3 4 Press any key to continue

Does your program match the expected output and execute without errors? If not, reconsider your implementation of FloatArray.

258

Page 259: C++ Módulo I e II

Chapter 8

File Input and Output

259

Page 260: C++ Módulo I e II

Introduction

Many g pect the player to complete the game in one sitting, and we can further assume that most gamers do not wish to start a game from the beginning each time they play. Therefore, it is

a game be able to be saved at certain points of progress, and then resumed from those points at a later time. In order to satisfy this requirement, we will need to be able to save/load game information to/from a place where it can persist after the program has terminated, and after the computer has been turned off. The obvious place for such storage is the hard drive. Thus, the primary theme of this chapter is saving files from our program to disk (file output) and loading files from disk into our program (file input).

Chapter Objectives

• Learn how to load and save text files to and from your program. • Learn how to load and save binary files to and from your program.

8.1 Streams

Recall that cout and cin are instances of the class ostream and istream, respectively: extern ostream cout; extern istream cin; What are ostream and istream? For starters, the ‘o’ in ostream stands for “output,” and thus

means “output stream.” Likewise, the ‘i’ in stands for “input,” and thus istream stination. It is used analogously

to a water stream. As water flows down a stream so data flows as well. In the context of cout, the stream flows data from our program to the console window for display. In the context of cin, the stream flows data from the keyboard into our program. We discuss cout and cin because file I/O works similarly. Indeed we will use streams for file I/O as well. In particular, we instantiate objects of type ofstream to create an “output file stream” and objects of type ifstream to create an “input file stream.” An ofstream object flows data from our program to a file, thereby “writing (saving) data to the file.” An ifstream object flows data from a file into our program, thereby “reading (loading) data from the file.”

ames do not ex

necessary that

ostream istreammeans “input stream.” A stream is a flow of data from a source to a de

260

Page 261: C++ Módulo I e II

8.2 Text File I/O

In this section we concern ourselves with saving and loading text files. Text files contain data written in a format readable by humans, as opposed to binary files (which we will examine later) which simply contain pure numeric data. We will use two standard classes to facilitate file I/O:

: An instance of this class contains methods that are used to write (save) data to a file.

• ifstream: An instance of this class contains methods that are used to read (load) data from a file.

m, you must include the standard library header file <fstream> (file stream) into your source code file. Also realize that these objects exist in the standard namespace.

The ov :

1. Open the file. 2. Write data to the file or read data from the file. 3.

8.2.1 Saving Data

To open a file which we will write to, we have two options: 1) We can create an ofstream object and pass a string specifying the filename we wish to write to (if this file does not exist then it will be created by the object) 2) We can create an ofstream object using the default constructor and then call the open method. Both styles are illustrated next, and one is not necessarily preferable over the other. ofstream outFile("data.txt"); Or: ofstream outFile; outFile.open("data.txt"); Interestingly, ofstream overloads the conversion operator to type bool. This conversion returns true if the stream is valid and false otherwise. For example, to verify that outFile was constructed (or opened) correctly we can write:

• ofstream

Note: In order to use ofstream and ifstrea

erall process of file I/O can be broken down into three simple steps

Close the file.

261

Page 262: C++ Módulo I e II

if( outFile ) // outFile valid--construction/open OK. else // construction/open failed. Once we have an open file, data can be “dumped” from our program into the stream. The data will flow down the stream and into the file. To do this, the insertion operator (<<) is used, just as with cout: outFile << "Hello, world!" << endl; float pi = 3.14f; outFile << "pi = " << pi << endl; This would write the following output to the file: Hello, world! pi = 3.14 The symmetry between cout and ofstream be ent now. Whereas cout sends data from our program to the console window to be displayed, ofstream sends data from our program to be written to a file for storage purposes. Finally, to close the file, the close method is called: outFile.close();

8.2.2 Loading Data

To open a file, which we will read from, we have two options:

1) We can create an ifstream object and pass a string specifying the filename from which we wish to read.

create an ifstream object using the default constructor and then call the open method.

Both styles are illustrated next, and one is not necessarily preferable over the other. ifstream inFile("data.txt"); Or: ifstreinFile.open("data.txt"); ifstream also overloads the conversion operator to type bool. This conversion returns true if the stream t inFile was constructed (or opened) correctly we can write:

comes more appar

2) We can

am inFile;

is valid and false otherwise. For example, to verify tha

262

Page 263: C++ Módulo I e II

if( inFile ) // inFile valid--construction/open OK. else Once we have an open file, data can be read from the input file stream into our program. The data will flow do n operator (>>) is used, as with cin: string data; inFile >> data; // Read a string from the file. float f; inFile >> f; // Read a float from the file. The symm cin ifstream reads data from the console window, ifstream reads data from a file. Finally, to close the file, the close method is called: inFile.close();

8.2.3 File I/O Example

Now that you are familiar with the concepts of file I/O and the types of objects and methods we will be working with, let us look at an example program. Recall the Wizard class from Chapter 5, which we present now in a modified form:

// construction/open failed.

wn the stream from the file into our program. To do this, the extractio

etry between and is more apparent now. Whereas cin

// Wiz.h #ifndef WIZARD_H #define WIZARD_H #include <fstream> #include <string> class Wizard public: Wizard(); Wizard(std::string name, int hp, int mp, int armor); // [...] other methods snipped void print(); void save(std::ofstream& outFile); void load(std::ifstream& inFile); private: std::string mName;

263

Page 264: C++ Módulo I e II

int mHitPoints; int mMagicPoints; int mArmor; ; #endif // WIZARD_H // Wiz.cpp #include "Wiz.h" #include <iostream> using namespace std; Wizard::Wizard() mName = "Default"; mHitPoints = 0; mMagicPoints = 0; mArmor = 0; Wizard::Wizard(string name, int hp, int mp, int armor) mName = name; mHitPoints = hp; mMagicPoints = mp; mArmor = armor; void Wizard::print() cout << "Name= " << mName << endl; cout << "HP= " << mHitPoints << endl; cout << "MP= " << mMagicPoints << endl; cout << "Armor= " << mArmor << endl; cout << endl; // [...] ‘save’ and ‘load’ implementations follow shortly. Specifically, we have removed methods which are of no concern to us in this chapter. Additionally, we added two methods, save and load, which do what their names imply. The save method writes a Wizard object to file, and the load method reads a Wizard object from file. Let us look at the implementation of these two methods one at a time: void Wizard::save(ofstream& outFile) outFile << "Name= " << mName << endl; outFile << "HP= " << mHitPoints << endl; outFile << "MP= " << mMagicPoints << endl; outFile << "Armor= " << mArmor << endl; outFile << endl;

264

Page 265: C++ Módulo I e II

The save method has a reference parameter to an ofstream object called outFile. outFile is the output file stream through which our data will be sent. Inside the save method, our data is “dumped” into the output file stream using the insertion operator (<<) just as we would with cout. To apply our save method, consider the following driver program: Program 8.1: Saving text data to file.

// main.cpp #include "Wiz.h" using namespace std; int main() // Create wizards with specific data. Wizard wiz0("Gandalf", 25, 100, 10); Wizard wiz1("Loki", 50, 150, 12); Wizard wiz2("Magius", 10, 75, 6); // Create a stream which will transfer the data from // our program to the specified file "wizdata.tex". ofstream outFile("wizdata.txt"); // If the file opened correctly then call save methods. if( outFile ) // Dump data into the stream. wiz0.save(outFile); wiz1.save(outFile); wiz2.save(outFile); // Done with stream--close it. outFile.close(); This program does not output anything. Rather, it creates a text file called “wizdata.txt” in the project’s working directory4. If we open that file, we find the following data was saved to it: “wizdata.txt”

Name= Gandalf HP= 25 MP= 100 Armor= 10 Name= Loki HP= 50 MP= 150 Armor= 12

4 When you specify the string to the ofstream constructor or the open method, you can specify a path as well. For example, you can specify “C:\wizdata.txt” to write the file “wizdata.txt” to the root of the C-drive.

265

Page 266: C++ Módulo I e II

Name= Magius HP= 10 MP= 75 Armor= 6

From the file output, it is concluded that the program did indeed save wiz0, wiz1, and wiz2 correctly. Now let us examine the load method: void Wizard::load(ifstream& inFile) string garbage; inFile >> garbage >> mName; // read name inFile >> garbage >> mHitPoints; // read hit points inFile >> garbage >> mMagicPoints;// read magic points inFile >> garbage >> mArmor; // read armor This method is symmetrically similar to the save method. The load method has a reference parameter to an ifstream object called inFile. inFile is the input file stream from which we will extract the file data and into our program. Inside the load method we extract the data out of the stream using the extraction operator (>>), just like we would with cin.

ion may seem odd at first (inFile >> garbage). However, note that when we saved the wizard data, we wrote out a string describing the data (see “wizdata.txt”). For example, before we wrote mName to file in save, we first wrote “Name =”. Before we can extract the actual wizard name from the file, we must first extract “Name =”. To do that, we feed it into a “garbage” variable because it is not used. To apply our load method, consider the following driver program:

Program 8.2: Loading text data from file.

The garbage extract

// main.cpp #include "Wiz.h" #include <iostream> using namespace std; int main() // Create some 'blank' wizards, which we will load // data from file into. Wizard wiz0; Wizard wiz1; Wizard wiz2; // Output the wizards before they are loaded. cout << "BEFORE LOADING..." << endl; wiz0.print();

266

Page 267: C++ Módulo I e II

wiz1.print(); wiz2.print(); // Create a stream which will transfer the data from // the specified file "wizdata.txt" to our program. ifstream inFile("wizdata.txt"); . // If the file opened correctly then call load methods if( inFile ) wiz0.load(inFile); wiz1.load(inFile); wiz2.load(inFile); // Output the wizards to show the data was loaded correctly. cout << "AFTER LOADING..." << endl; wiz0.print(); wiz1.print(); wiz2.print(); BEFORE LOADING... Name= Default HP= 0 MP= 0 Armor= 0 Name= Default HP= 0 MP= 0 Armor= 0 Name= Default HP= 0 MP= 0 Armor= 0 AFTER LOADING... Name= Gandalf HP= 25 MP= 100 Armor= 10 Name= Loki HP= 50 MP= 150 Armor= 12 Name= Magius HP= 10 MP= 75 Armor= 6 Press any key to continue

267

Page 268: C++ Módulo I e II

As the output shows, the data was successfully extracted from “wizdata.txt.”

m we did with cin; that is, cin reads up to same solution we used with cin—the getline function, which can read up to a line of input. Recall that getline’s first parameter is a re bject; however, we can still use ifstream ne ce ifstream is a kind of istream.

8.3 Binary File I/O

When working with text files, there is some overhead that occurs when converting between numeric and text types. Additionally, a text-based representation tends to consume more memory. Thus, we have two motivations for using binary-based files:

1. Binary files tend to consume less memory than equivalent text files. 2. Binary files store data in the computer’s native binary representation so no conversion needs to

be done when saving or loading the data. However, text files are convenient because a human can read them, and this makes the files easier to edit manually, and I/O bugs easier to fix. Creating file streams that work in binary instead of text is quite straightforward. An extra flag modifier, which specifies binary usage, must be passed to the file stream’s constructor or open method: ofstream outFile("pointdata.txt", ios_base::binary); ifstream inFile("pointdata.txt", ios_base::binary); Or: outFile.open("pointdata.txt", ios_base::binary); inFile.open("pointdata.txt", ios_base::binary); Where ios_base is a member of the standard namespace; that is, std::ios_base.

8.3.1 Saving Data

Because there is no necessary conversion required in binary mode, we do not need to worry about writing specific types. All that is required for transferring data in binary form is to stream raw bytes. When writing data, we specify a pointer to the first byte of the data-chunk, and the number of bytes it contains. All the bytes of the data-chunk will then be streamed directly to the file in their byte (binary)

Note: When extracting data with ifstream, we run into the same problea space character. To get around this problem we use the

ference to an istream object and not an ifstream owith getli , sin

268

Page 269: C++ Módulo I e II

form. Consequently, a large amount of bytes can be streamed if they are contiguous, like an array or class object, with one method call. To write data to a binary stream the write method is used, as the following code snippet illustrates: struct Point int x; int y; ; float fArray[4] = 1, 2, 3, 4; Point p = 0, 0; int x = 10; outFile.write((char*)fArray, sizeof(float)*4); outFile.write((char*)&p, sizeof(Point)); outFile.write((char*)&x, sizeof(int)); The first parameter is a char pointer. Recall that a char is one byte. By casting our data-chunk (be it a built-in type, a class, or array of any type) to a char pointer, we are returning the address of the first byte of the data-chunk. The second parameter is the number of bytes we are going to stream in this call, starting from the first byte pointed to by the first parameter. Typically, we use the sizeof operator to get the number of bytes of the entire data-chunk so that the whole data-chunk is streamed to the file.

8.3.2 Loading Data

Loading binary data is similar to writing it. Once we have a binary input file stream setup, we simply specify the number of bytes we wish to stream in from the file into our program. As with writing bytes, we can stream in a large amount of contiguous bytes with one function call, labeled read. float fArray[4]; Point p; int x; inFile.read((char*)fArray, sizeof(float)*4); inFile.read((char*)&p, sizeof(Point)); inFile.read((char*)&x, sizeof(int)); The read method is the inverse of the write method. The first parameter is a pointer to the first byte of the data-chunk into which we wish to read the bytes. The second parameter is the number of bytes to stream into the data-chunk specified by the first parameter.

269

Page 270: C++ Módulo I e II

8.3.3 Examples

Now that we are familiar with the basics of binary file writing and reading, let us look at a full example. Figure 8.1 shows the vertices of a unit cube.

Figure 8.1: Unit cube with vertices specified.

In the first program, we will create the vertices of a unit cube and stream the data to a binary file called “pointdata.txt.” In the second program, we will do the inverse operation and stream the point data contained in “pointdata.txt” into our program. First, we create a basic data structure to represent a point in 3D space: struct Point3 Point3(); Point3(float x, float y, float z); float mX; float mY; float mZ; ; // Implementation Point3::Point3() mX = mY = mZ = 0.0f; Point3::Point3(float x, float y, float z) mX = x; mY = y; mZ = z;

270

Page 271: C++ Módulo I e II

The first program is written as follows: Program 8.3: Saving binary data to file.

#include <fstream> #include <iostream> #include “Point.h” using namespace std; int main() // Create 8 points to define a unit cube. Point3 cube[8]; cube[0] = Point3(-1.0f, -1.0f, -1.0f); cube[1] = Point3(-1.0f, 1.0f, -1.0f); cube[2] = Point3( 1.0f, 1.0f, -1.0f); cube[3] = Point3( 1.0f, -1.0f, -1.0f); cube[4] = Point3(-1.0f, -1.0f, 1.0f); cube[5] = Point3(-1.0f, 1.0f, 1.0f); cube[6] = Point3( 1.0f, 1.0f, 1.0f); cube[7] = Point3( 1.0f, -1.0f, 1.0f); // Create a stream which will transfer the data from // our program to the specified file "pointdata.tex". // Observe how we add the binary flag modifier // ios_base::binary. ofstream outFile("pointdata.txt", ios_base::binary); // If the file opened correctly then save the data. if( outFile ) // Dump data into the stream in binary format. // That is, stream the bytes of the entire array. outFile.write((char*)cube, sizeof(Point3)*8); // Done with stream--close it. outFile.close(); This program produces no console output. However, it does create the file “pointdata.txt” and writes the eight points of the cube to that file. If you open “pointdata.txt” in a text editor, you will see what appears to be nonsense, because the data is written in binary. We now proceed to write the inverse program. Program 8.4: Loading binary data from file.

#include <fstream> #include <iostream> #include “Point.h” using namespace std;

271

Page 272: C++ Módulo I e II

int main() Point3 cube[8]; cout << "BEFORE LOADING..." << endl; for(int i = 0; i < 8; ++i) cout << "cube[" << i << "] = "; cout << "("; cout << cube[i].mX << ", "; cout << cube[i].mY << ", "; cout << cube[i].mZ << ")" << endl; // Create a stream which will transfer the data from // the specified file "pointdata.txt" to our program. // Observe how we add the binary flag modifier // ios_base::binary. ifstream inFile("pointdata.txt", ios_base::binary); // If the file opened correctly then call load methods. if( inFile ) // Stream the bytes in from the file into our // program array. inFile.read((char*)cube, sizeof(Point3)*8); // Done with stream--close it. inFile.close(); // Output the points to show the data was loaded correctly. cout << "AFTER LOADING..." << endl; for(int i = 0; i < 8; ++i) cout << "cube[" << i << "] = "; cout << "("; cout << cube[i].mX << ", "; cout << cube[i].mY << ", "; cout << cube[i].mZ << ")" << endl; BEFORE LOADING... cube[0] = (0, 0, 0) cube[1] = (0, 0, 0) cube[2] = (0, 0, 0) cube[3] = (0, 0, 0) cube[4] = (0, 0, 0) cube[5] = (0, 0, 0) cube[6] = (0, 0, 0) cube[7] = (0, 0, 0) AFTER LOADING... cube[0] = (-1, -1, -1) cube[1] = (-1, 1, -1)

272

Page 273: C++ Módulo I e II

cube[2] = (1, 1, -1) cube[3] = (1, -1, -1) cube[4] = (-1, -1, 1) cube[5] = (-1, 1, 1) cube[6] = (1, 1, 1) cube[7] = (1, -1, 1) Press any key to continue

As the output shows, the data was successfully extracted from “pointdata.txt.”

8.4 Summary

1. Use file I/O to save data files from your programs to the hard drive and to load previously saved data files from the hard drive into your programs.

2. We generally use two standard library classes for file I/O:

a. ofstream: an instance of this class contains methods that are used to write (save) data to a file

b. ifstream: An instance of this class contains methods that are used to read (load) data from a file.

To use these objects you must include <fstream> (file stream).

3. A stream is essentially the flow of data from a source to a destination. It is used analogously to a

water stream. In the context of cout, the stream flows data from our program to the console window for display. In the context of cin, the stream flows data from the keyboard into our program. Similarly, an ofstream object flows data from our program to a file, thereby “writing (saving) data to the file,” and an ifstream object flows data from a file into our program, thereby “reading (loading) data from the file.”

4. There are two different kinds of files we work with: text files and binary files. Text files are

convenient because they are readable by humans, thereby making the files easier to edit and making file I/O bugs easier to fix. Binary files are convenient because they tend to consume less

ary data is not aved. When

constructing a binary file, remember to specify the ios_base::binary flag to the second parameter of the constructor or to the open method.

5. When writing and reading to and from text files you use the insertion (<<) and extraction (>>)

operators, just as you would with cout and cin, respectively. When writing and reading to and from binary files you use the ofstream::write and ifstream::read methods, respectively.

memory than equivalent text files and they are streamed more efficiently since binconverted to a text format; rather, the raw bytes of the data are directly s

273

Page 274: C++ Módulo I e II

8.5 Exercises

8.5.1 Line Count

Write a program that prompts the user to enter in a string path directory to a text file. For example:

C:/Data/file.txt Your program must then open this text file and count how many lines the text file contains. Before terminating the program, you should output to the console window how many lines the file contains. Example output: Enter a text file: C:/Data/file.txt C:/Data/file.txt contained 478 lines. Press any key to continue

text file has, you will need a way to determine when the end of the file is reached. You can do that with the ifstream::eof (eof = end of file) method, which returns true if the end of the file has been reached, and false otherwise. So, your algorithm for this exercise will look something like: while( not end of file )

read line increment line counter

8.5.2 Rewrite

1. Rewrite Programs 8.1 and 8.2 of this chapter to use binary files instead of text files.

2. Rewrite Programs 8.3 and 8.4 of this chapter to use text files instead of binary files.

Because, in general, you do not know how many lines a

274

Page 275: C++ Módulo I e II

Chapter 9

Inheritance and Polymorphism

275

Page 276: C++ Módulo I e II

Introduction

Any modern programming language must provide mechanisms for code reuse whenever possible. The main benefits of code reuse are increased productivity and easier maintenance. Part of code reuse is functio ng code is also equally important. In programming, we generalize things for the same reason the mathematician does. The mathematician generalizes m oes not solve the same problem more than once in different forms. By solving a problem with the most general form, the solution applies to all of the specific forms ritance mechanism, we can define a generalized class, and give all the data properties and functionality of that generalized class to more specific classes. In this way, we only have to write the general “shared” code once, and we can reuse it with several specific classes. So, saves work by applying a general solution to a variety of specific problems, the programmer saves work by applying general class code to a variety of specific classes. The concept of code reuse via inheritance is the first theme of this chapter. In addition to basic generalization, we would like to work with a set of specific class objects at a general level. For example, suppose we are writing an art program and we need various specific class shapes such as Lines, Rectangles, Circles, Curves, and so on. Since these are all shapes, we would likely use inheritance and give them properties and functions from a general class Shape. By combining all the L ne Shape list (e.g., array we give ourselves the ability to work with all shapes at a higher level. For instance, we can iterate over all the shapes and have them draw themselves, without regard to the specific shape. Moreo ific shape objects up to Shape, and for them to still “know” how to draw themselves (that is, the specific shape) correctly in this general form, with the use of polymorphism.

Cha

• Understand what inheritance means in C++ and why it is a useful code construct. • Understand the syntax of polym• Learn how to create general abstract types and interfaces.

ns and classes, but generalizing and abstracti

athematical objects so that he/she d

as well. Similarly, in C++, via the inhe

whereas the mathematician

ine objects, Rectangle objects, Circle objects, and Curve objects into o),

ver, it is possible to generalize the spec

pter Objectives

orphism, how it works, and why it is useful.

276

Page 277: C++ Módulo I e II

9.1 Inheritance Basics

Inheritance allows a derived class (also called a child class or subclass) to inherit the data and methods of a base class (also called a parent class or superclass). For example, suppose we are working on a futuristic spaceship simulator game, where earthlings must fight off an enemy alien race from another galaxy. We start off designing our class as generally as possible, with the hopes of reusing its general properties and methods for more specific classes, and thereby avoiding code duplication. First we will have a class Spaceship, which is quite general, as there may be many different kinds of models of Spaceships (such as cargo ships, mother ships, fighter ships, bomber ships, and so on). At the very least, we can say a Spaceship has a model name, a position in space, a velocity specifying its speed and direction, a fuel level, and a variable to keep track of the ship damage. As far as methods go—that is, what actions a Spaceship can do—we will say all spaceships can fly and print their statistics, but we do not say anything else about them at this general level. It is not hard to imagine some additional properties and possible methods that would fit at this general level, but this is good enough for our purposes in this example. Our general Spaceship class (and implementation) now looks like this: // From Spaceship.h class Spaceship public: Spaceship(); Spaceship( const string& name, const Vector3& pos, const Vector3& vel, int fuel, int damage); void fly(); void printStats(); protected: string mName; Vector3 mPosition; Vector3 mVelocity; int mFuelLevel; int mDamage; ; //======================================================================// From Spaceship.cpp Spaceship::Spaceship() mName = "DefaultName"; mPosition = Vector3(0.0f, 0.0f, 0.0f); mVelocity = Vector3(0.0f, 0.0f, 0.0f); mFuelLevel = 100; mDamage = 0;

277

Page 278: C++ Módulo I e II

Spaceship::Spaceship(const string& name, const Vector3& pos, const Vector3& vel, int fuel, int damage) mName = name; mPosition = pos; mVelocity = vel; mFuelLevel = fuel; mDamage = damage; void Spaceship::fly() cout << "Spaceship flying" << endl; void Spaceship::printStats() // Print out ship statistics. cout << "==========================" << endl; cout << "Name = " << mName << endl; cout << "Position = " << mPosition << endl; cout << "Velocity = " << mVelocity << endl; cout << "FuelLevel = " << mFuelLevel << endl; cout << "Damage = " << mDamage << endl; Note that we do not include any specific attributes, such as weapon properties nor specific methods such as attack, because such properties and methods are specific to particular kinds of spaceships—and are not general attributes for all spaceships. Remember, we are starting off generally first.

Note: Observe the new keyword protected, which we have used in place of private. Recall that only the class itself and friends can access data members in the private area. This would prevent a derived class from accessing the data members. We do not want such a restriction with a class that is designed for the purposes of inheritance and the derivation of child classes. After all, what good is inheriting properties and methods you cannot access?. In order to achieve the same effect as private, but allow derived classes to access the data members, C++ provides the protected keyword. The result is that derived classes get access to such members, but outsiders are still restricted.

With Spaceship defining the general properties and methods of spaceships, we can define some particular kinds of spaceships which inherit the properties and methods of Spaceships—after all, these specific spaceships are kinds of spaceships:

278

Page 279: C++ Módulo I e II

// From Spaceship.h class FighterShip : public Spaceship public: FighterShip( const string& name, const Vector3& pos, const Vector3& vel, int fuel, int damage, int numMissiles); void fireLaserGun(); void fireMissile(); private: int mNumMissiles; ; class BomberShip : public Spaceship public: BomberShip( const string& name, const Vector3& pos, const Vector3& vel, int fuel, int damage, int numBombs); void dropBomb(); private: int mNumBombs; ; //======================================================================// From Spaceship.cpp FighterShip::FighterShip(const string& name, const Vector3& pos, const Vector3& vel, int fuel, int damage, int numMissiles) // Call spaceship constructor to initialize "Spaceship" part. : Spaceship(name, pos, vel, fuel, damage) // Initialize "FighterShip" part. mNumMissiles = numMissiles;

279

Page 280: C++ Módulo I e II

void FighterShip::fireLaserGun() cout << "Firing laser gun." << endl; void FighterShip::fireMissile() // Check if we have missiles left. if( mNumMissiles > 0 ) // Yes, so fire the missile. cout << "Firing missile." << endl; // Decrement our missile count. mNumMissiles--; else // Nope, no missiles left. cout << "Out of missiles." << endl; BomberShip::BomberShip(const string& name, const Vector3& pos, const Vector3& vel, int fuel, int damage, int numBombs) // Call spaceship constructor to initialize "Spaceship" part. : Spaceship(name, pos, vel, fuel, damage) // Initialize "BomberShip" part. mNumBombs = numBombs; void BomberShip::dropBomb() // Check if we have bombs left. if( mNumBombs > 0 ) // Yes, so drop the bomb. cout << "Dropping bomb." << endl; // Decrement our bomb count. mNumBombs--; else // Nope, no bombs left. cout << "Out of bombs." << endl; We only show two specific spaceships here, but you could easily define and implement a cargo ship and a mother ship, as appropriate. Note how we added specific data; that is, bombs are specific to a BomberShip, and missiles are specific to a FighterShip. In a real game we would probably need to add more data and methods, but this will suffice for illustration.

280

Page 281: C++ Módulo I e II

Observe the colon syntax that follows the class name. Specifically: : public Spaceship This is the inheritance syntax, and reads “inherits publicly from Spaceship.” So the line: class FighterShip : public Spaceship says that the class FighterShip inherits publicly from Spaceship. Also, the line: class BomberShip : public Spaceship says that the class BomberShip inherits publicly from Spaceship. We discuss what public inheritance means and how it differs from, say, private inheritance in Section 9.2.3. Another important piece of syntax worth emphasizing is where we call the parent constructor: // Call spaceship constructor to initialize "Spaceship" part. : Spaceship(name, pos, vel, fuel, damage) As we shall discuss in more detail later on, we can view a derived class as being made up of two parts: the parent class part, and the part specific to the derived class. Consequently, we can invoke the parent’s constructor to construct the parent part of the class. What is interesting here is where we call the parent constructor—we do it after the derived constructor’s parameter list and following a colon, but before the derived constructor’s body. This is called a member initialization list: ClassName::ClassName(parameter-list…) : // Member initialization list When an object is instantiated, the memory of its members is first constructed (or initialized) to something before the class constructor code is executed. We can explicitly specify how a member variable should be constructed in the member initialization list. And in particular, if we want to invoke the parent constructor to construct the parent part of the class, then we must invoke it in the member initialization list—where the parent part is being constructed. Note that we are not limited to calling parent constructors in the member initialization list. We can also specify how other variables are initialized. For example, we could rewrite the FighterShip constructor like so: FighterShip::FighterShip(const string& name, const Vector3& pos, const Vector3& vel, int fuel, int damage, int numMissiles) // Call spaceship constructor to initialize "Spaceship" part. : Spaceship(name, pos, vel, fuel, damage), mNumMissiles(numMissiles) // Initialize "FighterShip" part.

281

Page 282: C++ Módulo I e II

Here we directly construct the integer mNumMissiles with the value numMissiles, rather than make an assignment to it in the constructor body after it has been constructed. That is: mNumMissiles = numMissiles; This has performance implications, as Scott Meyers points out in Effective C++. Specifically, by using a member initialization list, we only do one operation—construction. If we do not use a member initialization list we end up doing two operations: 1) construction to a default value, and 2) an assignment in the constructor body. So by using a member initialization list, we can reduce two operations down to one. Such a reduction can become significant with large classes and with large arrays of classes.

Note: Inheritance relationships are often depicted graphically. For example, our spaceship inheritance hierarchy would be drawn as follows:

Figure 9.1: A simple graphical inheritance relationship. Now that we have some specific spaceships, let us put them to use in a small sample program. Program 9.1: Using derived classes.

// main.cpp #include <iostream> #include "Spaceship.h" using namespace std; int main() FighterShip fighter("F1", Vector3(5.0f, 6.0f, -3.0f), Vector3(1.0f, 1.0f, 0.0f), 100, 0, 10); BomberShip bomber("B1", Vector3(0.0f, 0.0f, 0.0f), Vector3(1.0f, 0.0f, -1.0f), 79, 0, 5); fighter.printStats(); bomber.printStats(); cout << endl; fighter.fly(); fighter.fireLaserGun(); fighter.fireMissile(); fighter.fireMissile(); bomber.fly();

282

Page 283: C++ Módulo I e II

bomber.dropBomb(); bomber.dropBomb(); Program 9.1 Output

========================== Name = F1 Position = <5, 6, -3> Velocity = <1, 1, 0> FuelLevel = 100 Damage = 0 ========================== Name = B1 Position = <0, 0, 0> Velocity = <1, 0, -1> FuelLevel = 79 Damage = 0 Spaceship flying Firing laser gun. Firing missile. Firing missile. Spaceship flying Dropping bomb. Dropping bomb. Press any key to continue

This is a simple program where we just instantiate a few objects and call their methods. What is of interest to us is how fighter and bomber can call the methods of Spaceship; in particular, they both call the fly and printStats methods. Also notice that printStats prints the stats of fighter and bomber, thereby showing that they inherited the data members of Spaceship. Thus we can see that they have their own copies of these data members. Again, this is because FighterShip and BomberShip inherit from Spaceship. Do you see the benefit of inheritance? If we had not used inheritance then we would have had to duplicate all the data and methods (and their implementations) contained in Spaceship for both FighterShip and BomberShip, and any other new kind of spaceship we wanted to add. However, with inheritance, all of that information and functionality is inherited by the derived classes automatically, and we do not have to duplicate it. Hopefully this gives you a more intuitive notion of inheritance and its benefits.

283

Page 284: C++ Módulo I e II

9.2 Inheritance Details

9.2.1 Repeated Inheritance

In the previous section we used inheritance for a single generation; that is, parent and child. Naturally, one might wonder whether we can create more complex relationships, such as grandparent, parent, and child. In fact, we can create inheritance hierarchies as large and as deep as we like—there is no limit imposed. Figure 9.2 shows a more complex spaceship inheritance hierarchy.

Figure 9.2: An inheritance hierarchy.

To create this hierarchy in code we write the following (with class details omitted for brevity): class Spaceship [...] ; class AlienShip : public Spaceship [...] ; class AlienFighterShip : public AlienShip [...] ; class AlienBomberShip : public AlienShip [...] ; class AlienCargoShip : public AlienShip [...] ; class AlienMotherShip : public AlienShip [...] ; class HumanShip : public Spaceship [...] ; class HumanFighterShip : public HumanShip [...] ; class HumanBomberShip : public HumanShip [...] ; class HumanCargoShip : public HumanShip [...] ; class HumanMotherShip : public HumanShip [...] ;

9.2.2 isa versus hasa

When a class contains a variable of some type T as a member variable, we say the class “has a” T. For example, the data members of Spaceship were: string mName;

284

Page 285: C++ Módulo I e II

Vector3 mPosition; Vector3 mVelocity; int mFuelLevel; int mDamage; We say a Spaceship has a string, two Vector3s, and two ints. Incidentally, when we compose a class out of other types, object oriented programmers use the term composition to denote this. That is, the class is ‘composed of’ those other types. When a class A inherits publicly from a class B, object oriented programmers say that we are modeling an “is a” relationship; that is, A is a B, but not conversely. Essentially, this is what public inheritance means—is a. For example, in our previous spaceship examples, our specific spaceships FighterShip and BomberShip inherited publicly from Spaceship. This is conceptually correct because FighterShip is a kind of Spaceship and Bombership is a kind of Spaceship. However, the reverse is not true. That is, a Spaceship is not necessarily a FighterShip and a Spaceship is not necessarily a Bombership. This is important terminology. C++ Guru Scott Meyers says this about the terminology in his book Effective C++: “[…] the single most important rule in object-oriented programming with C++ is this: public inheritance means “isa.” Commit this rule to memory.”

9.2.3 Moving Between the Base Class and Derived Class

Why is an is a relationship important? As we said, when a class A inherits publicly from a class B, we specify the relationship that A is a B. Consequently, with this relationship defined, C++ allows us to convert an A object into a B object. After all, an A object is a kind of B object. To better illustrate, let us take a moment to review. Recall that inheritance extends a class. For example, consider a class called Base: class Base public: void f(); void g(); protected: int mBaseData1; float mBaseData2; std::string mBaseData3; ; Suppose we need to create a new distinct class, called Derived, which contains all the methods and data of Base, but adds some additional data and methods specific to Derived. In other words, Derived extends Base. Instead of recopying the data and functionality from Base into Derived, we can take advantage of the C++ inheritance mechanism to do this for us:

class Derived : public Base

285

Page 286: C++ Módulo I e II

public: void h(); protected: char mDerivedData1[4]; std::vector<int> mDerivedData2; ; We note that since Derived inherits publicly from Base, it contains all the methods5 and data of Base, plus the additional methods and data specific to Derived. Moreover, because of the public inheritance we specify that Derived is a Base. Because Derived inherits the data of Base, the data layout of a Derived object consists of a Base part. Figure 9.3 illustrates:

Figure9.3: A derived object consists of a Base part.

Furthermore, because Derived is a Base (public inheritance), we can switch back and forth between the Derived object and its Base part via pointer casting: Derived* derived = new Derived(); Base* base = (Base*)derived; // upcast Derived* d2 = (Derived*)base; // downcast The upcast is considered safe and can be done implicitly as shown here: Base* base = new Derived(); // upcast done implicitly Derived* d2 = (Derived*)base; // downcast We use the term upcast when casting up the inheritance chain; that is, from a derived object to its base part. Likewise, we use the term downcast when casting down the inheritance chain; that is, from the base part to the derived object. Note that downcasting is not always safe. Remember, a Derived object is a specific kind of Base object, but not conversely. In other words, a Derived object will always

5 Excepting constructors, destructors, and the assignment operator. Obviously the constructor and destructor are not inherited since they say nothing about creating or destroying the derived object. The assignment operator is masked since every class has its own assignment operator, by default; if you do not implement one, the compiler implements a default one for you.

286

Page 287: C++ Módulo I e II

have a ssarily part of a Derived object. For example, we can write this: Base* Here pureBase is purely a Base object—it is not part of a Derived object and therefore it is illegal to downcast. If you think about it for a moment you will realize why – the memory for the Derived object data was never allocated since a Derived object was never constructed. Only the Base data exists after the new call. Using a downcasted pointer to access derived data members that were never created would result in a nasty problem indeed. The following, on the other hand, is different: Derived* derived = new Derived(); Base* base = (Base*)derived; // upcast Derived* d2 = (Derived*)base; // downcast In this case we can downcast from base to d2. The difference is that base is part of a Derived object (i.e., we first upcasted from a Derived object to base). Before we conclude this section, let us examine some useful vocabulary. Consider the following: Base* base = new Derived(); We say that the variable base has the static type Base and the dynamic type Derived. Note that this assignment is completely legal since the memory for the Derived class object has been fully allocated in the new call. The base pointer would be able to access all of the Base class components and could be safely downcast to a Derived type pointer as needed. This is legal since any downcasted Derived pointer would have an actual Derived object (with all of its memory fully intact) to work with.

9.2.4 Public versus Private Inheritance

We have discussed public inheritance as modeling an is a relationship, but it seems that if we must explicitly specify public inheritance then there must be another type of inheritance. Indeed there is, and it is called private inheritance. To specify private inheritance you just replace public with private in the inheritance syntax: class Derived : private Base Private inheritance does not mean is a, and consequently, we should not be able to upcast and downcast the inheritance hierarchy. With private inheritance, inherited members and methods automatically become private no matter their previously declared access level. Private inheritance is useful when you want to reuse code via inheritance, but do not want to make the is a relationship claim. That is, you want to prevent upcasts and downcasts, because the is a relationship does not make sense for what you are modeling. Private inheritance is a way to express that in the language. To quote Scott Meyers’s Effective C++: “If you make a class D privately inherit from a class

base part, but a Base object is not nece

pureBase = new Base();

287

Page 288: C++ Módulo I e II

B, you do so because you are interested in taking advantage of some of the code that has already been written for class B, not because there is any conceptual relationship between objects of type B and objects of type D.”

9.2.5 Method Overriding

Recall that in Program 9.1 we wrote the line: fighter.printStats(); which produced the output: ========================== Name = F1 Position = <5, 6, -3> Velocity = <1, 1, 0> FuelLevel = 100 Damage = 0 Also recall that printStats was a method inherited from Spaceship, and was implemented like so: void Spaceship::printStats() // Print out ship statistics. cout << "==========================" << endl; cout << "Name = " << mName << endl; cout << "Position = " << mPosition << endl; cout << "Velocity = " << mVelocity << endl; cout << "FuelLevel = " << mFuelLevel << endl; cout << "Damage = " << mDamage << endl; The problem here is that FighterShip has properties which are specific to it, but Spaceship being the more general class, does not know about them and cannot print them. In particular, FighterShip has the member mNumMissiles, and most likely we would want to print the number of missiles a FighterShip has remaining along with its other statistics. Thus, what we must do is override printStats for FighterShips: void FighterShip::printStats() Spaceship::printStats(); cout << "Missiles = " << mNumMissiles << endl; Luckily, we can still reuse the code from Spaceship. As you can see, we call the parent version Spaceship::printStats to print the Spaceship part of FighterShip, and then we add only the

288

Page 289: C++ Módulo I e II

new code specific to FighterShip—so we are still reusing code. We could also do something similar for BomberShip. Program 9.2 shows our new overridden method in action: Program 9.2: Overridden method.

#include <iostream> #include "Spaceship.h" using namespace std; int main() FighterShip fighter("F1", Vector3(5.0f, 6.0f, -3.0f), Vector3(1.0f, 1.0f, 0.0f), 100, 0, 10); fighter.printStats(); cout << endl; fighter.fly(); fighter.fireLaserGun(); fighter.fireMissile(); fighter.fireMissile(); fighter.printStats(); Program 9.2 Output

========================== Name = F1 Position = <5, 6, -3> Velocity = <1, 1, 0> FuelLevel = 100 Damage = 0 Missiles = 10 Spaceship flying Firing laser gun. Firing missile. Firing missile. ========================== Name = F1 Position = <5, 6, -3> Velocity = <1, 1, 0> FuelLevel = 100 Damage = 0 Missiles = 8 Press any key to continue

As you can see, when we call printStats, it prints the number of missiles the ship has remaining. Now having said all of this, in general you should not override methods in this fashion—instead use polymorphism, which we will discuss later. The reason is that if you override the method at different levels of the inheritance hierarchy, and you cast up and down the hierarchy as Section 9.2.3 describes,

289

Page 290: C++ Módulo I e II

you will change what version of the method gets called depending where you are in the inheritance hierarchy. This is usually not desired.

9.3 Constructors and Destructors with Inheritance

Consider the following simple program: Program 9.3: Derived object’s construction and destruction order.

#include <iostream> using namespace std; class Spaceship public: Spaceship(); ~Spaceship(); ; class AlienShip : public Spaceship public: AlienShip(); ~AlienShip(); ; class AlienBomberShip : public AlienShip public: AlienBomberShip(); ~AlienBomberShip(); ; Spaceship::Spaceship() cout << "Spaceship() Constructor called." << endl; Spaceship::~Spaceship() cout << "~Spaceship() Destructor called." << endl; AlienShip::AlienShip() cout << "AlienShip() Constructor called." << endl;

290

Page 291: C++ Módulo I e II

AlienShip::~AlienShip() cout << "~AlienShip() Destructor called." << endl; AlienBomberShip::AlienBomberShip() cout << "AlienBomberShip() Constructor called." << endl; AlienBomberShip::~AlienBomberShip() cout << "~AlienBomberShip() Destructor called." << endl; int main() // Construct an AlienBomberShip AlienBomberShip alienShip; // 'alienShip' will be destroyed when 'main' exits. Program 9.3 Output

Spaceship() Constructor called. AlienShip() Constructor called. AlienBomberShip() Constructor called. ~AlienBomberShip() Destructor called. ~AlienShip() Destructor called. ~Spaceship() Destructor called. Press any key to continue

The output of Program 9.3 illustrates that when an object of a child class is constructed, the parent constructors are invoked in a descending order starting from the top of the hierarchy. For example, in Program 9.3, first the Spaceship part of AlienBomberShip is constructed, then the AlienShip part of AlienBomberShip is constructed, and finally the AlienBomberShip constructor is invoked. Destruction occurs in the reverse order; that is, the destructors are invoked in an ascending order starting at the bottom of the inheritance hierarchy. Particularly in Program 9.3, first the AlienBomberShip destructor is called, then the AlienShip destructor is called, and finally the Spaceship destructor is called.

291

Page 292: C++ Módulo I e II

9.4 Multiple Inheritance

One might wonder if a class can inherit from more than one class. After all, classes in the real world can be found that share properties of more than one class. For example, humans inherit traits from both of their parents. C++, unlike some other programming languages, does support multiple inheritance and the syntax is rather trivial; we simply specify an additional class to inherit from using a separating comma like so: class AlienShip : public Spaceship, public DrawableObject ... Here we have said an AlienShip is a Spaceship, but it is also a DrawableObject (drawable in the sense that our 3D engine is capable of drawing an AlienShip.) Although multiple inheritance can solve some problems quite elegantly, it is not without its pitfalls. However, we will not discuss them in this text because, although multiple inheritance is useful in some situations, it is not encountered very frequently. There is no need to spend a lot of time on a feature that is rarely used and indeed, multiple inheritance is not used at all in this course. Multiple inheritance is a feature that you need only be aware of in case you come across a situation where it might be useful. At that time, you can investigate the potential problems with using it. Scott Meyers’ Effective C++ spends a solid fourteen pages on the problems of multiple inheritance and how to use multiple inheritance effectively if you are interested in a deeper look.

9.5 Polymorphism

In order to introduce polymorphism, we will first present a problem. Our first attempt to solve this problem will result in failure. We will then show a correct solution to this problem using polymorphism, but we will not explain why and how polymorphism works until the following section. Problem: Create a Shape class and derive two other classes, Circle and Rectangle, from it. Each shape should know its type (a string describing what kind of shape it is; e.g., “circle” and “rectangle”), and be able to calculate its area and perimeter. Using these classes, instantiate five different circle objects and five different rectangle objects, and upcast them to Shape so that you can store all the circles and rectangles in a common Shape pointer container (i.e., array/std::vector). Finally, iterate through each element in the Shape container and output the element’s shape type (is it a circle or rectangle?), its area, and its perimeter.

292

Page 293: C++ Módulo I e II

9.5.1 First Attempt (Incorrect Solution)

The restrictions which the problem imposes are what make the problem difficult. In particular, a problem will occur when we upcast our Circle and Rectangle objects to Shapes. Let us attempt to do what the problem requires and see where it takes us. We create a general Shape class like so: Class Shape public: string type(); float area(); float perimeter(); ; We are already running into problems when we try to implement this class. Specifically, what is the type of a shape? What is the area of a shape? What is the perimeter of a shape? We only know types, areas and perimeters of specific shapes like circles and rectangles, but not of a general shape. In fact, the entire concept of a shape is abstract. In order to continue, let us have the Shape implementations of these methods return “undefined” for the shape type, and zero for area and perimeter. Then we will override the type, area and perimeter methods in the derived classes with a proper implementation. The following code shows the agreed implementation of Shape: string Shape::type() return "undefined"; float Shape::area() return 0.0f; float Shape::perimeter() return 0.0f; Moving on to the Circle and Rectangle class, we have the following class definitions: class Circle : public Shape public: Circle(float rad); string type(); float area(); float perimeter();

293

Page 294: C++ Módulo I e II

protected: float mRadius; ; //============================================================== class Rectangle : public Shape public: Rectangle(float w, float l); string type(); float area(); float perimeter(); protected: float mWidth; float mLength; ; We have the implementations: Circle::Circle(float rad) : mRadius(rad) string Circle::type() return "Circle"; const float PI = 3.14f; float Circle::area() return PI*mRadius*mRadius; float Circle::perimeter() return 2.0f*PI*mRadius; //============================================================== Rectangle::Rectangle(float w, float l) : mWidth(w), mLength(l) string Rectangle::type() return "Rectangle";

294

Page 295: C++ Módulo I e II

float Rectangle::area() return mWidth*mLength; float Rectangle::perimeter() return 2.0f*mWidth + 2.0f*mLength; No problems are encountered at this specific level. Now that we have our classes defined, the problem instructs us to create five different Circles and five different Rectangles, and to upcast them into a container of Shape pointers. Doing so yields the following code: int main() Shape* shapes[10]; shapes[0] = new Circle(1.0f); shapes[1] = new Circle(2.0f); shapes[2] = new Circle(3.0f); shapes[3] = new Circle(4.0f); shapes[4] = new Circle(5.0f); shapes[5] = new Rectangle(1.0f, 2.0f); shapes[6] = new Rectangle(2.0f, 4.0f); shapes[7] = new Rectangle(3.0f, 1.0f); shapes[8] = new Rectangle(4.0f, 6.0f); shapes[9] = new Rectangle(5.0f, 2.0f); Our final task is to iterate through each element in shapes and output the element’s shape type (a circle or rectangle), its area, and its circumference. Doing so yields the following code: for(int i = 0; i < 10; ++i) string type = shapes[i]->type(); float area = shapes[i]->area(); float peri = shapes[i]->perimeter(); cout << "Shape[" << i << "]'s "; cout << "Type = " << type << ", "; cout << "Area = " << area << ", "; cout << "Perimeter = " << peri << endl; // Delete the memory. for(int i = 0; i < 10; ++i) delete shapes[i]; // end main Now if we execute this program we get the following output:

295

Page 296: C++ Módulo I e II

Shape[0]'s Type = undefined, Area = 0, Perimeter = 0 Shape[1]'s Type = undefined, Area = 0, Perimeter = 0 Shape[2]'s Type = undefined, Area = 0, Perimeter = 0 Shape[3]'s Type = undefined, Area = 0, Perimeter = 0 Shape[4]'s Type = undefined, Area = 0, Perimeter = 0 Shape[5]'s Type = undefined, Area = 0, Perimeter = 0 Shape[6]'s Type = undefined, Area = 0, Perimeter = 0 Shape[7]'s Type = undefined, Area = 0, Perimeter = 0 Shape[8]'s Type = undefined, Area = 0, Perimeter = 0 Shape[9]'s Type = undefined, Area = 0, Perimeter = 0 Press any key to continue

This is not correct at all. What happened? The Shape versions of type, area, and circumference were called. This i pcast our objects to Shape. We hinted at this problem at the end of Section 9.2. t we are at a dead end. However, it is possible to generalize the specific shape o to draw themselves (that is, the specific shape) correctly in this general form, by using polymorphism. This brings us to our second attempt at solving the problem. Before we start, remember the following statement:

Base* base = new We say that the va ase and the dynamic type Derived. What we want is to be able to upcast a derived type to the base type, but for the dynamic type of the object to be “remembered” so that the dynamic type methods can be invoked, and not the base (static type) methods.

9.5.2 Second Attempt (Correct Solution)

We begin as we did in the first attempt, by defining and implementing the Shape class. We quickly run into the same previous problem; namely, what is the type of a shape? What is the area of a shape? What is the circumference of a shape? We again choose to ignore this problem and return “dummy” values. So far it seems we are taking the same road we took in the previous attempt. However, we diverge by modifying the methods type, area, and circumference with the virtual keyword:

s not surprising as we u5 and it might seem thabjects up to Shape, and for them to still “know” how

Derived();

riable base has the static type B

class Shape public: virtual string type(); virtual float area(); virtual float perimeter(); ; string Shape::type() return "undefined";

296

Page 297: C++ Módulo I e II

float Shape::area() return 0.0f; float Shape::perimeter() return 0.0f; Methods prefixed with the virtual keyword are called virtual functions or virtual methods (usually the former). We now proceed to finish the program exactly as we did in the first attempt. Moving on to the Circle and Rectangle class, we again have the following class definitions (same as in our first attempt): class Circle : public Shape public: Circle(float rad); string type(); float area(); float perimeter(); protected: float mRadius; ; //============================================================== class Rectangle : public Shape public: Rectangle(float w, float l); string type(); float area(); float perimeter(); protected: float mWidth; float mLength; ; Implementations: Circle::Circle(float rad) : mRadius(rad)

297

Page 298: C++ Módulo I e II

string Circle::type() return "Circle"; const float PI = 3.14f; float Circle::area() return PI*mRadius*mRadius; float Circle::perimeter() return 2.0f*PI*mRadius; //============================================================== Rectangle::Rectangle(float w, float l) : mWidth(w), mLength(l) string Rectangle::type() return "Rectangle"; float Rectangle::area() return mWidth*mLength; float Rectangle::perimeter() return 2.0f*mWidth + 2.0f*mLength; Now that we have our classes defined, the problem instructs us to create five different Circles and five different Rectangles, and to upcast them into a container of Shape pointers. Doing so yields the following code: int main() Shape* shapes[10]; shapes[0] = new Circle(1.0f); shapes[1] = new Circle(2.0f); shapes[2] = new Circle(3.0f); shapes[3] = new Circle(4.0f); shapes[4] = new Circle(5.0f); shapes[5] = new Rectangle(1.0f, 2.0f); shapes[6] = new Rectangle(2.0f, 4.0f);

298

Page 299: C++ Módulo I e II

shapes[7] = new Rectangle(3.0f, 1.0f); shapes[8] = new Rectangle(4.0f, 6.0f); shapes[9] = new Rectangle(5.0f, 2.0f); Our final task is to iterate through each element in shapes and output the element’s shape type (a circle or rectangle), its area, and its circumference. Doing so yields the following code: for(int i = 0; i < 10; ++i) string type = shapes[i]->type(); float area = shapes[i]->area(); float peri = shapes[i]->perimeter(); cout << "Shape[" << i << "]'s "; cout << "Type = " << type << ", "; cout << "Area = " << area << ", "; cout << "Perimeter = " << peri << endl; // Delete the memory. for(int i = 0; i < 10; ++i) delete shapes[i]; // end main Now if we execute this program we get the following output: Shape[0]'s Type = Circle, Area = 3.14, Perimeter = 6.28 Shape[1]'s Type = Circle, Area = 12.56, Perimeter = 12.56 Shape[2]'s Type = Circle, Area = 28.26, Perimeter = 18.84 Shape[3]'s Type = Circle, Area = 50.24, Perimeter = 25.12 Shape[4]'s Type = Circle, Area = 78.5, Perimeter = 31.4 Shape[5]'s Type = Rectangle, Area = 2, Perimeter = 6 Shape[6]'s Type = Rectangle, Area = 8, Perimeter = 12 Shape[7]'s Type = Rectangle, Area = 3, Perimeter = 8 Shape[8]'s Type = Rectangle, Area = 24, Perimeter = 20 Shape[9]'s Type = Rectangle, Area = 10, Perimeter = 14 Press any key to continue

We note that this time, the output is correct. We call this behavior polymorphism; that is, we have upcasted our objects to a more general Shape type, which behave like their original type. The Shapes have “many forms” (polymorphism). Classes with virtual functions are termed polymorphic. We emphasize that the only change we made in this second attempt was the virtual keyword. So it seems that virtual functions are the solution to our problems. By using virtual functions, the program “magically remembers” the dynamic type of the object and can therefore invoke the specific methods even after the objects are upcasted to a more general type up the inheritance ladder. The next section sets out to show that this is not magic, and describes how and why it works. In other words, we will learn what the virtual keyword is instructing the compiler to do behind the scenes.

299

Page 300: C++ Módulo I e II

Before we move on though, let us address the following question: Why use polymorphism? If it were not for the problem description, we would not have had to upcast our Circles and Rectangles to Shapes, and we could have just kept separate containers of Circles and Rectangles to output the same information. It is difficult to answer this question at this point. Polymorphism is best appreciated through experience. However, we can say that it is convenient to have a centralized general container of objects without having to realize their specific type. In particular, by having such a container we can work with and apply operations to the objects in the most general form, without regard to the specific form. This is convenient because we do not need to have separate branches of code for each specific case—the program “knows” to invoke the method of the dynamic type. We also explore another utility of polymorphism in Section 9.9.

9.6 How Virtual Functions Work

We demonstrated polymorphism with virtual functions but now we will discuss how they work. Typically6, a virtual table implementation is used. It works like this: When a class contains a virtual function it is given a corresponding virtual table (vtable) by the compiler, which is an array of pointers to all o a vtable contains only virtual functions, not regular functions. Also note that classes are given vtables, not object instances. Consider the following class hierarchy:

f its virtual functions. Note that

class Base public: virtual ~Base(); virtual void f(); virtual void g(); virtual void h(); virtual void r(); void s(); // non virtual void t(); // non virtual ; class Derived : public Base public: virtual ~Derived(); virtual void f(); // override virtual void g(); // override void x(); // new method, non virtual void y(); // new method, non virtual ; The corresponding virtual tables which the compiler will set aside would look like this:

6 We say typically because the actual implementation is compiler dependent.

300

Page 301: C++ Módulo I e II

Figure9.4: A class’ virtual table contains function pointers to the corresponding class methods. Note that only virtual

functions are in the virtual table. Also note that if a derived class does not override a virtual function, then the corresponding derived class’ virtual table entries point to the base class’ implementations (see h() and r()).

In addition to the vtable, each object instance whose class contains virtual functions is given a virtual table pointer7 (vptr), which points to the vtable that corresponds with the object's dynamic type. We note that during the construction of an object we know its dynamic type, and that is the time the vptr can be initialized. For example, Base* obj1 = new Base(); Base* obj2 = new Derived(); Here, obj1’s vptr points to the vtable of class Base. Likewise, obj2’s vptr points to the vtable of class Derived. Figure 9.5 shows a conceptual diagram to illustrate.

Figure 9.5: An object’s virtual pointer points to the virtual table of the object’s dynamic type.

7 The virtual table pointer is stored in some part of the object that only the compiler knows about. It is “hidden” from the programmer.

301

Page 302: C++ Módulo I e II

We now see how virtual functions work—the vptr points to the vtable that stores the “right” methods that correspond to the given object’s dynamic type. For example: obj2->h(); In this call, the compiler fetches obj2’s vptr and follows it to the vtable to which it points, which in this case is Derived’s vtable. In this case, once at the vtable it can quickly offset to the right function, Derived::h() since each virtual function is given a unique index within the vtable. Finally, Derived::h() can be invoked, thus performing the desired result.

Note: If you have virtual functions in your class, then the rule of thumb is to also have a virtual destructor. For example, suppose that Base and Derived do not have virtual destructors, which means the destructor is not in the vtable. What happens when you write: Base* base = new Derived(); ... delete base; Technically, your program is undefined by the C++ standard. However, what typically happens is that only Base::~Base will be called, which means Derived::~Derived is not called, which in turn means that you may get memory leaks and errors if you are expecting Derived::~Derived to be called. The solution to this problem is to make the destructor virtual so that it gets put into the vtable. Thus “delete base;” will call the destructor of the dynamic type (Derived) and destroy the object correctly. (Recall that when a derived object is deleted, its local destructor is called, followed by its base class destructor.)

9.7 The Cost of Virtual Functions

1. Increased memory taken up by storing a vtable for each class that contains virtual functions.

2. Increased memory taken up by storing a vptr in each object instance whose class contains virtual functions.

3. Some extra work associated with calling a virtual function; that is, accessing the vptr,

dereferencing the vptr to get to the vtable, offsetting into the vtable to get to the correct function pointer, and dereferencing the function pointer to invoke the actual function.

4. Virtual functions are usually not inlined since the compiler cannot determine which function to

call at compile time—remember that polymorphism is done at runtime, since dynamic types can be set at runtime.

Finally, to show some supporting evidence of the virtual function implementation just described (vtable/vptr), consider the following program:

302

Page 303: C++ Módulo I e II

Program 9.4: Providing evidence for the vptr.

#include <iostream> using namespace std; class NonVirtual public: void f() protected: int number; float x; ; class Virtual public: virtual void greeting() protected: int number; float x; ; int main() NonVirtual n; Virtual v; cout << "Size of NonVirtual = " << sizeof(n) << endl; cout << "Size of Virtual = " << sizeof(v) << endl; Program 9.4 Output

Size of NonVirtual = 8 Size of Virtual = 12 Press any key to continue

We see that the size of Virtual is four bytes more than that of NonVirtual, even though they have the same data. Where did these extra four bytes come from? They came from the vptr that is added to objects whose class contains virtual functions.

9.8 Abstract Classes

Despite solving the problem posed in Section 9.5 with polymorphism (i.e., virtual functions), we noted that it did not make sense to instantiate a Shape object. More specifically, we did not know how to implement the type, area, and perimeter functions of a Shape because a Shape is an abstract concept and we need concrete details to implement these functions. Consequently, we implemented Shape’s methods with “dummy” implementations like so:

303

Page 304: C++ Módulo I e II

string Shape::type() return "undefined"; float Shape::area() return 0.0f; float Shape::perimeter() return 0.0f; Another oddity is the fact that, at present, we can instantiate Shape objects: Shape shape; // no error. But such objects are completely meaningless. All the methods of Shape return worthless values. It only makes sense to use Shapes when using polymorphism where we can upcast from concrete Shapes (e.g., Circles, Rectangles), because then the methods of the dynamic type will be invoked. All of this is quite inelegant. There are two things we would like to do in this situation.

1. Provide the general Shape method prototypes in the Shape class (type, area, perimeter), but not an implementation since it does not make sense for abstract Shapes. So we want to force derived classes to override and implement these prototyped methods where it does make sense (at the concrete level).

2. Prevent a Shape class from being instantiated altogether; that is, to make the restriction that we

can only instantiate concrete types and upcast to Shape. C++ accomplishes these two things with abstract classes. An abstract class is a class that contains pure virtual functions. A pure virtual function is a virtual function that is declared in the base class (the abstract class) but is not implemented in the base class. It is the responsibility of derived classes to override the pure virtual functions and pr plementation for them.

Note: Derived classes must override and implement every pure virtual function an abstract class declares. Also realize that not every method in an abstract class must be “pure virtual.” However, it only takes one to make the class ct.

A virtual funct ing the function signature with the syntax ‘= 0’. Let us now rewrite Shape as an abstract class:

ovide an im

abstra

ion can be modified to “pure virtual” by append

304

Page 305: C++ Módulo I e II

class Shape public: // Pure virt virtual string type() = 0; virtual float area() = 0; virtual float perimeter() = 0; ; Note that we also delete the old “dummy” implementations—they are not needed with an abstract class since derived classes are guaranteed to override these pure virtual functions and provide an implementation. Now that we have made Shape abstract we observe that the following produces an error: Shape shape; // error, Shape abstract. In particular, the error: “C2259: 'Shape' : cannot instantiate abstract class.”

9.9 Interfaces

We mentioned that polymorphism is useful because it enables us to have a centralized general container of objects without having to realize their specific concrete type. In particular, by having such a container we can work with and apply operations to the objects in the most general form, without regard to the specific form. This is convenient because we do not need to have separate branches of code for each specific case—the program “knows” to invoke the method of the dynamic type. Interfaces provide yet another utility of polymorphism. Interfaces are closely related to abstract classes. In fact, an interface is usually defined as a class that consists of only pure virtual functions. To illustrate, let us suppose we have a class, called GraphicsEngine, which handles drawing 3D objects to the screen using the video card. In particular, we want to draw various 3D objects. One approach would be to have a method that draws each kind of shape the program supports: class GraphicsEngine public: void drawFighterShip(); void drawMothership(); void drawBomberShip(); void drawBomberShip(); void drawPlanet(); void drawAsteroid(); // ... etc ; This approach is cumbersome and inelegant. Every time we introduce a new kind of 3D object into the program, we must define a new method. Moreover, if GraphicsEngine was part of a third party

ual functions.

305

Page 306: C++ Módulo I e II

library, we would not be able to modify it. This would be very restrictive! An approach that is much more scalable and robust (from a software engineering perspective), is to use interfaces. We will define an Object3D interface with a pure virtual draw method. class Object3D public: virtual void draw() = 0; ; All of our concrete 3D object classes will inherit from this interface and implement the draw method, thereby specifying how to draw themselves. Then GraphicsEngine will have its own draw method implemented like so: void GraphicsEngine::draw(Object3D* obj) // Prepare hardware for drawing. obj->draw(); // draw the object // Do post drawing work. GraphicsEngine::draw takes a pointer to an Object3D and calls Object3D::draw at the appropriate time. GraphicsEngine does not know how any specific concrete Object3D draws itself—and it does not care. All it cares about is that the method draw exists and has been overridden in the concrete class, so that it can invoke the draw method, and the correct method corresponding to the object’s dynamic type will be invoked. This condition is guaranteed since draw is a pure virtual function in Object3D—therefore, it must be overridden and implemented by derived classes. Whenever we need to add a new 3D object to the program, we simply create a new class representing the 3D object, have it inherit from Object3D, implement the draw method, and then pass it off to GraphicsEngine::draw. Then, due to polymorphism, the method corresponding to the object’s dynamic type will be invoked. Incidentally, when a class inherits from an interface and implements the pure virtual functions, we say the class implements the interface. We now come to the analogy of an interface being viewed as a contract. Just as a contract guarantees some agreement, an interface guarantees that a method or set of methods exist and are implemented in any derived class.

306

Page 307: C++ Módulo I e II

9.10 Summary

1. Inheritance allows a derived class (also called a child class or subclass) to inherit the data and methods of a base class (also called a parent class or superclass). In this way, we only have to write the general “shared” code once, and we can pass it along to the specific classes via inheritance, thereby saving work and reusing code. Furthermore, from an object oriented programming standpoint, C++ inheritance enables us to model real world inheritance relationships in code, allowing us to more closely model real world objects with software objects.

2. When a class contains a variable of some type T as a member variable, we say the class “has a”

T. When we compose a class out of other types, object oriented programmers use the term “composition” to denote this; that is, the class is composed of those other types. When a class A inherits publicly from a class B, object oriented programmers say that we are modeling an “is a” relationship; that is, A is a B, but not conversely. Essentially, this is what public inheritance means—is a.

3. If Derived is a Base then we can switch back and fourth between the Derived object and its

Base part via pointer casting. We use the term “upcast” when casting up the inheritance chain; that is, from a derived object to its base part. Likewise, we use the term “downcast” when casting down the inheritance chain; that is, from the base part to the derived object. Note that downcasting is not always safe. Remember, a Derived object is a specific kind of Base object, but not conversely. In other words, a Derived object will always have a base part, but a Base object is not necessarily part of a Derived object

4. In Base* base = new Derived(); we say that the variable base has the “static type” Base

and the “dynamic type” Derived.

5. Polymorphism allows us to upcast concrete types to a more general type, such that the program still knows to invoke the methods that correspond to the object’s dynamic types. Virtual functions are what make a type polymorphic.

6. An abstract class is a class that contains pure virtual functions. A pure virtual function is a

virtual function that is declared in the base class (the abstract class) but is not implemented in the base class. It is the responsibility of derived classes to override the pure virtual functions and provide an implementation for them. Derived classes must override and implement every pure virtual function an abstract class declares. Also realize that not every method in an abstract class must be pure virtual. However, it only takes one to make the class abstract. A virtual function can be modified to “pure virtual” by appending the function signature with the syntax ‘= 0’.

7. Interfaces provide yet another utility of polymorphism. Interfaces are closely related to abstract

classes. In fact, an interface is usually defined as a class that consists of only pure virtual functions. A class that inherits from an interface and implements the pure virtual functions is said to “implement the interface.” Interfaces enforce contracts; that is, they say: “all classes that inherit from me must implement my interface.” In this way, other classes can be guaranteed that

307

Page 308: C++ Módulo I e II

all subclasses of an interface implement the pure virtual functions of that interface (i.e., they implement the interface). This allows software to call the methods of other objects without even knowing how they are implemented. Consequently, this leads to more general, expandable, and elegant software.

9.11 Exercises

9.11 Employee Database

Background Information Thus far we have been working with std::vector as a resizable array; that is, we call the resize method and then access elements using the bracket operator []. However, another, perhaps more convenient way, to work with std::vector is to view it as a container where we can add and remove items to and from the container. Instead of calling resize and assigning values directly to elements, we simply call an “add” method, which will add a specified item to the next “free” element in the std::vector. We can also remove items from the container with a “remove” method, which will erase an item at a specified index. Moreover, the std::vector will resize itself automatically as needed to grow and shrink as you add/remove items. The “add” and “remove” methods of std::vector are called push_back and erase, respectively, and they are summarized as follows:

• push_back(item): Add a copy of the item specified by the parameter to the next “free” element in the std::vector.

• erase(iterator): Removes the element specified by the iterator from the std::vector.

We discuss iterators when we discuss the STL in Chapter 13 in the next module. For now, just think of it as a special object that identifies an element in a std::vector. We can get iterators to the vector elements using an offset and the begin method. The begin method returns an iterator to the element at index [0]. We can then add an offset value to this iterator to get iterators to the other elements. E.g., vec.begin() + 1 evaluates to an iterator to element [1], vec.begin() + 2 evaluates to an iterator to element [2], and so on.

As you know, we can always get the current size of the std::vector with the size method. The following program displays the contents of a vector and its size for each loop cycle. Furthermore, for each loop cycle, the user can add or remove a new item to the vector. In this way, you can see how items are added and removed to the vector container in real-time.

308

Page 309: C++ Módulo I e II

Program 9.5: Using the std::vector methods push_back and erase.

#include <iostream> #include <string> #include <vector> using namespace std; int main() vector<int> vec; bool quit = false; while( !quit ) // Output size. cout << "vec.size() = " << vec.size() << endl; // Output vector contents. cout << "vec contains: "; for(int i = 0; i < vec.size(); ++i) cout << vec[i] << " "; cout << endl << endl; // Display option menu. cout << "1) Add int, 2) Remove int, 3) Exit. "; // Get menu input. int input = 1; cin >> input; // Do operation based on item chosen. switch( input ) case 1: // Add an inputted integer to the vector. cout << "Enter an integer: "; cin >> input; vec.push_back( input ); break; case 2: // Remove the element at the inputted index. cout << "Enter the index of an integer to remove: "; cin >> input; // Make sure index is inbounds. if( input > 0 && input < vec.size() ) vec.erase( vec.begin() + input ); break; case 3: // Exit. quit = true; break;

309

Page 310: C++ Módulo I e II

Program 9.5 Output

vec.size() = 0 vec contains: 1) Add int, 2) Remove int, 3) Exit. 1 Enter an integer: 1 vec.size() = 1 vec contains: 1 1) Add int, 2) Remove int, 3) Exit. 1 Enter an integer: 2 vec.size() = 2 vec contains: 1 2 1) Add int, 2) Remove int, 3) Exit. 1 Enter an integer: 3 vec.size() = 3 vec contains: 1 2 3 1) Add int, 2) Remove int, 3) Exit. 1 Enter an integer: 4 vec.size() = 4 vec contains: 1 2 3 4 1) Add int, 2) Remove int, 3) Exit. 2 Enter the index of an integer to remove: 1 vec.size() = 3 vec contains: 1 3 4 1) Add int, 2) Remove int, 3) Exit. 2 Enter the index of an integer to remove: 3 vec.size() = 3 vec contains: 1 3 4 1) Add int, 2) Remove int, 3) Exit. 2 Enter the index of an integer to remove: 2 vec.size() = 2 vec contains: 1 3 1) Add int, 2) Remove int, 3) Exit. 1 Enter an integer: 6 vec.size() = 3 vec contains: 1 3 6 1) Add int, 2) Remove int, 3) Exit. 3 Press any key to continue

Note that you can accomplish the same thing with resize, and doing some of your own “bookkeeping.” However, push_back and erase provide a much simpler interface—it does the “bookkeeping” for you.

310

Page 311: C++ Módulo I e II

311

Exercise Suppose a company has the following types of employees: 1) Manager, 2) Engineer, and 3) Researcher. The following box summarizes the data properties each kind of employee has. All employees have the following:

1. First name. 2. Last name. 3. Salary.

In addition to what all employees have, managers have the following:

1. Number of meetings per week. 2. Number of vacation days per year.

In addition to what all employees have, engineers have the following:

1. A value specifying whether or not they know C++. 2. Number of years of experience. 3. A string denoting the type of engineer they are (e.g., “mechanical,” “electric,”

“software.” In addition to what all employees have, researchers have the following:

1. A string specifying the school they received their PhD from. 2. A string specifying the topic of their PhD thesis.

Your task is to write the following program. Create a console application that allows the user to add employees to and delete employees from a database. Use exactly one std::vector of Employees as the database. Furthermore, the program should allow the user to save the database to a file (use a text file); in particular, implement a save method, which is responsible for writing the data of one employee to the file; so to save all the employees you iterate over the database and call the save method for each employee.

You should start this program by deciding on what the inheritance hierarchy should look like, what methods you need (i.e., what should the constructors look like, etc), which functions should be made virtual/pure virtual, which methods should be overridden in derived classes, and which data types to use to represent the properties of the various employees.

The menu displayed to the user should look something like this: 1) Add an Employee, 2) Delete an Employee 3) Save Database, 4) Exit. For example, when the user presses the “1” key, then a new menu “Add an Employee” should be displayed. Similarly for “Delete an Employee” and “Save Database.”

Page 312: C++ Módulo I e II

312

1. Add an Employee. This should display a submenu: a) Add a Manager, b) Add an Engineer, c)

Add a Researcher.

a) Add a Manager. This should ask the user to input the necessary information to construct a Manager. Using this information, construct a new Manager and add it to the database (i.e., the std::vector).

b) Add an Engineer. This should ask the user to input the necessary information to

construct an Engineer. Using this information, construct a new Engineer and add it to the database (i.e., the std::vector).

c) Add a Researcher. This should ask the user to input the necessary information to

construct a Researcher. Using this information, construct a new Researcher and add it to the database (i.e., the std::vector).

2. Delete an Employee. This should ask for the last name of the employee (your program does not

need to handle duplicate last names). You then need to write code to search the database for the given employee and delete him/her from the database (i.e., the std::vector). If the user enters in a name that does not exist in the database, report that information to the user and return to the main menu.

3. Save Database. This should traverse the entire database (i.e., the array/std::vector) and call

the save method of each Employee, thereby saving the data of each employee to file. After you have implemented this program, run the program and manually enter in three distinct managers, three distinct engineers, and three distinct researchers. Then delete one manager, one engineer, and one researcher from the database. Third, save the database to a file. Finally, inspect the file to verify that the information saved was the information you entered, and that the three deleted employees were not saved. If the file contains the correct information, then you are done with this exercise. If you are feeling ambitious, add a new option that reads the database from file back into RAM.

Page 313: C++ Módulo I e II

313

C++ Module I Conclusion Congratulations on completing a first course on the C++ programming language. It is hoped by now that you are proficient with core C++ topics such as variables, console input and output, functions, loops, programming logic, pointers, strings, and classes; in addition, you should have a basic understanding of more complex C++ subject matter, such as operator overloading, file input and output, inheritance, and polymorphism. Mastery in said advance C++ topics will come with time and experience, as your C++ programming matures. In the second C++ course, offered at Game Institute, we will begin to move away from the text-based console applications we have been building, and begin to examine Windows programming. By making the move to Windows, we enter a whole new world of programming; a world in which we will be exposed to a set of functions and data structures, collectively called the Win32 API, which is used to develop Windows programs. With the Win32 API, we will be able to write programs the way you are, no doubt, familiar to seeing; ones with resizable windows, mouse input, graphics, menus, toolbars, scroll bars, dialog boxes, and controls. Of particular interest to us as game programmers is the ability to do graphics with the Win32 API, something which is not possible with pure C++TP

8PT. We will learn about fundamental graphic concepts

such as double buffering, sprites, animation and timing, and masking. By the end of the course, we will have developed a fully functional 2D Air Hockey game (see Figure), complete with graphics, physics, artificial intelligence, and input via the mouse.

However, before we make the move to Windows, we need to make three stops, and examine some last minute C++ techniques; in particular, template programming, exception handling, alternative number systems and bit operations, and a primer of the STL (standard template library).

TP

8PT That is, C++ has nothing to say about graphic functionality—graphic routines must be exposed by the particular platform

you are working on.

Page 314: C++ Módulo I e II

314

After completing the next C++ module, you will be adequately prepared for your first course in 3D graphics programming. One of the benefits from that point forward is that you will be writing all sorts of interesting 3D, AI, physics, and other game related applications in C++, which gives you the opportunity to continue to mature your C++ programming abilities. By the time you graduate from the full program, not only will you be a well-trained game developer, but you will also be a highly skilled C++ programmer. This will open up a lot of career opportunities that you might not have even considered. Be sure to study hard for the final exam, and we hope to see you back here in short order so that you can begin Module II and start making games!

Page 315: C++ Módulo I e II

C++ Programming for

Game Developers

Module II

e-Institute Publishing, Inc.

Page 316: C++ Módulo I e II

©Copyright 2005 e-Institute, Inc. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system without prior written permission from e-Institute Inc., except for the inclusion of brief quotations in a review. Editor: Susan Nguyen Cover Design: Adam Hoult E-INSTITUTE PUBLISHING INC www.gameinstitute.com Frank Luna, C++ Programming for Games II All brand names and product names mentioned in this book are trademarks or service marks of their respective companies. Any omission or misuse of any kind of service marks or trademarks should not be regarded as intent to infringe on the property of others. The publisher recognizes and respects all marks used by companies, manufacturers, and developers as a means to distinguish their products. E-INSTITUTE PUBLISHING titles are available for site license or bulk purchase by institutions, user groups, corporations, etc. For additional information, please contact the Sales Department at [email protected]

Page 317: C++ Módulo I e II

i

Table of Contents

MODULE II OVERVIEW........................................................................................................................................................1 CHAPTER 10: INTRODUCTION TO TEMPLATES ..........................................................................................................2

INTRODUCTION ........................................................................................................................................................................3 CHAPTER OBJECTIVES .............................................................................................................................................................3 10.1 CLASS TEMPLATES...........................................................................................................................................................4

10.1.1 Class Template Definition .......................................................................................................................................7 10.1.2 Class Template Implementation ..............................................................................................................................7 10.1.3 Class Template Instantiation...................................................................................................................................8

10.2 EXAMPLE: A TABLE TEMPLATE CLASS.............................................................................................................................9 10.2.1 Table Data...............................................................................................................................................................9 10.2.2 Class Interface.......................................................................................................................................................10 10.2.3 The destroy Method..........................................................................................................................................11 10.2.4 The resize Method ............................................................................................................................................11 10.2.5 The Overloaded Parenthesis Operator .................................................................................................................13 10.2.6 The Table Class.....................................................................................................................................................13

10.3 FUNCTION TEMPLATES ..................................................................................................................................................17 10.3.1 Example Program .................................................................................................................................................18

10.4 SUMMARY......................................................................................................................................................................21 10.5 EXERCISES .....................................................................................................................................................................21

10.5.1 Template Array Class............................................................................................................................................21 10.5.2 Template Bubble Sort Function.............................................................................................................................22 10.5.3 Table Driver ..........................................................................................................................................................22

CHAPTER 11: ERRORS AND EXCEPTION HANDLING...............................................................................................23 INTRODUCTION ......................................................................................................................................................................24 CHAPTER OBJECTIVES ...........................................................................................................................................................24 11.1 ERROR CODES................................................................................................................................................................24 11.2 EXCEPTION HANDLING BASICS......................................................................................................................................26 11.3 ASSERT ..........................................................................................................................................................................29 11.4 SUMMARY......................................................................................................................................................................31 11.5 EXERCISES .....................................................................................................................................................................31

11.5.1 Exception Handling...............................................................................................................................................31 CHAPTER 12: NUMBER SYSTEMS ...................................................................................................................................33

INTRODUCTION ......................................................................................................................................................................34 CHAPTER OBJECTIVES ...........................................................................................................................................................34 12.1 NUMBER SYSTEMS.........................................................................................................................................................34

12.1.1 The Windows Calculator .......................................................................................................................................35 12.2 THE BINARY NUMBER SYSTEM......................................................................................................................................37

12.2.1 Counting in Binary ................................................................................................................................................37 12.2.2 Binary and Powers of 2 .........................................................................................................................................38 12.2.3 Binary Arithmetic ..................................................................................................................................................39

Addition .......................................................................................................................................................................................... 39 Subtraction...................................................................................................................................................................................... 41 Multiplication ................................................................................................................................................................................. 43

12.2.4 Converting Binary to Decimal ..............................................................................................................................43 12.2.5 Converting Decimal to Binary ..............................................................................................................................44

12.3 THE HEXADECIMAL NUMBER SYSTEM...........................................................................................................................45 12.3.1 Counting in Hexadecimal......................................................................................................................................45 12.3.2 Hexadecimal Arithmetic ........................................................................................................................................46

Addition .......................................................................................................................................................................................... 46

Page 318: C++ Módulo I e II

ii

Subtraction...................................................................................................................................................................................... 47 Multiplication ................................................................................................................................................................................. 48

12.3.3 Converting Hexadecimal to Binary .......................................................................................................................48 12.3.4 Converting Binary to Hexadecimal .......................................................................................................................49

12.4 BITS AND MEMORY........................................................................................................................................................50 12.5 BIT OPERATIONS............................................................................................................................................................51

12.5.1 AND.......................................................................................................................................................................51 12.5.2 Inclusive OR ..........................................................................................................................................................52 12.5.3 NOT.......................................................................................................................................................................52 12.5.4 Exclusive OR .........................................................................................................................................................53 12.5.5 Shifting ..................................................................................................................................................................53 12.5.6 Compound Bit Operators ......................................................................................................................................54

12.6 FLOATING-POINT NUMBERS ..........................................................................................................................................54 12.7 SUMMARY......................................................................................................................................................................56 12.8 EXERCISES .....................................................................................................................................................................57

12.8.1 Binary Arithmetic ..................................................................................................................................................57 12.8.2 Hex Arithmetic.......................................................................................................................................................57 12.8.3 Base Conversions ..................................................................................................................................................58 12.8.4 Bit Operations .......................................................................................................................................................59 12.8.5 Binary to Decimal .................................................................................................................................................61 12.8.6 Decimal to Binary .................................................................................................................................................61 12.8.7 Bit Operation Calculator.......................................................................................................................................61

12.9 REFERENCES ..................................................................................................................................................................62 CHAPTER 13: STL PRIMER................................................................................................................................................63

INTRODUCTION ......................................................................................................................................................................64 CHAPTER OBJECTIVES ...........................................................................................................................................................64 13.1 PROBLEMS WITH ARRAYS ..............................................................................................................................................64 13.2 LINKED LISTS.................................................................................................................................................................66

13.2.1 Theory ...................................................................................................................................................................66 13.2.2 Traversing .............................................................................................................................................................71 13.2.3 Insertion ................................................................................................................................................................72 13.2.4 Deletion .................................................................................................................................................................73

13.3 STACKS ..........................................................................................................................................................................74 13.3.1 Theory ...................................................................................................................................................................74 13.3.2 Stack Operations ...................................................................................................................................................76

13.4 QUEUES .........................................................................................................................................................................78 13.4.1 Theory ...................................................................................................................................................................78 13.4.2 Queue Operations .................................................................................................................................................78

13.5 DEQUES .........................................................................................................................................................................80 13.5.1 Theory ...................................................................................................................................................................80 13.5.2 Deque Operations .................................................................................................................................................81

13.6 MAPS .............................................................................................................................................................................81 13.6.1 Theory ...................................................................................................................................................................81 13.6.2 Insertion ................................................................................................................................................................81 13.6.3 Deletion .................................................................................................................................................................82 13.6.4 Traversal ...............................................................................................................................................................82 13.6.5 Searching...............................................................................................................................................................83

13.7 SOME ALGORITHMS .......................................................................................................................................................84 13.7.1 Functors ................................................................................................................................................................84 13.7.2 Some More Algorithms..........................................................................................................................................88 13.7.3 Predicates..............................................................................................................................................................90

13.8 SUMMARY......................................................................................................................................................................91 13.9 EXERCISES .....................................................................................................................................................................93

13.9.1 Linked List .............................................................................................................................................................93 13.9.2 Stack ......................................................................................................................................................................93

Page 319: C++ Módulo I e II

iii

13.9.3 Queue ....................................................................................................................................................................94 13.9.4 Algorithms .............................................................................................................................................................94

CHAPTER 14: INTRODUCTION TO WINDOWS PROGRAMMING ...........................................................................95 INTRODUCTION ......................................................................................................................................................................96 CHAPTER OBJECTIVES ...........................................................................................................................................................97 14.1 YOUR FIRST WINDOWS PROGRAM .................................................................................................................................97 14.2 THE EVENT DRIVEN PROGRAMMING MODEL...............................................................................................................103

14.2.1 Theory .................................................................................................................................................................103 14.2.2 The MSG Structure...............................................................................................................................................103

14.3 OVERVIEW OF CREATING A WINDOWS APPLICATION ..................................................................................................104 14.3.1 Defining the Window Procedure .........................................................................................................................105 14.3.2 The WNDCLASS Structure...................................................................................................................................108 14.3.3 WNDCLASS Registration .....................................................................................................................................110 14.3.4 CreateWindow.....................................................................................................................................................110 14.3.5 Showing and Updating the Window ....................................................................................................................112 14.3.6 The Message Loop...............................................................................................................................................113

14.4 YOUR SECOND WINDOWS PROGRAM...........................................................................................................................113 14.5 SUMMARY....................................................................................................................................................................116 14.6 EXERCISES ...................................................................................................................................................................117

14.6.1 Exit Message .......................................................................................................................................................117 14.6.2 Horizontal and Vertical Scroll Bars....................................................................................................................117 14.6.3 Multiple Windows................................................................................................................................................117 14.6.4 Change the Cursor ..............................................................................................................................................117 14.6.5 Blue Background .................................................................................................................................................118 14.6.6 Custom Icon ........................................................................................................................................................119

CHAPTER 15: INTRODUCTION TO GDI AND MENUS...............................................................................................122 INTRODUCTION ....................................................................................................................................................................123 CHAPTER OBJECTIVES .........................................................................................................................................................123 15.1 TEXT OUTPUT ..............................................................................................................................................................124

15.1.1 The WM_PAINT Message....................................................................................................................................124 15.1.2 The Device Context .............................................................................................................................................124 15.1.3 TextOut ............................................................................................................................................................125 15.1.3 Example Program ...............................................................................................................................................126

15.2 SHAPE PRIMITIVES .......................................................................................................................................................131 15.2.1 Drawing Lines .....................................................................................................................................................131 15.2.2 Drawing Rectangles ............................................................................................................................................137 15.2.3 Drawing Ellipses .................................................................................................................................................141

15.3 LOADING AND DRAWING BITMAPS ..............................................................................................................................142 15.3.1 Loading ...............................................................................................................................................................142 15.3.2 Rendering ............................................................................................................................................................145 15.3.3 Deleting ...............................................................................................................................................................146 15.3.4 Sample Program..................................................................................................................................................146

15.4 PENS AND BRUSHES .....................................................................................................................................................150 15.4.1 Pens.....................................................................................................................................................................150 15.4.2 Brushes................................................................................................................................................................151

15.5 SHAPE CLASSES ...........................................................................................................................................................152 15.5.1 Class Definitions .................................................................................................................................................152 15.5.2 Class Implementations ........................................................................................................................................154

15.6 MENUS.........................................................................................................................................................................157 15.6.1 Creating a Menu Resource..................................................................................................................................157 15.6.2 Loading a Menu and Attaching it to a Window...................................................................................................160 15.6.3 Checking Menu Items ..........................................................................................................................................160 15.6.4 Selecting Menu Items ..........................................................................................................................................161

Page 320: C++ Módulo I e II

iv

15.7 THE PAINT SAMPLE......................................................................................................................................................161 15.8 SUMMARY....................................................................................................................................................................171 15.9 EXERCISES ...................................................................................................................................................................172

15.9.1 Colors..................................................................................................................................................................172 15.9.2 Styles ...................................................................................................................................................................172 15.9.3 Cube ....................................................................................................................................................................172 15.9.4 Undo Feature ......................................................................................................................................................172

CHAPTER 16: INTRODUCTION TO DIALOGS AND CONTROLS............................................................................173 INTRODUCTION ....................................................................................................................................................................174 CHAPTER OBJECTIVES .........................................................................................................................................................174 16.1 MODAL DIALOG BOXES; THE STATIC TEXT CONTROL; THE BUTTON CONTROL .........................................................175

16.1.1 Designing the Dialog Box ...................................................................................................................................175 16.1.2 Modal Dialog Box Theory...................................................................................................................................179 16.1.3 The About Box Sample ........................................................................................................................................181

16.2 MODELESS DIALOG BOXES; THE EDIT CONTROL ........................................................................................................184 16.2.1 Modeless Dialog Box Theory ..............................................................................................................................184 16.2.2 The Edit Box Sample: Designing the Dialog Resource .......................................................................................186 16.2.3 The Edit Box Sample ...........................................................................................................................................187

16.3 RADIO BUTTONS ..........................................................................................................................................................191 16.3.1 Designing the Radio Dialog Resource ................................................................................................................191 16.3.2 Implementing the Radio Button Sample ..............................................................................................................192

16.4 COMBO BOXES.............................................................................................................................................................196 16.4.1 Designing the Combo Box Dialog Resource .......................................................................................................197 16.4.2 Implementing the Combo Box Sample.................................................................................................................197

16.5 SUMMARY....................................................................................................................................................................201 16.6 EXERCISES ...................................................................................................................................................................202

16.6.1 List Box................................................................................................................................................................202 16.6.2 Checkbox Controls ..............................................................................................................................................202 16.6.3 File Save and Open Dialogs................................................................................................................................204 16.6.4 Color Dialog .......................................................................................................................................................206

CHAPTER 17: TIMING, ANIMATION, AND SPRITES.................................................................................................207 INTRODUCTION ....................................................................................................................................................................208 CHAPTER OBJECTIVES .........................................................................................................................................................208 17.1 TIMING AND FRAMES PER SECOND ..............................................................................................................................208

17.1.1 The Windows Multimedia Timer Functions.........................................................................................................208 17.1.2 Computing the Time Elapsed Per Frame ............................................................................................................211 17.1.3 Computing the Frames Per Second.....................................................................................................................213

17.2 DOUBLE BUFFERING ....................................................................................................................................................214 17.2.1 Motivation ...........................................................................................................................................................214 17.2.2 Theory .................................................................................................................................................................214 17.2.3 Implementation....................................................................................................................................................215

17.3 TANK ANIMATION SAMPLE..........................................................................................................................................220 17.3.1 Creation...............................................................................................................................................................222 17.3.2 Destruction..........................................................................................................................................................222 17.3.3 Input ....................................................................................................................................................................223 17.3.4 Updating and Drawing........................................................................................................................................224 17.3.5 Point Rotation .....................................................................................................................................................228 17.3.6 Tank Application Code........................................................................................................................................229

17.4 SPRITES........................................................................................................................................................................237 17.4.1 Theory .................................................................................................................................................................237 17.4.2 Implementation....................................................................................................................................................241

17.5 SHIP ANIMATION SAMPLE............................................................................................................................................245 17.5.1 Art Resources ......................................................................................................................................................245 17.5.2 Program Code.....................................................................................................................................................246

Page 321: C++ Módulo I e II

v

17.6 SUMMARY....................................................................................................................................................................254 17.7 EXERCISES ...................................................................................................................................................................255

17.7.1 Colors..................................................................................................................................................................255 17.7.2 Draw Order.........................................................................................................................................................255 17.7.3 Masking ...............................................................................................................................................................255 17.7.4 Make Your Own Sprite ........................................................................................................................................256 17.7.5 Bouncing Ball......................................................................................................................................................256 17.7.6 Modify the Ship Program ....................................................................................................................................256 17.7.7 Pong ....................................................................................................................................................................256 17.7.8 More on Animation..............................................................................................................................................257

CHAPTER 18: THE AIR HOCKEY GAME......................................................................................................................259 INTRODUCTION ....................................................................................................................................................................260 CHAPTER OBJECTIVES .........................................................................................................................................................260 18.1 ANALYSIS ....................................................................................................................................................................261

18.1.1 Object Identification............................................................................................................................................262 18.1.2 Game Behavior and Corresponding Problems to Solve......................................................................................262

18.2 DESIGN ........................................................................................................................................................................264 18.2.1 Algorithms ...........................................................................................................................................................264

18.2.1.1 Mouse Velocity ............................................................................................................................................................... 264 18.2.1.2 Red Paddle Artificial Intelligence ................................................................................................................................... 265 18.2.1.3 Puck Paddle Collision ..................................................................................................................................................... 267 18.2.1.4 Puck Wall Collision ........................................................................................................................................................ 272 18.2.1.5 Paddle Wall Collision ..................................................................................................................................................... 273 18.2.1.6 Pausing/Unpausing.......................................................................................................................................................... 275 18.2.1.7 Detecting a Score ............................................................................................................................................................ 275

18.2.2 Software Design ..................................................................................................................................................276 18.3 IMPLEMENTATION ........................................................................................................................................................280

18.3.1 Circle ..............................................................................................................................................................280 18.3.2 Rect ...................................................................................................................................................................281 18.3.3 AirHockeyGame ..................................................................................................................................................282 18.3.4 Main Application Code .......................................................................................................................................288

18.4 COLLISION PHYSICS EXPLANATION (OPTIONAL) .........................................................................................................295 18.4.1 Linear Momentum ...............................................................................................................................................296 18.4.2 Newton’s Second Law of Motion.........................................................................................................................297 18.4.3 Impulse Defined...................................................................................................................................................297 18.4.4 Newton’s Third Law of Motion ...........................................................................................................................298 18.4.5 Kinetic Energy and Elastic Collisions.................................................................................................................300 18.4.6 Collision and Response .......................................................................................................................................304

18.5 CLOSING REMARKS......................................................................................................................................................308

Page 322: C++ Módulo I e II

1

Module II Overview

Module II is the second course in the C++ Programming for Game Developers series. Recall that in Module I we started off by studying fundamental programming concepts like variables, console input and output, arrays, conditional statements, strings, loops, and file input and output. We then pursued higher level programming methodologies such as classes, object oriented programming design, operator overloading, inheritance, and polymorphism. By now you should feel competent with the fundamentals and at least comfortable with the higher level subject matter. Our aim in Module II is twofold. Our first objective is to finish our study of C++ by examining templates, error handling, the standard template library, and bitwise operations. Templates can be thought of as a class factory, which allows us to generate similar yet unique classes, based on a code template; this allows us to avoid duplicating code that is only slightly different. Error handling is an important topic because things rarely work out as planned, and we will need to be able to detect hardware failures, illegal operations, invalid input, corrupted and missing files, and the like in our code. The standard template library is a set of generic ready to use C++ code that simplifies many day-to-day programming tasks. In the STL chapter you will learn about several useful STL data structures and algorithms, and the ideas behind them. The chapter on bitwise operations provides a deeper understanding of computer memory and how numbers are represented internally. You will also learn how to work in several other numbering systems such as binary and hexadecimal, which are more natural from a computer’s point of view. The second key theme in Module II is Windows programming. Here we will learn how to make familiar Windows applications with resizable windows, mouse input, graphics, menus, dialog boxes, and controls. In addition, we will learn how to implement 2D flicker free animation with double buffering, and how to render 2D sprite images (i.e., graphical representation of game objects such as the main character, landscape, and enemies). Finally, we conclude Module II by walking the reader through the design and analysis of a fully functional 2D Air Hockey game, complete with graphics, physics, artificial intelligence, and input via the mouse. This final project culminates much of the course material. By the end of this course, you will be well prepared for a first course in 3D game programming, as well as many other interesting computer related fields that require an understanding of computer programming as a qualification.

Page 323: C++ Módulo I e II

2

Chapter 10

Introduction to Templates

Page 324: C++ Módulo I e II

3

Introduction

You should recall from our discussions in Chapter 4 that std::vector can be thought of as a “resizable array” (Section 4.6). However, what is interesting about std::vector is that we can specify the type of vector to create with the angle bracket syntax: vector<int> intVec; vector<float> floatVec; vector<bool> boolVec; vector<string> stringVec; Thus we can create vectors of different types, just as we can create elementary arrays of different types. But, also recall that a std::vector is not some magical entity—it is just a class with methods that handle the internal dynamic memory array resizing. So the question is: how can we create a generic class that can work with any type (or at least, some types), like std::vector can? The answer to this question leads us to C++ templates and, more generally, generic programming. Before continuing on, we would like to say that the subject of templates is vast, and we can only introduce the basics in this chapter. For advanced/interested readers, we refer you to C++ Templates: The Complete Guide by David Vandevoorde and Nicolai M. Josuttis. This book should prove to be an excellent resource for you and is a highly recommended supplement to the material we will study in this chapter.

Chapter Objectives

• Learn how to design and implement generic classes. • Learn how to define generic functions.

Page 325: C++ Módulo I e II

4

10.1 Class Templates

Consider this small data structure, which represents an interval [a, b]: struct FloatInterval FloatInterval(); FloatInterval(float start, float end); float midpoint(); float a; float b; ; FloatInterval::FloatInterval() a = 0.0f; b = 0.0f; FloatInterval::FloatInterval(float start, float end) a = start; b = end; float FloatInterval::midpoint() // return the midpoint between a and b. return (a + b) * 0.5; Although very simple, it is not hard to imagine the need for other types of intervals. For example, we may want to describe integer intervals, character intervals, 3D vector intervals, and color intervals (color values between two colors). The most obvious solution would be just to define these additional interval classes. Here are a few of them: struct IntInterval IntInterval(); IntInterval(int start, int end); int midpoint(); int a; int b; ; IntInterval::IntInterval() a = 0.0f; b = 0.0f;

Page 326: C++ Módulo I e II

5

IntInterval::IntInterval(int start, int end) a = start; b = end; int IntInterval::midpoint() // return the midpoint between a and b. return (a + b) * 0.5; struct CharInterval CharInterval(); CharInterval(char start, char end); char midpoint(); char a; char b; ; CharInterval::CharInterval() a = 0.0f; b = 0.0f; CharInterval::CharInterval(char start, char end) a = start; b = end; char FloatInterval::midpoint() // return the midpoint between a and b. return (a + b) * 0.5; This approach results in a lot of additional code. Moreover, we may need to create new interval types later on, and we would then have to define additional classes. It does not take long to realize that this process can become cumbersome pretty quickly. Let us instead analyze the interval class to see if we can make any observations that will help us simplify our task. We note that the only difference between FloatInterval and IntInterval is that everywhere we see a float in FloatInterval, we see an int in IntInterval—and similarly with FloatInterval and CharInterval. That is, these classes are essentially the same—only the type they work with is different. This gives us an idea. We could create a generic Interval class using a variable-type like so:

Page 327: C++ Módulo I e II

6

Struct Interval Interval(); Interval(variable-type start, variable-type end); variable-type midpoint(); variable-type a; variable-type b; ; Interval::Interval() a = 0.0f; b = 0.0f; Interval::Interval(variable-type start, variable-type end) a = start; b = end; variable-type Interval::midpoint() // return the midpoint between a and b. return (a + b) * 0.5; Then, if we could specify a type-argument, we could generate new Interval classes that worked the same way, but with different types. All we have to do is substitute the type-argument for the variable-type. For example, if we specified type float as the type argument (assume argument types are specified in angle brackets < >), then the following class would be generated: Interval<float> ==

struct Interval Interval(); Interval(float start, float end); float midpoint(); float a; float b;

;

Note: The previous two code boxes do not use actual C++ syntax (though it is similar); pseudo-code was used to illustrate the idea of how template classes work.

This behavior is exactly what template classes allow us to do. Returning to std::vector, when we specify the type in the angle brackets, we instruct the compiler to create a vector class based on the specified type by substituting the type-argument (type in angle brackets) into the type-variables of the template vector class.

Page 328: C++ Módulo I e II

7

10.1.1 Class Template Definition

Now that we know what we would use templates for and the basic idea behind how they work, let us examine the actual C++ template syntax. Here is how we would define a template Interval class in C++: template <typename T> struct Interval Interval(); Interval(T start, T end); T midpoint(); T a; T b; ;

The first line, template <typename T> indicates that the class is a template class. The parameter inside the angle brackets <typename T> denotes a variable-type name—in this case, we named the variable-type, T. Later, when we want to instantiate an Interval of, say, ints, the compiler will substitute int everywhere there is a T.

10.1.2 Class Template Implementation

There is some special syntax required when implementing the methods of a template class. In particular, we must prefix the method with the template <typename T> syntax, and refer to the class name as ClassName<T>. The following shows how we would implement the methods of Interval: template <typename T> Interval<T>::Interval() a = T(); // Initialize with default b = T(); // constructors of whatever type. template <typename T> Interval<T>::Interval(T start, T end) a = start; b = end; template <typename T> T Interval<T>::midpoint() return (a + b) * 0.5; // return the midpoint between a and b. Again, observe how we use T to refer to the type, which will eventually be substituted into the template.

Note: The template functionality of C++ is not easy for compiler writers to implement.

Page 329: C++ Módulo I e II

8

10.1.3 Class Template Instantiation

We have already instantiated template classes earlier in Module I of this course, without your even realizing it. For example, the following code generates two classes: vector<int> intVec; vector<float> floatVec; It generates a vector class, where type int is substituted into the typename parameter, and it generates a second vector class, where type float is substituted into the typename parameter. The compiler generates these classes at compile time—after all, we specify the type-argument at compile time. Once the int-vector and float-vector classes are generated (remember, generating these classes is merely a matter of substituting the typename variable-type with the specified argument-type), we can create instances of them. That is what intVec and floatVec are—they are instances of the matching vector class generated by the compiler.

Note: To further clarify, classes of a particular type are generated only once. That is, if you write: vector<float> f1; vector<float> f2; A float version of vector is not generated twice—it only needs to be generated once to instantiate any number of float-vector object instances.

With our template Interval class defined and implemented, we can instantiate objects of various types the same way we do with std::vector: Interval<float> floatInterval(0.0f, 10.0f); Interval<int> intInterval(2, 4); float fMidPt = floatInterval.midpoint(); int iMidPt = intInterval.midpoint(); cout << "fMidPt = " << fMidPt << endl; cout << "iMidPt = " << iMidPt << endl;

Note: A class can contain more than one typename. For example, we can define a class like so: template <typename T1, typename T2> struct Foo T1 a; T2 b; ; When we instantiate a member, we have to specify two types: Foo<float, int> foo; The above substitutes float for T1, and int for T2.

Page 330: C++ Módulo I e II

9

10.2 Example: A Table Template Class

For additional template practice, we will create a template Table class. A Table is sort of like std::vector, except that instead of representing a resizable array, it represents a resizable 2D array—or matrix. This table class will be very useful, as there are many datasets in game development that are represented by a table (a game board/grid and 2D image immediately come to mind). We will want tables of many kinds of data types, so naturally we will make a template Table class.

10.2.1 Table Data

As we start off the design of our Table class, let us first discuss how we shall represent our table. For starters, our table size will not be fixed—it will have m rows and n columns. Since the number of rows and columns is variable, we must use dynamic memory. In this case, we use a pointer to an array of pointers to arrays. This probably sounds confusing, and it is definitely a bit tricky at first, but it will be pretty intuitive once you think about it. We first have a pointer to an array, which we allocate dynamically. This array describes the rows of the table. Now each element in this array is also a pointer. These pointers, in turn, each point to another dynamic array, which forms the columns of the table. Figure 10.1 illustrates.

Figure 10.1: A 5x7 table represented with a pointer to an array of pointers to arrays. That is, we first have a pointer to a “row” array. Each element in this row array, in turn, points to a “column,” thereby forming a 2D table.

Page 331: C++ Módulo I e II

10

Essentially, to have a variable sized 1D array, we needed one pointer to an array. To have a variable sized 2D array (variable in both rows and columns), we need a pointer to an array of pointers to arrays. Furthermore, we will want to maintain the number of rows and columns our table has. This yields the following data members: int mNumRows; int mNumCols; T** mDataMatrix; The double star notation ** means “pointer to a pointer.” This is how we can describe a pointer to an array of pointers to arrays.

10.2.2 Class Interface

As far as methods go, we need to be able to resize a table, construct tables, get the number of rows and columns a table has, provide access to entries in the table, and overload the assignment operator and copy constructor to provide deep copies since our class contains pointer data. The following definition provides these features via the interface: template <typename T> class Table public: Table(); Table(int m, int n); Table(int m, int n, const T& value); Table(const Table<T>& rhs); ~Table(); Table<T>& operator=(const Table& rhs); T& operator()(int i, int j); int numRows()const; int numCols()const; void resize(int m, int n); void resize(int m, int n, const T& value); private: // Make private because this method should only be used // internally by the class. void destroy(); private: int mNumRows; int mNumCols; T** mDataMatrix; ;

Page 332: C++ Módulo I e II

11

The next three subsections discuss three non-trivial methods of Table. The implementations for the rest of the methods are shown in Section 10.2.6.

10.2.3 The destroy Method

The first method we will examine is the destroy method. This method is responsible for destroying the dynamic memory allocated by a Table object. What makes this method a bit tricky is the pointer to an array of pointers to arrays, which stores our table data. The method is implemented as follows: template <typename T> void Table<T>::destroy() // Does the matrix exist? if( mDataMatrix ) // Iterate over each row i. for(int i = 0; i < _m; ++i) // Does the ith column array exist? if(mDataMatrix[i] ) // Yes, delete it. delete[]mDataMatrix[i]; mDataMatrix[i] = 0; // Now delete the row-array. delete[] mDataMatrix; mDataMatrix = 0; // Table was destroyed, so dimensions are zero. mNumRows = 0; mNumCols = 0; Be sure to read the comments slowly and deliberately. First, we traverse over each row and delete the column array that exists in each element of the row array (See Figure 10.1). Once we have deleted the column arrays, we delete the row array.

10.2.4 The resize Method

In this section, we examine the resize method, which is relatively more complicated than the other methods of the Table class. This method is somewhat tricky because it handles the memory allocation of the pointer to an array of pointers to arrays; the method is presented below:

Page 333: C++ Módulo I e II

12

template <typename T> void Table<T>::resize(int m, int n, const T& value) // Destroy the previous data. destroy(); // Save dimensions. mNumRows = m; mNumCols = n; // Allocate a row (array) of pointers. mDataMatrix = new T*[mNumRows]; // Now, loop through each pointer in this row array. for(int i = 0; i < mNumRows; ++i) // And allocate a column (array) for the ith row to build // the table. mDataMatrix[i] = new T[mNumCols]; // Now loop through each element in this row[i] // and copy 'value' into it. for(int j = 0; j < mNumCols; ++j) mDataMatrix[i][j] = value; Again, be sure to read the comments slowly and deliberately. The method takes three parameters: the first two specify the dimensions of the table; that is m by n. The third parameter is a default value to which we initialize all the elements of the table. The very first thing the method does is call the destroy method, as discussed in Section 10.2.3. This makes one thing immediately clear -- we lose the data in the table whenever we call resize. If you want to maintain the data, you will need to copy it into a separate table for temporary storage, resize the current table, and then copy the data in the temporary storage back into the newly resized table. After the resize method, we simply save the new dimensions. The next line: // Allocate a row (array) of pointers. mDataMatrix = new T*[mNumRows]; allocates an array of pointers (see the row in Figure 10.1). What we must do now is iterate over each of these pointers and allocate the column array: // Now, loop through each pointer in this row array. for(int i = 0; i < mNumRows; ++i) // And allocate a column (array) for the ith row to build // the table. mDataMatrix[i] = new T[mNumCols];

Page 334: C++ Módulo I e II

13

After we have done this, we iterate over each column in the i-th row, and initialize the table entry with value: // Now loop through each element in this row[i] // and copy 'value' into it. for(int j = 0; j < mNumCols; ++j) mDataMatrix[i][j] = value; On the whole, it is not too complex if you break the method down into parts, and use Figure 10.1 as a guide.

10.2.5 The Overloaded Parenthesis Operator

The final method we wish to discuss is the overloaded parenthesis operator. Although the implementation is straightforward, we draw attention to this method because we have not overloaded the parenthesis operator before. template <typename T> T& Table<T>::operator()(int i, int j) return mDataMatrix[i][j]; Because C++ does not have a double bracket operator [][], which we can overload, we cannot index into a table as we would a 2D array. We instead overload the parenthesis operator to take two arguments, and instruct the method to use these arguments to index into the internal 2D array, in order to return the i-th table entry. This allows us to index into a table “almost” like the double bracket operator would: Table<float> myTable(4, 4); MyTable(1, 1) = 2.0f; // Access entry [1][1] MyTable(3, 0) = -1.0f; // Access entry [3][0]

10.2.6 The Table Class

For reference, we provide the entire Table definition and implementation together here. Note in particular how the definition and implementation are in the same file—this is necessary for templates. You will be asked to use this class in one of the exercises, so be sure to give it a thorough examination. // Table.h #ifndef TABLE_H #define TABLE_H template <typename T> class Table

Page 335: C++ Módulo I e II

14

public: Table(); Table(int m, int n); Table(int m, int n, const T& value); Table(const Table<T>& rhs); ~Table(); Table<T>& operator=(const Table& rhs); T& operator()(int i, int j); int numRows()const; int numCols()const; void resize(int m, int n); void resize(int m, int n, const T& value); private: // Make private because this method should only be used // internally by the class. void destroy(); private: int mNumRows; int mNumCols; T** mDataMatrix; ; template <typename T> Table<T>::Table<T>() mDataMatrix = 0; mNumRows = 0; mNumCols = 0; template <typename T> Table<T>::Table<T>(int m, int n) mDataMatrix = 0; mNumRows = 0; mNumCols = 0; resize(m, n, T()); template <typename T> Table<T>::Table<T>(int m, int n, const T& value) mDataMatrix = 0; mNumRows = 0; mNumCols = 0; resize(m, n, value); template <typename T> Table<T>::Table<T>(const Table<T>& rhs) mDataMatrix = 0;

Page 336: C++ Módulo I e II

15

mNumRows = 0; mNumCols = 0; *this = rhs; template <typename T> Table<T>::~Table<T>() // Destroy any previous dynamic memory. destroy(); template <typename T> Table<T>& Table<T>::operator=(const Table& rhs) // Check for self assignment. if( this == &rhs ) return *this; // Reallocate the table based on rhs info. resize(rhs.mNumRows, rhs.mNumCols); // Copy the entries over element-by-element. for(int i = 0; i < mNumRows; ++i) for(int j = 0; j < mNumCols; ++j) mDataMatrix[i][j] = rhs.mDataMatrix[i][j]; // return a reference to *this so we can do chain // assignments: x = y = z = w = ... return *this; template <typename T> T& Table<T>::operator()(int i, int j) return mDataMatrix[i][j]; // return the ijth table entry. template <typename T> int Table<T>::numRows()const return mNumRows; // Return the number of rows. template <typename T> int Table<T>::numCols()const return mNumCols; // Return the number of columns. template <typename T> void Table<T>::resize(int m, int n) // Call resize and use default constructor T() // as 'value'. resize(m, n, T());

Page 337: C++ Módulo I e II

16

template <typename T> void Table<T>::resize(int m, int n, const T& value) // Destroy the previous data. destroy(); // Save dimensions. mNumRows = m; mNumCols = n; // Allocate a row (array) of pointers. mDataMatrix = new T*[mNumRows]; // Now, loop through each pointer in this row array. for(int i = 0; i < mNumRows; ++i) // And allocate a column (array) to build the table. mDataMatrix[i] = new T[mNumCols]; // Now loop through each element in this row[i] // and copy 'value' into it. for(int j = 0; j < mNumCols; ++j) mDataMatrix[i][j] = value; template <typename T> void Table<T>::destroy() // Does the matrix exist? if( mDataMatrix ) for(int i = 0; i < _m; ++i) // Does the ith row exist? if(mDataMatrix[i] ) // Yes, delete it. delete[]mDataMatrix[i]; mDataMatrix[i] = 0; // Delete the row-array. delete[] mDataMatrix; mDataMatrix = 0; mNumRows = 0; mNumCols = 0; #endif // TABLE_H

Page 338: C++ Módulo I e II

17

10.3 Function Templates

Template functions extend the idea of template classes. Sometimes you will have a function which performs an operation that can be performed on a variety of data types. For example, consider a search function; naturally, we will want to search arrays of all kinds of data types. It would be cumbersome to implement a search function for each type, especially since the implementation would essentially be the same—only the types would be different. So a search function is a good candidate for a template design. The general syntax of a function template is as follows: template <typename T> return-type FunctionName(parameter-list...) // Function body Next we see two template function examples, one that performs a linear search and a second template function that prints an array. template <typename T> int LinearSearch(T dataArray[], int arraySize, T searchItem) // Search through array. for(int i = 0; i < arraySize; ++i) // Find it? if( dataArray[i] == searchItem ) // Yes, return the index we found it at. return i; // Did not find it, return -1. return -1; template <typename T> void Print(T data[], int arraySize) for(int i = 0; i < arraySize; ++i) cout << data[i] << " "; cout << endl; The only operator LinearSearch uses on the type T is the equals == operator. Thus, any type we use with LinearSearch (i.e., substitute in for T) must have that operator defined (i.e., overloaded). All the built-in types have the equals operator defined, so they pose no problem, and similarly with

Page 339: C++ Módulo I e II

18

std::string. But if we wish to use user-defined types (i.e., classes) we must overload the equals operator if we want to be able to use LinearSearch with them. Similarly, the only operator Print uses on T is the insertion operator. Thus, any type we use with Print (i.e., substitute in for T) must have that operator defined (i.e., overloaded). All the built-in types have the insertion operator defined, so they pose no problem, and similarly with std::string. But if we wish to use user-defined types (i.e., classes) we must overload the insertion operator if we want to be able to use Print with them.

10.3.1 Example Program

What follows is a sample program illustrating our template functions. We set up two arrays, a std::string array, and an int array. We then use the Print function to print both of these arrays, and we allow the user to search these arrays via LinearSearch. The key idea here is that we are using the same template function for different types—that is, these functions are generic and work on any types that implement the stated conditions (overloads the less than operator/insertion operator). Program 10.1: Template Functions.

#include <iostream> #include <string> using namespace std; template <typename T> int LinearSearch(T dataArray[], int arraySize, T searchItem) // Search through array. for(int i = 0; i < arraySize; ++i) // Find it? if( dataArray[i] == searchItem ) // Yes, return the index we found it at. return i; // Did not find it, return -1. return -1; template <typename T> void Print(T data[], int arraySize) for(int i = 0; i < arraySize; ++i) cout << data[i] << " "; cout << endl;

Page 340: C++ Módulo I e II

19

int main() string sArray[8] = "delta", "lambda", "alpha", "beta", "pi", "omega", "epsilon", "phi" ; int numStrings = 8; int iArray[14] = 7,3,32,2,55,34,6,13,29,22,11,9,1,5; int numInts = 14; int index = -1; bool quit = false; while( !quit ) //======================================= // String search. Print(sArray, numStrings); string str = ""; cout << "Enter a string to search for: "; cin >> str; index = LinearSearch(sArray, numStrings, str); if( index != -1 ) cout << str << " found at index " << index << endl; else cout << str << " not found." << endl; cout << endl; //======================================= // Int search. Print(iArray, numInts); int integer = 0; cout << "Enter an integer to search for: "; cin >> integer; index = LinearSearch(iArray, numInts, integer); if( index != -1 ) cout << integer << " found at index " << index<<endl; else cout << integer << " not found." << endl; cout << endl;

Page 341: C++ Módulo I e II

20

//======================================= // Search again? char input = '\0'; cout << "Quit? (y)/(n)"; cin >> input; if( input == 'y' || input == 'Y' ) quit = true; Program 10.1 Output

delta lambda alpha beta pi omega epsilon phi Enter a string to search for: temp temp not found. 7 3 32 2 55 34 6 13 29 22 11 9 1 5 Enter an integer to search for: 32 32 found at index 2 Quit? (y)/(n)n delta lambda alpha beta pi omega epsilon phi Enter a string to search for: beta beta found at index 3 7 3 32 2 55 34 6 13 29 22 11 9 1 5 Enter an integer to search for: 22 22 found at index 9 Quit? (y)/(n)n delta lambda alpha beta pi omega epsilon phi Enter a string to search for: phi phi found at index 7 7 3 32 2 55 34 6 13 29 22 11 9 1 5 Enter an integer to search for: 0 0 not found. Quit? (y)/(n)y Press any key to continue

Page 342: C++ Módulo I e II

21

10.4 Summary

Sometimes we would like to create several versions of a class, where the only difference is the data types involved. Template classes enable us to do this. Specifically, we create a generic class built using generic type-variables, where we can then substitute real concrete types into these type-variables to form new classes. The compiler forms these new classes at compile time. Template classes reduce the number of classes that the programmer must explicitly write, and therefore, they save time and reduce program size/complexity.

1. Template functions are generic and can work on any data type that implements the operations used inside the function body. In this way, we can write one generic function that works for several types, instead of a unique function for each type. Template functions reduce the number of functions that the programmer must explicitly write, and therefore, they save time and reduce program size/complexity.

2. Use templates for classes and functions, which can be generalized to work with more than one

type. std::vector, Table and LinearSearch are examples of classes and functions which can be generalized in this fashion. The following exercises illustrate a few more examples, and Chapter 13, on the STL, demonstrates a very sophisticated generic programming design.

10.5 Exercises

10.5.1 Template Array Class

Reconsider the exercise from Section 7.9.2, where you were instructed to implement a resizable FloatArray class: // FloatArray.h #ifndef FLOAT_ARRAY_H #define FLOAT_ARRAY_H class FloatArray public: // Create a FloatArray with zero elements. FloatArray(); // Create a FloatArray with 'size' elements. FloatArray(int size); // Create a FloatArray from another FloatArray-- // be sure to prevent memory leaks! FloatArray(const FloatArray& rhs);

Page 343: C++ Módulo I e II

22

// Free dynamic memory. ~FloatArray(); // Define how a FloatArray shall be assigned to // another FloatArray--be sure to prevent memory // leaks! FloatArray& operator=(const FloatArray& rhs); // Resize the FloatArray to a new size. void resize(int newSize); // Return the number of elements in the array. int size(); // Overload bracket operator so client can index // into FloatArray objects and access the elements. float& operator[](int i); private: float* mData; // Pointer to array of floats (dynamic memory). int mSize; // The number of elements in the array. ; #endif // FLOAT_ARRAY_H The problem with this class is that it only works with the float type. However, we would naturally want resizable arrays of any type of object. Generalize the class with templates so that it can be used with any type. Test your program by creating a std::string Array, and an int Array, from the generic template class. Call the template class Array.

10.5.2 Template Bubble Sort Function

Reconsider the exercise from Section 3.7.8, where you were instructed to implement a Bubble Sort function like so:

void BubbleSort(int data[], int n);

The problem with this function is that it only works with the int type; however, we would naturally want to sort arrays of any type of object. Generalize the function with templates so that it can be used with any type that implements the operations (e.g., less than, greater than, etc.) needed to implement the bubble sort algorithm. Test your program by sorting a std::string array, and an int array, with the template BubbleSort function.

10.5.3 Table Driver

Rewrite the exercise from Section 2.7.5, but instead use the Table class as discussed in Section 10.2, instead of 2D arrays.

Page 344: C++ Módulo I e II

23

Chapter 11

Errors and Exception Handling

Page 345: C++ Módulo I e II

24

Introduction

Throughout most of the previous chapters, we have assumed that all of our code was designed and implemented correctly and that the results could be anticipated. For example, we assumed that the user entered the expected kind of input, such as a string when a string was expected or a number when a number was expected. Additionally, we assumed that the arguments we passed into function parameters were valid. But this may not always be true. For example, what happens if we pass in a negative integer into a factorial function, which expects an integer greater than or equal to zero? Whenever we allocated memory, we assumed that the memory allocation succeeded, but this is not always true, because memory is finite and can run out. While we copied strings with strcpy, we assumed the destination string receiving the copy had enough characters to store a copy, but what would happen if it did not? It would be desirable if everything worked according to plan; however, in reality things tend to obey Murphy’s Law (which paraphrased says “if anything can go wrong, it will”). In this chapter, we spend some time getting familiar with several ways in which we can catch and handle errors. The overall goal is to write code that is easy to debug, can (possibly) recover from errors, and exits gracefully with useful error information if the program encounters a fatal error.

Chapter Objectives

• Understand the method of catching errors via function return codes, and an understanding of the shortcomings of this method.

• Become familiar with the concepts of exception handling, its syntax, and its benefits. • Learn how to write assumption verification code using asserts.

11.1 Error Codes

The method of using error codes is simple. For every function or method we write, we have it return a value which signifies whether the function/method executed successfully or not. If it succeeded then we return a code that signifies success. If it failed then we return a predefined value that specifies where and why the function/method failed. Let us take a moment to look at a real world example of an error return code system. In particular, we will look at the system used by DirectX (a code library for adding graphics, sound, and input to your applications). Consider the following DirectX function: HRESULT WINAPI D3DXCreateTextureFromFile( LPDIRECT3DDEVICE9 pDevice, LPCTSTR pSrcFile, LPDIRECT3DTEXTURE9 *ppTexture );

Page 346: C++ Módulo I e II

25

Do not worry about what this function does or the data types this function uses, which you are not familiar with. This function has a return type HRESULT, which is simply a numeric code that identifies the success of the function or an error. For instance, D3DXCreateTextureFromFile can return one of the following return codes, which are defined numerically (i.e., the symbolic name represents a number). Which one it returns depends upon what happens inside the function.

• D3D_OK: This return code means that the function executed completely successfully.

• D3DERR_NOTAVAILABLE: This return code means that the hardware cannot create a texture; that is, texture creation is an “unavailable” feature. This is a failure code.

• D3DERR_OUTOFVIDEOMEMORY: This return code means that there is not enough video memory

to put the texture in. This is a failure code.

• D3DERR_INVALIDCALL: This return code means that the arguments passed into the parameters are invalid. For example, you may have passed in a null pointer when the function expects a valid pointer. This is a failure code.

• D3DXERR_INVALIDDATA: This return code means the source data (that is, the texture file) is not

valid. This error could occur if the file is corrupt, or we specified a file that is not actually a texture file. This is a failure code.

• E_OUTOFMEMORY: This return code means there was not enough available memory to perform

the operation. This is a failure code. By examining the return codes from functions that return error codes, we can figure out if an error occurred, what potentially caused the error, and then respond appropriately. For example, we can write the following code: HRESULT hr = D3DXCreateTextureFromFile([...]); // Did an error occur? if( hr != D3D_OK ) // Yes, find our which specific error if( hr == D3DERR_NOTAVAILABLE ) DisplayErrorMsg("D3DERR_NOTAVAILABLE"); ExitProgram(); else if( hr == D3DERR_OUTOFVIDEOMEMORY ) DisplayErrorMsg("D3DERR_OUTOFVIDEOMEMORY"); ExitProgram(); else if( hr == D3DERR_INVALIDCALL )

Page 347: C++ Módulo I e II

26

DisplayErrorMsg("D3DERR_INVALIDCALL"); ExitProgram(); else if( hr == D3DXERR_INVALIDDATA ) DisplayErrorMsg("D3DXERR_INVALIDDATA"); ExitProgram(); else if( hr == E_OUTOFMEMORY ) DisplayErrorMsg("E_OUTOFMEMORY"); ExitProgram(); Here we simply display the error code to the user and then exit the program. Note that DisplayErrorMsg and ExitProgram are functions you would have to implement yourself. They are not part of the standard library.

11.2 Exception Handling Basics

One of the shortcomings of error codes is that for a single function call, we end up writing a lot of error handling code, thereby bloating the size of the program. For example, at the end of the previous section we saw that there were many lines of error handling code for a single function call. The problem becomes worse on a larger scale: FunctionCallA(); // Handle possible error codes FunctionCallB(); // Handle possible error codes FunctionCallC(); // Handle possible error codes ... Such a style of mixing error-handling code in with non-error-handling code becomes so cumbersome that it is seldom religiously followed throughout a program, and therefore, the program becomes unsafe. C++ provides an alternative error-handling solution called exception handling. Exception handling works like this: in a segment of code, if an error or something unexpected occurs, the code throws an exception. An exception is represented with a class object, and as such, can do anything a normal C++ class object can do. Once an exception has been thrown, the call stack unwinds (a bit like returning from functions) until it finds a catch block that handles the exception. Let us look at an example:

Page 348: C++ Módulo I e II

27

Program 11.1: Exception Handling.

#include <iostream> #include <string> using namespace std; class DivideByZero public: DivideByZero(const string& s); void errorMsg(); private: string mErrorMsg; ; DivideByZero::DivideByZero(const string& s) mErrorMsg = s; void DivideByZero::errorMsg() cout << mErrorMsg << endl; float Divide(float numerator, float denominator) if( denominator == 0.0f ) throw DivideByZero("Divide by zero: result undefined"); return numerator / denominator; int main() try float quotient = Divide(12.0f, 0.0f); cout << "12 / 0 = " << quotient << endl; catch(DivideByZero& e) e.errorMsg(); Program 11.1 Output

Divide by zero: result undefined Press any key to continue

The very first thing we do is define an exception class called DivideByZero. Remember that an exception class is just like an ordinary class, except that we use instances of it to represent exceptions.

Page 349: C++ Módulo I e II

28

The next item of importance is in the Divide function. This function tests for a “divide by zero” and if it occurs, we then construct and throw a DivideByZero object exception. Finally, in the main function, in order to catch an exception we must use a try-catch block. In particular, we wrap the code that can potentially throw an exception in the try block, and we write the exception handling code in the catch block. Note that the catch block takes an object. This object is the exception we are looking to catch and handle. It is definitely possible, and quite common, that a function or method will throw more than one kind of exception. We can list catch statements so that we can handle the different kinds of exceptions: try SomeFunction(); catch(LogicError& logic) // Handle logic error exception catch(OutOfMemory& outOfMem) // Handle out of memory exception catch(InvalidData& invalid) // Handle invalid data In addition to a chain of catches, we can “catch any exception” by specifying an ellipses argument: try SomeFunction(); catch(...) // Generic error handling code We can state two immediate benefits of exception handling.

1. The error-handling code (i.e., the catch block) is not intertwined with non-error-handling code; in other words, we move all error handling code into a catch block. This is convenient from an organizational standpoint.

2. We need not handle a thrown exception immediately; rather the stack will unwind until it finds a catch block that handles the exception. This is convenient because, as functions can call other functions, which call other functions, and so on, we do not want to have error handling code after every function/method. Instead, with exceptions we can catch the exception at, say, the top level function call, and any exception thrown in the inner function calls will eventually percolate up to the top function call which can catch the error.

As a final note, be aware that this section merely touched on the basics of exception handling, and there are many more details and special situations that can exist. Also note that the functionality of exception handling is not free, and introduces some (typically minor) performance overhead.

Page 350: C++ Módulo I e II

29

11.3 Assert

In general, your functions and methods make certain assumptions. For example, a “print array” function might assume that the program passes a valid array argument. However, it is possible that you might have forgotten to initialize an array, and consequently, passed in a null pointer to the “print array” function, thus causing an error. We could handle this problem with a traditional error handling system as described in the previous two sections. However, such errors should not be occurring as the program reaches completion. That is, if you are shipping a product that has a null pointer because you forgot to initialize it then you should not be shipping the product to begin with. Error handling should be for handling errors that are generally beyond the control of the program, such as missing or corrupt data resources, incompatible hardware, unavailable memory, flawed input data, and so on. Still, for debugging purposes, it is very convenient to have self-checks littered throughout the program to ensure certain assumptions are true, such as the validity of a pointer. However, based on the previous argument, we should not need these self-checks once we have agreed that the software is complete. In other words, we want to remove these checks in the final version of the program. This is where assert comes in. To use the assert function you must include the standard library <cassert>. The assert function takes a single boolean expression as an argument. If the expression is true then the assertion passes; what was asserted is true. Conversely, if the expression evaluates to false then the assertion fails and a dialog box like the one depicted in Figure 11.1 shows up, along with an assertion message in the console window:

Figure 11.1: Assert message and dialog.

Page 351: C++ Módulo I e II

30

The information the assertion prints to the console is quite useful for debugging; it displays the condition that failed, and it displays the source code file and line number of the condition that failed. The key fact about asserts is that they are only used in the debug version of a program. When you switch the compiler into “release mode” the assert functions are filtered out. This satisfies what we previously sought when we said: “[…] we want to remove these checks [asserts] in the final version of the program.” To conclude, let us look at a complete, albeit simple, program that uses asserts. Program 11.2: Using assert.

#include <iostream> #include <cassert> #include <string> using namespace std; void PrintIntArray(int array[], int size) assert( array != 0 ); // Check for null array. assert( size >= 1 ); // Check for a size >= 0. for(int i = 0; i < size; ++i) cout << array[i] << " "; cout << endl; int main() int* array = 0; PrintIntArray(array, 10); The function PrintIntArray makes two assumptions: 1) The array argument points to something (i.e., it is not null) 2) The array has a size of at least one element. Both of these assumptions are asserted in code: // Check for null array. assert( array != 0 ); // Check for a size >= 0. assert( size >= 1 ); Because we pass a null pointer into PrintIntArray, in main, the assert fails and the dialog box and assert message as shown in Figure 11.1 appear. As an exercise, correct the problem and verify that the assertion succeeds.

Page 352: C++ Módulo I e II

31

11.4 Summary

1. When using error codes to handle errors for every function or method we write, we have it return a value, which signifies whether the function/method executed successfully or not. If it succeeded then we return a code that signifies success. If it failed then we return a predefined value that specifies where and why the function/method failed. One of the shortcomings of error codes is that for a single function call, we end up writing much more error handling code, thereby bloating the size of the program.

2. Exception handling works like this: in a segment of code, if an error or something unexpected

occurs, the code throws an exception. An exception is represented with a class object, and as such, can do anything a normal C++ class object can do. Once an exception has been thrown, the stack unwinds (a bit like returning from functions) until it finds a catch block that handles the exception. One of the benefits of exception handling is that the error-handling code (i.e., the catch block) is not intertwined with non-error-handling code; in other words, we move all error handling code into a catch block. This is convenient from an organizational standpoint. Another benefit of exception handling is that we need not handle a thrown exception immediately; rather the stack will unwind until it finds a catch block that handles the exception. This is convenient because, as functions can call other functions, which call other functions, and so on, we do not want to have error handling code after every function/method. Instead, with exceptions we can catch the exception at the top level function call, and any exception thrown in the inner function calls will eventually percolate up to the top function call which can catch the error. Be aware that the functionality of exception handling is not free, and introduces some performance overhead.

3. To use the assert function, you must include the standard library <cassert>. The assert

function takes a single boolean expression as an argument. If the expression is true then the assertion passes; what was asserted is true. Conversely, if the expression evaluates to false then the assertion fails and a message is displayed along with a dialog box. The key fact about asserts is that they are only used in the debug version of a program. When you switch the compiler into “release mode” the assert functions are filtered out.

11.5 Exercises

11.5.1 Exception Handling

This is an open-ended exercise. You are to come up with some situation in which an exception could be thrown. You then are to create an exception class representing that type of exception. Finally, you are to write a program, where such an exception is thrown, and you should catch and handle the exception. It does not have to be fancy. The goal of this exercise is for you to simply go through the process of creating an exception class, throwing an exception, and catching an exception, at least once.

Page 353: C++ Módulo I e II
Page 354: C++ Módulo I e II

33

Chapter 12

Number Systems; Data Representation; Bit Operations

Page 355: C++ Módulo I e II

34

Introduction

For the most part, with the closing of the last chapter, we have concluded covering the core C++ topics. As far as C++ is concerned, all we have left is a tour of some additional elements of the standard library, and in particular, the STL (standard template library). But first, we will take a detour and become familiar with data at a lower level; more specifically, instead of looking at chars, ints, floats, etc., we will look at the individual bits that make up these types.

Chapter Objectives

• Learn how to represent numbers with the binary and hexadecimal numbering systems, how to perform basic arithmetic in these numbering systems, and how to convert between these numbering systems as well as the base ten numbering system.

• Gain an understanding of how the computer describes intrinsic C++ types internally.

• Become proficient with the various binary operations.

• Become familiar with the way in which floating-point numbers are represented internally.

12.1 Number Systems

We are all experienced with working in a base (or radix) ten number system, called the decimal number system, where each digit has ten possible values ranging from 0-9. However, after some reflection, it is not difficult to understand that selecting a base ten number system is completely arbitrary. Why ten? Why not two, eight, sixteen, and so on? You might suggest that ten is a convenient number, but it is only convenient because you have presumably worked in it your whole life. Throughout this chapter, we will become familiar with two other specific number systems, particularly the base two binary system and the base sixteen hexadecimal systems. We choose these systems because they are convenient when working with computers, as will be made clear in Section 12.4. These new numbering systems may be cumbersome to work with at first, but after a bit of practice you will become very fast at manipulating numbers in these systems. In order to distinguish between numbers in different systems, we will adopt a subscript notation when the context might not be clear:

• 10n : The number n is in the decimal number system. • 2n : The number n is in the binary number system. • 16n : The number n is in the hexadecimal number system.

Page 356: C++ Módulo I e II

35

12.1.1 The Windows Calculator

Microsoft Windows ships with a calculator program that can work in binary, octal (a base eight number system we will not discuss), decimal, and hexadecimal. In addition to the arithmetic operations, the program allows you switch (i.e., convert) from one system to the other by simply selecting a radio button. Furthermore, the calculator program can even do logical bit operations AND, OR, NOT, XOR (exclusive or), which we discuss in Section 12.5. Figures 12.1a-12.1e give a basic overview of the program.

Figure 12.1a: The calculator program in “standard” view. To use the other number systems we need to switch to “scientific” view.

Figure 12.1b: The calculator program in “scientific” view. Notice the four radio buttons, which allow us to switch numbering systems any time we want.

Page 357: C++ Módulo I e II

36

Figure 12.1c: Here we enter a number in the decimal system. We also point out the logical bit operations AND, OR, NOT, and XOR (exclusive or), which are discussed in Section 12.5.

Figure 12.1d: Here we switched from decimal to binary. That is, “11100001” in binary is “225” in decimal. So we can input a number in any base we want and then convert that number into another base by simply selecting the radio button that corresponds to the base we want to convert to. In addition, we also point out that the program automatically limits you to two possible values in binary.

Page 358: C++ Módulo I e II

37

Figure 12.1e: Here we switched from binary to hexadecimal. That is, “11100001” in binary is “E1” in hexadecimal, which is “225” in decimal. We also point out that there are now sixteen possible hexadecimal numbers to work with. Do not worry too much about this now; we discuss hexadecimal in Section 12.3.

You can use this calculator program to check your work for the exercises.

12.2 The Binary Number System

12.2.1 Counting in Binary

The binary number system is a base two number system. This means that only two possible values per digit exist; namely 0 and 1. In binary, we count similarly to the way we count in base ten. For example, in base ten we count 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, and then, because we have run out of values, to count ten we reset back to zero and add a new digit which we set to one, to form the number 10. In binary, the concept is the same except that we only have two possible values per digit. Thus, we end up having to “add” a new digit much sooner than we do in base ten. We count 0, 1, and then we already need to add a new digit. Let us count a few numbers one-by-one in binary to get the idea. For each number, we write the equivalent decimal number next to it.

Page 359: C++ Módulo I e II

38

102 00 =

102 11 = There is no ‘2’ in binary—only 0 and 1—so at this point, we must add a new digit:

102 210 =

102 311 = Again, we have run out of values in base two, so we must add another new digit to continue:

102 4100 =

102 5101 =

102 6110 =

102 7111 =

102 81000 =

102 91001 =

102 101010 =

102 111011 =

102 121100 =

102 131101 =

102 141110 =

102 151111 =

102 1610000 = It takes time to become familiar with this system as it is easy to confuse the binary number 21111 for the decimal number 101111 until your brain gets used to distinguishing between the two systems.

12.2.2 Binary and Powers of 2

An important observation about the binary number system is that each “digit place” corresponds to a power of 2 in decimal:

100102 121 ==

101102 2210 ==

102102 42100 ==

103102 821000 ==

104102 16210000 ==

Page 360: C++ Módulo I e II

39

105102 322100000 ==

106102 6421000000 ==

107102 128210000000 ==

108102 2562100000000 ==

109102 51221000000000 ==

1010102 1024201000000000 ==

1011102 20482001000000000 ==

1012102 409620001000000000 ==

… You should memorize these binary to decimal powers of two up to 10

1210 40962 = , in the same way you

memorized your multiplication tables when you were young. You should be able to recollect them quickly. This will allow you to convert binary numbers into decimal numbers and decimal numbers into binary numbers quickly in your head. As an aside, in base ten we have a similar pattern, but because we are in base ten, the powers are powers of ten instead of powers of two:

01010 101 =

11010 1010 =

21010 10100 =

31010 101000 =

41010 1010000 =

12.2.3 Binary Arithmetic

We can add numbers in binary just like we can in decimal. Again, the only difference being that binary only has two possible values per digit. Let us work out a few examples.

Addition

Example 1: 10 + 1 ----- ?

Page 361: C++ Módulo I e II

40

We add columns just like we do in decimal. The first column yields 0 + 1 = 1, and the second also 1 + 0 = 1. Thus, 10 + 1 ----- 11 Example 2: 101 + 11 ------ ? Adding columns from right to left, we have 1 + 1 = 10 in binary for the first column. So we write zero for this column and carry a one over to the next digit place: 1 101 + 11 ------ 0 Adding the second column leads to the previous situation, namely, 1 + 1 = 10. So we write zero for this column and carry a one over to the next digit place: 11 101 + 11 ------ 00 Adding the third column again leads to the previous situation, namely, 1 + 1 = 10. So we write zero for this column and carry a one over to the next digit place, yielding our final answer: 11 101 + 11 ------ 1000 Example 3: 10110 + 101 -------- ? Adding the first column (right most column) we have 0 + 1 = 1. So we write 1 for this column:

Page 362: C++ Módulo I e II

41

10110 + 101 -------- 1 Adding the second column, we again have 1 + 0 = 1: 10110 + 101 -------- 11 The third column yields 1 + 1 = 10, so we write zero for this column and carry over 1 to the next digit place: 1 10110 + 101 -------- 011 Add the fourth and fifth columns both yield 1 + 0 = 1: 1 10110 + 101 -------- 11011

Subtraction

Example 4: 10 - 1 ----- ? We subtract the columns from right to left as we do in decimal. In this first case we must “borrow” like we do in decimal: 10 00 - 1 ----- ? Now 10 – 1 in binary is 1, thereby giving the answer:

Page 363: C++ Módulo I e II

42

10 00 - 1 ----- 1 Example 5: 101 - 11 ------ ? Subtracting the first column we have 1 – 1 = 0. So we write one in the first column: 101 - 11 ------ 0 In the second column, we must borrow again yielding: 10 001 - 11 ------ 10 Example 6: 10110 - 101 -------- ? Here the first column must borrow, and then 10 – 1 = 1: 10 10100 - 101 -------- 1 In the second column we now have 0 – 0 = 0, in the third column we have 1 – 1 = 0, in the fourth column we have 0 – 0 = 0, and in the fifth column we have 1 – 0 = 1, which gives the answer: 10 10100 - 101 -------- 10001

Page 364: C++ Módulo I e II

43

Multiplication

Multiplying in binary is the same as in decimal; except all of our products have only four possible forms: 00× , 10× , 01× , and 11× . As such, binary multiplication is pretty easy. Example 7: 10 x 1 ----- 10 Example 8: 101 x 11 ------ 101 +1010 ------ 1111 Example 9: 10110 x 101 -------- 10110 000000 +1011000 --------- 1101110

12.2.4 Converting Binary to Decimal

There is a mechanical formula, which can be used to convert from binary to decimal, and it is useful for large numbers. However, in practice, we do not typically need to work with large numbers, and when we do, we use a calculator. So rather than show you the mechanical way, we will develop a way to convert mentally in our head. The mental conversion from binary to decimal relies on the fact that in the binary number system, each “digit place” corresponds to a power of 2 in decimal, as we showed in Section 12.2.2. Consider the following binary number:

211100001

Page 365: C++ Módulo I e II

44

Let us rewrite this number as the following sum: (1) 22222 0000000100100000010000001000000011100001 +++= From Section 12.2.2 we know each “digit place” corresponds to a power of 2 in decimal. In particular, we know:

107102 128210000000 ==

106102 6421000000 ==

105102 322100000 ==

100102 121 ==

Substituting the decimal equivalents into (1) yields:

10101010102 2251326412811100001 =+++= .

12.2.5 Converting Decimal to Binary

Converting from decimal to binary is similar. Consider the following decimal number:

10225 First we ask ourselves, what is the largest power of two that can fit into 10225 ? From Section 2.2.2 we find that 10

710 1282 = is the largest that will fit. We now subtract 10

710 1282 = from 10225 and get:

101010 97128225 =−

We now ask, what is the largest power of two that can fit into 1097 ? We find 10

610 642 = is the largest

that will fit. Again we subtract 10610 642 = from 1097 and get:

101010 336497 =−

We ask again, what is the largest power of two that can fit into 1033 ? We find that 10

510 322 = is the

largest that will fit. We subtract 10510 322 = from 1033 and get:

101010 13233 =−

Page 366: C++ Módulo I e II

45

Finally, we ask, what is the largest power of two that can fit into 101 ? We find that 10010 12 = is the

largest that will fit. We subtract 101 from 101 and get zero. Essentially, we have decomposed 10225 into a sum of powers of two; that is: (2) 0

10510

610

7101010101010 222213264128225 +++=+++=

But, we have the following binary to decimal relationships from Section 2.2.2:

107102 128210000000 ==

106102 6421000000 ==

105102 322100000 ==

100102 121 ==

Substituting the binary equivalents into (2) for the powers of twos yields:

2222210 1110000100000001001000000100000010000000225 =+++=

12.3 The Hexadecimal Number System

12.3.1 Counting in Hexadecimal

The hexadecimal (hex for short) number system is a base sixteen number system. This means that sixteen possible values per digit exists; namely 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F. Notice that we must add new symbols to stand for the values above 9 that can be placed in a single digit; that is, A = 1010 , B = 1011 , C = 1012 , D = 1013 , E = 1014 , F = 1015 . As we did in binary, let us count a few numbers one-by-one to get a feel for hex. For each number, we write the equivalent decimal and binary number next to it.

21016 000 == 21016 100001610 == 21016 1000003220 ==

21016 111 == 21016 100011711 == 21016 1000013321 ==

21016 1022 == 21016 100101812 == 21016 1000103422 ==

21016 1133 == 21016 100111913 == 21016 1000113523 ==

21016 10044 == 21016 101002014 == 21016 1001003624 ==

21016 10155 == 21016 101012115 == 21016 1001013725 ==

21016 11066 == 21016 101102216 == 21016 1001103826 ==

21016 11177 == 21016 101112317 == 21016 1001113927 ==

Page 367: C++ Módulo I e II

46

21016 100088 == 21016 110002418 == 21016 1010004028 ==

21016 100199 == 21016 110012519 == 21016 1010014129 ==

21016 101010 ==A 21016 11010261 ==A 21016 101010422 ==A

21016 101111 ==B 21016 11011271 ==B 21016 101011432 ==B

21016 110012 ==C 21016 11100281 ==C 21016 101100442 ==C

21016 110113 ==D 21016 11101291 ==D 21016 101101452 ==D

21016 111014 ==E 21016 11110301 ==E 21016 101110462 ==E

21016 111115 ==F 21016 11111311 ==F 21016 101111472 ==F

12.3.2 Hexadecimal Arithmetic

Again, as with binary, hex arithmetic is the same as decimal arithmetic in concept. The only difference being the number of digits we are working with.

Addition

Example 1: A + 5 ---- ? We know A is 10 in decimal, so 10 + 5 is 15, but 15 decimal corresponds to F in hex, thus: A + 5 ---- F Example 2: 53B + 2F ------ ? Again, it is probably easiest to convert from hex to decimal and then back again as you perform the sum, column-by-column. B + F correspond to 11 + 15 in decimal, which gives 26 decimal. Converting that to hex gives 1A. So we write an A down for the first column and carry a one over:

Page 368: C++ Módulo I e II

47

1 53B + 2F ------ A The second column gives 2 + 3 + 1, which is the number 6. And the third column gives 5. Thus we have: 1 53B + 2F ------ 56A

Subtraction

Example 3: 53B - 2F ------ ? Starting the subtraction at the rightmost column, we have B – F. But, because B < F, we must borrow. Borrowing yields: 10 52B - 2F ------ ? Note that the borrowed “10” is in hex, and corresponds to 16 in decimal. So we have 10 + B – F, or in decimal 16 + 11 – 15 = 12, which C in hex: 10 52B - 2F ------ C Subtracting the second column we get 2 – 2 = 0, and the third 5 – 0 = 5. Thus the answer is: 10 52B - 2F ------ 50C

Page 369: C++ Módulo I e II

48

Multiplication

Example 4: 20BA X 12 ------ ? Performing the multiplication yields: 11 20BA X 12 ------ 1 4174 +20BA0 ------- 24D14

12.3.3 Converting Hexadecimal to Binary

In converting hex to binary, the key idea is that each hex digit can be described by exactly four binary digits. This is because it takes four binary digits to describe a decimal number in the range [0, 15], which is the decimal range a hex digit takes on ([0, F]). Consequently, to convert from hex to binary we write four place holders underneath each hex digit, as Figure 12.2 shows:

Figure 12.2: Setting up binary placeholders for a conversion from hex to binary.

We then convert each hex digit to its binary form, which is quite easy to do mentally since we only need to look at four binary digits at a time. Figure 12.3 shows the conversion of each hex digit to binary:

Page 370: C++ Módulo I e II

49

Figure 12.3: Converting each hex digit to binary.

As we can see, we made the following conversions:

10216

10216

10216

10216

10216

300113101010151111111011810008

==========

AFB

Thus, we have 216 1110100011100010111138 =BFA .

Note: You will find it is quite easy to convert between binary and hex because both number systems are powers of 2. Converting between these bases to and from octal (base 8) is equally easy, again, because base 8 is also a power of two. One of the reasons we like to work in hex is because we can mentally “visualize” the binary digit layout in hex just as clearly as seeing the actual binary number. In addition, hex has the nice property of being able to describe the same number with a smaller number of digits. That is, big numbers in binary take a lot of digits to describe, so from that standpoint, hex is much more compact.

12.3.4 Converting Binary to Hexadecimal

Converting from binary to hex is equally easy. Consider the number 2101110001110101111 . We group the binary digits into sets of four starting from the right to the left:

Figure 12.4: Dividing a binary number into groups of four, from right to left.

Note that if the last (left-most) binary digits do not form a set of four, we then pad the set with zeros to make it four. For each set of four binary digits we equate that to a single hex digit (0-F). If you memorized the binary to decimal power of two conversions from Section 2.2.2 and how hex digits relate to decimal, then this is easy to do mentally.

Page 371: C++ Módulo I e II

50

Figure 12.5: Converting each binary group of four to hex.

Thus, we have 162 83101110001110101111 AFB= .

Note: We do not discuss conversions between hex and decimal because we can convert either one to binary first, and then from binary we convert to either hex or decimal.

12.4 Bits and Memory

It is typically common computer knowledge, even among laypersons, that a computer (at least most machines) works with 0’s and 1’s internally—the so-called “on-off” switches. Indeed, if we recall from Chapter 1 in Module I, the compiler is responsible for translating our C++ code into machine code, which is all 0’s and 1’s. For this reason, a computer’s natural number system is binary. The term bit comes from binary digit. So when we talk about an 8-bit byte, we are talking about a piece of memory that is represented by 8-bits; that is, 8 binary digits are used to express a byte sized piece of information. Similarly, when we talk about a 32-bit integer, we are talking about a piece of memory that is represented by 32-bits. Recall the following abridged table from Chapter 1, which are intrinsic type sizes on a 32-bit Windows system: Type Range Bytes Required unsigned char [0, 255] 1 unsigned short [0, 65535] 2 unsigned int [0, 4294967295] 4 Now that we know something about the binary number system we can see where these type ranges stem from. Given 8-bits to work with, we can express binary numbers in the range

]11111111,00000000[ 22 . However, that range is equivalent to ]255,0[ 1010 in decimal. Given 16-bits to work with, we can express binary numbers in the range

]1111111111111111,0000000000000000[ 22 . However, that range is equivalent to ]65535,0[ 1010 in decimal. The same logic can be made for 32-bit numbers, 64-bit numbers, and so on. We now see where those type ranges stem from. Clearly, the more bits we use, the more kinds of numbers we can represent.

Page 372: C++ Módulo I e II

51

Note: We do not discuss negative number representations here, as we are just trying to get a flavor for the underlying data representations. A more detailed look at internal computer data representation is best left to a course in computer architecture.

12.5 Bit Operations

As stated, a bit (binary digit) is a 0 or 1, which can be thought of as “on-off” or even as a “true-false” value. As such, it would make sense to be able to apply logical operations to bits. In our vocabulary, we say a bit is set if it is on; that is, it has a value of 1. Conversely, a bit is not set if it is off; that is, it has a value of 0.

12.5.1 AND

First, it should be noted that a bit is not directly addressable memory. The smallest addressable memory block is a byte, by definition. So when we do bit operations, we are doing them on the set of bits that make up larger integer types like chars, ints, and so on. That said, the bitwise AND examines the one-to-one corresponding bits in two segments of addressable memory. For illustration purposes we will work with 8-bit chars. For each corresponding bit, the AND operation asks if both bits are set (i.e., are both “true”), and if so, the AND operation returns true (1) for that bit, otherwise it returns false (0) for that bit. The bitwise AND is performed with a single & symbol. Here is an example: unsigned char A = 0xB9; unsigned char B = 0x91; unsigned char C = A & B; // AND the bits and store the result in C. To see what is happening, let us look at the binary: A = 1011 1001 B = 1001 0001 1011 1001 & 1001 0001 ----------- C = 1001 0001 So, what we do is match each corresponding bit in A and B together, and if both bits are set, then the corresponding bit in C is set. If both bits are not set, then the corresponding bit in C is not set. As you can see, only the first bit, fifth bit, and eighth bit are both on in A and B, and thus those are the only bits set in C.

Note: The “0x” syntax means we are specifying a number in hexadecimal.

Page 373: C++ Módulo I e II

52

12.5.2 Inclusive OR

The bitwise OR examines the one-to-one corresponding bits in two segments of addressable memory. For illustration purposes we will work with 8-bit chars. For each corresponding bit, the OR operation asks if at least one of the two bits is set (i.e., is at least one “true”), and if so, the OR operation returns true (1) for that bit, otherwise it returns false (0) for that bit. The bitwise OR is performed with a single vertical bar | symbol. Here is an example: unsigned char A = 0xB9; unsigned char B = 0x91; unsigned char C = A | B; // OR the bits and store the result in C. To see what is happening, let us look at the binary: A = 1011 1001 B = 1001 0001 1011 1001 | 1001 0001 ----------- C = 1011 1001 We match each corresponding bit in A and B together, and if at least one of the two bits is set, then the corresponding bit in C is set. If neither bit is set, then the corresponding bit in C is not set.

12.5.3 NOT

The bitwise NOT operator, or complement operator, simply negates each bit. That is, an on bit (1) becomes on off bit (0), and conversely, an off bit (0) becomes an on bit (1). The bitwise NOT operator is a unary operator and its symbol is the tilde (~). Here is an example: unsigned char A = 0xB9; unsigned char C = ~A; // NOT the bits in A and store the result in C. To see what is happening, let us look at the binary: A = 1011 1001 ~ 1011 1001 ----------- C = 0100 0110 As you can see, the bitwise NOT operator, or complement operator, simply negates each bit. That is, an on bit (1) becomes on off bit (0), and conversely, an off bit (0) becomes an on bit (1).

Page 374: C++ Módulo I e II

53

12.5.4 Exclusive OR

The bitwise exclusive or (XOR) examines the one-to-one corresponding bits in two segments of addressable memory. For illustration purposes we will work with 8-bit chars. For each corresponding bit, the XOR operation asks if one of the two bits is set, but not both (i.e., one is “true” but not both), and if this condition is so, the XOR operation returns true (1) for that bit, otherwise it returns false (0) for that bit. The bitwise XOR is performed with a caret ^ symbol. Here is an example: unsigned char A = 0xB9; unsigned char B = 0x91; unsigned char C = A ^ B; // XOR the bits and store the result in C. To see what is happening, let us look at the binary: A = 1011 1001 B = 1001 0001 1011 1001 ^ 1001 0001 ----------- C = 0010 1000 We match each corresponding bit in A and B together, and if one of the two bits is set but not both, then the corresponding bit in C is set. If either bit is set or both bits are set, then the corresponding bit in C is not set.

12.5.5 Shifting

The bit-shifting operation is not a logical operation. The bit-shifting operator allows you to shift the bits to the left or right. Let us look at an example: unsigned char A = 0xB9; unsigned char B = 0x91; unsigned char C = A << 3; // shift bits in A three bits to the left. unsigned char D = B >> 2; // shift bits in B two bits to the right. The symbol << means “left shift” and the symbol >> means “right shift.” The right hand operand specifies how many bits to shift. So in the above example, C = A << 3 means shift the bits in A three bits to the left, and store the result in C; and D = B >> 2 means shift the bits in B two bits to the right, and store the result in D. Let us look at the binary to see what is happening more closely: A = 1011 1001 B = 1001 0001

Page 375: C++ Módulo I e II

54

C = 1011 1001 << 3 = 1100 1000 D = 1001 0001 >> 2 = 0010 0100 Can you see how the bits “shifted over” by the specified number? Note that when you shift bits “off” the memory region you are working with, they are lost—they do not “loop” back around.

12.5.6 Compound Bit Operators

C++ provides compound bit operators as well. The following table summarizes: Compound Operator

Meaning

A &= B A = A & B A |= B A = A | B A ^= B A = A ^ B A <<= B A = A << B A >>= B A = A >> B

12.6 Floating-Point Numbers

Before concluding this chapter, we want to spend a little time giving you an overview of how floating-point numbers are represented. The floating-point representation your common PC uses is the IEEE 754 (Institute of Electrical and Electronics Engineers) standard, which was a standard developed in order to increase software portability among computers. A floating-point number is represented in a normalized scientific notation such as 12101234.5 ×± , and has three components: 1) a sign bit 2) an exponent 3) a mantissa (non-exponent part) Normalized means that there is only one non-zero digit before the radix point (we say radix point to be general since we may be working in number systems other than decimal, so we do not call it a decimal point). A normalized form is used so that the same number cannot be described in two different forms. For example, the following are equivalent:

012 1014.3104.31100.314 ×=×=× −− Having two different forms of the same number can make implementation of floating-point numbers more complicated. Therefore, we always use the normalized form ( 01014.3 × in the above example) so that there is only one form of a given number.

Page 376: C++ Módulo I e II

55

As some background vocabulary, the exponent part of a floating-point number determines the range of the number (how big and small it can get) and the mantissa part of a floating-point number determines the precision of the number (the “step” size between two adjacent numbers on the number line). The more bits we devote to the exponent part, the larger the range becomes, and the more bits we devote to the mantissa part, the more precise the number becomes. Because the radix point always follows the left-most non-zero digit in normalized form (e.g.,

01014.3 × ), in binary the left most non-zero binary digit will always be 1 in normalized form. Here are some examples of normalized binary numbers:

320101.1 × 52011101.1 −× 820100001.1 ×

Because the left-most non-zero digit is always a 1 in normalized binary form, we do not need to store that 1, as we can assume it is always there, and thus get an extra bit of precision. This understood bit is called the hidden bit. The following figure shows the layout of a 32-bit floating-point number (i.e., a float) and a 64-bit float-point number (i.e., a double).

Figure 12.6: 32-bit float layout and 64-bit double layout.

There are some special reserved bit patterns that take on special meaning:

• An exponent part of all zeros and a mantissa of all zeros (in binary) are interpreted as zero. • An exponent part of all ones and a mantissa of all zeros (in binary) are interpreted as infinity. • An exponent part of all ones and a non-zero mantissa (in binary) are interpreted as “not a

number” (NaN), which is used to denote the result of undefined operations such as division by zero, division of infinity over infinity, and the like.

This is all we want to say about floating-point numbers in an introductory C++ course. A more detailed look at internal computer data representation is best left to a course in computer architecture.

Page 377: C++ Módulo I e II

56

12.7 Summary

1. The binary number system is a base two number system. This means that two possible values per digit exists; namely 0 and 1. The hexadecimal (hex for short) number system is a base sixteen number system. There are exists sixteen possible values per digit; namely 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F. Notice that we must add new symbols to stand for the values above 9 that can be placed in a single digit; that is, A = 1010 , B = 1011 , C = 1012 , D = 1013 , E =

1014 , F = 1015 .

2. The term “bit” comes from “binary digit.” When we talk about an 8-bit byte, we are talking about a piece of memory that is represented by 8-bits; that is, 8 binary digits are used to express a byte sized piece of information. Similarly, when we talk about a 32-bit integer, we are talking about a piece of memory that is represented by 32-bits. Given 8-bits to work with, we can express binary numbers in the range ]11111111,00000000[ 22 . However, that range is equivalent to ]255,0[ 1010 in decimal. Given 16-bits to work with, we can express binary numbers in the range ]1111111111111111,0000000000000000[ 22 . However, that range is equivalent to ]65535,0[ 1010 in decimal. The same logic can be made for 32-bit numbers, 64-bit numbers, and so on. We now see where those type ranges come from. Clearly, the more bits we use, the more kinds of numbers we can represent.

3. Five bit operations exist:

a. A bitwise AND written as A & B, which for each corresponding bit in A and B, returns a

“true” bit if the bits are both “true”, otherwise it returns a “false” bit. b. A bitwise OR written as A | B, which for each corresponding bit in A and B, returns a

“true” bit if at least one of the bits is “true,” otherwise it returns a “false” bit. c. A bitwise NOT written as ~A, which negates each bit in A (“true” bits become “false”

and conversely). d. A bitwise XOR written as A ^ B, which for each corresponding bit in A and B, returns a

“true” bit if one of the bits is “true” but not both, otherwise it returns a “false” bit. e. A bit shift operation written as A << m for a shift in the left direction of m bit spaces, or

as A >> n for a shift in the right direction of n bit spaces.

4. A floating-point number is represented in a normalized scientific notation such as 12101234.5 ×± , and has three components: 1) a sign bit, 2) an exponent, and a mantissa (non-

exponent part). Normalized means that there is only one non-zero digit before the radix point (we say radix point to be general since we may be working in number systems other than decimal; we do not call it a decimal point).

5. The exponent part of a floating-point number determines the range of the number (how big and

small it can get) and the mantissa part of a floating-point number determines the precision of the number (the “step” size between two adjacent numbers on the number line).

Page 378: C++ Módulo I e II

57

6. Because the radix point always follows the left-most nonzero digit in normalized form (e.g., 01014.3 × ), in binary the left most nonzero binary digit will always be 1 in normalized form.

Because the left-most nonzero digit is always a 1 in normalized binary form, we do not really need to store that 1, as we can assume it is always there, and thus get an extra bit of precision. This understood bit is called the “hidden bit.”

12.8 Exercises

12.8.1 Binary Arithmetic

Perform the following binary arithmetic operations. Use a calculator, such as the Windows calculator program, to check your work. 110 1111 010101 11001101 + 101 + 1111 + 101010 + 11110101 ------ ------- --------- ---------- ? ? ? ? 110 1111 1010101 1111001101 - 101 - 1111 - 101010 - 11110101 ------ ------- --------- ------------ ? ? ? ? 110 1111 10101 11001101 x 10 x 11 x 110 + 1 ------ ------- -------- ---------- ? ? ? ?

12.8.2 Hex Arithmetic

Perform the following hex arithmetic operations. Use a calculator, such as the Windows calculator program, to check your work. AAA BB FEDCA 12FE5BA + 555 + A4 + 1234 + 45AFCD ------ ----- ------- --------- ? ? ? ?

Page 379: C++ Módulo I e II

58

AAA BB FEDCA 12FE5BA - 555 - A4 - 1234 - 45AFCD ------ ----- ------- --------- ? ? ? ? AAA BB FEDCA 12FE5BA x 5 x A4 x A x CF ------ ------ ------- --------- ? ? ? ?

12.8.3 Base Conversions

For the following conversions, use a calculator, such as the Windows calculator program, to check your work. Convert the following decimal numbers to binary:

210 ?22 =

210 ?63 =

210 ?95 =

210 ?133 =

Convert the following binary numbers to decimal:

102 ?10001 =

102 ?101101 =

102 ?10101010 =

102 ?11111011 = Convert the following binary numbers to hex:

102 ?1101 =

102 ?11000101 =

102 ?1110111101101010 =

102 ?01101110101011111001101 = Convert the following hex numbers to binary:

216 ?5 =B

Page 380: C++ Módulo I e II

59

216 ?9 =BD

216 ?34 =DFBC

216 ?7654321 =FEDCBA

12.8.4 Bit Operations

For the following operations, use a calculator, such as the Windows calculator program, to check your work. Note that some operations may not be available on a calculator. If that is the case, write a C++ program to do the operation in order to check your work. Given: unsigned char A = 0xF8; unsigned char B = 0x5A; Evaluate the following expressions for C; give your answer in binary:

1. unsigned char C = A & B;

2. unsigned char C = A | B;

3. unsigned char C = ~A;

4. unsigned char C = ~B;

Page 381: C++ Módulo I e II

60

5. unsigned char C = A ^ B;

6. unsigned char C = A << 2;

7. unsigned char C = A >> 3;

8. unsigned char C = B << 1;

9. unsigned char C = B >> 4;

Page 382: C++ Módulo I e II

61

12.8.5 Binary to Decimal

Write a program that asks the user to input a binary string (e.g., “11100010101”). Note that the input is a string, not a number. The string specifies a binary number. The program then is to convert the binary number which the string specifies into a decimal number and output the result. Your output should look like this: Enter a binary string: 101 101 = 5 in decimal. Press any key to continue.

12.8.6 Decimal to Binary

Write the reverse conversion of Exercise 12.8.5. That is, write a program that asks the user to input a decimal number (this time it is not a string but a number). The program then is to convert the decimal number which the user entered into a binary string (e.g., “11100010101”) and output the result. Your output should look like this: Enter a decimal number: 14 14 = 1110 in binary. Press any key to continue.

12.8.7 Bit Operation Calculator

Write a program that allows the user to enter in two integer values, A and B, in radix ten, and store them in chars, shorts, or ints (your choice depending on the size of the numbers you want to support). Convert these decimal numbers to binary string representations, as you did in Exercise 12.8.6, and output the binary form to the user. Then have the program compute and output (in binary) A & B, A | B, ~A, ~B, A ^ B, A << 2, and B >> 3. For example, in the program you will write (assuming we are working with chars): unsigned char C = A & B; We now want to output the result of A & B (that is C) as a binary string, so we pump C through our decimal to binary string converter for output: cout << “A & B = “ << ToBinaryString( C ) << endl; Your output should look like this: Enter a number A in the range [0, 255]: 5 Enter a number B in the range [0, 255]: 8

Page 383: C++ Módulo I e II

62

A = 5 in binary is 00000101 B = 8 in binary is 00001000 A & B = 00000000 A | B = 00001101 ~A = 11111010 ~B = 11110111 A ^ B = 00001101 A << 2 = 00010100 B >> 3 = 00000001

12.9 References

Murdocca, Miles J., and Vincent P. Heuring. Principles of Computer Architecture. Prentice Hall, 2000.

Page 384: C++ Módulo I e II

63

Chapter 13

STL Primer

Page 385: C++ Módulo I e II

64

Introduction

In this chapter, we take a tour of the standard template library and examine several data structures, algorithms, and programming paradigms. Essentially, data structures are objects for storing collections of data. An array is a data structure at the most primitive level, and although arrays have some advantages (fast indexing of elements), they also have some pitfalls (slow arbitrary insertion), which motivate the employment of better suited data structures for the task at hand. Algorithms act on data structures. Sorting, searching, generating, removing, and counting are all examples of operations that we would apply on data structures in this chapter using the pre-built standard template library algorithm functions.

Chapter Objectives

• Discover how lists, stacks, queues, deques, and maps work internally, and in which situations they should be used.

• Become familiar with a handful of the generic algorithms the standard library provides and how

to apply these algorithms on a variety of data structures.

• Learn how to create objects that act like functions, called functors, and learn how to create and use predicates with the standard library.

13.1 Problems with Arrays

New students often wonder at first why we even need other data structures. Why not simply use arrays (or the resizable std::vector) for everything? To motivate our discussion of various data structures, let us first discuss some problems with arrays, and also with std::vector, as std::vector is implemented internally using arrays. Suppose that we have an array of ten integers, where the first five array elements are used and the second five are free:

Figure 13.1: An array with five empty elements.

Assume that we want to insert a new integer 7 at element [2]. Because an integer already exists at [2], and assuming we do not want to overwrite that value, we will shift the elements starting at [2] over one element to the right, in order to make room for the new integer 7:

Page 386: C++ Módulo I e II

65

Figure 13.2: Shifting some array elements to the right to make room for an insertion into element [2].

We can now write 7 to element [2]:

Figure 13.3: The array after insert 7 into element [2].

The point of the previous exercise is to demonstrate that inserting values into arbitrary array positions requires some work. In particular, the work required is shifting the elements around to make room for the value to be inserted. (Note: a similar argument can be made for deletion of an arbitrary element.) With large arrays, this can require a lot of shifts and these memory shifts can get expensive. The key point to understand is that arrays are not inherently suited for arbitrary data insertions. However, inserting at the end of an array requires no shifting. Still, there is another problem with arrays. Specifically, arrays are slow to resize. Suppose that we have filled up the ten elements from the previous array and need more space. We must then resize the array. Recall from Chapter 4 in Module I the following function: int* ResizeArray(int* array, int oldSize, int newSize) // Create an array with the new size. int* newArray = new int[newSize]; // New array is a greater size than old array. if( newSize >= oldSize ) // Copy old elements to new array. for(int i = 0; i < oldSize; ++i) newArray[i] = array[i]; // New array is a lesser size than old array. else // newSize < oldSize // Copy as many old elements to new array as can fit. for(int i = 0; i < newSize; ++i) newArray[i] = array[i]; // Delete the old array. delete[] array; // Return a pointer to the new array. return newArray;

Page 387: C++ Módulo I e II

66

We must allocate new memory for the newly resized array and then copy the elements from the old array into the new array. Again, this is slow for large arrays. We have an expensive memory allocation and, potentially, we have a lot of memory copying to do. The key point is that arrays are not inherently suited for growing in size. Third, in some of the previous exercises you did, you implemented a linear search and binary search function. We learned that the linear search is quite slow, and that the binary search is quite fast. However, the binary search required the array to be previously sorted. The key point is that arrays are not inherently optimized for fast searching. To summarize, we have three problems with arrays:

1. Slow arbitrary insertion. 2. Slow resizing. 3. Not optimized for searching.

Note: Despite these shortcomings, arrays have some benefits, and it is your job to look at the particular application and weigh the cost to benefit ratios. If you do not need arbitrary insertions (you only need to insert at the end of the array) then problem (1) goes away. If you are not resizing the array very frequently then problem (2) becomes negligible. As already stated, array elements can be accessed very fast. In fact, this is the primary advantage of arrays—fast arbitrary element access. By simply providing an index, we can offset very quickly to an element. This is not true for some of the other data structures we will encounter. Again, you will need to use judgment in selecting a data structure for a task. Each has advantages and disadvantages, and you want to pick the one that is best suited for the task.

13.2 Linked Lists

13.2.1 Theory

A linked list can be thought of as a chain of data elements, which are called nodes in the context of linked lists. Figure 13.4 gives a typical graphical representation of a linked list.

Figure 13.4: A linked list as a chain of data elements.

Unlike arrays, the nodes of a linked list are not in contiguous memory, as Figure 13.4 alludes to. Therefore, we need a way of connecting nodes together to build the “chain.” This is accomplished via

Page 388: C++ Módulo I e II

67

pointers. Each node has a pointer to the next node and a pointer to the previous node. This is called a double-ended linked list or doubly-linked list. Figure 13.5 illustrates:

Figure 13.5: We connect the nodes together with pointers, thereby forming the chain of connectivity.

Notice that the first node’s previous pointer is null, and the last node’s next pointer is null. This follows from the fact that the first node, by definition, has no previous node, and the last node, by definition, has no next node. Therefore, in addition to having data stored at a node, it must additionally store two pointers. Thus a typical linked list node looks like this in code: template<typename T> struct LinkedNode T data; LinkedNode* next; LinkedNode* prev; ; We use templates because we would probably like to have linked lists that can support many different types of data. We use a linked list class to store the first and last node. This class also provides methods for inserting/deleting, and traversing nodes in the linked list, as well as an overloaded copy constructor, and an overloaded assignment operator. Template<typename T> class LinkedList public: LinkedList(); ~LinkedList(); LinkedList(const LinkedList& rhs); LinkedList& operator=(const LinkedList& rhs); bool isEmpty(); LinkedNode* getFirst(); LinkedNode* getLast(); void insertFirst(T data); void insertLast(T data); void insertAfter(T tKey, T tData); void removeFirst(); void removeLast();

Page 389: C++ Módulo I e II

68

void remove(T removalCandidate); void destroy(); private: LinkedNode* mFirst; LinkedNode* mLast; ; The pointer connection between nodes makes it easy to insert and delete elements at any position. Figure 13.6 graphically represents insertion of a node after another node.

Figure 13.6: To insert a new node between two nodes we simply need to reassign some pointers.

To insert a newly allocated node after a node, we must adjust the next and previous pointers around the nodes where we are inserting. A sample implementation of this insertion is as follows: bool LinkedList::insertAfter(T tKey, T tData) if(IsEmpty()) return false; // Get a ptr to the front of the list LinkedNode<T>* current = mFirst; // Loop until we find tKey (the value of the node to // insert after) while(current->data != tKey) // Hop to the next node current = current->next; // Test if we reached the end, if we did we didn't // find the node to insert after (tKey). if(current == 0) return false;

Page 390: C++ Módulo I e II

69

// Allocate memory for the new node to insert. LinkedNode<T>* newNode = new LinkedNode<T>(tData); // Special case: Are we inserting after the last node? if(current == mLast) newNode->next = 0; mLast = newNode; // No, link in newNode after the current node. else newNode->next = current->next; current->next->prev = newNode; newNode->prev = current; current->next = newNode; return true; Deletion is essentially the reverse operation. That is, find the node, delete it, and readjust the pointers so they do not connect to the deleted node. Inserting a node to the front or to the back of the list is the same idea, but simpler, since we are not inserting between two nodes. Here is a sample implementation of the insertFirst method: void LinkedList::insertFirst(T tData) LinkedNode<T>* newNode = new LinkedNode<T>(tData); // If the list is empty, this is the first and the last node // and doesn't have a previous ptr. if(IsEmpty()) mLast = newNode; // If the list is not empty, the new node becomes the previous // node of the current first node. else mFirst->prev = newNode; // The new node's next ptr is the old first ptr. May be null // if this is the first node being added to the list. newNode->next = mFirst; // The new node becomes the first ptr. mFirst = newNode; Because all of the nodes of a linked list are allocated dynamically, we must traverse the list node-by-node and delete each element when we want to destroy the entire linked list. Here is a sample implementation:

Page 391: C++ Módulo I e II

70

void LinkedList::destroy() // Is there at least one node in the list? if(mFirst != 0) // Get a pointer to the first node. LinkedNode<T>* current = mFirst; // Loop until the end of the list. while(current != 0) // Save the current node. LinkedNode<T>* oldNode = current; // Move to the next node. current = current->next; // Delete the old node. delete oldNode; oldNode = 0; Before we can delete the current node, we must get a pointer to the next node. If we did not first save a pointer to the next node, then we would lose it when we deleted the node: delete current; current = current->next; // ERROR, current was deleted—can’t // access next. Unfortunately, because a linked list is not contiguous in memory, we cannot access a node by supplying an index, which gives us a direct offset. Instead we must traverse the linked list one node at a time until we reach the desired node. Thus, we have the following linked list performance properties:

1. Linked lists have fast arbitrary insertions and deletions. 2. Linked lists have slow arbitrary node access. 3. Linked lists, like arrays, have no inherent searching optimization properties.

In the standard library, a doubly-ended linked list is modeled with the std::list class (#include <list>). The subsequent sections examine how to use this class.

Page 392: C++ Módulo I e II

71

13.2.2 Traversing

For general purposes, the C++ containers work with iterators. Iterators are objects that refer to a particular element in a container. Iterators provide operators for navigating the container and for accessing the actual element to which is being referred. Iterators overload pointer related operations for this functionality. In this way, pointers can be thought of as iterators for raw C++ arrays. For example, the data to which an iterator refers is accessed with the dereference operator (*). We can move the iterator to the next and previous element using pointer arithmetic operators such as ++ and --. (We can use these operators because they have been overloaded for the iterator.) Note, however, that although these operators behave similarly, the underlying implementation will be different for different containers. For example, moving up and down the nodes of a linked list is different than moving up and down the elements of an array. However, by using iterators and the same symbols to move up and down, a very beautiful generic framework is constructed. Because every container has the same iterators using the same symbols that know how to iterate over the container, a client of the code does not necessarily need to know the type of container. It can work generally with the iterators, and the iterators will “know” how to iterate over the respective container in a polymorphic fashion.

Note: The obvious implementation of an iterator is as a pointer. However, this need not be the case, and you are best not thinking that it necessarily is. The implementations of iterators vary depending on the container they are used for.

Getting back to std::list, we can iterate over all the elements in a list as follows: list<int>::iterator iter = 0; for( iter = myIntList.begin(); iter != myIntList.end(); ++iter ) cout << *iter << " "; cout << endl; First we declare a list iterator called iter. We then initialize this iterator with myIntList.begin(), which returns an iterator to the first node in the list. Then we loop as long as iter does not reach the end of the list. The method call myIntList.end() returns an iterator to the node after the last node—the terminating null node. We increment from one node to the next node by calling the increment operator on the iterator: ++iter. This is sort of like pointer arithmetic, moving from one array element to the next; however, linked lists are not arrays in contiguous memory. Internally, the ++ operator for list iterators does whatever is necessary to move from one node to the next. Also, you should note that we can move from one node to the previous node using the decrement operator --. Again, iterators were designed to be pointer-like for generic purposes. Finally, to get the data value to which an iterator refers we use the dereference operator: *iter. Again, this is like dereferencing a pointer, at least syntax-wise. Remember, we will view iterators as pointer-like, but not necessarily as pointers.

Page 393: C++ Módulo I e II

72

13.2.3 Insertion

There are three methods we are concerned with for inserting elements into a list.

1. push_front: Inserts a node at the front of the list. 2. push_back: Inserts a node at the end of the list. 3. insert: Inserts a node at some position identified by an iterator.

The following program illustrates: Program 13.1: Sample of std::list.

#include <list> #include <iostream> #include <string> using namespace std; int main() list<int> myIntList; // Insert to the front of the list. myIntList.push_front(4); myIntList.push_front(3); myIntList.push_front(2); myIntList.push_front(1); // Insert to the back of the list. myIntList.push_back(5); myIntList.push_back(7); myIntList.push_back(8); myIntList.push_back(9); // Forgot to add 6 to the list, insert before 7. But first // we must get an iterator that refers to the position // we want to insert 6 at. So do a quick linear search // of the list to find that position. list<int>::iterator i = 0; for( i = myIntList.begin(); i != myIntList.end(); ++i ) if( *i == 7 ) break; // Insert 6 were 7 is (the iterator I refers to the position // that 7 is located. This does not overwrite 7; rather it // inserts 6 between 5 and 7. myIntList.insert(i, 6); // Print the list to the console window. for( i = myIntList.begin(); i != myIntList.end(); ++i ) cout << *i << " "; cout << endl;

Page 394: C++ Módulo I e II

73

Program 13.1 Output

1 2 3 4 5 6 7 8 9 Press any key to continue

13.2.4 Deletion

There are three methods of concern for deleting elements from a list.

4. pop_front: Deletes the node at the front of the list. 5. pop_back: Deletes the node at the end of the list. 6. remove(x): Searches the list and deletes the node with the value x.

The following program illustrates (the new deletion code is bolded): Program 13.2: Sample of std::list deletion methods.

#include <list> #include <iostream> #include <string> using namespace std; int main() list<int> myIntList; // Insert to the front of the list. myIntList.push_front(4); myIntList.push_front(3); myIntList.push_front(2); myIntList.push_front(1); // Insert to the back of the list. myIntList.push_back(5); myIntList.push_back(7); myIntList.push_back(8); myIntList.push_back(9); // Forgot to add 6 to the list, insert before 7. But first // we must get an iterator that refers to the position // we want to insert 6. cout << "Before deletion..." << endl; list<int>::iterator i = 0; for( i = myIntList.begin(); i != myIntList.end(); ++i ) if( *i == 7 ) break; myIntList.insert(i, 6); // Print the list to the console window. for( i = myIntList.begin(); i != myIntList.end(); ++i ) cout << *i << " ";

Page 395: C++ Módulo I e II

74

cout << endl; // Remove the first node in the list. myIntList.pop_front(); // Remove the last node in the list. myIntList.pop_back(); // Remove the node that has a value of 5 myIntList.remove( 5 ); // Print the list to the console window. cout << "After deletion..." << endl; for( i = myIntList.begin(); i != myIntList.end(); ++i ) cout << *i << " "; cout << endl; Program 13.2 Output

Before deletion... 1 2 3 4 5 6 7 8 9 After deletion... 2 3 4 6 7 8 Press any key to continue

13.3 Stacks

13.3.1 Theory

A stack is a LIFO (last in first out) data structure. A stack does what it sounds like—it manages a stack of data. Consider a stack of objects; say a stack of dinner plates. You stack the plates on top of each other and then to remove a plate, you remove it from the top. Thus, the last plate you added to the stack is the first one you would remove: hence the name LIFO (last in first out). Figure 13.7 shows a graphical representation of a stack:

Page 396: C++ Módulo I e II

75

Figure 13.7: In a stack, data items are “stacked,” conceptually, on top of each other.

As far as vocabulary is concerned, placing items on the stack is called pushing and taking items off the stack is called popping. A stack container is useful for when you need to model a set of data that is naturally modeled as a stack. For example, consider how you might implement an “undo” feature in a program. As the user makes changes you push the changes onto the stack:

Figure 13.8 Pushing program changes onto a stack.

As you know, an “undo” feature erases the last change you made, so to “undo” the last modification you would simply pop the last change off the stack to erase it:

Page 397: C++ Módulo I e II

76

Figure 13.9: Popping a program change off of the stack.

You can implement a stack using an array, but a linked list works well also. Essentially, every time the client pushes an item on the stack, you will append that item to the back of the linked list. When the user wishes to pop an item off the stack, you will simply delete the last item from the linked list. The last linked list node represents the top of the stack and the first linked list node represents the bottom of the stack. Besides pushing and popping, the client often wants to access the top item of the stack. We could implement this method by returning a reference to the top item on the stack, which, in our linked list implementation, would be the last node.

13.3.2 Stack Operations

In the standard library, a stack is represented with std::stack (#include <stack>). std::stack contains four methods of interest.

1. empty: Returns true if the stack is empty (contains no items) or false otherwise. 2. push: Pushes an item onto the stack. 3. pop: Pops the top item from the stack. 4. top: Returns a reference to the top item on the stack.

Let us look at a way to write a word reverse program using a stack.

Page 398: C++ Módulo I e II

77

Program 13.3: String reverse using a stack.

#include <stack> #include <iostream> #include <string> using namespace std; int main() stack<char> charStack; cout << "Enter a string: "; string input = ""; getline(cin, input); for(int i = 0; i < input.size(); ++i) charStack.push(input[i]); cout << "The reverse string is: "; for(int j = 0; j < input.size(); ++j) cout << charStack.top(); charStack.pop(); cout << endl; Program 13.3 Output

Enter a string: Hello World The reverse string is: dlroW olleH Press any key to continue

The program instructs the user to enter a string. We then loop through each character from beginning to end and push the character onto the stack—Figure (13.10a). To output the string in reverse order we simply output and pop each character in the stack one by one—Figure (13.10b). As you can see, by the nature of the stack, the characters will be popped in reverse order.

Figure 13.10: As we push a string onto a stack it gets stored backwards by the nature of the stack.

Page 399: C++ Módulo I e II

78

13.4 Queues

13.4.1 Theory

A queue is a FIFO (first in first out) data structure. A queue does what it sounds like—it manages a queue of data. Consider a line of customers in a store. The first customer in line is the first to be processed, the second customer is the second to be processed, and so on. A queue container is useful for when you need to model a set of data that is naturally modeled in a first come, first serve situation. For example, when we get into Windows programming later in the course, we will find that our application responds to events. An event may be a mouse click, a key press, a window resize, etc. Generally, events should be processed in the order that they occur (a first come, first serve situation). As events occur, Windows (the OS) adds these events to an application “event queue.” The application then processes these events one-by-one as fast as it can. Obviously, when the system is idle, no events occur and the event queue is empty. In some situations, some Windows events are considered more important than others, and they should have “priority” over the other events. A queue that moves items ahead in the line is called a priority queue (std::priority_queue, also in <queue>). This happens in other situations as well; VIP clients may be allowed to “cut in line” in some business institutions. We will talk more about the Windows event system in the following chapters. For now we simply wanted to provide a real world utility of a queue. As with a stack, a queue can be implemented with an array or linked list as the underlying container type.

13.4.2 Queue Operations

In the standard library, a queue is represented with std::queue (#include <queue>). std::queue contains five methods of interest.

1. empty: Returns true if the queue is empty (contains no items) or false otherwise. 2. push: Adds an item to the end of the queue. 3. pop: Removes the item from the front of the queue (the item first in line). 4. front: Returns a reference to the first item in the queue. 5. back: Returns a reference to the last item in the queue.

We now show a way to write a palindrome-testing program using a stack and queue.

Page 400: C++ Módulo I e II

79

Program 13.4: Using a stack and queue to determine if a string is a palindrome.

#include <queue> #include <stack> #include <iostream> #include <string> using namespace std; int main() // Input a string. string input; cout << "Enter a string: "; getline(cin, input); // Add the strings characters to a stack and a queue. stack<char> wordStack; queue<char> wordQueue; for(int i = 0; i < input.size(); ++i) wordStack.push(input[i]); wordQueue.push(input[i]); // For each character in the stack and queue test to see if the // front and top characters match, if they don't then we can // conclude that we do not have a palindrome. bool isPalindrome = true; for(int i = 0; i < input.size(); ++i) if( wordStack.top() != wordQueue.front() ) isPalindrome = false; break; // Pop and compare next characters. wordStack.pop(); wordQueue.pop(); if( isPalindrome ) cout << input << " is a palindrome." << endl; else cout << input << " is _not_ a palindrome." << endl;

Program 13.4 Output 1

Enter a string: Hello World Hello World is _not_ a palindrome. Press any key to continue

Page 401: C++ Módulo I e II

80

Program 13.4 Output 2

Enter a string: abcdcba abcdcba is a palindrome.

Press any key to continue

First, we input the string and place the characters into the respective data structure. Figure 13.11 shows an example of how the queue and stack look after this. One way of looking at a palindrome is as a string that is the same when read front-to-back or back-to-front. Due to the nature of the data structures, the queue stores the string in front-to-back order and the stack stores the string in back-to-front order. So, by comparing the front character of the queue with the top character of the stack, one-by-one, we can test if the string is the same when read front-to-back or back-to-front.

Figure 13.11: The stack and queue after pushing some characters into them.

13.5 Deques

13.5.1 Theory

A deque (pronounced “deck”) is a double-ended queue. That is, we can insert and pop from both the front end and the back end. Clearly, this kind of behavior can be accomplished with a double-ended linked list. However, what makes the deque novel is that it uses an array internally. Thus, accessing the elements is still fast. Stroustrup’s The C++ Programming Language: Special Edition gives a clear and concise description: “A deque is a sequence optimized so that operations at both ends are about as efficient as for a list, whereas subscripting approaches the efficiency of a vector. […] Insertion and deletion of elements "in the middle" have vector like (in)efficiencies rather than list like efficiencies. Consequently, a deque is used where additions and deletions take place ‘at the ends.’” (474).

Page 402: C++ Módulo I e II

81

13.5.2 Deque Operations

In the standard library, a deque is represented with std::deque (#include <deque>). std::deque contains five methods of interest.

1. empty: Returns true if the deque is empty (contains no items) or false otherwise. 2. push_front: Adds an item to the front of the deque. 3. push_back: Adds an item to the end of the deque. 4. pop_front: Removes an item from the front of the deque. 5. pop_back: Removes an item from the back of the deque. 6. front: Returns a reference to the first item in the deque. 7. back: Returns a reference to the last item in the deque.

In addition to the above-mentioned methods, you can get the size of a deque with the size method and you can access an element anywhere in the deque with the overloaded bracket operator [].

13.6 Maps

13.6.1 Theory

Often we want the ability to associate some search key with some value. For example, in writing a dictionary program and given a word (the key), we would like to be able to quickly find and extract the definition (the value). This is an example of a map; that is, the word maps to the definition. So far, a map does not seem like anything special, as we could just use an array or list and do a search for the specified item. However, what makes the C++ map interesting is that the searching is done very quickly because the internal map data structure is typically a red-black binary search tree, which is inherently optimized for fast searching—it is essentially a data structure that can always be searched in a binary search method by its very nature. We will not get into tree data structures in this course (see the 3D Graphics Programming Module II course here at the Game Institute for a thorough explanation of tree data structures). Rather, we will just learn how to use the standard library map class.

Note: An important fact about maps is that the keys should be unique.

13.6.2 Insertion

A map is represented with the standard library std::map class (#include <map>). Unlike the other STL containers discussed so far, std::map takes two type parameters: map<key type, value type>

Page 403: C++ Módulo I e II

82

For example, here we instantiate a map where the search key is a string, and the value type is an integer: map<string, int> myMap; To insert an item into the map, you use the overloaded bracket operator, where the argument inside the brackets denotes the key, and the right hand operand of the assignment operator specifies the value you wish to associate with that key: myMap[key] = value; // Inserts value with associated key If a value is already associated with that key (i.e., a value with that key has already been inserted into the map) then the preceding syntax overwrites the value at that key with the new value: myMap[key] = value1; myMap[key] = value2; // Overwrite the previous value at key with value2 Using our (string, int) map, we could insert values like so: map<string, int> myStringIntMap; myStringIntMap["Tim"] = 22; myStringIntMap["Vanessa"] = 18; myStringIntMap["Adam"] = 25; myStringIntMap["Jennifer"] = 27; We will assume the integer represents the person’s age. Realistically, we would probably associate the person’s name with more than just an age—probably an entire Person object.

13.6.3 Deletion

To delete an item from the map, you use the erase method, where we specify the key of the item to erase: // Remove adam from the map. myStringIntMap.erase("Adam");

13.6.4 Traversal

Traversals of the map are done with iterators. We use the begin method to get an iterator to the first element and we increment the iterator until we reach the end: Program 13.5: Map traversal.

#include <map> #include <string>

Page 404: C++ Módulo I e II

83

#include <iostream> using namespace std; int main() map<string, int> myStringIntMap; myStringIntMap["Tim"] = 22; myStringIntMap["Vanessa"] = 18; myStringIntMap["Adam"] = 25; myStringIntMap["Jennifer"] = 27; // Remove adam from the map. myStringIntMap.erase("Adam"); map<string, int>::iterator i = 0; for(i = myStringIntMap.begin(); i != myStringIntMap.end(); ++i) cout << "Key = " << i->first << endl; cout << "Age = " << i->second << endl; Program 13.5 Output

Key = Jennifer Age = 27 Key = Tim Age = 22 Key = Vanessa Age = 18 Press any key to continue

What is interesting here is that, since a map has two associated values, the map iterator provides access to those two values via the first member of the map iterator (i->first) and via the second member of the map iterator (i->second).

13.6.5 Searching

We started our discussion with maps saying that, given a search key we can find the associated value very quickly with the map data structure. To find an item given the key, we use the find method: map<string, int>::iterator i = 0; i = myStringIntMap.find( "Vanessa" ); cout << i->first << " is " << i->second << " years old." << endl; Assuming the data was inserted as before, this would output: Vanessa is 18 years old.

Page 405: C++ Módulo I e II

84

Note: All the containers we have studied in this chapter have been templates. This is because we want to use containers for many different types of objects. The template related code of the standard library is termed the standard template library (STL).

13.7 Some Algorithms

The C++ standard library includes some pre-built functional algorithms (#include <algorithm>) which operate on data sets. These functions are template functions and can thus work on a variety of data types. To act on data sets, the algorithms typically need to traverse the data; to traverse a container the functions rely on iterators. For example, the find algorithm takes two iterator arguments marking the range of a container to search for a value: vector<int> intVec(10); // [...] Fill intVec // Search for '5' std::vector<int>::iterator i = 0; i = find(intVec.begin(), intVec.end(), 5); We could just as well use find with a list, deque, or map. Note that we use vectors in the examples, but they can work for all containers that can be navigated with iterators. The C++ Programming Language: Special Edition states that there are 60 standard library algorithms. We will only present a few in this course to give you a general idea. The important thing to realize is that such an algorithmic library exists in C++, and information about it can be pursued at your leisure.

Note: It is recommended at this point that you spend some time perusing the complete reference documentation of the standard template library here: http://www.sgi.com/tech/stl/table_of_contents.html You do not need to read it all, but you should become familiar with the layout so that you can look things up as needed for reference, as we cannot cover the entire library here in one chapter.

13.7.1 Functors

To understand many of the standard library algorithms, we need to understand the idea of functors first. Basically, a functor is a “function object;” that is, an object that acts like a function. How do we do that? Well, when a function is called we have the syntax of the function name followed by parentheses: functionname(). We could simulate that syntax by creating a class and overloading the parenthesis operator:

Page 406: C++ Módulo I e II

85

class Print public: void operator()(int t) cout << t << " "; ; Then we can write: Print p; p( 5 ); // Print 5, notice how this looks like function syntax. This may seem obscure; why not just create a print function? We give two reasons:

1. Functors are classes and thus can also contain member variables. This can lead to more flexible kinds of functions since we can store extra data in the member variables.

2. We can pass “functions” disguised as functors into other functions. These other functions, which

receive a functor as an argument, can then invoke the function the functor represents as necessary and where necessary. You cannot do this with a traditional function.

Perhaps an example will clear things up. Let us examine the generate algorithm. The generate algorithm iterates over a container and calls a function for each element to “generate” a value for that element. The generate function cannot know what kind of value to generate—it is task-specific. Therefore, we must supply the generation function to generate, and generate will invoke that function for each element. However, we cannot pass a function as a parameter to a function. Syntax such as: Foo( Bar() ); will simply pass the value which Bar evaluates into Foo—it does not pass in the function itself, and we cannot call it inside Foo. Therefore, to get around this, we pass a functor, which is an object that acts like a function. To clarify, here is a sample implementation of how generate might be implemented: template<typename Iter, typename Function> Function generate(Iter begin, Iter end, Function f) while( begin != end ) *begin = f(); // Invoke functor f on element ++begin; // Next return f;

Page 407: C++ Módulo I e II

86

As you can see, generate needs to invoke some functor f. Notice the function is a template, so that it can work with any functor. In our example, we will generate a random number for each element in a container. We define the following functor: class Random public: Random(int low, int high) : mLow(low), mHigh(high) int operator()() return mLow + rand() % (mHigh - mLow + 1); private: int mLow; int mHigh; ; Then to generate a random number for each element we write: int main() srand(time(0)); // Allocate 15 integers vector<int> intVec; intVec.resize(15); Random r(2, 7); generate(intVec.begin(), intVec.end(), r);

Note: By passing different functors into generate, you can have the generate function generate values in different ways, which thereby makes the generate function (and the other STL algorithms) extremely generic, since the client supplies the custom function in the form of a functor.

At this point we would like to print our randomly generated vector. Just as there is an algorithm that generates a value for each element, there is also an algorithm that “does something” for each element, where we define the “something” as a functor. This algorithm is called for_each. Let us now restate the printing functor we showed earlier: class Print public: void operator()(int t) cout << t << " "; ;

Page 408: C++ Módulo I e II

87

To print each element we would write: Print p; for_each(intVec.begin(), intVec.end(), p); Let us now put everything together into one compilable program and examine the results. Program 13.6: generate and for_each demonstration.

#include <iostream> #include <string> #include <vector> #include <algorithm> #include <cstdlib> #include <ctime> using namespace std; class Random public: Random(int low, int high) : mLow(low), mHigh(high) int operator()() return mLow + rand() % (mHigh - mLow + 1); private: int mLow; int mHigh; ; class Print public: void operator()(int t) cout << t << " "; ; int main() srand(time(0)); // Allocate 15 integers vector<int> intVec; intVec.resize(15); Random r(1, 20); generate(intVec.begin(), intVec.end(), r);

Page 409: C++ Módulo I e II

88

Print p; for_each(intVec.begin(), intVec.end(), p); cout << endl;

Program 13.6 Output

14 19 20 15 2 10 12 5 2 9 3 15 8 6 17 Press any key to continue

Note that in practice we would make Print a template functor so that it could print many different kinds of types.

13.7.2 Some More Algorithms

Sometimes we may want to count the number of times a particular value appears in a sequence. We can do this with the count algorithm. Here is some sample code: Program 13.7: count demonstration.

// [...] Functors snipped. int main() srand(time(0)); // Allocate 15 integers vector<int> intVec; intVec.resize(15); Random r(1, 10); generate(intVec.begin(), intVec.end(), r); Print p; for_each(intVec.begin(), intVec.end(), p); cout << endl; // count how many times '5' occurs. int result = count(intVec.begin(), intVec.end(), 5); cout << "5 appears " << result << " times." << endl;

Program 13.7 Output

5 8 1 7 1 10 10 5 9 10 7 5 2 2 1 5 appears 3 times. Press any key to continue

Page 410: C++ Módulo I e II

89

Another useful algorithm is the replace algorithm. This algorithm will replace every occurrence of a specified value in a container with a new value. Sample snippet: // replace 5 with 99. replace(intVec.begin(), intVec.end(), 5, 99); The reverse algorithm will reverse the order of a container. The following sample illustrates: Program 13.8: reverse demonstration.

// [...] Functors snipped. int main() srand(time(0)); // Allocate 15 integers vector<int> intVec; intVec.resize(15); Random r(1, 10); generate(intVec.begin(), intVec.end(), r); cout << "Before reversing: " << endl; Print p; for_each(intVec.begin(), intVec.end(), p); cout << endl; cout << "After reversing: " << endl; reverse(intVec.begin(), intVec.end()); for_each(intVec.begin(), intVec.end(), p); cout << endl; Program 13.8 Output

Before reversing: 9 8 8 10 2 10 1 10 10 1 5 6 4 5 4 After reversing: 4 5 4 6 5 1 10 10 1 10 2 10 8 8 9 Press any key to continue

Note: To emphasize, these algorithms will work for any containers that can be iterated over, such as a list, deque, map, or array. Also remember that there are 60 algorithms and we have only mentioned a handful. The goal here was to give you an idea of what exists and what the standard library algorithms can do for you. Be sure to check the documentation contained at: http://www.sgi.com/tech/stl/table_of_contents.html or at the MSDN library (http://msdn.microsoft.com/).

Page 411: C++ Módulo I e II

90

13.7.3 Predicates

A predicate is a special functor that returns a bool value. For example, to implement a generic sorting algorithm we need to know how to compare two objects. For simple types, using the comparison operators (<, >) may suffice. However, for more complex objects, a more complex comparison operation may be needed. Predicates allow us to define and supply the comparison “function” (the predicate functor) that should be used with the algorithm. For example, in one program the staff recently needed to sort a list of 3D objects based on their distance from the virtual camera in a 3D scene. To facilitate this, we made the following predicate: class TriPatchPredicate public: TriPatchPredicate(const D3DXVECTOR3& eyePosL) : mEyePosL(eyePosL) bool operator()(TriPatch* lhs, TriPatch* rhs) // Sort front to back the list based on distance from // the eye. D3DXVECTOR3 a = lhs->aabb.center() - mEyePosL; D3DXVECTOR3 b = rhs->aabb.center() - mEyePosL; float d0 = D3DXVec3Length( &a ); float d1 = D3DXVec3Length( &b ); // Is the distance d0 less than d1. return d0 < d1; private: D3DXVECTOR3 mEyePosL; ; This code has some processes occurring which you are not familiar with, but what is important is the predicate, which as you can see, is a functor where the overloaded parenthesis operator returns a bool. To use this predicate in a sorting algorithm, we wrote: TriPatchPredicate tpp(gMainCamera->getPos()); mVisibilityList.sort( tpp ); Where mVisibilityList is a std::list object. Despite predicates giving increased flexibility in terms of how to define a logical operation, sometimes you may just want to use the basic logical operations with the standard library. To facilitate this, the standard library provides some predefined predicates that just do what their corresponding operator does:

Page 412: C++ Módulo I e II

91

Predicate Corresponding Operator

equal_to == Not_equal_to != Greater > Less < greater_equal >= less_equal <= logical_and && logical_or || logical_not !

To use these pre-built predicates you must #include <functional>.

13.8 Summary

1. Arrays have three general problems that motivate us to look at other data structures. First, inserting or deleting a value in the middle of the array is slow because it requires the data elements to be shifted about. Second, resizing an array can be slow because a memory allocation and deallocation must be performed and the array elements must be copied from the old array into the newly resized array. Third, arrays are not optimized for searching inherently. Still, these problems are only significant in some cases: If you are only adding or deleting elements at the end of an array then problem one disappears; if you do not need to resize the array frequently then problem two becomes negligible; and if you do not need to search the array then problem three becomes insignificant. The primary advantage of arrays is that they we can access random elements in the array very efficiently with only an offset index into the array.

2. A linked list can be thought of as a chain of data elements, which are called nodes. A linked list

that allows forward and backward traversals is called a double-ended linked list. The main benefit of linked lists is that random insertions and deletions are efficient because no shifting must occur—only some pointer reassignments occur. The main disadvantage of a linked list is that we cannot randomly access nodes quickly (as we can with arrays); that is, the list must be traversed one by one until we find the node we seek. Linked lists can grow pretty efficiently, as there is no necessary copy from the old container to the newly resized container. Although, for each item we add/delete we must do a memory allocation/deallocation.

3. A stack is a LIFO (last in first out) data structure. A stack does what it sounds like—it manages

a stack of data (i.e., data stacked on top of one another). A stack container is useful for when you need to model a set of data that is naturally modeled in a stack-like fashion. As far as vocabulary is concerned, placing items on the stack is called pushing and taking items off the stack is called popping.

Page 413: C++ Módulo I e II

92

4. A queue is a FIFO (first in first out) data structure. A queue does what it sounds like—it manages a queue of data. Consider a line of customers in a store. The first customer in line is the first to be processed; the second customer is the second to be processed, and so on. Essentially, a queue can model a “first come, first serve” system.

5. A deque (pronounced “deck”) is a double-ended queue. That is, we can insert and pop from both

the front end and the back end. This kind of behavior can be accomplished with a double-ended linked list. However, what makes the deque novel is that it uses an array internally. Thus, accessing the elements is still fast.

6. A map associates a search key with a value. Given the search key, the map can find the

associated value very quickly. Maps are inherently optimized for searching due to their underlying implementation (usually a red-black binary search tree). Maps should be preferred when you need to search a container often.

7. The standard library provides a plethora of algorithms that can operator on the various STL

containers. Such algorithms include, but are not limited to, searches, reversals, counting, generating, and sorting. To use some of the standard library algorithms you must supply a functor, which is a function object (an object that behaves like a function by overloading the parenthesis operator). In addition, to use other standard library algorithms you must supply a predicate, which is a particular kind of functor that returns a bool. Predicates are used to define complex comparison functions in the case that the built-in comparison operations (e.g., <, >) are unsatisfactory.

Page 414: C++ Módulo I e II

93

13.9 Exercises

13.9.1 Linked List

Write your own linked list class. More specifically, implement the class definition of the linked list discussed in this chapter: template<typename T> class LinkedList public: LinkedList(); ~LinkedList(); LinkedList(const LinkedList& rhs); LinkedList& operator=(const LinkedList& rhs); bool isEmpty(); LinkedNode* getFirst(); LinkedNode* getLast(); void insertFirst(T data); void insertLast(T data); void insertAfter(T tKey, T tData); void removeFirst(); void removeLast(); void remove(T removalCandidate); void destroy(); private: LinkedNode* mFirst; LinkedNode* mLast; ;

13.9.2 Stack

Write your own stack class. That is, implement the following class definition: template<typename T> class Stack public: Stack(); ~Stack(); T& getTopItem(); bool isEmpty(void); void push(T newElement); void pop();

Page 415: C++ Módulo I e II

94

private: LinkedList<T> mList; ;

13.9.3 Queue

Write your own queue class. That is, implement the following class definition: template<class T> class Queue public: Queue(); ~Queue(); T& getFirst(); bool isEmpty(); void push(T newElement); void pop(); private: LinkedList<T> mList; ;

13.9.4 Algorithms

Design and implement your own for_each, count, reverse, and sort algorithms for an array.

Page 416: C++ Módulo I e II

95

Chapter 14

Introduction to Windows Programming

Page 417: C++ Módulo I e II

96

Introduction

With the core C++ language behind us, we now begin the second major theme of this course: Windows (Win32 for short) programming. What we mean by Win32 programming is that instead of writing console applications, we will learn to write programs that look like traditional Windows programs. You are, no doubt, used to seeing and working with these programs. Figure 14.1 shows a basic Win32 program and outlines some of its characteristics, which you should already be familiar with.

Figure 14.1: GUI features labeled. The primary characteristic of a Win32 application is the GUI (Graphical User Interface). GUI refers to the graphics with which the user interacts—the menus, buttons, scroll bars, etc. Instead of interacting with a console, users now interact with a GUI. Students often have difficulty making the transition from pure C++ to Windows programming. The primary reason is that to program Windows, we must use a set of code which Microsoft developed that is known as the Win32 API (Application Programming Interface). The Win32 library is a large low-level set of functions, structures, and types that allow you to create and customize windows. At this point, you are already familiar with using library code with the STL. However, unlike the STL where you can pick and choose the pieces of code you want to use, in Win32 programming you will have to use quite a bit of library code from the start. As all of the new structures, types, and functions, must be introduced fairly rapidly, and in combination with the new concepts, this can be daunting at first. Furthermore, the Win32 library code will do some things which you will probably not understand (at least, not understand what the code is doing internally). So you should take it slow, and do not get frustrated if there is something you do not fully understand—the theory will come in time. For now just concentrate on getting the programs working rather than on what the Win32 library is doing behind the scenes.

Page 418: C++ Módulo I e II

97

Chapter Objectives

• Learn how to create a basic Win32 application. • Gain an understanding of the event driven programming model.

14.1 Your First Windows Program

The first thing you need be aware of when creating Win32 applications is that you must create your Visual C++ projects a little differently than you do with a console application. In particular, instead of selecting “Console application” from the “Application Settings” dialog box, you must select “Windows application,” as Figure 14.2 shows.

Figure 14.2: Window Application Settings for a “Windows application.”

By selecting “Windows application,” Visual C++ will include all the necessary Win32 library files you need in order to write Win32 programs. That is the only necessary project change. Let us start by examining the following simple Win32 program:

Note: When we use the Win32 API and write Win32 programs, you must be aware that the code is Windows specific; that is, it is no longer portable code.

Page 419: C++ Módulo I e II

98

Program 14.1: Your First Windows Program.

#include <windows.h> int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) MessageBox(0, "First Win32 Program.", "Window Title", MB_OK); This program displays the following message box as output:

Figure 14.3: Output to Program 14.1.

Program 14.1 is useful because it allows us to introduce three Win32 concepts concisely. First, in order to write Win32 programs, you need to include the Windows header file (#include <windows.h>); this header file provides the prototypes and definitions of various functions and types you will need to program the Win32 API. Second, Win32 programs no longer start with the main function. Instead, they start with the WinMain function. WinMain returns an integer, which specifies the code that is returned to the Windows operating system. The prefix WINAPI modifies the calling convention of WinMain; that is, the way in which it places items on the memory stack. Such low-level details are best left to a course in assembly language, and so we will not discuss it further here.

The first parameter, hInstance of type HINSTANCE, is essentially a value that identifies the application for Windows—an application ID, if you will, that Windows OS passes into your application when it begins. It is necessary to ID various applications because Windows can be running several different applications at once. Note that the Win32 API defines the type HINSTANCE.

The second parameter hPrevInstance is no longer used in 32-bit Windows programming—it is a legacy of 16-bit Windows.

The third parameter cmdLine is a string of command line arguments. (PSTR is essentially a typedef for a char*). Command line arguments are string arguments a user can pass into an application before it starts. Command line arguments typically give the application special instructions on how it should execute.

Finally, the fourth parameter, showCmd, is an integer that specifies how the main application window should be initially shown. For example, should it be maximized, minimized or normal? The Windows OS makes the decision based on a variety of factors. For instance, if you try to launch an application while the system is busy, Windows will pass in a value for showCmd indicating that it, perhaps, be

Page 420: C++ Módulo I e II

99

minimized. In summary, the parameters to WinMain are parameters the OS passes into the application when the application starts. The third key idea Program 14.1 illustrates is the MessageBox function. Again, this is a function the Win32 API defines and implements. MSDN gives the following declaration: int MessageBox( HWND hWnd, // handle to owner window LPCTSTR lpText, // text in message box LPCTSTR lpCaption, // message box title UINT uType // message box style ); Before getting to the specifics of this function, there are a few fundamental Win32 types we need to briefly discuss. First, an HWND is an ID for a particular window, just like an HINSTANCE is an ID for a particular application. The ‘H’ in both of these stands for handle: HWND means “windows handle” and HINSTANCE means “application instance handle.” Again we need these IDs or handles, so that we can refer to individual Windows objects, such as applications and windows. The second new type is LPCTSTR; this is nothing more than a typedef to a const char* (if Unicode1 is not defined). Finally, UINT is a typedef for unsigned int. As the comment says, the first parameter (hWnd) is a handle to the window that will “own” the message box. That is, the handle of the window with which you want to associate the message box. You can pass null (0) for this parameter if you do not want to specify any association. The second parameter (lpText) is the string of text you want to be displayed in the message box’s body. In Program 14.1 we specify “First Win32 Program.” As the output shows, this is the string that is displayed in the message box’s body. The third parameter (lpCaption) is the string that is to be displayed in the message box’s caption bar. In Program 14.1 we specify “Window Title” for this parameter, and sure enough, that is the string displayed in the message box’s caption bar. The fourth parameter (uType) is an unsigned integer value that denotes a style flag. Here is an abridged list of possible style flags (these are predefined values of type unsigned int): MB_OK: Instructs the message box to display an OK button.

Figure 14.4: The MB_OK message box style.

1 Unicode uses 16-bit characters, which allows for a far greater range of characters we can represent. This is useful for supporting international character sets. We do not use Unicode in this book.

Page 421: C++ Módulo I e II

100

MB_OKCANCEL: Instructs the message box to display an OK and CANCEL button.

Figure 14.5: The MB_OKCANCEL message box style.

MB_YESNO: Instructs the message box to display a YES and NO button.

Figure 14.6: The MB_YESNO message box style.

MB_YESNOCANCEL: Instructs the message box to display a YES, NO, and CANCEL button.

Figure 14.7: The YESNOCANCEL message box style.

Finally, the message box’s return value depends on which button the user pressed; here is an abridged list of return values (see the Win32 documentation for more details): IDOK: The user pressed the OK button. IDCANCEL: The user pressed the CANCEL button. IDYES: The user pressed the YES button. IDNO: The user pressed the NO button. You can test which value was returned using an “if” statement and thus determine which button the user selected and then take appropriate action.

Note: Many of the Win32 functions will have different style flags or values that enable you to customize various things. However, because there are so many different flags for the different API functions, we cannot cover all of them in this text. Therefore, it is important that you learn to use the Win32 documentation to obtain further info on a Win32 function or type. The documentation is typically included in the help file of Visual C++. For example, in Visual C++ .NET, you would go to the Menu->Help->Index (Figure 14.8).

Page 422: C++ Módulo I e II

101

Figure 14.8: Launching the documentation index.

The index help should then open to the right of the interface. Enter the function of type you would like more information on, as Figure 14.9 shows we search for MessageBox.

Figure 14.9: Searching for the MessageBox documentation.

Page 423: C++ Módulo I e II

102

Finally, selecting (double click) on the found MessageBox entry gives us several subcategories more information can be found:

Figure 14.10: Selecting the MessageBox documentation for the Win32 API.

Here, we want to select the “Windows User Interface: Platform SDK;” this is essentially the Win32 API documentation guide. So selecting that yields the complete documentation for the MessageBox function:

Figure 14.11: The MessageBox documentation.

Page 424: C++ Módulo I e II

103

14.2 The Event Driven Programming Model

14.2.1 Theory

One of the key differences between the console programming we have been doing since Module I and Windows programming is the event driven programming model. In console programming, your code begins at main and then executes line-by-line, while looping, branching, or jumping to function calls along the way. Windows programming is different. Instead, a Windows program typically sits and waits for something to happen—an event. An event can be a mouse click, a button press, a menu item selection, a key press, and so on. Once Windows recognizes an event, it adds a message to the application’s priority message queue for which the event was targeted (remember Windows can be running several applications concurrently). A Windows application constantly scans the message queue for messages and when one is received it is forwarded to the window it was intended for (a single Windows application can consists of multiple windows itself—a main window and child windows, for example). More specifically, a message in the application message queue is forwarded to the window procedure of the window it was intended for. The window procedure (also called a message handler) is a special function each window has (though several windows can share the same message procedure), which contains the code necessary to handle the specified event the message originated from. For example, if a button is pressed (a button is a child window to the parent window it lies on) then the button’s window procedure will contain the code that gets executed when that button is pressed.

14.2.2 The MSG Structure

A message in Windows is represented with the following MSG structure: struct MSG HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; ; hwnd: This member is the handle to the window for which the message is designated. message: This member is a predefined unique unsigned integer symbol that denotes the specific type of message. wParam: A 32-bit value that contains extra information about the message. The exact information is specific to the particular message.

Page 425: C++ Módulo I e II

104

lParam: Another 32-bit value that contains extra information about the message. The exact information is specific to the particular message. time: The time stamp at which time the message was generated. pt: The (x, y) coordinates, in screen space, of the mouse cursor at the time the message was generated. The POINT structure is defined by the Win32 API and looks like this: struct POINT LONG x; // x-coordinate LONG y; // y-coordinate ;

Here are some example message types, which would be placed in message: WM_QUIT: This message is sent when the user has indicated their desire to quit the application (by pressing the close ‘X’ button, for example). WM_COMMAND: This message is sent to a window when the user selects an item from the window’s menu. Some child windows, such as button controls, also send this message when they are pressed. WM_LBUTTONDBLCLK: This message is sent to a window when the user double-clicks with the left mouse button over the window’s client area. WM_LBUTTONDOWN: This message is sent to a window when the user presses the left mouse button over the window’s client area. WM_KEYDOWN: This message is sent to the window with keyboard focus when the user presses a key. The wParam of the message denotes the specific key that was pressed. WM_SIZE: This message is sent to a window when the user resizes the window.

14.3 Overview of Creating a Windows Application

The creation of even a simple Windows application is quite lengthy in terms of lines of code, but not too lengthy when we consider the amount of functionality we will get in return. We will have actually drawn a window (we have not done any drawing in this course so far), and moreover, the window can be resized, minimized, maximized, and other such things. The key steps for creating a basic Windows application are outlined below:

1. Define the window procedure for the main application window. Recall that a window procedure is a special function each window has (though several windows can share the same message procedure), which contains the code necessary to handle the specified event the message originated from.

Page 426: C++ Módulo I e II

105

2. Fill out a WNDCLASS instance. By filling out this structure you are able to define some core properties that your window will have.

3. Register the WNDCLASS instance. Before you can create a window based on the WNDCLASS

instance you have filled out, you must register it with Windows.

4. Create the window. Now that you have registered a WNDCLASS instance with Windows, you can create a window. Creating a window is done with a single function call, which again allows you to customize some of the features of the window.

5. Show and update the window. In this step, you need to actually instruct Windows to display

(show) your window (by default it will not be visible). In addition, you must update the window for the first time.

6. Finally, enter the message loop. After you have created the main window of your application,

you are ready to enter the message loop. The message loop will constantly check for and handle messages as the message queue gets filled. The message loop will not exit until a quit message (WM_QUIT) is received.

With the preceding basic roadmap in place, we can now discuss the details of each step.

14.3.1 Defining the Window Procedure

As already stated, a window procedure is a special function each window has (though several windows can share the same message procedure), which contains the code necessary to handle the specified event from which the message originated. However, the window procedure must follow some Win32 API guidelines. In particular, all window procedures must have a certain declaration: LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); You can name the window procedure whatever you want; here we call it WndProc. Additionally, you can name the parameters whatever you want as well (although the names used above are very commonly used); however, all four parameters of the shown types must be present. The return type of the window procedure must be of type LRESULT, which is simply typedefed as a long—an error code will be returned via this return value. Observe that the function name is prefixed with the symbol CALLBACK. This denotes that the window procedure is a callback function. A callback function is a function that we do not directly call ourselves. Rather, the Win32 API will call this function automatically. In particular, the Win32 API will call the window procedure function when a message from the message loop is dispatched to it. As you can see, the window procedure takes four parameters. Together, these parameters provide you with enough information to handle the message.

Page 427: C++ Módulo I e II

106

hWnd: The handle to the window the message is aimed for. This parameter corresponds with the MSG::hwnd member. msg: A predefined unique unsigned integer symbol that denotes the specific type of message. This parameter corresponds with the MSG::message member. wParam: A 32-bit value that contains extra information about the message. The exact information is specific to the particular message. This parameter corresponds with the MSG::wParam member. lParam: Another 32-bit value that contains extra information about the message. The exact information is specific to the particular message. This parameter corresponds with the MSG::lParam member. And again, just to reiterate, the Win32 API will call the window procedure passing the appropriate arguments into the window procedure’s parameters. Now that we know how the window procedure must be declared, how would we go about implementing it? A window procedure is typically implemented as one large switch statement. The switch statement is used to determine which block of code should be executed based on the specific message. For example, if the left mouse button was pressed, then the code to handle a WM_LBUTTONDOWN message should be executed. Likewise, if a key was pressed then the code to handle a WM_KEYDOWN message should be executed. Here is an example: LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) switch( msg ) case WM_LBUTTONDOWN: MessageBox(0, "WM_LBUTTONDOWN message.", "Msg", MB_OK); return 0; case WM_KEYDOWN: if( wParam == VK_ESCAPE ) DestroyWindow(hWnd); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; return DefWindowProc(hWnd, msg, wParam, lParam); The switch statement grows as you add different kinds of messages to handle. In the above code we only directly handle three types of messages. Let us now examine the components of this function definition.

Page 428: C++ Módulo I e II

107

case WM_LBUTTONDOWN: ::MessageBox(0, "Hello, World", "Hello", MB_OK); return 0; If the sent message is of type WM_LBUTTONDOWN, then our particular window (remember window procedures are window specific—you define one, unless you share them, for each window) displays a message box. Once we handle the message, we are done and, therefore, can return out of the function (return 0). case WM_KEYDOWN: if( wParam == VK_ESCAPE ) ::DestroyWindow(hWnd); return 0; If the sent message is of type WM_KEYDOWN and the key pressed was the escape key (symbolically defined in code as VK_ESCAPE) then we have specified that the window should be destroyed (DestroyWindow(hWnd)). The only parameter for DestroyWindow is a handle to the window that is to be destroyed. This function sends a WM_DESTROY message to the window identified by hWnd. Observe that for the WM_KEYDOWN message, the wParam contains the key code for the key that was pressed. Again, wParam and lParam provide extra message specific information—some messages do not need extra information and these values are zeroed out. case WM_DESTROY:

PostQuitMessage(0); return 0; The last type of message we specifically handle is the WM_DESTROY message. This message would be sent if the user presses the escape key (we defined the window to be destroyed if the escape key was pressed in the previous message) or if the user pressed the ‘X’ button on the window to close it. In response to this message, we use the PostQuitMessage API function to add a WM_QUIT message to the application message queue. This will effectively terminate the loop so that the program can end. The only parameter to PostQuitMessage is an exit code and this is almost always zero. There is also some functionality that is common to almost every window. For example, just about every window can be resized, minimized and maximized. It seems redundant to define this behavior repeatedly for each window. Consequently, the Win32 API provides a default window procedure that implements this common generic functionality. So for any message we do not specifically handle, we can just forward the message off the default window procedure: return DefWindowProc(hWnd, msg, wParam, lParam); This buys us some extra, albeit generic, functionality for free. If you do not like the default behavior, then you simply handle the message yourself in the window procedure so that it never gets forwarded to the default window procedure.

Note: While small Windows programs typically only have one window procedure, large windows programs will usually have much more. Games usually only have one, because games do not typically work much with the Win32 API; rather they use a lower level API such as DirectX.

Page 429: C++ Módulo I e II

108

14.3.2 The WNDCLASS Structure

An instance of the WNDCLASS structure is used to define the properties of your window, such as styles, the background color, the icon image, the cursor image, and the window procedure associated with any window you create based on this WNDCLASS instance. Here is the definition: typedef struct _WNDCLASS UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; WNDCLASS, *PWNDCLASS; style: A combination of style flags for customization. There exists a myriad of bit flags that can be combined to create various styles; see the Win32 documentation for details. Generally, the flag combination CS_HREDRAW and CS_VREDRAW are used as the style (combined via a bitwise OR), which means the window will repaint itself when either the horizontal or vertical window size changes. lpfnWndProc: Pointer to the window procedure you want to associate with the windows that are built based on this WNDCLASS instance. cbClsExtra: Extra 32-bit memory slot to reserve custom information. We do not use this value in this course. cbWndExtra: Extra 32-bit memory slot to reserve custom information. We do not use this value in this course. hInstance: A handle to the application with which you want the windows you create to be associated. Recall that WinMain passes in the application instance handle through its first parameter. hIcon: A handle to an icon which will be used for the window. You can get a handle to an icon via the API function LoadIcon. To load the default application icon, for example, you would write:

LoadIcon(0, IDI_APPLICATION); // returns an HICON Some other intrinsic icons are:

• IDI_WINLOGO – Windows logo icon • IDI_QUESTION – Question mark icon • IDI_INFORMATION – Information icon • IDI_EXCLAMATION – Exclamation icon

Page 430: C++ Módulo I e II

109

hCursor: A handle to a cursor which will be used for the window. You can get a handle to a cursor with the API function LoadCursor. To load the default arrow cursor, for example, you would write: LoadCursor(0, IDC_ARROW); // returns an HCURSOR Some other intrinsic cursors are:

• IDC_CROSS – Crosshair cursor • IDC_WAIT – Hourglass cursor

hbrBackground: A handle to a brush which specifies the windows background color. You can get a handle to a brush with the API function GetStockObject. To get a handle to a white brush, for example, you would write: (HBRUSH)GetStockObject(WHITE_BRUSH); // returns a HBRUSH Note that we have to cast the return value to an HBRUSH. Some other intrinsic brush types are:

• BLACK_BRUSH – Black brush • DKGRAY_BRUSH – Dark gray brush • GRAY_BRUSH – Gray brush • LTGRAY_BRUSH – Light gray brush

lpszMenuName: The name of the window menu. We will be creating and enabling menus via another method, so we will be setting this value to zero. lpszClassName: A unique string name (identifier) we want to give the WNDCLASS instance, so that we can refer to it later. This can be any name you want. A typical WNDCLASS instance would be created and filled out like so: WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName";

Page 431: C++ Módulo I e II

110

14.3.3 WNDCLASS Registration

Before you can create a window based on the WNDCLASS instance you have filled out, you must register it with Windows. To register a WNDCLASS instance, you use the RegisterClass function: RegisterClass( &wc ); This is how we pass in a pointer to the WNDCLASS instance which we want to register.

14.3.4 CreateWindow

After we have registered a WNDCLASS instance with Windows, we can create a window. To create a window, we use the CreateWindow function: HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam ); lpClassName: The name of the WNDCLASS instance to use in order to create the window (i.e., the name we specified for wc.lpszClassName). lpWindowName: A unique string name to give the window we are creating. This is the name that will appear in the window’s title/caption bar. dwStyle: A combination of style flags specifying how the window should look. Typically, this is set to WS_OVERLAPPEDWINDOW, which is a combination of styles WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, and WS_MAXIMIZEBOX. See the Win32 API documentation for complete details on window styles. x: The x-coordinate position of the upper-left corner of the window, relative to the screen, and measured in pixels. y: The y-coordinate position of the upper-left corner of the window, relative to the screen, and measured in pixels.

Page 432: C++ Módulo I e II

111

nWidth: The width of the window, measured in pixels. nHeight: The height of the window, measured in pixels. hWndParent: Handle to a parent window. Windows can be arranged in a hierarchical fashion. For example, controls such as buttons are child windows, and the window they lie on is the parent window. If you wish to create a window with no parent (e.g., the main application window) then specify null for this value. hMenu: Handle to a menu which would be attached to the window. We will examine menus in later chapters. For now we set this to null. hInstance: Handle to the application instance the window is associated with. lpParam: A pointer to optional user-defined data; this is optional and can be set to null.

Note: Windows uses a different coordinate system than those which you may be familiar with. Typical mathematics uses a coordinate system where +y goes “up” and –y goes “down.” However, Windows uses a coordinate system where +y goes “down” and –y goes “up.” Moreover, the upper-left corner of the screen corresponds to the origin. This system is referred to as screen space. Figure 14.12 illustrates the differences:

Figure 14.12: A typical coordinate system on the left, and the Windows coordinate system on the right.

Another simple but important structure in the Win32 API is the RECT structure. It is defined like so: typedef struct _RECT LONG left; LONG top; LONG right; LONG bottom; RECT, *PRECT; The members describe a rectangle in screen space. Figure 14.13 illustrates:

Page 433: C++ Módulo I e II

112

Figure 14.13: A rectangle in screen space coordinates.

As Figure 14.13 shows, the point (left, type) defines the upper-left vertex of the rectangle and the point (right, bottom) defines the lower-right vertex of the rectangle.

This function returns a window handle (HWND) to the newly created window if the function is successful. If the function fails then it returns null (0). Here is a typical example call: hWnd = ::CreateWindow("MyWndClassName", "MyWindow", WS_OVERLAPPEDWINDOW, 0, 0, 500, 500, 0, 0, hInstance, 0); if(hWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false;

14.3.5 Showing and Updating the Window

A window is not shown by default, so after we create it we must show it and, in addition, update it for the first time: ShowWindow(hWnd, showCmd); UpdateWindow(hWnd); Both of these functions require an HWND argument that identifies the window that should be shown and updated. Additionally, ShowWindow requires a second argument that specifies how the window should be shown. Some valid values are:

• SW_SHOW – Shows the window in the position and dimensions specified in CreateWindow

• SW_MAXIMIZE – Shows the window maximized • SW_MINIMIZE – Shows the window minimized.

It is actually good form to show the window as Windows instructs; that is, using the value the showCmd parameter, from WinMain, contains.

Page 434: C++ Módulo I e II

113

14.3.6 The Message Loop

We said that a Windows application constantly checks the message queue for messages; this is done with the message loop. A typical message loop looks like this: MSG msg; ZeroMemory(&msg, sizeof(MSG)); while(GetMessage(&msg, 0, 0, 0) ) TranslateMessage(&msg); DispatchMessage(&msg); First, ZeroMemory is a Win32 function that clears out all the bits of a variable to zero, thereby “zeroing out” the object; the first parameter is a pointer to the object to zero out and the second parameter is the size, in bytes, of the object to zero out. Moving on to the loop, for every loop cycle we call the API function GetMessage, which extracts the next message from the message queue and stores it in the passed-in msg object. The remaining three parameters of GetMessage are uninteresting and we can specify null (0) for them all. If the message extracted was a quit message (WM_QUIT) then GetMessage returns false, thereby causing the while loop to end. If the message was not a quit message then GetMessage returns true. Inside the while loop, we call two more API functions: TranslateMessage and DispatchMessage. TranslateMessage does some key code translations into character code translations. Finally, DispatchMessage forwards the message off to the window procedure it is aimed for. In summary, for each cycle, the message loop gets the next message from the message queue. If the message is not a quit message then the message is sent to the appropriate window procedure to be handled.

14.4 Your Second Windows Program

The following annotated program ties in everything we have discussed in this chapter to create a basic Windows program using the steps described in the previous section: Program 14.2: Your Second Windows Program.

#include <windows.h> // Store handles to the main window and application // instance globally. HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; // Step 1: Define and implement the window procedure. LRESULT CALLBACK

Page 435: C++ Módulo I e II

114

WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) switch( msg ) // Handle left mouse button click message. case WM_LBUTTONDOWN: MessageBox(0, "WM_LBUTTONDOWN message.", "Msg", MB_OK); return 0; // Handle key down message. case WM_KEYDOWN: if( wParam == VK_ESCAPE ) DestroyWindow(ghMainWnd); return 0; // Handle destroy window message. case WM_DESTROY: PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the // default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam); // WinMain: Entry point for a Windows application. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Save handle to application instance. ghAppInst = hInstance; // Step 2: Fill out a WNDCLASS instance. WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; // Step 3: Register the WNDCLASS instance with Windows. RegisterClass( &wc ); // Step 4: Create the window, and save handle in globla // window handle variable ghMainWnd. ghMainWnd = ::CreateWindow("MyWndClassName", "MyWindow", WS_OVERLAPPEDWINDOW, 0, 0, 500, 500, 0, 0, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false;

Page 436: C++ Módulo I e II

115

// Step 5: Show and update the window. ShowWindow(ghMainWnd, showCmd); UpdateWindow(ghMainWnd); // Step 6: Enter the message loop and don't quit until // a WM_QUIT message is received. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage(&msg, 0, 0, 0) ) TranslateMessage(&msg); DispatchMessage(&msg); // Return exit code back to operating system. return (int)msg.wParam; Program 14.2 outputs a window with a caption “My Window,” a minimize button, a maximize button, and a close button. If you click the client area (white rectangle) a message box is displayed that indicates you generated a WM_LBUTTONDOWN message. Finally, you can close the window by pressing the close ‘X’ button or by pressing the escape key. Figure 14.14 shows the output after the left mouse button was pressed:

Figure 14.14: Program 14.2 output after the user left mouse clicked the client area.

Page 437: C++ Módulo I e II

116

14.5 Summary

1. The primary characteristic of a Win32 application is the GUI (Graphical User Interface). “GUI” refers to the menus, buttons, scroll bars, and other graphical objects users interact with.

2. The Win32 library is a large low-level set of functions, structures, and types that allow you to

create and customize windows.

3. In order to write Win32 programs you need to include the Windows header file (#include <windows.h>); this header file gives the prototypes and definitions of various functions and types you will need to program the Win32 API.

4. Win32 programs no longer start with the main function. Instead, they start with the WinMain

function. WinMain returns an integer, which specifies the code that is returned to the Windows operating system.

5. An HWND is an ID to a particular window, just like an HINSTANCE is an ID to a particular

application. The ‘H’ in both of these stands for “handle”: HWND means “windows handle” and HINSTANCE means “application instance handle.” We need these IDs or handles, so that we can refer to individual Windows objects, such as applications and windows, which are internally maintained by the Win32 API.

6. Windows programs are event driven; that is, they sit and wait for an event to occur, and then

execute some code in response to that event. An event can be a key press, a mouse click, a button press, a menu selection, scrolling the scroll bars, etc. When an event occurs, Windows adds a message to the application’s message queue for which the event was aimed for. The application’s message loop then retrieves and processes the messages from the message queue. When a message is processed it is dispatched to the window procedure of the window the event was targeted for. The window procedure then handles the message by executing some code in response to the message.

7. Creating and displaying a window requires several steps:

a. You must define the window procedure of the main application window b. You must fill out a WNDCLASS instance describing some of the properties of your window c. You must register the WNDCLASS instance with Windows using the RegisterClass

function d. You must create the window with the CreateWindow function e. You must show and update the window with ShowWindow and UpdateWindow,

respectively f. You must enter the message loop so that your application can retrieve and process

messages.

Page 438: C++ Módulo I e II

117

14.6 Exercises

These exercises should be done as modification to Program 4.2.

14.6.1 Exit Message

When the user presses the escape key, instead of exiting immediately, display a “YES/NO” style message box asking the user if he really wants to quit. If the user selects “Yes” then exit, else if the user selects “No” then do not exit.

14.6.2 Horizontal and Vertical Scroll Bars

Add horizontal and vertical scroll bars to the main window. These scroll bars only need to be displayed—they do not need to be functional. (Hint: See the CreateWindow style flags in the Win32 documentation.)

14.6.3 Multiple Windows

Instead of just displaying one window, make the application create and show two more windows. The windows’ positions and dimensions should be set so that they occupy different regions of the screen, thereby making all three visible at once. Give all three windows their own window procedure. When the user left mouse clicks in one of the windows, display a message box with the message “You clicked Window #x,” where x should be replaced with 1, 2, or 3 depending on which window was selected. (Hint: Create and fill out three WNDCLASS instances that correspond to each window and which contain the corresponding window procedure. Also create three HWNDs (one for each window) and call CreateWindow three times to create each window. You will also need to show and update each window.)

14.6.4 Change the Cursor

Change the mouse cursor to something other than the arrow cursor.

Page 439: C++ Módulo I e II

118

14.6.5 Blue Background

Change the background of the window (or the three windows if you did Exercise 14.6.3) from white to blue (or red, green, and blue if you did Exercise 14.6.3). You will need to create a blue brush for this (or red, green, and blue if you did Exercise 14.6.3). You can create a custom brush with the following function: HBRUSH CreateBrushIndirect( CONST LOGBRUSH *lplb // brush information ); Observe that this function returns an HBRUSH. This function has a parameter that specifies the details of the brush to create. Specifically, LOGBRUSH looks like this: typedef struct tagLOGBRUSH UINT lbStyle; COLORREF lbColor; LONG lbHatch; LOGBRUSH, *PLOGBRUSH; The style allows you to specify a brush style. We want a solid brush, so we select BS_SOLID.

A COLORREF is a 32-bit value, where 1 byte is used to describe the shade of red, 1 byte is used to describe the shade of green, and 1 byte is used to describe the shade of blue—the so-called RGB color—and one byte is not used. We can use the following macro to create a COLORREF: COLORREF RGB( BYTE byRed, // red component of color in the range [0, 255]. BYTE byGreen, // green component of color in the range [0, 255]. BYTE byBlue // blue component of color in the range [0, 255]. ); BYTE is typedefed as an unsigned char. For the color ranges, 0 would mean zero color intensity/shade (i.e., black) and 255 would mean full intensity—bright red, green, or blue. Thus, we have 256 shades of each color component. The mixing of various shades of red, green, and blue allows us to describe millions of different colors. The hatch member of LOGBRUSH is another value that allows you to describe a hatch pattern. Since we are using a solid brush, we can ignore this value—see the Win32 documentation if you are curious about why. With all that said, we can create our blue brush like so: LOGBRUSH lb; lb.lbStyle = BS_SOLID; lb.lbColor = RGB(0, 0, 255); HBRUSH blueBrush = CreateBrushIndirect(&lb);

Page 440: C++ Módulo I e II

119

After you have made a window with a blue background color, try experimenting with different colors (i.e., changing the values in the RGB macro).

14.6.6 Custom Icon

Instead of using the default Windows icon, let us make our own. To do this, we need to add an icon resource to our project. To do this, go to the menu, select View->Resource View, as Figure 14.15 shows.

Figure 14.15: Opening the Resource View panel.

On the right hand side of Visual C++ .NET, you should see a “Resource View” panel, with the name of your project. Right click the name, select Add, then Add Resource, as Figure 14.16 shows.

Figure 14.16: Adding a resource to the project.

A dialog box appears (Figure 14.17). Select the Icon selection and press the “New” button.

Page 441: C++ Módulo I e II

120

Figure 14.17: Adding an icon resource.

An icon resource will be added to your project, and the Visual C++ .NET icon editor will launch, thereby allowing you to paint your icon. Figure 14.18 shows a quick Game Institute icon that was painted in the editor.

Figure 14.18: Painting an icon in the Visual C++ Resource Editor.

As you can see from the “Resource View” panel (Figure 14.19), the icon that we added was given the default name IDI_ICON1. We can change this name, but it is fine for now.

Page 442: C++ Módulo I e II

121

Figure 14.19: The Resource View panel shows the project resources.

To get a HICON handle to our icon, we use the LoadIcon function like so: ::LoadIcon(ghAppInst, MAKEINTRESOURCE(IDI_ICON1)); Because we are not using a system icon, we must specify the application instance for the first parameter. For the second parameter, we must pass our icon name through a macro, which will convert a numeric ID to the icon name. Also note that when we add a new resource, a new header file called “resource.h” is automatically created, which contains our resource names and symbols. In order for the application to recognize the symbol IDI_ICON1, you will need to #include “resource.h”. Figure 14.20 shows our window, now with a custom icon:

Figure 14.20: Our window with a custom icon.

Page 443: C++ Módulo I e II

122

Chapter 15

Introduction to GDI and Menus

Page 444: C++ Módulo I e II

123

Introduction

The primary theme of this chapter is GDI (Graphics Device Interface). GDI is the component of the Win32 API that is concerned with drawing graphics; it provides all the necessary structures and functions for drawing graphics onto your windows. This is definitely one of the more “fun” chapters we have encountered so far this course, as we finally have enough C++ background and Windows background to make an interesting application. Moreover, the basic graphics and menu features we learn about here will provide us with a solid foundation when we begin to make some Windows games. By the end of this chapter, you will have learned how to create a basic paint program that allows you to draw various shapes with different colors and styles. Figure 15.1 shows our ultimate goal for this chapter.

Figure 15.1: A screenshot of the paint program that we will develop in this chapter.

Chapter Objectives

• Learn how to output text onto a window, and how to draw several GDI shape primitives like lines, rectangles and ellipses.

• Understand how different pens and brushes can be used to change the way in which the GDI shapes are colored and drawn.

• Learn how to load bitmap (.bmp) images from file into our Windows programs, and how to draw them on the client area of our windows.

• Become familiar with the Visual C++ menu resource editor, and learn how to create menus with it.

Page 445: C++ Módulo I e II

124

15.1 Text Output

15.1.1 The WM_PAINT Message

Before we can begin a discussion of outputting text to a window’s client area (which is a form of drawing), we need to talk about the WM_PAINT message. First, note that in Windows, you can have several windows open at once. For example, you may have a web browser open, a word processor open, and perhaps a couple different windows displaying the contents of various folders. You have certainly observed a time where a window A was partially or fully obscured by another window B (or even obscured by several windows, but let us keep it simple). The key idea is that when B obscures a part of A’s client area (call the obscured region R), the data drawn on R is not saved by Windows. Consequently, when we move B off of A’s client area, so that a part of R becomes visible, the data previously drawn to R no longer exists—it was not saved. However, you might argue that this is not what happens. That is, you have obscured windows, and then made them visible again and nothing was lost. What is actually happening is that the windows “know” when previously obscured parts become visible, and they then redraw themselves to restore the previous data. Thus, it looks like nothing was really lost. Note though, that this functionality is not automatic—the applications were programmed to repaint themselves at the appropriate time.

Note: Resizing a window so that it becomes smaller can also hide a region you once painted to. When you resize back to a larger size, the data drawn to the previously hidden region will not be “remembered” by Windows—you will have to redraw it yourself. Window also sends a WM_PAINT message when a window is resized.

The window knows to redraw itself when part of it becomes visible because Windows will send the window a WM_PAINT message informing the window that it needs to repaint its client area (or a region of the client area). This means that we need to structure our programs so that the drawing code occurs in response to a WM_PAINT message. By placing all the drawing code in the WM_PAINT message handler, we can ensure that the window will repaint itself whenever necessary; that is, whenever a WM_PAINT occurs.

15.1.2 The Device Context

A device context is a software abstraction of a display device, such as the video card or a printer. We do all drawing operations through the device context (abbreviated as DC). The first question is how to obtain a DC. There are several ways. We will look at one method now, and then look at another method a few chapters down the road. As said, we will try to structure our programs so that all the drawing will be done in the WM_PAINT handler. Because the WM_PAINT handler is a common place for drawing, the Win32 API provides a special function, which can be used there that returns a handle to a device context. More specifically, in a WM_PAINT handler, we can get a handle to device context (HDC) with the BeginPaint function:

Page 446: C++ Módulo I e II

125

HDC hdc = 0; PAINTSTRUCT ps; switch( msg ) case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Drawing code goes here. EndPaint(hWnd, &ps); return 0; Note that BeginPaint takes an argument to a window handle (hWnd). This is necessary because a DC must be associated with some object onto which it is to render. In this context, we want to render onto a window, so we must specify the handle of the window we want to associate the DC with. Also observe that every BeginPaint function call must have a corresponding EndPaint function call, and that the actual drawing code goes in between the two calls. The HDC is a handle to a device context. The PAINTSTRUCT structure is defined like so: typedef struct tagPAINTSTRUCT HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[32]; PAINTSTRUCT, *PPAINTSTRUCT; You should only touch the first three members; the Win32 API uses the others internally. Although BeginPaint returns a handle to a device context, you can also get it from the hdc member of the PAINTSTRUCT. The fErase member is true if the background should be erased, and false otherwise. The rcPaint rectangle defines the smallest rectangle that needs to be updated. In this way, you do not have to repaint the entire client area, only the necessary region.

15.1.3 TextOut

In the previous section, we learned how to obtain a DC. We can now learn how to draw text. Drawing text is quite simple, and is handled with one API function. In particular, it is handled with the TextOut API function. Here is a sample call: string s = “Hello, World!”; TextOut(hdc, 10, 10, s.c_str(), s.size()); The first parameter of TextOut is a handle to the device context, which is associated with the window’s client area onto which we will be drawing the text. The second and third parameters are used

Page 447: C++ Módulo I e II

126

to specify the (x, y) position, relative to the window’s client rectangle in screen space (upper-left corner of client area is the origin and +y “goes down”) and where the text should be displayed on the client area rectangle. The fourth parameter is the c-string to display, and the fifth parameter is the size of the c-string to display.

15.1.3 Example Program

In this sample program, we will develop an application where we output the text “Hello, World.” wherever the user presses the left mouse button on the window’s client area. Figure 15.2 demonstrates the product of numerous mouse clicks:

Figure 15.2: We output the text “Hello, World.” wherever the user presses the left mouse button.

Recall that Windows does not save our drawn data if a part of the client area gets obscured. Therefore, we need to save all the data ourselves so that we can redraw it all when a WM_PAINT message occurs. To facilitate this, we maintain a global vector of TextObjs, which will store the position and string of every string we create. (In this program, we will create a string whenever we press the left mouse button on the window’s client area.) Thus we have the following: struct TextObj string s; // The string. POINT p; // String position, relative to the client area rect. ; vector<TextObj> gTextObjs;

Page 448: C++ Módulo I e II

127

Now we will create a TextObj instance and add it to gTextObjs whenever the user presses the left mouse button on the window’s client area. To do this we must handle the WM_LBUTTONDOWN message, which is sent when the user presses the left mouse button: TextObj to; switch( msg ) // Handle left mouse button click message. case WM_LBUTTONDOWN: to.s = "Hello, World."; // Point that was clicked is stored in the lParam. to.p.x = LOWORD(lParam); to.p.y = HIWORD(lParam); // Add to our global list of text objects. gTextObjs.push_back( to ); InvalidateRect(hWnd, 0, false); return 0; There are a few new ideas here. First, we note that the point the user clicked is provided in the lParam of the WM_LBUTTONDOWN message. The lParam is a 32-bit integer value, so what Windows does to save space is to store the x-coordinate in the lower 16-bits and the y-coordinate in the higher 16-bits. For convenience, the Win32 API also provides the macro LOWORD, which will extract the lower 16-bit value of a 32-bit integer and return the result, and the macro HIWORD, which will extract the higher 16-bit value of a 32-bit integer, and return the result. Thus, for the WM_LBUTTONDOWN message, we can extract the point that was clicked, relative to the client area rectangle, with the code: to.p.x = LOWORD(lParam); to.p.y = HIWORD(lParam); In this program, all of our strings will be “Hello, World.” so we assign a literal to our TextObj’s string member: to.s = "Hello, World."; After creating and filling out the data members of the TextObj instance, we add it to our global container so that it is saved: // Add to our global list of text objects. gTextObjs.push_back( to ); Now that we have created a new TextObj, we want to display it immediately. However, all of our drawing code is in the WM_PAINT handler. Essentially, we want to force a repaint now (i.e., send a WM_PAINT message now). We can do that by calling the InvalidateRect function, which will post a WM_PAINT message.

Page 449: C++ Módulo I e II

128

InvalidateRect takes three parameters: the first is a handle to the window to which we want to send a WM_PAINT message; the second is a pointer to a RECT structure, which defines the region that needs to be updated—we can pass null for this value to update the entire client area; the third parameter is a Boolean value that specifies whether the region that parameter two defines should be erased with the background color prior to being drawn upon. We create a new TextObj whenever the user presses the left mouse button. Each TextObj stores the string and its position. This provides us with all the information we need to draw the string the TextObj contains with TextOut. Thus, to draw all the TextObjs we have created (which are added to gTextObjs), all we need to do is iterate through each element in gTextObjs and call TextOut: case WM_PAINT: hdc = BeginPaint(hWnd, &ps); for(int i = 0; i < gTextObjs.size(); ++i) TextOut( hdc, gTextObjs[i].p.x, gTextObjs[i].p.y, gTextObjs[i].s.c_str(), gTextObjs[i].s.size()); EndPaint(hWnd, &ps); return 0; Program 15.1 shows the code in its entirety, with the concepts new to this chapter in bold font (the rest of the code is the same window creation code we discussed in the previous chapter). Program 15.1: Text drawing program.

#include <windows.h> #include <string> #include <vector> using namespace std; //========================================================= // Globals. HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; struct TextObj string s; // The string object. POINT p; // The position of the string, relative to the // upper-left corner of the client rectangle of // the window. ; vector<TextObj> gTextObjs;

Page 450: C++ Módulo I e II

129

// Step 1: Define and implement the window procedure. LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) // Objects for painting. HDC hdc = 0; PAINTSTRUCT ps; TextObj to; switch( msg ) // Handle left mouse button click message. case WM_LBUTTONDOWN: to.s = "Hello, World."; // Point that was clicked is stored in the lParam. to.p.x = LOWORD(lParam); to.p.y = HIWORD(lParam); // Add to our global list of text objects. gTextObjs.push_back( to ); InvalidateRect(hWnd, 0, false); return 0; // Handle paint message. case WM_PAINT: hdc = BeginPaint(hWnd, &ps); for(int i = 0; i < gTextObjs.size(); ++i) TextOut( hdc, gTextObjs[i].p.x, gTextObjs[i].p.y, gTextObjs[i].s.c_str(), gTextObjs[i].s.size()); EndPaint(hWnd, &ps); return 0; // Handle key down message. case WM_KEYDOWN: if( wParam == VK_ESCAPE ) DestroyWindow(ghMainWnd); return 0; // Handle destroy window message. case WM_DESTROY: PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the // default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam);

Page 451: C++ Módulo I e II

130

// WinMain: Entry point for a Windows application. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Save handle to application instance. ghAppInst = hInstance; // Step 2: Fill out a WNDCLASS instance. WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; // Step 3: Register the WNDCLASS instance with Windows. RegisterClass( &wc ); // Step 4: Create the window, and save handle in globla // window handle variable ghMainWnd. ghMainWnd = ::CreateWindow("MyWndClassName", "TextOut Example", WS_OVERLAPPEDWINDOW, 200, 200, 640, 480, 0, 0, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false; // Step 5: Show and update the window. ShowWindow(ghMainWnd, showCmd); UpdateWindow(ghMainWnd); // Step 6: Enter the message loop and don't quit until // a WM_QUIT message is received. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage(&msg, 0, 0, 0) ) TranslateMessage(&msg); DispatchMessage(&msg); // Return exit code back to operating system. return (int)msg.wParam;

Page 452: C++ Módulo I e II

131

15.2 Shape Primitives

15.2.1 Drawing Lines

Line drawing is done with two functions. The first function moves the “virtual pen” to the starting point of the line. The second function draws a line from the previously specified starting point, to a newly specified second point: // Following two functions draws a line from (startX, startY) // to (endX, endY). MoveToEx(hdc, startX, startY, 0); LineTo(hdc, endX, endY); Both functions take a handle to the device context, as all drawing must be done through the device context. The second and third parameters for both functions are the x- and y-coordinates of a point, which is relative to the client area rectangle. For MoveToEx, that point is the starting point of the line. For LineTo, that point is the ending point of the line. The fourth parameter of MoveToEx returns a POINT object of the last “start” point that was specified, via a pointer parameter. If this value is not needed, you can specify null. The program we will write to illustrate line drawing allows the user to define the line’s “start” point by pressing the left mouse button. The user holds the left mouse button down and moves the mouse to a new point. When the user has found the point where he/she wants the line’s “end” point to be, the user raises the left mouse button up. Figure 15.3 shows the program after some lines were drawn.

Figure 15.3: The line drawing program. Users can draw lines by holding the left mouse button down

Page 453: C++ Módulo I e II

132

As the user moves the mouse around looking for the “end” point, he/she will expect to see the new line being drawn in real-time. That is, a line from the “start” point to the current mouse position should constantly be drawn and updated interactively. In this way, the user can see exactly how the line will look before raising the left mouse button to make the line permanent. This functionality requires some special code. Let us get started. First, we have the following global variables (and also a structure definition): struct Line POINT p0; POINT p1; ; vector<Line> gLines; Line gLine; bool gMouseDown = false; A Line is simply defined by two points, p0, and p1, where p0 is the “start” point and p1 is the “end” point. We recall that Windows does not save our drawn data if a part of the client area gets obscured. Therefore, we need to save all the data ourselves so that we can redraw it all when a WM_PAINT message occurs. To facilitate this, we maintain a global vector of Lines, called gLines, which will store the lines we create. The global variable gLine is our temporary line; that is, it is the line we will draw as the user moves the mouse around when deciding where the “end” point of the line should be. We do not actually add a line to gLines until the user has lifted the left mouse button. Finally, gMouseDown is a Boolean variable that denotes whether or not the left mouse button is currently down or not. The first message we need to handle is the WM_LBUTTONDOWN message, which is where the line’s “starting” point is defined. case WM_LBUTTONDOWN: // Capture the mouse (we still get mouse input // even after the mouse cursor moves off the client area. SetCapture(hWnd); gMouseDown = true; // Point that was clicked is stored in the lParam. gLine.p0.x = LOWORD(lParam); gLine.p0.y = HIWORD(lParam); return 0; Note that we set the “start” point in our temporary line. We do not actually add the line to our global line container gLines until the user lifts the left mouse button.

Page 454: C++ Módulo I e II

133

A new API function in this message handler is the SetCapture function. This function “captures” the mouse for the specified window. Capturing means that the window will continue to receive mouse messages even if the mouse moves off the window’s client area. As long as the user has the left mouse button down, we would like to have the mouse captured—we free the mouse when the user lifts the left mouse button. Finally, if a WM_LBUTTONDOWN message occurs, we know the mouse is now down, so we set our flag gMouseDown to true. The next message we handle is the WM_MOUSEMOVE message. This message is sent whenever the mouse moves. case WM_MOUSEMOVE: if( gMouseDown ) // Current mouse position is stored in the lParam. gLine.p1.x = LOWORD(lParam); gLine.p1.y = HIWORD(lParam); InvalidateRect(hWnd, 0, true); return 0; Notice that we only care about this message if the left mouse button is down (if( gMouseDown )). If it is not, we do not care about the WM_MOUSEMOVE message and do not execute any code. So, assuming the left mouse button is down, as the mouse moves we obtain the current mouse position (given in the lParam for the WM_MOUSEMOVE message) and set it as the “end” point for the temporary line. We then invalidate the window’s client rectangle so that it is forced to repaint itself. In this way, the new temporary line will be redrawn interactively as the mouse moves. Also note that here we invalidate the rectangle with true specified for the third parameter—this will cause the background to be erased, which is necessary since we need to erase any previously drawn temporary lines. That is, every time the mouse moves we will draw a temporary line, but we do not want to accumulate these lines; we just want to draw the latest temporary line. Therefore, we must erase any old lines. The third message we handle is the WM_LBUTTONUP message. This message is generated when the left mouse button is lifted up. case WM_LBUTTONUP: // Release the captured mouse when the left mouse button // is lifted. ReleaseCapture(); gMouseDown = false; // Current mouse position is stored in the lParam. gLine.p1.x = LOWORD(lParam); gLine.p1.y = HIWORD(lParam); gLines.push_back( gLine );

Page 455: C++ Módulo I e II

134

InvalidateRect(hWnd, 0, true); return 0; First, as we said before, when the left mouse button is raised, we can release our capture of the mouse; this is done with the ReleaseCapture function. Also, because the left mouse button has now been raised up, we set gMouseDown to false. Next, we update the “end” point of the temporary line to reflect the position of the point where the left mouse button was lifted up. Then, by raising the left mouse button up, the user has chosen where to make a permanent line. Thus, we add a copy of gLine to our line container: gLines.push_back( gLine ); Finally, we invalidate the window’s client rectangle so that the newly added permanent line is drawn. Program 15.2 shows the code for the line drawing program in its entirety, with the new concepts bolded. Program 15.2: Line drawing program. #include <windows.h> #include <string> #include <vector> using namespace std; //========================================================= // Globals. HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; struct Line POINT p0; POINT p1; ; vector<Line> gLines; Line gLine; bool gMouseDown = false; // Step 1: Define and implement the window procedure. LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) // Objects for painting. HDC hdc = 0; PAINTSTRUCT ps; switch( msg ) // Handle left mouse button click message. case WM_LBUTTONDOWN:

Page 456: C++ Módulo I e II

135

// Capture the mouse (we still get mouse input // even after the mouse cursor moves off the client area. SetCapture(hWnd); gMouseDown = true; // Point that was clicked is stored in the lParam. gLine.p0.x = LOWORD(lParam); gLine.p0.y = HIWORD(lParam); return 0; // Message sent whenever the mouse moves. case WM_MOUSEMOVE: if( gMouseDown ) // Current mouse position is stored in the lParam. gLine.p1.x = LOWORD(lParam); gLine.p1.y = HIWORD(lParam); InvalidateRect(hWnd, 0, true); return 0; case WM_LBUTTONUP: // Release the captured mouse when the left mouse // button is lifted. ReleaseCapture(); gMouseDown = false; // Current mouse position is stored in the lParam. gLine.p1.x = LOWORD(lParam); gLine.p1.y = HIWORD(lParam); gLines.push_back( gLine ); InvalidateRect(hWnd, 0, true); return 0; // Handle paint message. case WM_PAINT: hdc = BeginPaint(hWnd, &ps); if( gMouseDown ) MoveToEx(hdc, gLine.p0.x, gLine.p0.y, 0); LineTo(hdc, gLine.p1.x, gLine.p1.y); for(int i = 0; i < gLines.size(); ++i) MoveToEx(hdc, gLines[i].p0.x, gLines[i].p0.y, 0); LineTo(hdc, gLines[i].p1.x, gLines[i].p1.y); EndPaint(hWnd, &ps); return 0;

Page 457: C++ Módulo I e II

136

// Handle key down message. case WM_KEYDOWN: if( wParam == VK_ESCAPE ) DestroyWindow(ghMainWnd); return 0; // Handle destroy window message. case WM_DESTROY: PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the // default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam); // WinMain: Entry point for a Windows application. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Save handle to application instance. ghAppInst = hInstance; // Step 2: Fill out a WNDCLASS instance. WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; // Step 3: Register the WNDCLASS instance with Windows. RegisterClass( &wc ); // Step 4: Create the window, and save handle in globla // window handle variable ghMainWnd. ghMainWnd = ::CreateWindow("MyWndClassName", "MyWindow", WS_OVERLAPPEDWINDOW, 200, 200, 640, 480, 0, 0, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false; // Step 5: Show and update the window. ShowWindow(ghMainWnd, showCmd); UpdateWindow(ghMainWnd); // Step 6: Enter the message loop and don't quit until

Page 458: C++ Módulo I e II

137

// a WM_QUIT message is received. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage(&msg, 0, 0, 0) ) TranslateMessage(&msg); DispatchMessage(&msg); // Return exit code back to operating system. return (int)msg.wParam;

15.2.2 Drawing Rectangles

To draw a rectangle we can use the Rectangle API function. The Rectangle function takes five parameters; the first is a handle to a DC, and the last four define the dimensions of the rectangle to draw. That is, the last four parameters take the left, top, right, and bottom coordinates of the rectangle. Here is an example call. Rectangle(hdc, gRect.left, gRect.top, gRect.right, gRect.bottom); gRect is of type RECT, which was discussed in a Note in the previous chapter. The program we will write in order to illustrate rectangle drawing allows the user to define the (left, top) and (right, bottom) points on the rectangle by pressing and lifting the left mouse button. Figure 15.4 shows the program after some rectangles were drawn.

Figure 15.4: A screenshot of the rectangle sample.

In a way similar to the line drawing program, as the user moves the mouse around looking for the (right, bottom) point, he/she will expect to see the new rectangle being drawn in real-time. That is, a rectangle from the (left, top) point to the current mouse position should constantly be drawn and updated

Page 459: C++ Módulo I e II

138

interactively. In this way, the user can see exactly how the rectangle will look before raising the left mouse button to make the rectangle permanent. The logic to do this is exactly the same as the line program. For example, we have the following global variables: HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; vector<RECT> gRects; RECT gRect; bool gMouseDown = false;

These are basically the same as the line program, except we have replaced the Line structure with RECT. But logically, everything is going to be the same as the line drawing program. We are just drawing rectangles instead of lines (which are described with two points, just as lines are). Therefore, rather than duplicate an analogous discussion, we will simply show the rectangle program, and bold the parts that have changed. There should be no trouble understanding the logic if the line drawing program was understood. Program 15.3: Rectangle drawing program.

#include <windows.h> #include <string> #include <vector> using namespace std; //========================================================= // Globals. HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; vector<RECT> gRects; RECT gRect; bool gMouseDown = false; // Step 1: Define and implement the window procedure. LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) // Objects for painting. HDC hdc = 0; PAINTSTRUCT ps; switch( msg ) // Handle left mouse button click message. case WM_LBUTTONDOWN: // Capture the mouse (we still get mouse input // even after the mouse cursor moves off the client area. SetCapture(hWnd); gMouseDown = true; // Point that was clicked is stored in the lParam. gRect.left = LOWORD(lParam);

Page 460: C++ Módulo I e II

139

gRect.top = HIWORD(lParam); return 0; // Message sent whenever the mouse moves. case WM_MOUSEMOVE: if(gMouseDown) // Current mouse position is stored in the lParam. gRect.right = LOWORD(lParam); gRect.bottom = HIWORD(lParam); InvalidateRect(hWnd, 0, true); return 0; case WM_LBUTTONUP: // Release the captured mouse when the left mouse button // is lifted. ReleaseCapture(); gMouseDown = false; // Current mouse position is stored in the lParam. gRect.right = LOWORD(lParam); gRect.bottom = HIWORD(lParam); gRects.push_back( gRect ); InvalidateRect(hWnd, 0, true); return 0; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); if( gMouseDown ) Rectangle(hdc, gRect.left, gRect.top, gRect.right, gRect.bottom); for(int i = 0; i < gRects.size(); ++i) Rectangle(hdc, gRects[i].left, gRects[i].top, gRects[i].right, gRects[i].bottom); EndPaint(hWnd, &ps); return 0; // Handle key down message. case WM_KEYDOWN: if( wParam == VK_ESCAPE ) DestroyWindow(ghMainWnd); return 0; // Handle destroy window message. case WM_DESTROY: PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the

Page 461: C++ Módulo I e II

140

// default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam); // WinMain: Entry point for a Windows application. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Save handle to application instance. ghAppInst = hInstance; // Step 2: Fill out a WNDCLASS instance. WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; // Step 3: Register the WNDCLASS instance with Windows. RegisterClass( &wc ); // Step 4: Create the window, and save handle in global // window handle variable ghMainWnd. ghMainWnd = ::CreateWindow("MyWndClassName", "MyWindow", WS_OVERLAPPEDWINDOW, 200, 200, 640, 480, 0, 0, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false; // Step 5: Show and update the window. ShowWindow(ghMainWnd, showCmd); UpdateWindow(ghMainWnd); // Step 6: Enter the message loop and don't quit until // a WM_QUIT message is received. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage(&msg, 0, 0, 0) ) TranslateMessage(&msg); DispatchMessage(&msg); // Return exit code back to operating system. return (int)msg.wParam;

Page 462: C++ Módulo I e II

141

15.2.3 Drawing Ellipses

Drawing an ellipse is done with the Ellipse function. What is interesting about the Ellipse function is that instead of specifying properties on an ellipse, we specify the dimensions of the ellipse’s bounding rectangle. Figure 15.5 illustrates:

Figure 15.5: The Ellipse function draws an ellipse tightly inside the bounding rectangle specified.

Thus, it turns out that the Ellipse function has the exact same parameters as the Rectangle function. Here is an example call: Ellipse(hdc, gRect.left, gRect.top, gRect.right, gRect.bottom); Where gRect is of type RECT, which specifies the bounding rectangle of the ellipse. We could write an ellipse-drawing program. However, the code would be practically the same as the rectangle sample. In fact, we can simply replace the Rectangle function with Ellipse function in Program 15.3 to draw ellipses instead of rectangles. We leave this as an exercise for you to try. Figure 15.6 shows how the output of such a program would look:

Figure 15.6: A screenshot of an ellipse drawing program.

Page 463: C++ Módulo I e II

142

15.3 Loading and Drawing Bitmaps

In this section, we will see how to load a .bmp image from file and display it in the client area of a window. This is not particularly difficult, but it is an important task because, later on when we begin to program some games, we will be working with bitmaps extensively. Before we begin, a brief definition of bitmaps is in order. A bitmap is simply a matrix of data, where each element in the matrix describes a color. We can map this color matrix to a rectangle of pixels on the monitor screen to display the bitmap image. Because the pixels are small and close together, a continuous image can be displayed on your monitor.

15.3.1 Loading

To load a bitmap, the first thing we must do is to load a bitmap as a resource. To do this, go the Visual C++ .NET menu and select View->Resource View as Figure 15.7 shows.

Figure 15.7: Opening the Resource View.

The “Resource View” panel should be displayed near the right side of the Visual C++ .NET interface. In the “Resource View” panel, right click your project name, and then select Add->Add Resource as Figure 15.8 shows.

Figure 15.8: Adding a resource to the application.

Page 464: C++ Módulo I e II

143

A new “Add Resource” dialog will appear (Figure 15.9). Select “Bitmap” and then the “Import…” button.

Figure 15.9: Adding a bitmap resource.

Now an “Import” dialog box appears (Figure 15.10). Use this dialog box to find the .bmp file you wish to load. Here we have placed a gilogo.bmp in the application project directory. Select the .bmp file you wish to load and select the “Open” button.

Figure 15.10: Selecting the bitmap file to import.

Page 465: C++ Módulo I e II

144

At this point, the .bmp file should be loaded as an application resource. Your “Resource View” panel should look like Figure 15.11.

Figure 15.11: The Resource View panel.

By default, Visual C++ .NET automatically named the bitmap resource IDB_BITMAP1. You can change this if you like, but we will keep it as is for the sample program. Note that this resource name is a numeric identification symbol, which allows us to make reference to the bitmap in our program code. Now that we have successfully loaded a bitmap resource, we can load it into our application. This is done with the LoadBitmap API function: HBITMAP hBitMap = LoadBitmap(ghAppInst, MAKEINTRESOURCE(IDB_BITMAP1)); This function returns an HBITMAP; that is, a handle to the loaded bitmap. This function takes two arguments: the first is a handle to the application instance; the second is the numeric identification symbol name of the bitmap resource we wish to load. For the second parameter, we must pass our bitmap name through a macro (MAKEINTRESOURCE), which will convert a numeric ID to the bitmap name. Also note that when we add a new resource, a new header file called “resource.h” is automatically created, which contains our resource names and symbols. In order for the application to recognize the symbol IDB_BITMAP1, you will need to #include “resource.h”. Given a bitmap handle, we would like to get information about the bitmap, such as its width and height. To get the corresponding data structure of a bitmap we use the GetObject function: BITMAP bitmap; GetObject(hBitMap, // Handle to GDI object. sizeof(BITMAP), // Size in bytes of GDI object. &bitmap); // Instance to fill data members of. This function fills out the bitmap instance. The BITMAP structure is defined like so: typedef struct tagBITMAP LONG bmType; LONG bmWidth; LONG bmHeight; LONG bmWidthBytes; WORD bmPlanes; WORD bmBitsPixel; LPVOID bmBits; BITMAP, *PBITMAP;

Page 466: C++ Módulo I e II

145

We only discuss the four most important data members: bmWidth: The width of the bitmap, measured in pixels. bmHeight: The height of the bitmap, measured in pixels. bmBitsPixel: The number of bits used to describe a single pixel in the bitmap (i.e., the number of bits used to describe a single color element). 24 bits per pixel is common for colored bitmaps; that is, 8-bits for red, 8-bits for green, and 8-bits for blue. bmBits: A pointer to the actual bitmap elements; that is, a pointer to the matrix array.

15.3.2 Rendering

To render a bitmap to the client area of a rectangle (i.e., copy the pixels of the bitmap over to the pixels of the client area) we must first associate the bitmap with a separate system memory device context. We do this with the CreateCompatibleDC function: hdc = BeginPaint(hWnd, &ps); HDC bmHDC = CreateCompatibleDC(hdc); Next, we need to associate our bitmap with this new system memory device context. We can do that with the SelectObject function: HBITMAP oldBM = (HBITMAP)SelectObject(bmHDC, hBitMap); Note that the SelectObject function returns a handle to the previously selected object. It is considered good practice to restore the original object back to the device context when finished with the newly selected object. The first parameter of SelectObject is a handle to the device context into which you wish to select the GDI object; the second parameter is the handle to the GDI object you wish to select—in this case, the bitmap handle. Now that we have our bitmap associated with a system memory device context (bmHDC), we can copy the pixels of the bitmap (source) over to the window’s client area (destination) with the BitBlt function. // Now copy the pixels from the bitmap bmHDC has selected // to the pixels from the client area hdc has selected. BitBlt( hdc, // Destination DC. 0, // 'left' coordinate of destination rectangle. 0, // 'top' coordinate of destination rectangle. bmWidth, // 'right' coordinate of destination rectangle. bmHeight, // 'bottom' coordinate of destination rectangle. bmHDC, // Bitmap source DC. 0, // 'left' coordinate of source rectangle. 0, // 'top' coordinate of source rectangle. SRCCOPY); // Copy the source pixels from bitmap directly // to the destination pixels (client area)

Page 467: C++ Módulo I e II

146

Finally, to clean up, we restore the original bitmap object back to the system memory device context, and then we delete the system memory device context because we are done with it: SelectObject(bmHDC, oldBM); DeleteDC(bmHDC); EndPaint(hWnd, &ps);

15.3.3 Deleting

To destroy a bitmap object—that is, to free the bitmap memory—we use the DeleteObject function, which takes a handle to the object to delete: DeleteObject(hBitMap);

15.3.4 Sample Program

This next sample program loads the Game Institute .bmp logo from file and displays it in the window’s client area, as Figure 15.12 shows.

Figure 15.12: A screenshot of the bitmap sample.

Note: You may notice this sample, and the other graphics samples, flicker slightly as you resize the window and under some other circumstances. We will address the flickering problem in later chapters.

There is one new key concept which this program uses which we have not discussed. It is the WM_CREATE message. This message is sent when the window is first created, and so, it is common to place initialization code in the WM_CREATE message handler. In Program 15.4, we load the bitmap in the WM_CREATE message handler:

Page 468: C++ Módulo I e II

147

case WM_CREATE: ghBitMap = LoadBitmap(ghAppInst, MAKEINTRESOURCE(IDB_BITMAP1)); For convenience, we have bolded the new bitmap loading related segments of code. Program 15.4: Bitmap drawing program.

#include <windows.h> #include <string> #include <vector> #include "resource.h" using namespace std; //========================================================= // Globals. HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; HBITMAP ghBitMap = 0; // Step 1: Define and implement the window procedure. LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) // Objects for painting. HDC hdc = 0; HDC bmHDC = 0; PAINTSTRUCT ps; BITMAP bitmap = 0; static int bmWidth = 0; static int bmHeight = 0; HBITMAP oldBM = 0; switch( msg ) case WM_CREATE: ghBitMap = LoadBitmap(ghAppInst, MAKEINTRESOURCE(IDB_BITMAP1)); GetObject(ghBitMap, sizeof(BITMAP), &bitmap); bmWidth = bitmap.bmWidth; bmHeight = bitmap.bmHeight; return 0; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Create a system memory device context. bmHDC = CreateCompatibleDC(hdc);

Page 469: C++ Módulo I e II

148

// Hook up the bitmap to the bmHDC. oldBM = (HBITMAP)SelectObject(bmHDC, ghBitMap); // Now copy the pixels from the bitmap bmHDC has selected // to the pixels from the client area hdc has selected. BitBlt( hdc, // Destination DC. 0, // 'left' coordinate of destination rectangle. 0, // 'top' coordinate of destination rectangle. bmWidth, // 'right' coordinate of destination rectangle. bmHeight, // 'bottom' coordinate of destination rectangle. bmHDC, // Bitmap source DC. 0, // 'left' coordinate of source rectangle. 0, // 'top' coordinate of source rectangle. SRCCOPY); // Copy the source pixels directly // to the destination pixels // Select the originally loaded bitmap. SelectObject(bmHDC, oldBM); // Delete the system memory device context. DeleteDC(bmHDC); EndPaint(hWnd, &ps); return 0; // Handle key down message. case WM_KEYDOWN: if( wParam == VK_ESCAPE ) DestroyWindow(ghMainWnd); return 0; // Handle destroy window message. case WM_DESTROY: DeleteObject(ghBitMap); PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the // default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam); // WinMain: Entry point for a Windows application. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Save handle to application instance. ghAppInst = hInstance; // Step 2: Fill out a WNDCLASS instance. WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0;

Page 470: C++ Módulo I e II

149

wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; // Step 3: Register the WNDCLASS instance with Windows. RegisterClass( &wc ); // Step 4: Create the window, and save handle in globla // window handle variable ghMainWnd. ghMainWnd = ::CreateWindow("MyWndClassName", "Bitmap Program", WS_OVERLAPPEDWINDOW, 200, 200, 640, 480, 0, 0, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false; // Step 5: Show and update the window. ShowWindow(ghMainWnd, showCmd); UpdateWindow(ghMainWnd); // Step 6: Enter the message loop and don't quit until // a WM_QUIT message is received. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage(&msg, 0, 0, 0) ) TranslateMessage(&msg); DispatchMessage(&msg); // Return exit code back to operating system. return (int)msg.wParam;

Page 471: C++ Módulo I e II

150

15.4 Pens and Brushes

Just as an artist can select different pens and brushes to achieve different results, the Win32 API provides conceptual pens and brushes in code, which allows us to modify the way in which things are drawn. Via pens and brushes it was possible to draw the shapes in Figure 15.1 with different colors, hatch patterns, and styles.

15.4.1 Pens

Pens are used to draw lines, curves, and also to draw the border of solid shapes like rectangles and ellipses. The properties of a pen are described with the LOGPEN structure: typedef struct tagLOGPEN UINT lopnStyle; POINT lopnWidth; COLORREF lopnColor; LOGPEN, *PLOGPEN;

• lopnStyle: The pen style; some common styles are PS_SOLID (solid pen), PS_DOT (for dotted lines/borders), and PS_DASH (for dashed lines/borders).

• lopnWidth: The thickness of the pen, in pixels.

• lopnColor: The pen color.

Given a filled out LOGPEN instance that describes a pen, we can create a pen with those properties using the CreatePenIndirect function: LOGPEN lp; lp.lopnStyle = PS_SOLID; lp.lopnWidth.x = 1; lp.lopnWidth.y = 1; lp.lopnColor = RGB(255, 0, 255); HPEN hPen = CreatePenIndirect(&lp); When you are done with a pen, you should delete it with the DeleteObject function: DeleteObject(hPen); Once a pen is created, it must be selected by the device context before it can be used. This is done with the SelectObject function: mOldPen = (HPEN)SelectObject(hdc, mPen);

Page 472: C++ Módulo I e II

151

Again, SelectObject will return a handle to the previously selected GDI object. It is advised that you reselect the old GDI object when you are done with the new one: hOldPen = (HPEN)SelectObject(hdc, hPen); // do work with hPen SelectObject(hdc, hOldPen);

15.4.2 Brushes

Brushes are used to fill in the interiors of solid shapes like rectangles and ellipses. The properties of a brush are described with the LOGBRUSH structure:

typedef struct tagLOGBRUSH UINT lbStyle; COLORREF lbColor; LONG lbHatch; LOGBRUSH, *PLOGBRUSH;

• lbStyle: The brush style; some common styles are BS_SOLID (solid brush), BS_NULL (creates a brush that does not fill in the interior regions of solid shapes), BS_HATCHED (creates a brush that draws hatched marks; the hatching style depends on the lbHatch member of the LOGBRUSH structure).

• lbColor: The brush color.

• lbHatch: The hatching style; some common hatching styles are HS_BDIAGONAL (diagonal

hatch marks), and HS_CROSS (crossed hatch marks). Given a filled out LOGBRUSH instance that describes a brush, we can create a brush with those properties using the CreateBrushIndirect function: LOGBRUSH lb; lb.lbStyle = BS_HATCHED; lb.lbColor = RGB(255, 0, 255); lb.lbHatch = HS_CROSS; HBRUSH hBrush = CreateBrushIndirect(&lp);

Note: If you create a brush with the style BS_SOLID then the value in lbHatch is ignored, because, by definition, a solid brush is not hatched.

When you are done with a brush, you should delete it with the DeleteObject function: DeleteObject(hBrush);

Page 473: C++ Módulo I e II

152

Once a brush is created, it must be selected by the device context before it can be used. This is done with the SelectObject function: hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); Again, SelectObject will return a handle to the previously selected GDI object. It is advised that you reselect the old GDI object when you are done with the new one: hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); // do work with hBrush SelectObject(hdc, hOldBrush);

15.5 Shape Classes

We are nearing the end of this chapter, where we will be able to finally implement the drawing program displayed in Figure 15.1. In that program we will need to be able to draw different kinds of shapes, so it will simplify matters if we create some shape classes to represent the three different kinds of shapes we will draw—lines, rectangles and ellipses. Moreover, we will want to use polymorphism because it will make our implementation easier if we can work directly with general shapes rather than different kinds of concrete shapes. (i.e., we can store all shapes, LineShapes, RectShapes, and EllipseShapes in one Shape* container and work with the shapes without caring about what specific kind of shape they are—review polymorphism in Module I if this is confusing.)

15.5.1 Class Definitions

First, what do lines, rectangles, and ellipses all have in common? We have seen that all shapes can be represented with two points. A line has a start and end point. A rectangle has the (left, top) and (right, bottom) points and so does an ellipse, since an ellipse is drawn based on its specified bounding rectangle. Additionally, all shapes will have a pen and brush that specifies the color and style in which it should be drawn. Thus we have the following data that is common to all shapes: POINT mPt0; POINT mPt1; HPEN mhPen; HBRUSH mhBrush; HPEN mhOldPen; HBRUSH mhOldBrush; Note that we have added mhOldPen and mhOldBrush, so that we can restore the original pen and brush after we are done drawing with mhPen and mhBrush.

Page 474: C++ Módulo I e II

153

Next, what can all shapes do? We will say that all shapes know how to draw themselves. However, at the general Shape level, we do not know what shape to draw. Therefore, we make the draw method an abstract method, which must be overridden and implemented in the derived classes. Also note that because we do all drawing through the device context, the draw method takes a parameter to the handle of a device context. In addition to drawing, we provide methods for setting the start and end points of the shape. For rectangles (and ellipses), the start point corresponds to the point (left, top) and the end point corresponds to the point (right, bottom). Thus we have the following methods that are common to all shapes: void setStartPt(const POINT& p0); void setEndPt(const POINT& p1); virtual void draw(HDC hdc) = 0; In addition to that, we will also need a constructor and destructor: Shape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb); virtual ~Shape(); Observe that the Shape class contains all the data any derived class will need. Thus, derived classes need only to override the draw method. The following code shows the full definition of the Shape class, as well as the definitions of three derived classes LineShape, RectShape, and EllipseShape. // Shape.h #ifndef SHAPE_H #define SHAPE_H #include <windows.h> class Shape public: Shape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb); virtual~Shape(); void setStartPt(const POINT& p0); void setEndPt(const POINT& p1); virtual void draw(HDC hdc) = 0; protected: POINT mPt0; POINT mPt1; HPEN mhPen; HBRUSH mhBrush; HPEN mhOldPen; HBRUSH mhOldBrush; ;

Page 475: C++ Módulo I e II

154

class LineShape : public Shape public: LineShape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb); void draw(HDC hdc); ; class RectShape : public Shape public: RectShape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb); void draw(HDC hdc); ; class EllipseShape : public Shape public: EllipseShape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb); void draw(HDC hdc); ; #endif // SHAPE_H

15.5.2 Class Implementations

We start with the constructor of the Shape class. As you can see, the constructor has a LOGPEN and LOGBRUSH parameter from which we can get an HPEN and HBRUSH instance: // Shape.cpp #include "Shape.h" Shape::Shape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb) mPt0.x = u.x; mPt0.y = u.y; mPt1.x = v.x; mPt1.y = v.y; mhPen = CreatePenIndirect(&lp); mhBrush = CreateBrushIndirect(&lb); mhOldPen = 0; mhOldBrush = 0;

Page 476: C++ Módulo I e II

155

A destructor should free any resources that were allocated in the constructor. The only resource we allocate in the constructor of Shape was a pen and brush, so we delete these GDI objects in the destructor like so: Shape::~Shape() DeleteObject(mhPen); DeleteObject(mhBrush); The Shape set* methods are implemented trivially: void Shape::setStartPt(const POINT& p0) mPt0 = p0; void Shape::setEndPt(const POINT& p1) mPt1 = p1; Moving onto the derived classes, the LineShape constructor does nothing except call the parent constructor: LineShape::LineShape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb) : Shape(u, v, lp, lb) This follows from the fact that the LineShape added no new data members, so there is nothing else to construct except the Shape part of LineShape. The LineShape draw method simply selects its pen and brush before drawing (so that the line will be drawn using that pen and brush), draws the line based on the start and end point (mPt0 and mPt1), and restores the original pen and brush. void LineShape::draw(HDC hdc) // Select the current pen and brush. mhOldPen = (HPEN)SelectObject(hdc, mhPen); mhOldBrush = (HBRUSH)SelectObject(hdc, mhBrush); // Draw the line. MoveToEx(hdc, mPt0.x, mPt0.y, 0); LineTo(hdc, mPt1.x, mPt1.y); // Restore the old pen and brush. SelectObject(hdc, mhOldPen); SelectObject(hdc, mhOldBrush);

Page 477: C++ Módulo I e II

156

The implementations for the other classes, RectShape and EllipseShape, are exactly the same as LineShape, except we replace the line drawing GDI functions with the Rectangle and Ellipse functions, respectively. Their implementations are as follows: RectShape::RectShape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb) : Shape(u, v, lp, lb) void RectShape::draw(HDC hdc) // Select the current pen and brush. mhOldPen = (HPEN)SelectObject(hdc, mhPen); mhOldBrush = (HBRUSH)SelectObject(hdc, mhBrush); // Draw the rectangle. Rectangle(hdc, mPt0.x, mPt0.y, mPt1.x, mPt1.y); // Restore the old pen and brush. SelectObject(hdc, mhOldPen); SelectObject(hdc, mhOldBrush); EllipseShape::EllipseShape(const POINT u, const POINT v, const LOGPEN& lp, const LOGBRUSH& lb) : Shape(u, v, lp, lb) void EllipseShape::draw(HDC hdc) // Select the current pen and brush. mhOldPen = (HPEN)SelectObject(hdc, mhPen); mhOldBrush = (HBRUSH)SelectObject(hdc, mhBrush); // Draw the ellipse. Ellipse(hdc, mPt0.x, mPt0.y, mPt1.x, mPt1.y); // Restore the old pen and brush. SelectObject(hdc, mhOldPen); SelectObject(hdc, mhOldBrush);

Page 478: C++ Módulo I e II

157

15.6 Menus

The last feature we need to learn about in order to implement the drawing program is how to create and use menus.

15.6.1 Creating a Menu Resource

To create a menu, you need to go to the Visual C++ .NET menu and select View->Resource View. When the “Resource View” panel comes up, right click the project name in it, and select Add->Add Resource. When the “Add Resource” dialog is displayed, select “Menu” and then press the “New” button—Figure 15.13.

Figure 15.13: Adding a menu resource.

The Visual C++ .NET menu editor will be displayed. As with the other resources you have created, you can change the resource ID name (Figure 15.14) if you so desire, however, we will leave it set to the default, IDR_MENU1. Again, the resource ID is how we refer to a particular resource in code.

Figure 15.14: The Resource View panel.

Adding menu items to the resource is fairly intuitive with the menu editor. Just select the box and type in the menu item string name you want displayed. Figure 15.15 shows the menu items we create for the paint drawing sample program.

Page 479: C++ Módulo I e II

158

Figure 15:15: Screenshots of each menu category we made for the paint drawing program.

Note: If you put an ampersand, “&”, before a character in the menu item name, the character that follows the ampersand will be underlined, which means you can use the keyboard to select the menu item instead of the mouse. That is, you can “alt-key.”

As you are typing the menu item name for a new menu item, notice the properties box in the lower right hand corner of Visual C++ .NET (Figure 15.16). This properties box allows you to set various properties for the menu item. The “caption” or menu item name that is displayed is only one property. To check a menu item by default, set “Checked” to true. To disable a menu item by default, set “Enabled” to false. Also observe that each menu item is given an ID. Again, this resource ID is how we will refer to a particular menu item in code. You can name the ID whatever you want, but Visual C++ .NET will typically pick a good descriptive ID name based on the menu caption.

Page 480: C++ Módulo I e II

159

Figure 15.16: The Menu Item properties table.

We used the following menu item IDs in our drawing program: ID_FILE_EXIT ID_PRIMITIVE_LINE ID_PRIMITIVE_RECTANGLE ID_PRIMITIVE_ELLIPSE ID_PRIMITIVE_TEXT ID_PENCOLOR_BLACK ID_PENCOLOR_WHITE ID_PENCOLOR_RED ID_PENCOLOR_GREEN ID_PENCOLOR_BLUE ID_BRUSHCOLOR_BLACK ID_BRUSHCOLOR_WHITE ID_BRUSHCOLOR_RED ID_BRUSHCOLOR_GREEN ID_BRUSHCOLOR_BLUE ID_PENSTYLE_SOLID ID_PENSTYLE_NULL ID_PENSTYLE_DOTTED ID_PENSTYLE_DASHED ID_BRUSHSTYLE_SOLID ID_BRUSHSTYLE_NULL ID_BRUSHSTYLE_DIAGONAL ID_BRUSHSTYLE_CROSS

Page 481: C++ Módulo I e II

160

15.6.2 Loading a Menu and Attaching it to a Window

Now that we have created a menu resource, we need to get a handle to that menu in our program. We can do that with the LoadMenu function: HMENU hMenu = LoadMenu(ghAppInst, MAKEINTRESOURCE(IDR_MENU1)); Recall that you will need to #include “resource.h” as that is where the menu symbols are defined, such as IDR_MENU1. Now that we have a handle to a menu, we can attach it to a window when we create the window in the CreateWindow function: ghMainWnd = ::CreateWindow("MyWndClassName", "My Paint Program", WS_OVERLAPPEDWINDOW, 200, 200, 640, 480, 0, hMenu, ghAppInst, 0); Recall that a parameter of CreateWindow is an HMENU. Before, we always specified null for that parameter because we were not using a menu. But now that we have created and loaded a menu, we can pass the menu handle to CreateWindow to attach the menu to the window.

15.6.3 Checking Menu Items

Programmatically, we can put a check mark next to a menu item (or remove a check mark from a menu item) with the CheckMenuItem API function. This function is prototyped like so: DWORD CheckMenuItem( HMENU hmenu, UINT uIDCheckItem, UINT uCheck ); The first parameter, hmenu, is a handle to the menu we are working with. We need to specify the specific menu because a program could actually have several menus on various sub-windows. The second parameter uIDCheckItem, is the resource ID of the menu item to check. Finally, the third parameter uCheck, is either MF_CHECKED or MF_UNCHECKED. MF_CHECKED puts a check mark next to the menu item and MF_UNCHECKED removes a check mark from a menu item (if it was previously checked). For example, to check the menu item with the ID ID_PRIMITIVE_LINE, we would write: CheckMenuItem(hMenu, ID_PRIMITIVE_LINE, MF_CHECKED);

Page 482: C++ Módulo I e II

161

15.6.4 Selecting Menu Items

When the user selects a menu item, we will generally want to execute some code in response to that menu item selection. To facilitate this, when the user selects a menu item, a WM_COMMAND message is sent to the window’s window procedure. The lower 16-bits of the WM_COMMAND’s wParam value stores the menu item’s resource ID. Thus, in code, we can figure out which item was selected and execute the appropriate code like so: // User selected a menu item. case WM_COMMAND: // Determine which menu item. switch( LOWORD(wParam) ) // Check lower 16-bits of wParam // User selected the “Exit” menu item. case ID_FILE_EXIT: DestroyWindow(ghMainWnd); return 0; // User selected the “Line” menu item. case ID_PRIMITIVE_LINE: // Execute code in response. return 0; // User selected the “Rectangle” menu item. case ID_PRIMITIVE_RECTANGLE: // Execute code in response. return 0; // User selected the “Ellipse” menu item. case ID_PRIMITIVE_ELLIPSE: // Execute code in response. return 0;

15.7 The Paint Sample

We now have enough background knowledge to implement the paint program shown in Figure 15.1. With the exception of the menu handling code, the program logic is very similar to the other drawing programs we have made. The first thing we do is define some constant color values at the global scope: const COLORREF BLACK = RGB(0, 0, 0); const COLORREF WHITE = RGB(255, 255, 255); const COLORREF RED = RGB(255, 0, 0); const COLORREF GREEN = RGB(0, 255, 0); const COLORREF BLUE = RGB(0, 0, 255); This simply allows us to easily refer to some basic colors using the symbol name, instead of RGB macros.

Page 483: C++ Módulo I e II

162

Next, we create a global vector of Shape pointers, gShapes, which will maintain all the shapes we have created so that we can draw all the shapes whenever a WM_PAINT message is generated. In addition, we keep a global shape pointer gShape, which will be the temporary shape we will draw while the user is moving the mouse around and deciding where exactly to make the shape permanent. Notice how we can talk about shapes in general. This is because of the polymorphism. Each shape will know how to draw itself correctly based on its concrete dynamic type (that is, if it is a LineShape then it will know to draw a line, if it is a RectShape then it will know to draw a rectangle, and so on). vector<Shape*> gShapes; Shape* gShape = 0; Recall that the program allows the user to draw three kinds of shapes. The primitive menu item that is selected determines the shape that will be drawn at a given time. For example, if the “Line” menu item is selected (checked) then the user can draw lines. If the “Rectangle” menu item is selected (checked) then the user can draw rectangles.

Figure 15.17: User can select which type of shape to draw via the menu.

We need to keep track of which primitive menu item is selected so that we know which kind of shape to create. To facilitate this, we keep a global variable that keeps track of the currently selected primitive: int gCurrPrimSel = ID_PRIMITIVE_LINE; Later in the WM_LBUTTONDOWN message handler, we create the shape based on the currently selected type: switch( gCurrPrimSel ) case ID_PRIMITIVE_LINE: gShape = new LineShape(p0, p1, gLogPen, gLogBrush); break; case ID_PRIMITIVE_RECTANGLE: gShape = new RectShape(p0, p1, gLogPen, gLogBrush); break; case ID_PRIMITIVE_ELLIPSE: gShape = new EllipseShape(p0, p1,gLogPen,gLogBrush); break; ; In addition to keeping track of the selected primitive type, we will also want to keep track of the selected pen and brush color, and the selected pen and brush style. For this purpose, we add the following global variables:

Page 484: C++ Módulo I e II

163

int gCurrPenColSel = ID_PENCOLOR_BLACK; int gCurrBrushColSel = ID_BRUSHCOLOR_BLACK; int gCurrPenStyleSel = ID_PENSTYLE_SOLID; int gCurrBrushStyleSel = ID_BRUSHSTYLE_SOLID; The values to which these global variables are initialized are the program’s “default” selected values. Therefore, we also need to check the corresponding menu items at the start of the program so that the default selections are checked. We implement this functionality in the WM_CREATE message: case WM_CREATE: CheckMenuItem(ghMenu, ID_PRIMITIVE_LINE, MF_CHECKED); CheckMenuItem(ghMenu, ID_PENCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, ID_BRUSHCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, ID_PENSTYLE_SOLID, MF_CHECKED); CheckMenuItem(ghMenu, ID_BRUSHSTYLE_SOLID, MF_CHECKED); return 0; This way, the default-selected menu items will be checked when the window is first created. We also keep a global instance of a LOGPEN and LOGBRUSH: LOGPEN gLogPen; LOGBRUSH gLogBrush; We update the data members of these structures interactively as the user makes new menu selections. For instance, if the user selects the menu item with the ID ID_PENSTYLE_DOTTED then we update gLogPen like so: case ID_PENSTYLE_DOTTED: CheckMenuItem(ghMenu, ID_PENSTYLE_DOTTED, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenStyleSel, MF_UNCHECKED); gCurrPenStyleSel = ID_PENSTYLE_DOTTED; gLogPen.lopnStyle = PS_DOT; // update pen style return 0; Also notice how we check the newly selected item, uncheck the previously selected item, then update gCurrPenStyleSel. When a shape is created, we pass gLogPen and gLogBrush into the constructor. For example, gShape = new LineShape(p0, p1, gLogPen, gLogBrush); Because we update gLogPen and gLogBrush immediately as the user selects new colors and styles from the menu, these objects always reflect the current user menu selections. Therefore, any object created will always be created with the currently selected pen and brush colors, and pen and brush styles, which is the exact functionality which should happen. The majority of code in the paint-drawing program has to do with the menu and updating the primitive type that is selected, the colors of the pen and brush, and the styles of the pen and brush. The code is

Page 485: C++ Módulo I e II

164

long, but simple. For example, the part of the code that checks for which primitive type the user selected is as follows: case WM_COMMAND:

switch( LOWORD(wParam) ) //======================================= // Primitive Types (Shape Types) //======================================= case ID_PRIMITIVE_LINE: CheckMenuItem(ghMenu, ID_PRIMITIVE_LINE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED); gCurrPrimSel = ID_PRIMITIVE_LINE; return 0; case ID_PRIMITIVE_RECTANGLE: CheckMenuItem(ghMenu, ID_PRIMITIVE_RECTANGLE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED); gCurrPrimSel = ID_PRIMITIVE_RECTANGLE; return 0; case ID_PRIMITIVE_ELLIPSE: CheckMenuItem(ghMenu, ID_PRIMITIVE_ELLIPSE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED); gCurrPrimSel = ID_PRIMITIVE_ELLIPSE; return 0; It is all quite redundant. We simply update which menu items should be checked, and update the global gCurrPrimSel variable so that it reflects the currently selected item. We execute similar code for the pen color menu items, the brush color menu items, the pen style menu items, and the brush style menu items. As mentioned, the code is all largely the same, so we will not discuss it further. You should now be able to understand the drawing program. The code is listed in Program 15.5. Program 15.5: Paint drawing program.

#include <windows.h> #include <string> #include <vector> #include "Shape.h" #include "resource.h" using namespace std; //========================================================= // Globals. const COLORREF BLACK = RGB(0, 0, 0); const COLORREF WHITE = RGB(255, 255, 255); const COLORREF RED = RGB(255, 0, 0); const COLORREF GREEN = RGB(0, 255, 0); const COLORREF BLUE = RGB(0, 0, 255); HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; HMENU ghMenu = 0;

Page 486: C++ Módulo I e II

165

vector<Shape*> gShapes; Shape* gShape = 0; bool gMouseDown = false; int gCurrPrimSel = ID_PRIMITIVE_LINE; int gCurrPenColSel = ID_PENCOLOR_BLACK; int gCurrBrushColSel = ID_BRUSHCOLOR_BLACK; int gCurrPenStyleSel = ID_PENSTYLE_SOLID; int gCurrBrushStyleSel = ID_BRUSHSTYLE_SOLID; LOGPEN gLogPen; LOGBRUSH gLogBrush; //========================================================= // Step 1: Define and implement the window procedure. LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) // Objects for painting. HDC hdc = 0; PAINTSTRUCT ps; // Local POINT variables we will use in some of the case // statements. POINT p0; POINT p1; switch( msg ) case WM_CREATE: CheckMenuItem(ghMenu, ID_PRIMITIVE_LINE, MF_CHECKED); CheckMenuItem(ghMenu, ID_PENCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, ID_BRUSHCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, ID_PENSTYLE_SOLID, MF_CHECKED); CheckMenuItem(ghMenu, ID_BRUSHSTYLE_SOLID, MF_CHECKED); return 0; case WM_COMMAND: switch( LOWORD(wParam) ) //======================================= // File Menu //======================================= case ID_FILE_EXIT: DestroyWindow(ghMainWnd); return 0; //======================================= // Primitive Types (Shape Types) //======================================= case ID_PRIMITIVE_LINE: CheckMenuItem(ghMenu, ID_PRIMITIVE_LINE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED); gCurrPrimSel = ID_PRIMITIVE_LINE; return 0; case ID_PRIMITIVE_RECTANGLE:

Page 487: C++ Módulo I e II

166

CheckMenuItem(ghMenu, ID_PRIMITIVE_RECTANGLE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED); gCurrPrimSel = ID_PRIMITIVE_RECTANGLE; return 0; case ID_PRIMITIVE_ELLIPSE: CheckMenuItem(ghMenu, ID_PRIMITIVE_ELLIPSE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED); gCurrPrimSel = ID_PRIMITIVE_ELLIPSE; return 0; //======================================= // Pen Colors //======================================= case ID_PENCOLOR_BLACK: CheckMenuItem(ghMenu, ID_PENCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenColSel, MF_UNCHECKED); gCurrPenColSel = ID_PENCOLOR_BLACK; gLogPen.lopnColor = BLACK; return 0; case ID_PENCOLOR_WHITE: CheckMenuItem(ghMenu, ID_PENCOLOR_WHITE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenColSel, MF_UNCHECKED); gCurrPenColSel = ID_PENCOLOR_WHITE; gLogPen.lopnColor = WHITE; return 0; case ID_PENCOLOR_RED: CheckMenuItem(ghMenu, ID_PENCOLOR_RED, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenColSel, MF_UNCHECKED); gCurrPenColSel = ID_PENCOLOR_RED; gLogPen.lopnColor = RED; return 0; case ID_PENCOLOR_GREEN: CheckMenuItem(ghMenu, ID_PENCOLOR_GREEN, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenColSel, MF_UNCHECKED); gCurrPenColSel = ID_PENCOLOR_GREEN; gLogPen.lopnColor = GREEN; return 0; case ID_PENCOLOR_BLUE: CheckMenuItem(ghMenu, ID_PENCOLOR_BLUE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenColSel, MF_UNCHECKED); gCurrPenColSel = ID_PENCOLOR_BLUE; gLogPen.lopnColor = BLUE; return 0; //======================================= // Brush Colors //======================================= case ID_BRUSHCOLOR_BLACK: CheckMenuItem(ghMenu, ID_BRUSHCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushColSel, MF_UNCHECKED); gCurrBrushColSel = ID_BRUSHCOLOR_BLACK; gLogBrush.lbColor = BLACK; return 0; case ID_BRUSHCOLOR_WHITE: CheckMenuItem(ghMenu, ID_BRUSHCOLOR_WHITE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushColSel, MF_UNCHECKED); gCurrBrushColSel = ID_BRUSHCOLOR_WHITE; gLogBrush.lbColor = WHITE; return 0;

Page 488: C++ Módulo I e II

167

case ID_BRUSHCOLOR_RED: CheckMenuItem(ghMenu, ID_BRUSHCOLOR_RED, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushColSel, MF_UNCHECKED); gCurrBrushColSel = ID_BRUSHCOLOR_RED; gLogBrush.lbColor = RED; return 0; case ID_BRUSHCOLOR_GREEN: CheckMenuItem(ghMenu, ID_BRUSHCOLOR_GREEN, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushColSel, MF_UNCHECKED); gCurrBrushColSel = ID_BRUSHCOLOR_GREEN; gLogBrush.lbColor = GREEN; return 0; case ID_BRUSHCOLOR_BLUE: CheckMenuItem(ghMenu, ID_BRUSHCOLOR_BLUE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushColSel, MF_UNCHECKED); gCurrBrushColSel = ID_BRUSHCOLOR_BLUE; gLogBrush.lbColor = BLUE; return 0; //======================================= // Pen Styles //======================================= case ID_PENSTYLE_SOLID: CheckMenuItem(ghMenu, ID_PENSTYLE_SOLID, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenStyleSel, MF_UNCHECKED); gCurrPenStyleSel = ID_PENSTYLE_SOLID; gLogPen.lopnStyle = PS_SOLID; return 0; case ID_PENSTYLE_DOTTED: CheckMenuItem(ghMenu, ID_PENSTYLE_DOTTED, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenStyleSel, MF_UNCHECKED); gCurrPenStyleSel = ID_PENSTYLE_DOTTED; gLogPen.lopnStyle = PS_DOT; return 0; case ID_PENSTYLE_DASHED: CheckMenuItem(ghMenu, ID_PENSTYLE_DASHED, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenStyleSel, MF_UNCHECKED); gCurrPenStyleSel = ID_PENSTYLE_DASHED; gLogPen.lopnStyle = PS_DASH; return 0; //======================================= // Brush Styles //======================================= case ID_BRUSHSTYLE_SOLID: CheckMenuItem(ghMenu, ID_BRUSHSTYLE_SOLID, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushStyleSel, MF_UNCHECKED); gCurrBrushStyleSel = ID_BRUSHSTYLE_SOLID; gLogBrush.lbStyle = BS_SOLID; return 0; case ID_BRUSHSTYLE_NULL: CheckMenuItem(ghMenu, ID_BRUSHSTYLE_NULL, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushStyleSel, MF_UNCHECKED); gCurrBrushStyleSel = ID_BRUSHSTYLE_NULL; gLogBrush.lbStyle = BS_NULL; return 0; case ID_BRUSHSTYLE_DIAGONAL: CheckMenuItem(ghMenu, ID_BRUSHSTYLE_DIAGONAL, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushStyleSel, MF_UNCHECKED);

Page 489: C++ Módulo I e II

168

gCurrBrushStyleSel = ID_BRUSHSTYLE_DIAGONAL; gLogBrush.lbStyle = BS_HATCHED; gLogBrush.lbHatch = HS_BDIAGONAL; return 0; case ID_BRUSHSTYLE_CROSS: CheckMenuItem(ghMenu, ID_BRUSHSTYLE_CROSS, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushStyleSel, MF_UNCHECKED); gCurrBrushStyleSel = ID_BRUSHSTYLE_CROSS; gLogBrush.lbStyle = BS_HATCHED; gLogBrush.lbHatch = HS_CROSS; return 0; // Handle left mouse button click message. case WM_LBUTTONDOWN: // Capture the mouse (we still get mouse input // even after the mouse cursor moves off the client area. SetCapture(hWnd); gMouseDown = true; // Point that was clicked is stored in the lParam. p0.x = LOWORD(lParam); p0.y = HIWORD(lParam); // We don’t know the end point yet, so set to zero. p1.x = 0; p1.y = 0; // Create the shape based on what shape the user has // selected in the menu. switch( gCurrPrimSel ) case ID_PRIMITIVE_LINE: gShape = new LineShape(p0, p1, gLogPen, gLogBrush); break; case ID_PRIMITIVE_RECTANGLE: gShape = new RectShape(p0, p1, gLogPen, gLogBrush); break; case ID_PRIMITIVE_ELLIPSE: gShape = new EllipseShape(p0, p1,gLogPen,gLogBrush); break; ; return 0; // Message sent whenever the mouse moves. case WM_MOUSEMOVE: if(gMouseDown) // Current mouse position is stored in the lParam. p1.x = LOWORD(lParam); p1.y = HIWORD(lParam); // Update the end point of the current temporary // shape based on the mouse position. gShape->setEndPt(p1); // Repaint the window so the temporary shape

Page 490: C++ Módulo I e II

169

// is redrawn interactively as the mouse moves. InvalidateRect(hWnd, 0, true); return 0; case WM_LBUTTONUP: // Release the captured mouse when the left mouse button // is lifted. ReleaseCapture(); gMouseDown = false; // Current mouse position is stored in the lParam. p1.x = LOWORD(lParam); p1.y = HIWORD(lParam); // Update the end point of the current temporary shape // based on the mouse position. gShape->setEndPt(p1); // The user lifted the left mouse button, so the shape // becomes permanent, so add it to the shape container. gShapes.push_back( gShape ); // Repaint the window so the new permanent shape will // be displayed. InvalidateRect(hWnd, 0, true); return 0; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // Only draw temporary shape if the mouse is down. if( gMouseDown ) gShape->draw(hdc); // Draw all the permenent shapes. for(int i = 0; i < gShapes.size(); ++i) gShapes[i]->draw(hdc); EndPaint(hWnd, &ps); // Handle key down message. case WM_KEYDOWN: if( wParam == VK_ESCAPE ) DestroyWindow(ghMainWnd); return 0; // Handle destroy window message. case WM_DESTROY: PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the // default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam);

Page 491: C++ Módulo I e II

170

// WinMain: Entry point for a Windows application. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Save handle to application instance. ghAppInst = hInstance; // Step 2: Fill out a WNDCLASS instance. WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; // Step 3: Register the WNDCLASS instance with Windows. RegisterClass( &wc ); // Step 4: Create the window, and save handle in globla // window handle variable ghMainWnd. ghMenu = LoadMenu(ghAppInst, MAKEINTRESOURCE(IDR_MENU1)); ghMainWnd = ::CreateWindow("MyWndClassName", "My Paint Program", WS_OVERLAPPEDWINDOW, 200, 200, 640, 480, 0, ghMenu, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false; // Step 5: Show and update the window. ShowWindow(ghMainWnd, showCmd); UpdateWindow(ghMainWnd); // Step 6: Enter the message loop and don't quit until // a WM_QUIT message is received. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage(&msg, 0, 0, 0) ) TranslateMessage(&msg); DispatchMessage(&msg); for(int i = 0; i < gShapes.size(); ++i) delete gShapes[i]; // Return exit code back to operating system. return (int)msg.wParam;

Page 492: C++ Módulo I e II

171

15.8 Summary

1. Windows sends a window a WM_PAINT message whenever a window needs to repaint its client area (or a region of the client area). A window may need to repaint its client area when it is resized, or when a previously obscured part of it becomes visible. The program can explicitly send a WM_PAINT message with the InvalidateRect function. A Windows program should know how to draw all of its data, so that if all or part of its client area drawing data is erased (by resizing the window or obscuring the window, for example) it can all be restored at the appropriate time when the next WM_PAINT is sent to the window.

2. A device context is a software abstraction of a display device, such as the video card or a printer.

We do all drawing operations through the device context (abbreviated as DC). As such, we must pass a handle to a device context (HDC) to all GDI related functions. One way to obtain an HDC is with the BeginPaint function.

3. To draw text onto the client area of a window, we use the TextOut function. To draw a line

onto the client area of a window, we use the MoveToEx function to specify the line’s start point, and the LineTo function to specify the line’s end point. To draw a rectangle onto the client area of a window, we use the Rectangle function. Finally, to draw an ellipse onto the client area of a window, we use the Ellipse function. Recall that when specifying an ellipse we specify its bounding rectangle; that is, an ellipse that fits tightly into that bounding rectangle will be drawn.

4. A bitmap is a matrix of color elements, or pixels. To load a bitmap we must first load the .bmp

file as a resource. After that, we can load the bitmap resource into our program and obtain a handle to it with the LoadBitmap function. To copy the pixels in the bitmap to the window’s client area we use the BitBlt function. When we are finished with a bitmap, we should delete it with the DeleteObject function.

5. Pens and brushes allow us to alter the way in which shapes are drawn with GDI. In particular,

we can draw shapes with different colors, styles, and patterns. Pens are used to draw lines, curves, and also to draw the border of solid shapes like rectangles and ellipses. The properties of a pen are described with the LOGPEN structure. Brushes are used to fill in the interiors of solid shapes like rectangles and ellipses. The properties of a brush are described with the LOGBRUSH structure.

6. Menus allow the user to make selections. To load a menu, we must first create a menu resource

in the Visual C++ resource editor. After we have finished editing the menu resource, we can load it with the LoadMenu function. To attach the menu to a window, we can pass in a handle to the menu (HMENU returned from LoadMenu) to the hMenu parameter of CreateWindow. We can check/uncheck a menu item with the CheckMenuItem function. When the user selects a menu item, Windows sends the window a WM_COMMAND message. The lower 16-bits of the wParam parameter of this message specify the numeric resource ID of the menu item that was selected. In this way, we can test which menu item was selected and execute the appropriate consequential code.

Page 493: C++ Módulo I e II

172

15.9 Exercises

15.9.1 Colors

Modify Program 15.5 by adding additional pen and brush colors (you pick the colors), which the user can select from the menu and draw with.

15.9.2 Styles

Look up the LOGPEN and LOGBRUSH structures in the Win32 documentation to see some of the additional styles and hatch patterns that are supported. Modify Program 15.5 by adding additional pen and brush styles/hatch patterns (you pick the styles/patterns), which the user can select from the menu and draw with.

15.9.3 Cube

Figure 15.18 shows a cube. As you can see, a cube is made up of twelve lines. Write a function that draws a cube to the client area of a window. Then add a new “cube” primitive to Program 15.5 that the user can select from the menu and draw.

Figure 15.18: A cube outline drawn with twelve lines.

15.9.4 Undo Feature

Modify Program 15.5 by implementing an undo feature that erases the last shape that was drawn (add an undo menu item). Be sure to avoid memory leaks.

Page 494: C++ Módulo I e II

173

Chapter 16

Introduction to Dialogs and Controls

Page 495: C++ Módulo I e II

174

Introduction

The final Win32 API-related topics we need to discuss are dialog boxes and controls. A dialog box is a type of window, which is commonly used to display a variety of controls the user can set and configure. Figure 16.1 shows a dialog box that is used in the demos programs for the DirectX Graphics Programming course here at Game Institute.

Figure 16.1: An example of a dialog box with controls.

As you can see from Figure 16.1, controls and dialog boxes go hand in hand.

Chapter Objectives

• Learn how to create modal and modeless dialog boxes, and how to distinguish between the two.

• Discover how to create and design dialog boxes with the Visual C++ resource editor.

• Become familiar with several Win32 controls such as static text controls, picture box controls, edit box controls, radio button controls, button controls, and combo box controls.

Note: As with the previous two Win32 API chapters in this book, we cannot possibly cover everything. Therefore, if you are interested in learning how to create the various other Win32 controls you may have seen, but which we do not cover here (e.g., slider controls, color controls, spin controls, tree controls), then you will need to consult MSDN or a book devoted to the Win32 API. The standard “bible” text on the Win32 API is Programming Windows 5th Edition By Charles Petzold.

Page 496: C++ Módulo I e II

175

16.1 Modal Dialog Boxes; The Static Text Control; The Button Control

A modal dialog box is a dialog box that does not close until the user has made some sort of selection (i.e., the user cannot switch to another window until the modal dialog box has ended). Typically, an application will use a modal dialog box when it needs immediate input from the user, and cannot continue until it receives that input. For our first program, we will illustrate a modal dialog box by creating a simple window that can display an “About” dialog box. Figure 16.2 shows our goal:

Figure 16.2: The dialog box is launched by going to File and selecting the “About” menu item.

16.1.1 Designing the Dialog Box

Dialogs are created and edited in the Visual C++ Resource editor, just like menus and icons. Select the Add Resource button and select a “Dialog” from the “Add Resource” dialog and then select the “New” button—Figure 16.3.

Page 497: C++ Módulo I e II

176

Figure 16.3: Select “Dialog” and then press the “New” button.

At this point, the Dialog Editor will open up. Before we begin designing the dialog box, let us rename the dialog resource ID to IDD_ABOUTBOX—Figure 16.4.

Figure 16.4: Renaming the dialog resource ID to IDD_ABOUTBOX.

Now, design your dialog box as Figure 16.5 shows.

Page 498: C++ Módulo I e II

177

Figure 16.5: Designing the dialog. To add a control to the dialog, select the control from the left hand side control

listing and drag it onto the dialog.

To delete a control, select the control from the dialog and press the delete key on your keyboard. To add a control to the dialog, select the control from the left hand side control listing and drag it onto the dialog. You can move controls around the dialog by selecting them and dragging them about; controls can also be resized by dragging the borders of the control windows. First, change the dialog box caption from “Dialog” to something else. We chose to rename it as “About Box.” You can rename the dialog box caption by selecting the dialog box and editing the “Caption” property—Figure 16.6a. The dialog properties should be displayed in the lower right corner of Visual C++ .NET when you select the dialog. While we are editing the dialog box properties, also set the “System Menu” property to false—Figure 16.6b. This removes the ‘X’ close button from the dialog box—typically, modal dialog boxes do not have system menus. Second, change the static text from the default text to “Game Institute About Dialog Box. Version 1.0.” Just like menu items, each control has a list of properties, which are displayed in the lower right corner of Visual C++ .NET when you select the control. You can change the caption of the static text control by editing the “Caption” property.

Page 499: C++ Módulo I e II

178

Next, load the Game Institute logo into the picture box (you can use another image if you like, of course). First, you need to load the bitmap image as a resource. We showed how to load a bitmap resource in the previous chapter. In our program, we keep the default bitmap resource ID, namely, IDB_BITMAP1. Now, select the picture control so that its properties are displayed. Change the “Type” property to “Bitmap” and then change the “Image” property to the bitmap resource ID which you want displayed in the picture box. In our example, this is IDB_BITMAP1—Figure 16.6c.

Figure 16.6: (a) Changing the dialog box’s caption. (b) Removing the dialog box’s system menu. (c) Setting the

picture control type to bitmap, and loading the bitmap IDB_BITMAP1 as the picture box’s image.

Your loaded image logo should now be displayed in the picture box, as it is in Figure 16.5. Note that we do not care about the resource ID of the picture box control or the static text control. This is because these controls are static and do not change, in general. Thus we will not need to access them from our program. However, we should take a brief look at the properties of the OK button control. Recall that the button was placed on the dialog box by default. If we select the OK button control, the properties window looks like Figure 16.7. The key observation for now is that the resource ID that was given to it: IDOK. That name sounds reasonable, so we will keep it, but we must remember it because we will need to know the ID so that we can execute the appropriate code when the user presses the button in the program. In particular, we will kill the modal dialog when the user presses the OK button.

Page 500: C++ Módulo I e II

179

Figure 16.7: The OK Button’s control properties.

16.1.2 Modal Dialog Box Theory

A dialog box is a kind of window, and as such, has its own window procedure function. A dialog box window procedure looks similar to a regular window procedure except that it returns BOOL, which is typedefed as int by the Win32 API. Here is a typical dialog procedure declaration: BOOL CALLBACK AboutDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam); A special message that is specific to dialog boxes is the WM_INITDIALOG message. This message is an analog to the WM_CREATE message we handle for non-dialog windows. Typically, we handle the WM_INITDIALOG message so that we can initialize the controls of the dialog to default values. In addition, the WM_INITDIALOG is a good place to obtain window handles to each control on the dialog (remember, controls are child windows and the dialog is the parent window). After we have implemented a dialog window procedure, we can display a modal dialog with the DialogBox function: DialogBox( ghAppInst, // Application instance. MAKEINTRESOURCE(IDD_ABOUTBOX), // Dialog resource ID. hWnd, // Parent window of dialog box. AboutDlgProc); // Ptr to dialog box window procedure.

Page 501: C++ Módulo I e II

180

Note that DialogBox is used only for modal dialog boxes. We will illustrate how to create and show modeless dialog boxes in the next section. In the sample program we developed, we display the “About” dialog box when the user selects the “About” menu item—see Figure 16.8.

Figure 16.8: The user can launch the “About” dialog box by selecting the “About” menu item.

In the main window procedure, we execute the DialogBox function in response to the user selecting the “About” menu item like so: // User selected About menu item, so show the modal dialog box. case ID_FILE_ABOUT: // About menu item resource ID. DialogBox( ghAppInst, // Application instance. MAKEINTRESOURCE(IDD_ABOUTBOX), // Dialog resource ID. hWnd, // Parent window of dialog box. AboutDlgProc); // Ptr to dialog box window procedure. return 0; To close a modal dialog box, we use the EndDialog function. This function is used only for modal dialog boxes—modeless dialog boxes are destroyed via another approach. By convention, a modal dialog box is typically ended when the user presses the OK or CANCEL buttons of the dialog box. In our “About” dialog box we only have an OK button. Now we need to address how button clicks are handled with dialog boxes. When a button is clicked, a WM_COMMAND message is sent to the parent window, which would be the dialog window. This is similar to what happens when a menu item is clicked. The lower 16-bits of the wParam parameter specify the resource ID of the button that was clicked. Consequently, in the dialog procedure, we can check if a particular button was clicked by handling the WM_COMMAND message and examining the lower 16-bits of the wParam: case WM_COMMAND: // The low 16-bits of the wParam stores the resource // ID of the button control the user pressed. So from // that information, we can determine which button was pressed

Page 502: C++ Módulo I e II

181

switch(LOWORD(wParam)) // Did the user press the OK button (resource ID = IDOK)? // If so, close the dialog box. case IDOK: EndDialog( hDlg, // Handle to dialog to end. 0); // Return code--generally always zero. return true;

break; In summary:

• Create and design the graphical layout of a dialog box with the Visual C++ Resource Editor. • A dialog box is a kind of window and, as such, needs its own dialog procedure. • A modal dialog can be displayed with the DialogBox function. • A modal dialog can be ended with the EndDialog function. • When a button control is pushed, it sends a WM_COMMAND message to the parent dialog’s window

procedure.

16.1.3 The About Box Sample

Now that we have created and designed a dialog resource, and have discussed the necessary background for showing and ending modal dialog boxes, we present the code for the “About” dialog box sample, as displayed in Figure 16.2. The concepts related to dialog boxes have been bolded.

Program 16.1: This program displays a window with a menu. The menu has an “About” menu item, which, when clicked, displays an about dialog box. See Figure 16.2 for an illustration.

#include <windows.h> #include "resource.h" // For dialog and menu resource. // Store handles to the main window and application // instance globally. HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; HMENU ghMenu = 0; // Define a window procedure for the dialog box: // A dialog box is a kind of window, and as such we need to // define a window procedure for it. BOOL CALLBACK AboutDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) switch( msg ) // A WM_INITDIALOG message is sent right before the // dialog is displayed. This gives you a chance to // initialize any dialog related variables. For an // About Box, we do not need to do any initialization

Page 503: C++ Módulo I e II

182

// so we just return true. case WM_INITDIALOG: return true; // We learned in the last chapter that a WM_COMMAND // message is sent when the user selects a menu item // from the menu. In addition to that, a WM_COMMAND // message is also sent when the user presses a // button control. case WM_COMMAND: // The low 16-bits of the wParam stores the resource // ID of the button control the user pressed. So from // that information, we can determine which button was // pressed. switch(LOWORD(wParam)) // Did the user press the OK button (resource ID = IDOK)? // If so, close the dialog box. case IDOK: EndDialog( hDlg, // Handle to dialog to end. 0); // Return code--generally always zero. return true; break; return false; // Step 1: Define and implement the window procedure. LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) switch( msg ) case WM_COMMAND: switch( LOWORD(wParam) ) case ID_FILE_EXIT: DestroyWindow(ghMainWnd); return 0; // User click the “About” menu item? case ID_FILE_ABOUT: DialogBox( ghAppInst, // Application instance. MAKEINTRESOURCE(IDD_ABOUTBOX), // Dialog resource ID. hWnd, // Parent window of dialog box. AboutDlgProc); // Ptr to dialog box window procedure. return 0; return 0; // Handle destroy window message. case WM_DESTROY: PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the

Page 504: C++ Módulo I e II

183

// default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam); // WinMain: Entry point for a Windows application. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Save handle to application instance. ghAppInst = hInstance; // Step 2: Fill out a WNDCLASS instance. WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; // Step 3: Register the WNDCLASS instance with Windows. RegisterClass( &wc ); // Step 4: Create the window, and save handle in globla // window handle variable ghMainWnd. ghMenu = LoadMenu(ghAppInst, MAKEINTRESOURCE(IDR_MENU1)); ghMainWnd = ::CreateWindow("MyWndClassName", "About Box", WS_OVERLAPPEDWINDOW, 0, 0, 540, 380, 0, ghMenu, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false; // Step 5: Show and update the window. ShowWindow(ghMainWnd, showCmd); UpdateWindow(ghMainWnd); // Step 6: Enter the message loop and don't quit until // a WM_QUIT message is received. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage(&msg, 0, 0, 0) ) TranslateMessage(&msg); DispatchMessage(&msg); // Return exit code back to operating system. return (int)msg.wParam;

Page 505: C++ Módulo I e II

184

16.2 Modeless Dialog Boxes; The Edit Control

A modeless dialog box is a dialog box that behaves like a normal window, but is still a dialog box. Unlike modal dialog boxes, the user can switch to other windows as desired. Therefore, the modeless dialog box acts like an independent freestanding window. In fact, you can make a modeless dialog box your primary application window. Take a look at the Calculator program—Figure 16.9—that ships with Windows and you will observe that it is a pure dialog application.

Figure 16.9: The Windows Calculator program is a pure dialog based application.

Typically, an application uses a modeless dialog box as a tool dialog. That is, the user wants the dialog to be floating around so that it can be accessed quickly and efficiently. Adobe Photoshop is a good example of a program that uses a floating tool dialog. The rest of the programs we create in this chapter will be purely dialog based and use a modeless dialog box. The next sample program illustrates how to use an edit box control. But before we get to that, we must first learn how modeless dialog boxes are created and destroyed (recall it is different from modal dialog boxes).

16.2.1 Modeless Dialog Box Theory

First, as with modal dialog boxes, modeless dialog boxes are also a kind of window and as such, they too have a window procedure. The window procedure is implemented in the same way a modal dialog box procedure is, so we will not discuss it further. Let us now examine how modeless dialog boxes are created. To create a modeless dialog box, we use the CreateDialog function instead of the DialogBox function:

Page 506: C++ Módulo I e II

185

HWND ghDlg = CreateDialog( hInstance, // Application instance. MAKEINTRESOURCE(IDD_MSGDLG), // Dialog resource ID. 0, // Parent window--null for no parent. EditDlgProc); // Dialog window procedure. The parameters are similar to that of DialogBox. We note, however, that CreateDialog returns a window handle to the created dialog box window. After a dialog is created, we need to show it with ShowWindow: // Show the dialog. ShowWindow(ghDlg, showCmd);

Note: There is a dialog property you can set in the dialog resource editor called “Visible.” If the property “Visible” is set to true then the dialog will be shown by default and a call to ShowWindow is unnecessary. Try it.

To destroy a modeless dialog, we use the DestroyWindow function instead of the EndDialog function: // If the dialog was closed (user pressed 'X' button) // then terminate the dialog. case WM_CLOSE: DestroyWindow(hDlg); return true; The final difference between a modeless dialog box and a modal dialog box has to do with the way messages are processed; we must do a little more work with a modeless dialog. In particular, we need to intercept messages aimed for the dialog box from the message loop and forward them to the dialog box procedure. This is done for us with modal dialog boxes. To do this interception we use the IsDialogMessage function. This function returns true if the message is indeed a dialog message, and if it is a dialog message it will also dispatch the message to the dialog’s window procedure. Here is the rewritten message loop: while( GetMessage( &msg, 0, 0, 0 ) ) // Is the message a dialog message? If so the function // IsDialogMessage will return true and then dispatch // the message to the dialog window procedure. // Otherwise, we process as the message as normal. if( ghDlg == 0 || !IsDialogMessage(ghDlg, &msg ) ) // Process message as normal. TranslateMessage(&msg); DispatchMessage(&msg);

Page 507: C++ Módulo I e II

186

Essentially, the line IsDialogMessage(ghDlg, &msg ) can be read as “is the message stored in msg aimed for the dialog box referred to by ghDlg?”

Also note that if the dialog handle is null (ghDlg == 0, which means the dialog was destroyed or has not been created yet) then the ‘if’ statement will be true and the message will be processed normally.

16.2.2 The Edit Box Sample: Designing the Dialog Resource

We now need to design the dialog for our second sample program. This sample program will have an edit box, which allows the user to input some text. The program will also have a button. When the user presses the button a message box will be displayed. The message box will contain whatever text the user entered into the edit box. The goal of this program is to illustrate how text is extracted from an edit box control. Figure 16.10 shows a screenshot.

Figure 16.10: This program extracts the text entered in the edit box and displays it in a message box whenever the user presses the “Post Msg” button.

The first task is to design a dialog box in the resource editor as Figure 16.11 shows:

Figure 16.11: Designing the message dialog.

Page 508: C++ Módulo I e II

187

In our program, we have used the following resource IDs for the various controls.

• Dialog box -- IDD_MSGDLG • Edit box -- IDC_MSGTEXT • Static text -- IDC_STATIC • Button -- IDB_MSG

Note that we keep the “System Menu” set to true in this example, so that the dialog has the ‘X’ close button in the upper right hand corner.

16.2.3 The Edit Box Sample

Now that we have created the dialog resource, let us examine some of the program logic we will use. One of the key ideas of this sample is extracting the text from the edit box control. In order to do this, we need a handle to the control so we can access it. To obtain a handle to a child window on a dialog, we can use the GetDlgItem function: HWND hEditBox = GetDlgItem(hDlg, IDC_MSGTEXT); The first parameter is a HWND to the dialog on which the control “lives”, and the second parameter is the resource ID of the child control. This function then returns an HWND to that child control. Given an HWND to the edit box control, the program can manually set the text in the edit box with the SetWindowText function: // Set some default text in the edit box. SetWindowText(hEditBox, "Enter a message here."); The first parameter is a handle to the window in which we want to set some text, and the second parameter is the string we wish to set. Why would the program manually set text in an edit box when the user is supposed to input text there? There are a variety of reasons why a program might need to do this. For example, we might want to fill the edit box with some default text, as we do in Program 16.2. The reverse operation is to extract text from the message box. To do that we use the GetWindowText function: // Text buffer to be filled with string user entered into edit control. char msgText[256]; // Extract the text from the edit box. GetWindowText(hEditBox, msgText, 256); The first parameter is a handle to the window from which we want to extract the text (in this case, the edit box), the second parameter is a pointer to a buffer which will receive the text, and the third parameter is the maximum number of characters to read—we do not want to read more characters than

Page 509: C++ Módulo I e II

188

can fit in our buffer. If the text stored in the edit box is longer than the maximum number of characters you specify to extract then the string is truncated. Let us look at some of the specifics of Program 16.2. By default, we want the edit box to contain the string “Enter a message here.” So, in the dialog procedure, in the WM_INITDIALOG message handler, we obtain a handle to that edit box, and also set the default text: case WM_INITDIALOG: hEditBox = GetDlgItem(hDlg, IDC_MSGTEXT); // Set some default text in the edit box. SetWindowText(hEditBox, "Enter a message here."); return true; hEditBox is declared at a higher scope and saved statically in the dialog procedure. When the button “Post Msg” is pressed, we extract the text from the edit box (be it the default text or text which the user entered), and display it in a message box: case WM_COMMAND: switch(LOWORD(wParam)) case IDB_MSG: // Extract the text from the edit box. GetWindowText(hEditBox, msgText, 256); // Now display the text in the edit box in // a message box. MessageBox(0, msgText, "Message", MB_OK); return true; return true; Finally, when the user presses the close button (the ‘X’ in the upper right hand corner), we destroy the dialog and exit the application: case WM_CLOSE: DestroyWindow(hDlg); return true; case WM_DESTROY: PostQuitMessage(0); return true; We now present the edit box sample in its entirety, with key lines of code bolded:

Page 510: C++ Módulo I e II

189

Program 16.2: The Edit Box Control Sample. This program shows how to set/get text to and from an edit box control. It also illustrates how to create a modeless dialog box.

#include <windows.h> #include <string> #include "resource.h" using namespace std; // Dialog handle. HWND ghDlg = 0; // Dialog window procedure. BOOL CALLBACK EditDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) // Text buffer to be filled with string user entered // into edit control. char msgText[256]; // Handle to the edit box control (ID = IDC_MSGTEXT). static HWND hEditBox = 0; switch( msg ) case WM_INITDIALOG: // Controls are child windows to the dialog they lie on. // In order to get and send information to and from a // control we will need a handle to it. So save a handle // to the edit box control as the dialog is being // initialized. Recall that we get a handle to a child // control on a dialog box with the GetDlgItem. hEditBox = GetDlgItem(hDlg, IDC_MSGTEXT); // Set some default text in the edit box. SetWindowText(hEditBox, "Enter a message here."); return true; case WM_COMMAND: switch(LOWORD(wParam)) case IDB_MSG: // Extract the text from the edit box. GetWindowText(hEditBox, msgText, 256); // Now display the text in the edit box in // a message box. MessageBox(0, msgText, "Message", MB_OK); return true; return true; // If the dialog was closed (user pressed 'X' button) // then terminate the dialog. case WM_CLOSE: DestroyWindow(hDlg); return true;

Page 511: C++ Módulo I e II

190

case WM_DESTROY: PostQuitMessage(0); return true; return false; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Create the modeless dialog window. This program is a pure // dialog window application, and the dialog window is the // "main" window. ghDlg = CreateDialog( hInstance, // Application instance. MAKEINTRESOURCE(IDD_MSGDLG), // Dialog resource ID. 0, // Parent window--null for no parent. EditDlgProc); // Dialog window procedure. // Show the dialog. ShowWindow(ghDlg, showCmd); // Enter the message loop. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage( &msg, 0, 0, 0 ) ) // Is the message a dialog message? If so the function // IsDialogMessage will return true and then dispatch // the message to the dialog window procedure. // Otherwise, we process as the message as normal. if( ghDlg == 0 || !IsDialogMessage(ghDlg, &msg ) ) TranslateMessage(&msg); DispatchMessage(&msg); return (int)msg.wParam;

Page 512: C++ Módulo I e II

191

16.3 Radio Buttons

We now turn our attention to radio button controls. The program we will develop to illustrate radio buttons will allow the user to make a selection. After the user makes a selection and presses the OK button, the program will output a message box based on the user’s selection. Consider Figure 6.12.

Figure 6.12: A screen shot of the radio button sample we develop in this section.

Radio buttons are used to provide users with a set of options, where one, and only one, option in the group must be selected at any time. Figure 6.12 shows how we might use radio buttons to implement a character class selection for a role playing game. Observe how we surround the radio buttons in a group control; a group control functions merely as an aesthetic tool to provide a visual grouping of related controls. Figure 16.1 also gives an example of radio buttons which allow the user to switch between windowed mode and fullscreen mode.

16.3.1 Designing the Radio Dialog Resource

Our first task in making the radio button sample program is to create a dialog resource and design it so that it looks like Figure 16.12. We use the following control resource names:

• Dialog box -- IDD_RADIODLG • Group box -- IDC_STATIC • Fighter radio button -- IDC_RADIO_FIGHTER • Cleric radio button -- IDC_RADIO_CLERIC • Thief radio button -- IDC_RADIO_THIEF • Wizard radio button -- IDC_RADIO_WIZARD • OK button -- IDOK

Page 513: C++ Módulo I e II

192

16.3.2 Implementing the Radio Button Sample

The key API function needed when implementing radio buttons is the CheckRadioButton function. This function updates the GUI of a group of radio buttons by selecting a new radio button and deselecting the previously selected radio button. Here is an example of how it would be called: CheckRadioButton(hDlg, IDC_RADIO_FIGHTER, // First radio button in group IDC_RADIO_WIZARD, // Last radio button in group IDC_RADIO_FIGHTER);// Button to select. This function call would check the “Fighter” radio button and deselect any previously selected radio button. The key idea is that a group of radio button IDs must be consecutive for this function to work (the function assumes they are consecutive). If you create the radio buttons in the resource dialog one after another, they will be made consecutive. To understand why this is necessary, first observe how we specify the first radio button and last radio button ID to the CheckRadioButton function. If the radio buttons IDs are consecutive, the first and last ID essentially marks the range of the radio button IDs. Thus the function can iterate one-by-one over each ID and update its graphical appearance (selected/deselected). Let us now examine the program implementation details. The first important task we do is to select a default radio button. We do this when the dialog box is being initialized: case WM_INITDIALOG:

// Select the default radio button. // Note: Assumes the radio buttons were created sequentially. CheckRadioButton(hDlg, IDC_RADIO_FIGHTER, // First radio button in group IDC_RADIO_WIZARD, // Last radio button in group IDC_RADIO_FIGHTER);// Button to select. return true; We will also want to maintain the currently selected radio button, so we include a static variable that maintains that value: static int classSelection = IDC_RADIO_FIGHTER; We initialize it to the default value. When the user selects a new radio button, we update this value. When a radio button is selected, Windows sends the dialog procedure a WM_COMMAND message, just as it does for a regular button control. The lower 16-bits of the wParam stores the resource ID of the button selected: // Was *any* radio button selected? case IDC_RADIO_FIGHTER: case IDC_RADIO_CLERIC: case IDC_RADIO_THIEF: case IDC_RADIO_WIZARD:

Page 514: C++ Módulo I e II

193

// Yes, one of the radio buttons in the group was selected, // so select the new one (stored in LOWORD(wParam)) and // deselect the other to update the radio button GUI.

// Note: Assumes the radio buttons were created sequentially. CheckRadioButton(hDlg, IDC_RADIO_FIGHTER, // First radio button in group IDC_RADIO_WIZARD, // Last radio button in group LOWORD(wParam)); // Button to select.

// Save currently selected radio button.

classSelection = LOWORD(wParam); return true;

Observe that we do not test for each individual radio button selection; rather we test for any selection. LOWORD(wParam) will give us the actual button pressed, which we pass on to the CheckRadioButton function, which will handle selecting the correct button and deselecting the others. Finally, the last key idea of this program is the message box. More specifically, when the user presses the OK button, we want the program to display a message specific to the character (radio button) selected. This is easy enough since we have saved the current radio button selected in the classSelection variable. Thus we could do a simple ‘if’ statement to output an appropriate message based on the selection. However, instead we add the following variable to the dialog procedure that contains our messages: string classNames[4] = "You selected the Fighter.", "You selected the Cleric.", "You selected the Thief.", "You selected the Wizard." ; We then output the message like so: case IDOK: // Now display the class the user selected in a message box. MessageBox( 0, classNames[classSelection-IDC_RADIO_FIGHTER].c_str(), "Message", MB_OK); return true; The interesting line is: classNames[classSelection-IDC_RADIO_FIGHTER].c_str() The best way to explain this is to look at the actual numeric values of the resource IDs. If you open resource.h, you may see something similar to the following:

Page 515: C++ Módulo I e II

194

#define IDC_RADIO_FIGHTER 1001 #define IDC_RADIO_CLERIC 1002 #define IDC_RADIO_THIEF 1003 #define IDC_RADIO_WIZARD 1004 Again, the resource identifiers are just unique ways of identifying the resource items. Our array classNames is a four-element array, so we cannot index into it with values such as 1001, 1002, 1003, or 1004. What we do is subtract the first ID in the group. The first resource in the group is IDC_RADIO_FIGHTER, which is actually the number 1001. So this would give: IDC_RADIO_FIGHTER - IDC_RADIO_FIGHTER = 1001 – 1001 = 0 IDC_RADIO_CLERIC - IDC_RADIO_FIGHTER = 1002 – 1001 = 1 IDC_RADIO_THIEF - IDC_RADIO_FIGHTER = 1003 – 1001 = 2 IDC_RADIO_WIZARD - IDC_RADIO_FIGHTER = 1004 – 1001 = 3 The result of these subtractions gives us the corresponding text index for classNames! So for example, if classSelection == IDC_RADIO_THIEF, we would have: IDC_RADIO_THIEF - IDC_RADIO_FIGHTER = 1003 – 1001 = 2 and classNames[2] == "You selected the Thief." This is the exact string we want to use in this example. Program 16.3 shows the complete code listing for the radio button program. Program 16.3:

#include <windows.h> #include <string> #include "resource.h" using namespace std; // Dialog handle. HWND ghDlg = 0; // Dialog window procedure. BOOL CALLBACK MsgDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) // Default to fighter. static int classSelection = IDC_RADIO_FIGHTER; string classNames[4] = "You selected the Fighter.", "You selected the Cleric.", "You selected the Thief.", "You selected the Wizard." ;

Page 516: C++ Módulo I e II

195

switch( msg ) case WM_INITDIALOG: // Select the default radio button. // Note: Assumes the radio buttons were created // sequentially. CheckRadioButton(hDlg, IDC_RADIO_FIGHTER, // First radio button in group IDC_RADIO_WIZARD, // Last radio button in group IDC_RADIO_FIGHTER);// Button to select. return true; case WM_COMMAND: switch(LOWORD(wParam)) // Was *any* radio button selected? case IDC_RADIO_FIGHTER: case IDC_RADIO_CLERIC: case IDC_RADIO_THIEF: case IDC_RADIO_WIZARD: // Yes, one of the radio buttons in the group was selected, // so select the new one (stored in LOWORD(wParam)) and // deselect the other to update the radio button GUI. // Note: Assumes the radio buttons were created // sequentially. CheckRadioButton(hDlg, IDC_RADIO_FIGHTER,// First radio button in group IDC_RADIO_WIZARD, // Last radio button in group LOWORD(wParam)); // Button to select. // Save currently selected radio button. classSelection = LOWORD(wParam); return true; case IDOK: // Now display the class the user selected in // a message box. MessageBox( 0, classNames[classSelection-IDC_RADIO_FIGHTER].c_str(), "Message", MB_OK); return true; return true; case WM_CLOSE: DestroyWindow(hDlg); return true; case WM_DESTROY: PostQuitMessage(0); return true; return false;

Page 517: C++ Módulo I e II

196

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Create the modeless dialog window. ghDlg = CreateDialog( hInstance, // Application instance. MAKEINTRESOURCE(IDD_RADIODLG), // Dialog resource ID. 0, // Parent window--null for no parent. MsgDlgProc); // Dialog window procedure. // Show the dialog. ShowWindow(ghDlg, showCmd); // Enter the message loop. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage( &msg, 0, 0, 0 ) ) // Is the message a dialog message? If so the function // IsDialogMessage will return true and then dispatch // the message to the dialog window procedure. // Otherwise, we process as the message as normal. if( ghDlg == 0 || !IsDialogMessage(ghDlg, &msg ) ) TranslateMessage(&msg); DispatchMessage(&msg); return (int)msg.wParam;

16.4 Combo Boxes

The last program we will write in this chapter illustrates the combo box control. A combo box can be described as a drop down list box. The program we will implement has a combo box, edit box, and button control. The user can enter text in the edit box control. When the user presses the button, the text in the edit box will be added to the combo box. In addition, when the user selects an item from the combo box, a message box of that item’s text is shown. Figure 16.13 shows a screenshot of the program we will develop:

Page 518: C++ Módulo I e II

197

Figure 16.13: A screenshot of the combo box application. The user can add text to the combo box by writing some text in the edit control and pressing the “Add” button.

16.4.1 Designing the Combo Box Dialog Resource

Our first task in making the combo box sample program is to create a dialog resource and design it such that it looks like Figure 16.13. We use the following control resource names:

• Dialog box -- IDD_COMBODLG • Static text -- IDC_STATIC • Add button -- IDC_ADDBUTTON • Edit box -- IDC_EDIT_MSG • Combo box -- IDC_COMBOBOX

16.4.2 Implementing the Combo Box Sample

The first key implementation detail to note is that we need to get handles to the window controls on the dialog box. We obtain these handles as the dialog box is being initialized: hComboBox = GetDlgItem(hDlg, IDC_COMBOBOX); hEditBox = GetDlgItem(hDlg, IDC_EDIT_MSG); hAddButton = GetDlgItem(hDlg, IDC_ADDBUTTON); These variables are declared statically in the dialog window procedure like so: // Handles to the combo box controls. static HWND hComboBox = 0; static HWND hEditBox = 0; static HWND hAddButton = 0;

Page 519: C++ Módulo I e II

198

The second key implementation concept is how to add the text from the edit box to the combo box. We know that we can extract the edit box text using the GetWindowText function, but how do we add an item to the combo box? To add an item to the combo box we need to send it a message. (What is interesting about the combo box is that Windows creates a window procedure for it internally.) In particular, we need to send a CB_ADDSTRING message to the combo box, where the CB stands for combo box. We can do this with the SendMessage function: SendMessage( hComboBox, // Handle to window to send message to. CB_ADDSTRING, // Add string message. 0, // No WPARAM for this message. (LPARAM)msgText);// The LPARAM is a pointer to the string to add. msgText is some array of chars representing the string to add. Recall that we stated that when the user selects an item from the combo box we will display a message box showing the string item that was selected from the combo box. We can detect when the user interacts with a combo box item because a WM_COMMAND is sent to the dialog window procedure. The HIWORD of the wParam parameter gives the notification code, which explains specifically how the user interacted with the combo box. If the notification code is CBN_SELENDOK then the user selected an item from the combo box: case WM_COMMAND: switch(HIWORD(wParam)) // User selected a combo box item. case CBN_SELENDOK:

Note: Some other possible notification codes for a combo box are: CBN_CLOSEUP: Sent when the combo box drop down list is closed. CBN_DBLCLK: Sent when an item in the combo box is double-clicked. CBN_DROPDOWN: Sent when the combo box drop down list is opened. CBN_KILLFOCUS: Sent when the combo box loses focus. CBN_SETFOCUS: Sent when the combo box gains focus. All these special messages allow you to execute code when certain actions happen to the combo box. There are more—see the Win32 documentation for full details.

To display a message box showing the string item that was selected from the combo box, we need to send the combo box two messages. In the first message, we need to ask for the index (the strings added to a combo box are stored in an array-like structure) of the string that was selected. We can do that like so: index = SendMessage(hComboBox, CB_GETCURSEL, 0, 0); Where CB_GETCURSEL may be read as: “get the index of the currently selected item.” Given the index of the currently selected item in the combo box, we can get the actual string at that index by sending another message CB_GETLBTEXT:

Page 520: C++ Módulo I e II

199

char msgText[256]; SendMessage(hComboBox, CB_GETLBTEXT, (WPARAM)index, (LPARAM)msgText); The WPARAM is the index of the string to get, and the LPARAM returns a pointer to the string. Program 16.4 shows the complete code listing for the combo box program. Program 16.4: The combo box program.

#include <windows.h> #include <string> #include "resource.h" using namespace std; // Dialog handle. HWND ghDlg = 0; // Dialog window procedure. BOOL CALLBACK MsgDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) // Text buffer to be filled with string user entered // into edit control. char msgText[256]; // Handles to the combo box controls. static HWND hComboBox = 0; static HWND hEditBox = 0; static HWND hAddButton = 0; int index = 0; switch( msg ) case WM_INITDIALOG: // Controls are child windows to the dialog they lie on. // In order to get and send information to and from a // control we will need a handle to it. So save a handle // to the controls as the dialog is being initialized. // Recall that we get a handle to a child control on a // dialog box with the GetDlgItem. hComboBox = GetDlgItem(hDlg, IDC_COMBOBOX); hEditBox = GetDlgItem(hDlg, IDC_EDIT_MSG); hAddButton = GetDlgItem(hDlg, IDC_ADDBUTTON); return true; case WM_COMMAND: switch(HIWORD(wParam)) // User selected a combo box item. case CBN_SELENDOK: index = SendMessage(hComboBox, CB_GETCURSEL, 0, 0); SendMessage(hComboBox, CB_GETLBTEXT, (WPARAM)index, (LPARAM)msgText);

Page 521: C++ Módulo I e II

200

MessageBox(0, msgText, "Combo Message", MB_OK); return true; switch(LOWORD(wParam)) // User pressed the "Add" button. case IDC_ADDBUTTON: // Get the text from the edit box. GetWindowText(hEditBox, msgText, 256); // Add the text to the combo box only if the // user entered a string that is greater than zero. if( strlen(msgText) > 0 ) SendMessage( hComboBox, CB_ADDSTRING, 0, (LPARAM)msgText); return true; return true; case WM_CLOSE: DestroyWindow(hDlg); return true; case WM_DESTROY: PostQuitMessage(0); return true; return false; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) // Create the modeless dialog window. ghDlg = CreateDialog( hInstance, // Application instance. MAKEINTRESOURCE(IDD_COMBODLG), // Dialog resource ID. 0, // Parent window--null for no parent. MsgDlgProc); // Dialog window procedure. // Show the dialog. ShowWindow(ghDlg, showCmd); // Enter the message loop. MSG msg; ZeroMemory(&msg, sizeof(MSG)); while( GetMessage( &msg, 0, 0, 0 ) )

Page 522: C++ Módulo I e II

201

// Is the message a dialog message? If so the function // IsDialogMessage will return true and then dispatch // the message to the dialog window procedure. // Otherwise, we process as the message as normal. if( ghDlg == 0 || !IsDialogMessage(ghDlg, &msg ) ) TranslateMessage(&msg); DispatchMessage(&msg); return (int)msg.wParam;

16.5 Summary

1. A dialog box is a type of window which is commonly used to display a variety of controls the user can set and configure.

2. A modal dialog box is a dialog box that does not close until the user has made some sort of

selection (i.e., the user cannot switch to another window until the modal dialog box is ended). Typically, an application will use a modal dialog box when it needs immediate input from the user, and cannot continue until it receives that input.

3. A modeless dialog box is a dialog box that behaves like a normal window, but is still a dialog

box. Unlike modal dialog boxes, the user can switch to other windows as desired. Therefore, the modeless dialog box acts like an independent window. In fact, you can make a modeless dialog box your primary application window. Typically, an application uses a modeless dialog box as a tool dialog. That is, the user wants the dialog to be floating around so that it can be accessed quickly and efficiently. Adobe Photoshop is a good example of a program that uses a floating tool dialog.

4. We create and design dialog boxes in the Visual C++ Resource Editor. Each control, which is

not static, should be given a resource ID so that the application can refer to that control in code. Remember to include “resource.h” in your source code. Also note that you can modify various properties of the dialog and its controls through the properties window.

5. We can create and show a modal dialog box with the DialogBox function and close a modal

dialog box with the EndDialog function. We can create a modeless dialog box with the CreateDialog function and show it with ShowWindow; we can close a modeless dialog box with the DestroyWindow function.

6. We can set the text in an edit box control with the SetWindowText function, and we can extract

the text from an edit box control with the GetWindowText function.

Page 523: C++ Módulo I e II

202

7. Radio buttons are used to provide users with a set of options, where one, and only one, option in the group must be selected at any time. A radio button sends a WM_COMMAND message to its parent dialog window procedure when it is clicked. The lower 16-bits of the wParam contains the resource ID of the button that was selected. We can update the graphics of a group of radio buttons with the CheckRadioButton function; this function selects a specified radio button and deselects the previously selected button.

8. A combo box can be described as a drop down list box. What is interesting about the combo box

is that Windows creates a window procedure for it internally. Consequently, we communicate with a combo box by sending it messages. To add an item to the combo box we need to send it a CB_ADDSTRING message. To get the index of an item in the combo box the user selects we need to send the combo box a CB_GETCURSEL message. Given the index of the currently selected item in the combo box, we can get the actual string at that index by sending a GETLBTEXT message.

16.6 Exercises

The following exercises are research oriented exercises where you will need to spend some time looking up the solution in the Win32 documentation or on the Internet. An online resource of the Win32 documentation is available at www.msdn.microsoft.com.

16.6.1 List Box

Rewrite Program 16.4, but instead of using a combo box, use a list box, which is essentially the same thing, but a list box is not “drop down;” rather, it is always “expanded.” (Hint: List box messages begin with LB; for example, LB_ADDSTRING.)

16.6.2 Checkbox Controls

Implement a program that displays the modeless dialog box in Figure 16.14:

Page 524: C++ Módulo I e II

203

Figure 16.14: The dialog layout for the checkbox exercise.

This might be how an options screen looks for a game where you can customize the graphics by enabling and disabling features with checkbox controls. When the user presses the “Get Selection” button, a message box should be displayed that corresponds to the user selections. For example, if the boxes are checked like so:

Figure 16.15: The checkbox dialog after the user checked some items.

Then the message box should output the message: “Lighting on, wireframe mode on, detailed textures off, shadows on.” Hints:

• Investigate the BM_SETCHECK message and the BM_GETCHECK messages.

Page 525: C++ Módulo I e II

204

16.6.3 File Save and Open Dialogs

No doubt you are familiar with the common file save and open dialogs found in Windows. Figure 16.16 shows an example of the common save dialog from Microsoft Word.

Figure 16.16: The standard Win32 Save Dialog.

You will notice that Visual C++ .NET uses the same common save and open dialogs as well. Most likely, other Windows programs you use also employ these same common dialogs. The reason why many programs use these common dialogs is because they are prewritten by the Win32 API, and therefore, are straightforward to include, and also because they provide a standard user interface among Windows applications. A standard user interface is a good thing because users experienced with other Windows programs can quickly become experienced with your program if you provide a standard interface. Your assignment for this exercise is to create a dialog application like that in Figure 16.17.

Page 526: C++ Módulo I e II

205

Figure 16.17: The dialog box you are to design for the File Save and File Open exercise.

Note that the “white rectangle” is an edit box—you can adjust the size of the edit box. In this program the user can enter text into the edit box. Then when the user presses the “Save” button, the common save dialog box should open. After the user has specified a filename in which to save the work, the program should proceed to save the text entered in the edit box to the file (using file output mechanisms: see Chapter 8 in Module I). In addition to the save feature, your program should also be able to load text files. When the user presses the “Open” button, the common open dialog box should appear. After the user has specified the text filename in which to load, the program should proceed to load the text file (using file input mechanisms: see Chapter 8 in Module I), and copy the loaded text into the edit box. In this way, you will have a simple word processor that can save and load text files to and from the edit box. Hints:

• You will want to modify some of the edit box’s properties. In particular, you will want to set “Multiline” to true, so that the text will wrap to the next line. You will also want to add a vertical scroll bar in case the text exceeds the visible viewing rectangle; you can do this by setting “Vertical Scroll” to true. Finally, you will also want to set “Auto HScroll” to false so that the text will not scroll horizontally—we want it to drop to the next line.

• You will need to include the common dialog header file (commdlg.h). • Look up the OPENFILENAME structure. • Look up the GetOpenFileName and GetSaveFileName functions.

Page 527: C++ Módulo I e II

206

16.6.4 Color Dialog

In addition to the standard file save and open dialogs, Windows also provides a standard color dialog (Figure 16.18).

Figure 16.18: The standard color dialog.

Write a program that displays the standard color dialog. Your program does not need to do anything with the dialog—it only needs to display the dialog. Hints:

• You will need to include the common dialog header file (commdlg.h). • Look up the CHOOSECOLOR structure and the ChooseColor function.

Page 528: C++ Módulo I e II

207

Chapter 17

Timing, Animation and Sprites

Page 529: C++ Módulo I e II

208

Introduction

We now have enough C++ and Win32 programming background to begin work on a 2D game. However, in order to accomplish such a task, we have one more stepping stone to cross; namely, we need to discuss some techniques related to animation and 2D graphics. Those issues are timing, double buffering, and sprites.

Chapter Objectives

• Learn how to use the Windows multimedia timer functions, so that we can keep track of how much time has elapsed per frame, for smooth and consistent animation.

• Discover how to do basic 2D computer animation by moving graphical objects over small

increments as time passes.

• Understand the technique of double buffering and how it works to avoid flicker.

• Learn how to draw complex 2D image bitmaps, which are not rectangular, via sprites using the GDI raster operations.

17.1 Timing and Frames Per Second

17.1.1 The Windows Multimedia Timer Functions

The Windows media library (#include <mmsystem.h> and link winmm.lib) provides a timer function called timeGetTime. This function returns the number of milliseconds that has elapsed since Windows started. In order to use this function, you will need to link winmm.lib, which is not linked by default. To do this in VC++ .NET, go to the “Solution Explorer,” right click on your project name and select “Properties” from the popup menu. On the “Property Pages” dialog, select Linker->Input. Next, enter in “winmm.lib” in the “Additional Dependencies” field, as Figure 17.1 shows. Finally, press the “Apply” button and then the “OK” button.

Page 530: C++ Módulo I e II

209

Figure 17.1: Linking the winmm.lib file.

Often we will want the time in seconds, and not milliseconds. We can convert a time mst ms in milliseconds to a time st s in seconds by multiplying mst ms by the ratio 1 s / 1000 ms (there is one second per one-thousand milliseconds), as follows:

1 s mst · 1 s mst ms · ----------- = ------------ = st s

1000 ms 1000 Observe how the millisecond units (ms) cancel and we are left with only units of seconds. Also note that 1 s / 1000 ms = 1, and therefore, we can multiply any number by it (that is, 1) without actually changing it—we are changing units, not the physical meaning. Recall that timeGetTime returns the number of milliseconds that has elapsed since Windows started. This number in and of itself is not particularly interesting. What is interesting is that if we call this function at time 0t , and then again at a later time 1t , we can compute the elapsed time t∆ (change in time) as follows:

01 ttt −=∆

Page 531: C++ Módulo I e II

210

We can use the result to determine how much time has passed between two successive calls to timeGetTime. For measuring very short time intervals (such as the time elapsed between frames) at a high resolution, the timeGetTime function may not be accurate enough. Instead, you should use the more precise performance timer. Using the performance timer requires a small additional step; namely, we must first obtain its frequency (number of counts per second) so that we can convert the time units (i.e., “counts”) measured by the performance counter to seconds. The frequency is obtained with the function QueryPerformanceFrequency: // Get the performance timer frequency. __int64 cntsPerSec = 0; bool perfExists = QueryPerformanceFrequency((LARGE_INTEGER*)&cntsPerSec)!=0; if( !perfExists ) MessageBox(0, "Performance timer does not exist!", 0, 0); return 0; Notice that this function returns the result into a 64-bit integer value. It also returns true if the performance timer is available (it is on all modern CPUs), and false if not. To obtain a scaling factor that will convert performance counter units to seconds, we just divide 1 by the frequency. This gives us seconds per count. // Get a scaling factor which will convert the timer units to seconds. double timeScale = 1.0 / (double)cntsPerSec; Then to get the current time, we use the QueryPerformanceCounter function: __int64 time = 0; QueryPerformanceCounter((LARGE_INTEGER*)&time); To convert the value to seconds, we multiply by the scaling factor which is in units of seconds per count. This has the effect of canceling out the performance counter units, leaving us with units in seconds. The idea is the same as when we cancelled out the milliseconds unit with the timeGetTime function: double timeInSeconds = (double)(time) * timeScale; Note that you still need to include <mmsystem.h> and link with winmm.lib to use the performance timer.

Page 532: C++ Módulo I e II

211

17.1.2 Computing the Time Elapsed Per Frame

One of the applications for which we will use timers is to compute the elapsed time between frames. A frame is one complete image in an animation sequence. For example, a game running at sixty frames per second is displaying sixty slightly different images per second. The rapid successive display of slightly different images over time gives the illusion of a smooth and continuous animation. As an aside, in practice, an animation running at approximately thirty frames per second is enough to fool the eye into seeing a smooth and continuous animation. How do we compute the elapsed time between frames? If we saved the time of the last frame 1−it using timeGetTime, and we get the time of the current frame it using timeGetTime, it follows that the time difference between the current frame i and the last frame 1−i is given by:

1−−=∆ ii ttt The real issue is how to do this in code. First of all, we are going to modify our message loop into a more “game friendly” version. We do this for two reasons: 1) games do not typically interact with Windows as much as other applications and therefore are not as dependent upon the Win32 messaging system and 2) this will prepare you for how the message loop will be written in other Game Institute courses, and other game programming related literature. Essentially, our new message loop can be summarized in the following words: If there is a Windows message that needs to be processed, then process it; otherwise, execute our game code, which will typically be updating and drawing a frame. In order to implement this behavior, we need a function to determine if there is a message that needs to be processed. The Win32 API provides such a function called PeekMessage, which returns true if there is a message that needs to be processed; otherwise, it returns false. Thus our new message loop looks like this: while(msg.message != WM_QUIT) // IF there is a Windows message then process it. if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) TranslateMessage(&msg); DispatchMessage(&msg); // ELSE, do game stuff. else ... Note that if there is a message, then PeekMessage also extracts the message into the first argument msg, just as GetMessage does. Also, the last parameter PM_REMOVE simply indicates that the message should be removed from the application message queue once the message is processed.

Page 533: C++ Módulo I e II

212

Our revised message loop is often termed the game loop, since it is the loop where we process most of our game related happenings. Note that in this course we will still use some Windows messages, but as you move on to study DirectX or OpenGL, you will begin to phase out more and more Windows messages. Also, we will commonly call each game loop cycle a frame (not to be confused with an image frame), because it is common to draw a frame (image) for each game loop cycle and drawing is generally the task that takes the most time to complete. Thus there is a correspondence between a game loop cycle and the image frame being drawn during that cycle. We return to the question about implementing the time elapsed per frame 1−−=∆ ii ttt . Essentially, for each frame we get the time it , and this time will then become the last time for the next frame. Here is the game loop with the t∆ calculations added. Note that we represent t∆ with the variable deltaTime. In addition, the variable currTime corresponds to it , and lastTime corresponds to 1−it . // Get the current time. float lastTime = (float)timeGetTime(); while(msg.message != WM_QUIT) // IF there is a Windows message then process it. if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) TranslateMessage(&msg); DispatchMessage(&msg); // ELSE, do game stuff. else // Get the time now. float currTime = (float)timeGetTime(); // Compute the differences in time from the last // time we checked. Since the last time we checked // was the previous loop iteration, this difference // gives us the time between loop iterations... // or, I.e., the time between frames. float deltaTime = (currTime - lastTime)*0.001f; // // [...] Do Work this Frame (I.e., update and draw frame) // // We are at the end of the loop iteration, so // prepare for the next loop iteration by making // the "current time" the "last time." lastTime = currTime; Observe that for the very first frame, there is no last frame, and so we initialize the last time variable outside the message loop, before we get the time of the first frame: float lastTime = (float)timeGetTime();

Page 534: C++ Módulo I e II

213

17.1.3 Computing the Frames Per Second

We now present a function that computes the frames per second: //========================================================= // Name: DrawFramesPerSecond // Desc: This function is called every frame and updates // the frame per second display in the main window // caption. //========================================================= void DrawFramesPerSecond(float deltaTime) // Make static so the variables persist even after // the function returns. static int frameCnt = 0; static float timeElapsed = 0.0f; static char buffer[256]; // Function called implies a new frame, so increment // the frame count. ++frameCnt; // Also accumulate how much time has passed since the // last frame. timeElapsed += deltaTime; // Has one second passed? if( timeElapsed >= 1.0f ) // Yes, so compute the frames per second. // FPS = frameCnt / timeElapsed, but since we // compute only when timeElapsed = 1.0, we can // reduce to: // FPS = frameCnt / 1.0 = frameCnt. sprintf(buffer, "--Frames Per Second = %d", frameCnt); // Add the frames per second string to the main // window caption--that is, we'll display the frames // per second in the window's caption bar. string newCaption = gWndCaption + buffer; // Now set the new caption to the main window. SetWindowText(ghMainWnd, newCaption.c_str()); // Reset the counters to prepare for the next time // we compute the frames per second. frameCnt = 0; timeElapsed = 0.0f;

Page 535: C++ Módulo I e II

214

Items to note:

1. The function SetWindowText, which we previously used to set the text of an edit box control, is actually generic to all windows, and can be used to set the text of any window. In main windows, SetWindowText sets the text of the window’s caption bar.

2. We wait until a second of time has elapsed before we make the frames per second computation.

This is done for a few reasons. First, it is inefficient to make the calculation too frequently. Second, the frames per second are not constant, and so, by waiting for a second, we get a good average, which is more meaningful. And third, if we output the updated frames per second count every frame, it would appear too quickly to see!

17.2 Double Buffering

17.2.1 Motivation

In the drawing programs in Chapter 15, you probably noticed some flickering. Flickering occurs when you draw one thing and then draw something over it quickly afterwards. The process of drawing over a previously drawn pixel is called overdraw. The eye, which is quite sensitive to change and movement, picks up on overdraw and a flickering phenomenon is seen. Although flickering may be acceptable for some applications that are not graphics intensive, it is not acceptable for games. Therefore, we are motivated to look for a solution, which double buffering provides. Note that double buffering is an important concept, which is also used in Direct3D programming (the graphics aspect of DirectX) and in OpenGL.

17.2.2 Theory

The problem with overdraw is not so much overdraw in and of itself, but rather that we can see the overdraw. More specifically, because we are drawing to the client rectangle, which we can always see, we are literally watching the image get drawn. Thus the user can notice the brief flicker in color when an overdraw occurs—we see the color of the first graphic object that was drawn, and then a fraction of a second later, a new color is drawn as we draw an object on top of it. Our solution to the flickering problem does not necessarily need to solve the overdraw problem. It only needs to keep the viewer from seeing the overdraw. This is where double buffering comes in. The idea of double buffering is quite simple: instead of drawing directly to the client area of the window, we instead draw to an off screen surface called the backbuffer, which will be a blank bitmap that will represent our virtual “canvas.” Since the backbuffer is not directly visible, overdraw is not a problem. That is, we will not see the overdraw as it happens because we cannot see the backbuffer—we are no longer watching the image get “painted.” Once we have drawn all the graphics to the backbuffer,

Page 536: C++ Módulo I e II

215

we will have a completed 2D image on the backbuffer. We can then copy the backbuffer image contents to the window client area all at once with the BitBlt function. Since the backbuffer contains the completed frame, the only thing we will need to draw to the window client area is the contents of the backbuffer. Therefore, we will have zero overdraw on the client area window, and thus avoid flicker. The process of copying the backbuffer image to the window client area is known as presenting. For example, saying, “we present the backbuffer,” means we copy the backbuffer to the window client area.

Note: Drawing graphical objects are not the only things that are drawn. Windows usually clears the window client area background by default. This means that even if we use double buffering, we will still get overdraw: Windows clears the client area (1st draw) and then we present the backbuffer (2nd draw). To fix this, we simply instruct Windows not to clear the window client area. To do this, we specify a “null brush” for the window when we fill out the hbrBackground member of the WNDCLASS: wc.hbrBackground = (HBRUSH)::GetStockObject(NULL_BRUSH); This way, we will only draw to the window client area once—when we present the backbuffer.

17.2.3 Implementation

To implement double buffering, we will create a class to represent the backbuffer. Here is the class definition: // BackBuffer.h // By Frank Luna // August 24, 2004. #ifndef BACKBUFFER_H #define BACKBUFFER_H #include <windows.h> class BackBuffer public: BackBuffer(HWND hWnd, int width, int height); ~BackBuffer(); HDC getDC(); int width(); int height(); void present(); private: // Make copy constructor and assignment operator private // so client cannot copy BackBuffers. We do this because // this class is not designed to be copied because it // is not efficient--copying bitmaps is slow (lots of memory).

Page 537: C++ Módulo I e II

216

// In addition, most applications will probably only need one // BackBuffer anyway. BackBuffer(const BackBuffer& rhs); BackBuffer& operator=(const BackBuffer& rhs); private: HWND mhWnd; HDC mhDC; HBITMAP mhSurface; HBITMAP mhOldObject; int mWidth; int mHeight; ; #endif // BACKBUFFER_H Let us begin analysis of this class by first looking at the data members.

1. mhWnd: A handle to the main window. We need this handle to obtain a device context associated with the main window.

2. mhDC: A handle to a system memory device context, upon which we will associate the backbuffer

bitmap. We need this because all GDI functions are done through a device context. So in order to draw to the backbuffer bitmap, we need a device context associated with it. Note that this device context is not the window device context, which is associated with the main window’s client area.

3. mhSurface: A handle to the bitmap that serves as our backbuffer. That is, our render target—

the bitmap we will draw onto.

4. mhOldObject: A handle to the previous bitmap that was loaded by the device context. Recall that it is “good form” to restore the original GDI object when done with the new one.

5. mWidth: The width of the bitmap matrix in pixels; in other words, how many horizontal pixels

across the bitmap surface.

6. mHeight: The height of the bitmap matrix in pixels; in other words, how many vertical pixels across the bitmap surface.

A backbuffer is represented with a bitmap and an associated device context, which is needed to render onto that bitmap. Let us now examine the methods, one-by-one.

1. BackBuffer(HWND hWnd, int width, int height); The constructor is responsible for creating a backbuffer. It takes three parameters; the first is a handle to the main window from which we will get the associated window device context. This is needed because we need to create the backbuffer bitmap in a way that is compatible with the window’s client area, in order that we can copy the backbuffer contents to the window’s client

Page 538: C++ Módulo I e II

217

area. The width and height parameters are simply used to specify the backbuffer dimensions and these dimensions should match the window’s client area dimensions so that there is a one-to-one pixel correspondence between the backbuffer and window’s client area. The constructor is implemented like so:

// BackBuffer.cpp // By Frank Luna // August 24, 2004. #include "BackBuffer.h" BackBuffer::BackBuffer(HWND hWnd, int width, int height) // Save a copy of the main window handle. mhWnd = hWnd; // Get a handle to the device context associated with // the window. HDC hWndDC = GetDC(hWnd); // Save the backbuffer dimensions. mWidth = width; mHeight = height; // Create system memory device context that is compatible // with the window one. mhDC = CreateCompatibleDC(hWndDC); // Create the backbuffer surface bitmap that is compatible // with the window device context bitmap format. That is // the surface we will render onto. mhSurface = CreateCompatibleBitmap( hWndDC, width, height); // Done with window DC. ReleaseDC(hWnd, hWndDC); // At this point, the back buffer surface is uninitialized, // so lets clear it to some non-zero value. Note that it // needs to be non-zero. If it is zero then it will mess // up our sprite blending logic. // Select the backbuffer bitmap into the DC. mhOldObject = (HBITMAP)SelectObject(mhDC, mhSurface); // Select a white brush. HBRUSH white = (HBRUSH)GetStockObject(WHITE_BRUSH); HBRUSH oldBrush = (HBRUSH)SelectObject(mhDC, white); // Clear the backbuffer rectangle. Rectangle(mhDC, 0, 0, mWidth, mHeight); // Restore the original brush. SelectObject(mhDC, oldBrush);

Page 539: C++ Módulo I e II

218

2. ~BackBuffer(); The destructor is responsible for cleaning up any resources the constructor has allocated. We observe that the constructor allocates two resources: 1) a system memory device context, and 2) a bitmap. Thus we have the following destructor implementation:

BackBuffer::~BackBuffer() SelectObject(mhDC, mhOldObject); DeleteObject(mhSurface); DeleteDC(mhDC); Note that we restore the original selected bitmap before deleting anything. Again, it is just “good form” to restore the original GDI object when done with the new one.

3. HDC getDC(); This function is trivial and simply returns a copy of the backbuffer’s associated device context:

HDC BackBuffer::getDC() return mhDC;

4. int width(); Another trivial method, this one returns the width of the backbuffer bitmap in pixels.

int BackBuffer::width() return mWidth;

5. int height(); This method returns the height of the backbuffer bitmap in pixels.

int BackBuffer::height() return mHeight;

6. void present(); This method presents the contents of the backbuffer to the main window’s client area. In order to do this, the method needs a handle to the device context associated with the main window’s client area; this can be obtained from the internal mhWnd data member. The implementation of this method is fairly simple; it copies the pixels from the backbuffer to the window’s client area using the BitBlt function. We copied bitmaps to the client area in Chapter 15. At that time

Page 540: C++ Módulo I e II

219

we loaded our bitmap data from .bmp files. Here, we are actually drawing to the bitmap and generating it ourselves, and then we copy it to the client area.

void BackBuffer::present() // Get a handle to the device context associated with // the window. HDC hWndDC = GetDC(mhWnd); // Copy the backbuffer contents over to the // window client area. BitBlt(hWndDC, 0, 0, mWidth, mHeight, mhDC, 0, 0, SRCCOPY); // Always free window DC when done. ReleaseDC(mhWnd, hWndDC); One thing worth emphasizing is that we should release the window DC when we are finished with it. The window DC is a valuable GDI resource and we should only “hold on” to it when we need to use it to draw. Now that we have our backbuffer set up, let us review the basic process of how we will use it in the game loop. First, we only need one backbuffer per application, and so we will declare it as a global variable: BackBuffer* gBackBuffer = 0; [...] // Create the backbuffer. gBackBuffer = new BackBuffer( ghWindowDC, gClientWidth, gClientHeight); Observe how the dimensions of the backbuffer match the client area dimensions. Remember that for each game loop cycle we draw a frame of animation. Thus, for every animated application we will have the following pattern executed per loop cycle:

1. Compute the time difference from the previous frame t∆ . 2. Update the positions of the graphical objects slightly. 3. Draw the graphical objects to the backbuffer. 4. Present the backbuffer contents to the main window’s client area.

The astute reader may object, and point out that we do all of our drawing in a WM_PAINT message handler. This is no longer the case with an animation intensive application. Here we want to update and draw the graphics continuously. However, this brings up something else to think about. Namely, if we are no longer drawing in a WM_PAINT message handler, how do we obtain a device context to the window’s client area? Before, we always used BeginPaint, which may only be called in a WM_PAINT message handler. As you already saw from the BackBuffer class implementation, the Win32 API

Page 541: C++ Módulo I e II

220

provides another function, which can give us a handle to a device context associated with a window’s client area; the function is called GetDC: // Get a DC associated with the window's client area. HDC hWndDC = GetDC(mhWnd); The GetDC function takes a parameter to a window handle (HWND), which specifies the window with which we want to associate the device context. The GetDC function then returns a handle to such a device context.

17.3 Tank Animation Sample

Figure 17.2 shows a screenshot of the Tank animation sample we will write in this section.

Figure 17.2: A screenshot of the Tank sample.

The tank is drawn using a rectangle for the tank base, an ellipse for the gun base, and a thick line (i.e., pen width > 1) for the gun. You can move the tank up and down and from side to side with the ‘W’, ‘S’, ‘A’ and ‘D’ keys. You can rotate the gun with the ‘Q’ and ‘E’ keys. Finally, you can fire bullets with the spacebar key. The bullets are modeled using ellipses.

Page 542: C++ Módulo I e II

221

Be aware that this program uses a 2D vector class called Vec2. This class is remarkably similar to the Vector3 class we developed in Chapter 7 so please take a moment to review the vector mathematics discussed in that chapter if you do not recall the concepts. We will be using vectors to determine directions. For example, we will need to determine the direction a bullet should travel. In addition, we will sometimes interpret the components of vectors as points. Before we begin an analysis of the tank program, let us first look at the global variables the program uses; the comments explain their purpose: HWND ghMainWnd = 0; // Main window handle. HINSTANCE ghAppInst = 0; // Application instance handle. HMENU ghMainMenu = 0; // Menu handle. // The backbuffer we will render onto. BackBuffer* gBackBuffer = 0; // The text that will appear in the main window's caption bar. string gWndCaption = "Game Institute Tank Sample"; // Client rectangle dimensions we will use. const int gClientWidth = 800; const int gClientHeight = 600; // Center point of client rectangle. const POINT gClientCenter = gClientWidth / 2, gClientHeight / 2 ; // Pad window dimensions so that there is room for window // borders, caption bar, and menu. const int gWindowWidth = gClientWidth + 6; const int gWindowHeight = gClientHeight + 52; // Client area rectangle, which we will use to detect // if a bullet travels "out-of-bounds." RECT gMapRect = 0, 0, 800, 600; // Vector to store the center position of the tank, // relative to the client area rectangle. Vec2 gTankPos(400.0f, 300.0f); // Handle to a pen we will use to draw the tank's gun. HPEN gGunPen; // A vector describing the direction the tank's gun // is aimed in. The vector’s magnitude denotes the // length of the gun. Vec2 gGunDir(0.0f, -120.0f); // A list, where we will add bullets to as they are fired. // The list stores the bullet positions, so that we can // draw an ellipse at the position of each bullet. list<Vec2> gBulletList;

Page 543: C++ Módulo I e II

222

17.3.1 Creation

The very first thing we need to do is initialize some of our resources. To do this, we need a valid handle to the main window, and therefore, the WM_CREATE message is a good place to do resource acquisition. We have two resources we need to create. First, we need to create the pen, which we will use to draw the tank gun. This pen needs to be somewhat thick, so we specify 10 units for its width. Finally, we create the backbuffer. Here is the implementation for the WM_CREATE message handler: case WM_CREATE: // Create the tank's gun pen. lp.lopnColor = RGB(150, 150, 150); lp.lopnStyle = PS_SOLID; lp.lopnWidth.x = 10; lp.lopnWidth.y = 10; gGunPen = CreatePenIndirect(&lp); // Create the backbuffer. gBackBuffer = new BackBuffer( hWnd, gClientWidth, gClientHeight); return 0; Where lp is a LOGPEN.

17.3.2 Destruction

The application destruction process should free any resource allocated in the application creation process. Thus we need to delete the pen we created and the backbuffer as well. The natural place to do such resource deletion is in the WM_DESTROY message handler: case WM_DESTROY: DeleteObject(gGunPen); delete gBackBuffer; PostQuitMessage(0); return 0;

Page 544: C++ Módulo I e II

223

17.3.3 Input

We said that you can move the tank up and down and from side to side with the ‘W’, ‘S’, ‘A’ and ‘D’ keys, that you can rotate the gun with the ‘Q’ and ‘E’ keys, and that you can fire bullets with the spacebar key. Implementing such functionality is simply a matter of handling the WM_KEYDOWN message: case WM_KEYDOWN: switch(wParam) // Move left. case 'A': gTankPos.x -= 5.0f; break; // Move right. case 'D': gTankPos.x += 5.0f; break; // Move up--remember in Windows coords, -y = up. case 'W': gTankPos.y -= 5.0f; break; // Move down. case 'S': gTankPos.y += 5.0f; break; // Rotate tank gun to the left. case 'Q': gGunDir.rotate(-0.1f); break; // Rotate tank gun to the right. case 'E': gGunDir.rotate(0.1f); break; // Fire a bullet. case VK_SPACE: gBulletList.push_back(gTankPos + gGunDir); break; return 0; As you can see, pressing either the ‘A’, ‘W’, ‘S’, or ‘D’ key simply updates the tank’s position slightly along the appropriate axis. The ‘Q’ and ‘E’ keys rotate the tank’s gun. We will discuss how Vec2::rotate is implemented in Section 17.3.6. For now, just realize that this rotates the gun’s direction vector by some angle in a circular fashion. Finally, pressing the spacebar button (symbolized with VK_SPACE), adds a bullet to our global list of bullets. Recall that the bullet list stores the positions of the bullets. We will update the bullets in another function, but when we first create the bullet (add it to the list) we want the bullet to be created at the tip of the gun, not the center point of the tank. Thus we have to do some vector addition to get that gun tip point. That is, gTankPos + gGunDir. Figure 17.3 shows what this means geometrically.

Page 545: C++ Módulo I e II

224

Figure 17.3: The position of the gun’s tip point is given by gTankPos + gGunDir.

17.3.4 Updating and Drawing

We are now ready to examine the game loop for the tank program. However, the implementation is a bit lengthy, so let us first look at a general roadmap of the function:

1. Compute the time elapsed between frames ( t∆ ). 2. Draw a black rectangle spanning the entire backbuffer to clear the backbuffer to black. This

provides our background. 3. Draw the tank to the backbuffer, which includes the base rectangle, the circular gun base, and the

gun itself. 4. Iterate over the entire bullet list, and for each bullet, update the bullet position and draw the

bullet to the backbuffer. 5. Draw the frames per second into the Window Caption bar. 6. Present the backbuffer contents to the main window’s client area.

The implementation is as follows: while(msg.message != WM_QUIT) // IF there is a Windows message then process it. if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) TranslateMessage(&msg); DispatchMessage(&msg); // ELSE, do game stuff. else // Get the time now. float currTime = (float)timeGetTime(); // Compute the differences in time from the last // time we checked. Since the last time we checked

Page 546: C++ Módulo I e II

225

// was the previous loop iteration, this difference // gives us the time between loop iterations... // or, I.e., the time between frames. float deltaTime = (currTime - lastTime)*0.001f; // Get the backbuffer DC. HDC bbDC = gBackBuffer->getDC(); // Clear the entire backbuffer black. This gives // up a black background. HBRUSH oldBrush = (HBRUSH)SelectObject(bbDC, GetStockObject(BLACK_BRUSH)); Rectangle(bbDC, 0, 0, 800, 600); // Draw the base of the tank--a rectangle surrounding // the tank's center position point. SelectObject(bbDC, GetStockObject(DKGRAY_BRUSH)); Rectangle(bbDC, (int)gTankPos.x - 50, (int)gTankPos.y - 75, (int)gTankPos.x + 50, (int)gTankPos.y + 75); // Draw the gun base--an ellipse surrounding // the tank's center position point. SelectObject(bbDC, GetStockObject(GRAY_BRUSH)); Ellipse(bbDC, (int)gTankPos.x - 40, (int)gTankPos.y - 40, (int)gTankPos.x + 40, (int)gTankPos.y + 40); // Draw the gun itself--a line from the tank's // center position point to the tip of the gun. HPEN oldPen = (HPEN)SelectObject(bbDC, gGunPen); MoveToEx(bbDC, (int)gTankPos.x, (int)gTankPos.y, 0); LineTo(bbDC, (int)(gTankPos.x + gGunDir.x), (int)(gTankPos.y + gGunDir.y)); // Draw any bullets that where fired. SelectObject(bbDC, GetStockObject(WHITE_BRUSH)); SelectObject(bbDC, oldPen); // Bullet velocity is 5X the gun's direction's // magnitude. Vec2 bulletVel = gGunDir * 5.0f; list<Vec2>::iterator i = gBulletList.begin(); while( i != gBulletList.end() ) // Update the bullet position. *i += bulletVel * deltaTime; // Get POINT form. POINT p = *i; // Only draw bullet if it is still inside the

Page 547: C++ Módulo I e II

226

// map boundaries, otherwise, delete it. if( !PtInRect(&gMapRect, p) ) i = gBulletList.erase(i); else // Draw bullet as a circle. Ellipse(bbDC, p.x - 4, p.y - 4, p.x + 4, p.y + 4); ++i; // Next in list. SelectObject(bbDC, oldBrush); DrawFramesPerSecond(deltaTime); // Now present the backbuffer contents to the main // window client area. gBackBuffer->present(ghWindowDC); // We are at the end of the loop iteration, so // prepare for the next loop iteration by making // the "current time" the "last time." lastTime = currTime; // Free 20 miliseconds to Windows so we don't hog // the system resources. Sleep(20); A new function that we have not discussed is the Sleep function. This Win32 function takes a single parameter, which specifies the number of milliseconds to sleep. Sleeping is defined as suspending execution of the current application so that Windows is free to perform other processes. Despite being long, the game loop implementation is fairly straightforward. The only tricky part might be updating the bullets, so let us examine that section more closely. First, we define a bullet’s velocity to be in the direction the gun is aimed, but five times the magnitude. Recall that velocity describes a speed (magnitude) and the direction of travel. Vec2 bulletVel = gGunDir * 5.0f; Given the velocity, we update the bullet’s position like so: *i += bulletVel * deltaTime; But what exactly is bulletVel * deltaTime? To see this, we must go to the definition of velocity, which is the change in position over time:

Page 548: C++ Módulo I e II

227

tvptpv ∆⋅=∆⇒∆∆

=rr

rr

That is, the change in position of the bullet pr∆ (displacement) over t∆ seconds is tvp ∆⋅=∆

rr . So the formula tvp ∆⋅=∆

rr tells us how much the position pr needs to be displaced given the velocity vr , over a time of t∆ seconds. Recall that t∆ is the time elapsed between frames, thus this formula tells us how much to displace a point pr per frame given the velocity vr ; that is, tvpppp ∆⋅+=∆+=′

rrrrr —see Figure 17.4.

Figure 17.4: Displacement. The displaced point p′r equals pr + pr∆ , where tvp ∆⋅=∆

rr. Note that this figure shows

a “typical” coordinate system. Recall that in Windows coordinates, +Y goes “down.” However, the idea of displacement is the same, nonetheless.

Note that the value t∆ will typically be very small: if we are running at 30 frames per second, then t∆ will approximately being 1/30th of a second. Thus, the displacement vector pr∆ will also be small. These small displacements over time give a smooth continuous animation. Finally, the std::list::erase method is a method that allows us to delete an element in the list given an iterator to it: i = gBulletList.erase(i); This function deletes the iterator i and returns an iterator to the next element in the list.

Page 549: C++ Módulo I e II

228

17.3.5 Point Rotation

We stated in Section 17.3.3 that we are able to rotate the gun’s directional vector with the code: // Rotate tank gun to the left. case 'Q': gGunDir.rotate(-0.1f); break; // Rotate tank gun to the right. case 'E': gGunDir.rotate(0.1f); break; However, we did not elaborate on how the Vec2::rotate function worked. Let us examine that now. The implementation to Vec2::rotate looks like so: Vec2& Vec2::rotate(float t) x = x * cosf(t) - y * sinf(t); y = y * cosf(t) + x * sinf(t); return *this; The mathematical operations taking place in the implementation do not make any sense until we derive the rotation equations, which we will do now. Consider Figure 17.5, where we have a given point ( )yx, , which makes an angleα with the x-axis, and we want to know the coordinates of that point if we rotate it by an angleθ in a counterclockwise direction. That is, we want to know ( )yx ′′, .

Figure 17.5: Rotating a point (x, y) by and angle θ to a new point (x’, y’).

Page 550: C++ Módulo I e II

229

Trigonometry dictates that:

(1) ( )( )αα

sincos

RyRx

==

and similarly that:

(2) ( )( )θα

θα+=′+=′

sincos

RyRx

Moreover, there is a trigonometric identity for angle sum relations:

(3) ( ) ( ) ( ) ( ) ( )( ) ( ) ( ) ( ) ( )θαθαθα

θαθαθαsincoscossinsinsinsincoscoscos

+=+−=+

Thus, (2) can be rewritten as:

(4) ( ) ( ) ( ) ( )( ) ( ) ( ) ( )θαθα

θαθαsincoscossinsinsincoscos

RRyRRx

+=′−=′

However, we note that the ( )αcosR and ( )αsinR factors in equations (4) can be substituted with x and y, respectively, due to the relationships specified in (1). Thus, the rotated point in terms of the original point and the angle of rotation θ is: The 2D Rotation Counterclockwise Rotation Formula.

(5) )sin()cos()sin()cos(

θθθθ

xyyyxx

+=′−=′

And we can now see that the implementation of Vec2::rotate is a direct application of equations (5).

17.3.6 Tank Application Code

To conclude the Tank sample discussion, we now present the main application code in its entirety so that you can see everything together at once, instead of in separate parts. However, be sure to download the complete project from the Game Institute C++ Course Website so that you see the entire project as a whole with the other .h/.cpp files (BackBuffer.h/.cpp, and Vec2.h/.cpp). Program 17.1: The Tank Sample Main Application Code. You still need the other files like Sprite.h/.cpp, BackBuffer.h/.cpp, and Vec2.h/.cpp to compile. To obtain these files download the entire project off of the Game Institute C++ Course Website.

Page 551: C++ Módulo I e II

230

// tank.cpp // By Frank Luna // August 24, 2004. //========================================================= // Includes //========================================================= #include <string> #include "resource.h" #include "BackBuffer.h" #include "Vec2.h" #include <list> using namespace std; //========================================================= // Globals //========================================================= HWND ghMainWnd = 0; // Main window handle. HINSTANCE ghAppInst = 0; // Application instance handle. HMENU ghMainMenu = 0; // Menu handle. // The backbuffer we will render onto. BackBuffer* gBackBuffer = 0; // The text that will appear in the main window's caption bar. string gWndCaption = "Game Institute Tank Sample"; // Client rectangle dimensions we will use. const int gClientWidth = 800; const int gClientHeight = 600; // Center point of client rectangle. const POINT gClientCenter = gClientWidth / 2, gClientHeight / 2 ; // Pad window dimensions so that there is room for window // borders, caption bar, and menu. const int gWindowWidth = gClientWidth + 6; const int gWindowHeight = gClientHeight + 52; // Client area rectangle, which we will use to detect // if a bullet travels "out-of-bounds." RECT gMapRect = 0, 0, 800, 600; // Vector to store the center position of the tank, // relative to the client area rectangle. Vec2 gTankPos(400.0f, 300.0f); // Handle to a pen we will use to draw the tank's gun. HPEN gGunPen; // A vector describing the direction the tank's gun // is aimed in. The vector's magnitude denotes the // length of the gun.

Page 552: C++ Módulo I e II

231

Vec2 gGunDir(0.0f, -120.0f); // A list, where we will add bullets to as they are fired. // The list stores the bullet positions, so that we can // draw an ellipse at the position of each bullet. list<Vec2> gBulletList; //========================================================= // Function Prototypes //========================================================= bool InitMainWindow(); int Run(); void DrawFramesPerSecond(float deltaTime); LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); //========================================================= // Name: WinMain // Desc: Program execution starts here. //========================================================= int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) ghAppInst = hInstance; // Create the main window. if( !InitMainWindow() ) MessageBox(0, "Window Creation Failed.", "Error", MB_OK); return 0; // Enter the message loop. return Run(); //========================================================= // Name: InitMainWindow // Desc: Creates the main window upon which we will // draw the game graphics onto. //========================================================= bool InitMainWindow() WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(NULL_BRUSH); wc.lpszMenuName = 0;

Page 553: C++ Módulo I e II

232

wc.lpszClassName = "MyWndClassName"; RegisterClass( &wc ); // WS_OVERLAPPED | WS_SYSMENU: Window cannot be resized // and does not have a min/max button. ghMainMenu = LoadMenu(ghAppInst, MAKEINTRESOURCE(IDR_MENU)); ghMainWnd = ::CreateWindow("MyWndClassName", gWndCaption.c_str(), WS_OVERLAPPED | WS_SYSMENU, 200, 200, gWindowWidth, gWindowHeight, 0, ghMainMenu, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return 0; ShowWindow(ghMainWnd, SW_NORMAL); UpdateWindow(ghMainWnd); return true; //========================================================= // Name: Run // Desc: Encapsulates the message loop. //========================================================= int Run() MSG msg; ZeroMemory(&msg, sizeof(MSG)); // Get the current time. float lastTime = (float)timeGetTime(); while(msg.message != WM_QUIT) // IF there is a Windows message then process it. if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) TranslateMessage(&msg); DispatchMessage(&msg); // ELSE, do game stuff. else // Get the time now. float currTime = (float)timeGetTime(); // Compute the differences in time from the last // time we checked. Since the last time we checked // was the previous loop iteration, this difference // gives us the time between loop iterations... // or, I.e., the time between frames. float deltaTime = (currTime - lastTime)*0.001f;

Page 554: C++ Módulo I e II

233

// Get the backbuffer DC. HDC bbDC = gBackBuffer->getDC(); // Clear the entire backbuffer black. This gives // up a black background. HBRUSH oldBrush = (HBRUSH)SelectObject(bbDC, GetStockObject(BLACK_BRUSH)); Rectangle(bbDC, 0, 0, 800, 600); // Draw the base of the tank--a rectangle surrounding // the tank's center position point. SelectObject(bbDC, GetStockObject(DKGRAY_BRUSH)); Rectangle(bbDC, (int)gTankPos.x - 50, (int)gTankPos.y - 75, (int)gTankPos.x + 50, (int)gTankPos.y + 75); // Draw the gun base--an ellipse surrounding // the tank's center position point. SelectObject(bbDC, GetStockObject(GRAY_BRUSH)); Ellipse(bbDC, (int)gTankPos.x - 40, (int)gTankPos.y - 40, (int)gTankPos.x + 40, (int)gTankPos.y + 40); // Draw the gun itself--a line from the tank's // center position point to the tip of the gun. HPEN oldPen = (HPEN)SelectObject(bbDC, gGunPen); MoveToEx(bbDC, (int)gTankPos.x, (int)gTankPos.y, 0); LineTo(bbDC, (int)(gTankPos.x + gGunDir.x), (int)(gTankPos.y + gGunDir.y)); // Draw any bullets that where fired. SelectObject(bbDC, GetStockObject(WHITE_BRUSH)); SelectObject(bbDC, oldPen); // Bullet velocity is 5X the gun's direction's // magnitude. Vec2 bulletVel = gGunDir * 5.0f; list<Vec2>::iterator i = gBulletList.begin(); while( i != gBulletList.end() ) // Update the bullet position. *i += bulletVel * deltaTime; // Get POINT form. POINT p = *i; // Only draw bullet if it is still inside the // map boundaries, otherwise, delete it. if( !PtInRect(&gMapRect, p) ) i = gBulletList.erase(i); else

Page 555: C++ Módulo I e II

234

// Draw bullet as a circle. Ellipse(bbDC, p.x - 4, p.y - 4, p.x + 4, p.y + 4); ++i; // Next in list. SelectObject(bbDC, oldBrush); DrawFramesPerSecond(deltaTime); // Now present the backbuffer contents to the main // window client area. gBackBuffer->present(); // We are at the end of the loop iteration, so // prepare for the next loop iteration by making // the "current time" the "last time." lastTime = currTime; // Free 20 miliseconds to Windows so we don't hog // the system resources. Sleep(20); // Return exit code back to operating system. return (int)msg.wParam; //========================================================= // Name: DrawFramesPerSecond // Desc: This function is called every frame and updates // the frame per second display in the main window // caption. //========================================================= void DrawFramesPerSecond(float deltaTime) // Make static so the variables persist even after // the function returns. static int frameCnt = 0; static float timeElapsed = 0.0f; static char buffer[256]; // Function called implies a new frame, so increment // the frame count. ++frameCnt; // Also increment how much time has passed since the // last frame. timeElapsed += deltaTime; // Has one second passed? if( timeElapsed >= 1.0f )

Page 556: C++ Módulo I e II

235

// Yes, so compute the frames per second. // FPS = frameCnt / timeElapsed, but since we // compute only when timeElapsed = 1.0, we can // reduce to: // FPS = frameCnt / 1.0 = frameCnt. sprintf(buffer, "--Frames Per Second = %d", frameCnt); // Add the frames per second string to the main // window caption--that is, we'll display the frames // per second in the window's caption bar. string newCaption = gWndCaption + buffer; // Now set the new caption to the main window. SetWindowText(ghMainWnd, newCaption.c_str()); // Reset the counters to prepare for the next time // we compute the frames per second. frameCnt = 0; timeElapsed = 0.0f; //========================================================= // Name: WndProc // Desc: The main window procedure. //========================================================= LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) LOGPEN lp; switch( msg ) // Create application resources. case WM_CREATE: // Create the tank's gun pen. lp.lopnColor = RGB(150, 150, 150); lp.lopnStyle = PS_SOLID; lp.lopnWidth.x = 10; lp.lopnWidth.y = 10; gGunPen = CreatePenIndirect(&lp); // Create the backbuffer. gBackBuffer = new BackBuffer( hWnd, gClientWidth, gClientHeight); return 0; case WM_COMMAND: switch(LOWORD(wParam))

Page 557: C++ Módulo I e II

236

// Destroy the window when the user selects the 'exit' // menu item. case ID_FILE_EXIT: DestroyWindow(ghMainWnd); break; return 0; case WM_KEYDOWN: switch(wParam) // Move left. case 'A': gTankPos.x -= 5.0f; break; // Move right. case 'D': gTankPos.x += 5.0f; break; // Move up--remember in Windows coords, -y = up. case 'W': gTankPos.y -= 5.0f; break; // Move down. case 'S': gTankPos.y += 5.0f; break; // Rotate tank gun to the left. case 'Q': gGunDir.rotate(-0.1f); break; // Rotate tank gun to the right. case 'E': gGunDir.rotate(0.1f); break; // Fire a bullet. case VK_SPACE: gBulletList.push_back(gTankPos + gGunDir); break; return 0; // Destroy application resources. case WM_DESTROY: DeleteObject(gGunPen); delete gBackBuffer; PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the // default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam);

Page 558: C++ Módulo I e II

237

17.4 Sprites

17.4.1 Theory

Although the tank from the tank sample is reminiscent of early classic computer gaming, its graphics are obviously extremely primitive. A more contemporary solution is for an artist to paint a detailed tank image using some image editing software, such as Adobe Photoshop™, and perhaps base it on a photograph of a real tank. The artist can then save the image, say as a .bmp file, which we can load and use in our programs. This can lead us to some better-looking graphics. For example, Figure 17.6 shows a bitmap of a military jet we could use in a 2D jet fighter style game.

Figure 17.6: A bitmap of a fighter jet.

However, there is a problem. Bitmaps are rectangular, by definition. The actual jet part of Figure 17.6 is not rectangular, but as you can see, it lies on a rectangular background. However, when we draw these bitmaps we do not want this black background to be drawn—Figure 17.7 illustrates the problem and Figure 17.8 shows the desired “correct” output.

Page 559: C++ Módulo I e II

238

Figure 17.7: Drawing bitmaps incorrectly. The problem is that the bitmap pixels that are not part of the actual image show through; that is, the black background pixels are visible.

Figure 17.8: Drawing bitmaps correctly—only the desired image pixels are drawn.

Page 560: C++ Módulo I e II

239

The task at hand is to figure out a way to not draw the black background part of the bitmap. These images we use to represent game objects, such as jets, missiles and such, are commonly referred to as sprites. The solution to this problem lies in the way we combine the pixels of the source bitmap (sprite) and the pixels of the destination bitmap (backbuffer). First, for each sprite we will create a corresponding mask bitmap. This bitmap will mark the pixels of the sprite which should be drawn to the backbuffer and mark the pixels of the sprite which should not be drawn to the backbuffer. It is important to realize that the mask bitmap must be of the same dimensions as the image bitmap so that the ijth pixel in the image corresponds with the ijth pixel in the mask. Figure 17.9a shows a jetfighter bitmap image, Figure 17.9b shows its mask, and Figure 17.9c shows the result when the image is combined with the mask. (We show how this combination is done in the next paragraph; our goal here is to intuitively show what is happening.)

Figure 17.9: (a) The image bitmap. (b) The mask, marking the pixels that should be drawn. (c) The result after combining the image and the mask.

In the mask, the black pixels mark the pixels of the sprite that should be drawn to the backbuffer and the white pixels mark the pixels of the sprite that should not be drawn to the backbuffer. To draw a sprite, we first draw the mask to the backbuffer using the raster operation SRCAND, instead of SRCCOPY. Recall that SRCCOPY simply copies the source pixels directly over the destination pixels—it overwrites whatever is there. On the other hand, SRCAND combines each source pixel with its corresponding destination pixel to produce the new final destination color, like so: F = D & S, where F is the final color eventually written to the destination pixel, D was the previous destination pixel color, and S is the source pixel color. This combination is a bitwise AND. (Hopefully your bitwise operations are well understood. If they are not, you may wish to review Chapter 12.) The AND operation is a key point because when we AND a pixel color with a black pixel we get black (zero) in all circumstances, and when we AND a pixel color with a white pixel we do not modify the original pixel (it is like multiplying by one):

1. D & S = 0x00?????? & 0x00000000 = 0x00000000 (S = Black) 2. D & S = 0x00?????? & 0x00FFFFFF = 0x00?????? (S = White)

Page 561: C++ Módulo I e II

240

The question marks simply mean the left-hand-side can be any value. What does this amount to? It means that when we draw the mask to the backbuffer, we draw black to the backbuffer where the mask is black (D & Black = Black) and we leave the backbuffer pixels unchanged where the mask is white (D & White = D).

Note: In case you are wondering how we can AND colors, recall that colors are typically represented as 32-bit integers, where 8-bits are used for the red component, 8-bits are used for the green component, and 8-bits are used for the blue component. (8-bits are not used.) That is what a COLORREF is: it is typedefed as a 32-bit integer. In hexadecimal, the format of the COLORREF type looks like so: 0x00bbggrr Where red takes the rightmost 8-bits, green takes the next 8-bits, blue takes the next 8-bits, and the top 8-bits is not used and just set to zero. We can do bitwise operations on integers and thus COLORREFs. Incidentally, the color black would be described as 0x00000000 (each 8-bit color component is 0), and white is represented as 0x00FFFFFF (each 8-bit color component is 255).

At this point, we have drawn black to the backbuffer pixels that correspond to the pixels in the sprite image we wish to draw—Figure 17.10. The next step is to draw the actual sprite image onto the backbuffer.

Figure 17.10: The backbuffer after the mask bitmap is drawn to it. It marks the pixels black where we will copy the image bitmap to.

Page 562: C++ Módulo I e II

241

However, when we draw the sprite image, we need to use the raster operation SRCPAINT, which specifies that the destination and source pixels be combined like so: F = D | S. The OR operation is a key point because, where the destination (backbuffer) is black, it copies all the source pixels: F = Black | S = S. This is exactly what we want, because we marked the pixels black (when we drew the mask) which correspond to the spite pixels we want to draw. Moreover, where the source (sprite) is black, it leaves the destination backbuffer color unchanged because, F = D | Black = D. Again, this is exactly what we want. We do not want to draw the black background part of the sprite bitmap. The end result is that only the sprite pixels we want to draw are drawn to the backbuffer—Figure 17.8.

17.4.2 Implementation

To facilitate the drawing of sprites, we will create a Sprite class, which will handle all the drawing details discussed in the previous section. In addition, it will handle the allocation and deallocation of the resources associated with sprites. Here is the class definition: // Sprite.h // By Frank Luna // August 24, 2004. #ifndef SPRITE_H #define SPRITE_H #include <windows.h> #include "Circle.h" #include "Vec2.h" class Sprite public: Sprite(HINSTANCE hAppInst, int imageID, int maskID, const Circle& bc, const Vec2& p0, const Vec2& v0); ~Sprite(); int width(); int height(); void update(float dt); void draw(HDC hBackBufferDC, HDC hSpriteDC); public: // Keep these public because they need to be // modified externally frequently. Circle mBoundingCircle; Vec2 mPosition; Vec2 mVelocity; private: // Make copy constructor and assignment operator private // so client cannot copy Sprites. We do this because // this class is not designed to be copied because it

Page 563: C++ Módulo I e II

242

// is not efficient--copying bitmaps is slow (lots of memory). Sprite(const Sprite& rhs); Sprite& operator=(const Sprite& rhs); protected: HINSTANCE mhAppInst; HBITMAP mhImage; HBITMAP mhMask; BITMAP mImageBM; BITMAP mMaskBM; ; #endif // SPRITE_H We will begin by analyzing the data members first.

1. mBoundingCircle: A circle that approximately describes the area of the sprite. We will discuss and implement the Circle class in the next chapter. It is not required in this chapter yet.

2. mPosition: The center position of the sprite rectangle.

3. mVelocity: The velocity of the sprite—the direction and speed the sprite is moving in.

4. mhAppInst: A handle to the application instance.

5. mhImage: A handle to the sprite image bitmap.

6. mhMask: A handle to the sprite mask bitmap.

7. mImageBM: A structure containing the sprite image bitmap info.

8. mMaskBM: A structure containing the sprite mask bitmap info.

Now we describe the methods.

1. Sprite(HINSTANCE hAppInst, int imageID, int maskID, const Circle& bc, const Vec2& p0, const Vec2& v0);

The constructor takes several parameters. The first is a handle to the application instance, which is needed for the LoadBitmap function. The second and third parameters are the resource IDs of the image bitmap and mask bitmap, respectively. The fourth parameter specifies the sprite’s bounding circle; the fifth parameter specifies the sprite’s initial position, and the sixth parameter specifies the sprite’s initial velocity. This constructor does four things. First, it initializes some of the sprite’s data members. It also loads the image and mask bitmaps given the resource IDs. Additionally, it obtains the corresponding BITMAP structures for both the image and mask bitmaps. Finally, it verifies that the image bitmap dimensions equal the mask bitmap dimensions. Here is the implementation:

Page 564: C++ Módulo I e II

243

Sprite::Sprite(HINSTANCE hAppInst, int imageID, int maskID, const Circle& bc, const Vec2& p0, const Vec2& v0) mhAppInst = hAppInst; // Load the bitmap resources. mhImage = LoadBitmap(hAppInst, MAKEINTRESOURCE(imageID)); mhMask = LoadBitmap(hAppInst, MAKEINTRESOURCE(maskID)); // Get the BITMAP structure for each of the bitmaps. GetObject(mhImage, sizeof(BITMAP), &mImageBM); GetObject(mhMask, sizeof(BITMAP), &mMaskBM); // Image and Mask should be the same dimensions. assert(mImageBM.bmWidth == mMaskBM.bmWidth); assert(mImageBM.bmHeight == mMaskBM.bmHeight); mBoundingCircle = bc; mPosition = p0; mVelocity = v0;

2. ~Sprite(); The destructor is responsible for deleting any resources we allocated in the constructor. The only resources we allocated were the bitmaps, and so we delete those in the destructor:

Sprite::~Sprite() // Free the resources we created in the constructor. DeleteObject(mhImage); DeleteObject(mhMask);

3. int width(); This method returns the width, in pixels, of the sprite.

int Sprite::width() return mImageBM.bmWidth;

4. int height(); This method returns the height, in pixels, of the sprite.

int Sprite::height() return mImageBM.bmHeight;

Page 565: C++ Módulo I e II

244

5. void update(float dt);

The update function essentially does what we did in the tank sample for the bullet. That is, it displaces the sprite’s position by some small displacement vector, given the sprite velocity (data member) and a small change in time (dt).

void Sprite::update(float dt) // Update the sprites position. mPosition += mVelocity * dt; // Update bounding circle, too. That is, the bounding // circle moves with the sprite. mBoundingCircle.c = mPosition;

6. void draw(HDC hBackBufferDC, HDC hSpriteDC);

This function draws the sprite as discussed in Section 17.4.1. This method takes two parameters. The first is a handle to the backbuffer device context, which we will need to render onto the backbuffer. The second is a handle to a second system memory device context with which we will associate our sprites. Remember, everything in GDI must be done through a device context. In order to draw a sprite bitmap onto the backbuffer, we need a device context associated with the sprite, as well as the backbuffer.

void Sprite::draw(HDC hBackBufferDC, HDC hSpriteDC) // The position BitBlt wants is not the sprite's center // position; rather, it wants the upper-left position, // so compute that. int w = width(); int h = height(); // Upper-left corner. int x = (int)mPosition.x - (w / 2); int y = (int)mPosition.y - (h / 2); // Note: For this masking technique to work, it is assumed // the backbuffer bitmap has been cleared to some // non-zero value. // Select the mask bitmap. HGDIOBJ oldObj = SelectObject(hSpriteDC, mhMask); // Draw the mask to the backbuffer with SRCAND. This // only draws the black pixels in the mask to the backbuffer, // thereby marking the pixels we want to draw the sprite // image onto. BitBlt(hBackBufferDC, x, y, w, h, hSpriteDC, 0, 0, SRCAND); // Now select the image bitmap. SelectObject(hSpriteDC, mhImage);

Page 566: C++ Módulo I e II

245

// Draw the image to the backbuffer with SRCPAINT. This // will only draw the image onto the pixels that where previously // marked black by the mask. BitBlt(hBackBufferDC, x, y, w, h, hSpriteDC, 0, 0, SRCPAINT); // Restore the original bitmap object. SelectObject(hSpriteDC, oldObj);

17.5 Ship Animation Sample

We will now describe how to make the Ship sample, which is illustrated by the screenshot shown in Figure 17.8. In this program, the user can control only the F-15 jet. The other jets remain motionless (animating the other jets will be left as an exercise for you to complete). The user uses the ‘A’ and ‘D’ keys to move horizontally, and the ‘W’ and ‘S’ keys to move vertically. The spacebar key fires a missile. In essence, this program is much like the tank program, but with better graphics.

17.5.1 Art Resources

We require the following art assets:

• A background image with mask—Figure 17.11a. • An F-15 Jet image with mask—Figure 17.11b. • An F-18 Jet image with mask—Figure 17.11c. • An F-117 Jet image with mask—Figure 17.11d. • A missile image with mask—Figure 17.11e.

Observe that for the background image, the mask is all black, indicating that we want to draw the entire background image. Although this is somewhat wasteful since we do not need to mask the background, it allows us to work generally with the Sprite class. We will use the Sprite class to draw the background as well. We use the following resource IDs for these art assets: Background image bitmap: IDB_BACKGROUND Background mask bitmap: IDB_BACKGROUNDMASK F-15 image bitmap: IDB_F15 F-15 mask bitmap: IDB_F15MASK F-18 image bitmap: IDB_F18 F-18 mask bitmap: IDB_F18MASK F-117 image bitmap: IDB_F117 F-117 mask bitmap: IDB_F117MASK Missile image bitmap: IDB_BULLET Missile mask bitmap: IDB_BULLETMASK

Page 567: C++ Módulo I e II

246

Figure 17.11: Art assets used in the Ship sample.

17.5.2 Program Code

There is not much to explain for the Ship sample. The only real difference between this sample and the Tank sample is that we are using sprites now instead of GDI shape functions. Other than that, it follows the same format. Moreover, the program is heavily commented. Therefore, we will simply present the main application code. Program 17.2: The Ship Sample Main Application Code. You still need the other files like Sprite.h/.cpp, BackBuffer.h/.cpp, Circle.h/.cpp and Vec2.h/.cpp to compile. To obtain these files download the entire project off of the Game Institute C++ Course Website.

// ship.cpp // By Frank Luna // August 24, 2004.

Page 568: C++ Módulo I e II

247

//========================================================= // Includes //========================================================= #include <string> #include "resource.h" #include "Sprite.h" #include "BackBuffer.h" #include <list> using namespace std; //========================================================= // Globals //========================================================= HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; HMENU ghMainMenu = 0; HDC ghSpriteDC = 0; BackBuffer* gBackBuffer = 0; Sprite* gBackground = 0; Sprite* gF15 = 0; Sprite* gF18 = 0; Sprite* gF117 = 0; Sprite* gBullet = 0; list<Vec2> gBulletPos; RECT gMapRect = 0, 0, 800, 600; string gWndCaption = "Game Institute Ship Sample"; // Client dimensions exactly equal dimensions of // background bitmap. This is found by inspecting // the bitmap in an image editor, for example. const int gClientWidth = 800; const int gClientHeight = 600; // Center point of client rectangle. const POINT gClientCenter = gClientWidth / 2, gClientHeight / 2 ; // Pad window dimensions so that there is room for window // borders, caption bar, and menu. const int gWindowWidth = gClientWidth + 6; const int gWindowHeight = gClientHeight + 52; //========================================================= // Function Prototypes //========================================================= bool InitMainWindow(); int Run(); void DrawFramesPerSecond(float deltaTime); LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

Page 569: C++ Módulo I e II

248

//========================================================= // Name: WinMain // Desc: Program execution starts here. //========================================================= int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) ghAppInst = hInstance; // Create the main window. if( !InitMainWindow() ) MessageBox(0, "Window Creation Failed.", "Error", MB_OK); return 0; // Enter the message loop. return Run(); //========================================================= // Name: InitMainWindow // Desc: Creates the main window upon which we will // draw the game graphics onto. //========================================================= bool InitMainWindow() WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(NULL_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; RegisterClass( &wc ); // WS_OVERLAPPED | WS_SYSMENU: Window cannot be resized // and does not have a min/max button. ghMainMenu = LoadMenu(ghAppInst, MAKEINTRESOURCE(IDR_MENU)); ghMainWnd = ::CreateWindow("MyWndClassName", gWndCaption.c_str(), WS_OVERLAPPED | WS_SYSMENU, 200, 200, gWindowWidth, gWindowHeight, 0, ghMainMenu, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return 0; ShowWindow(ghMainWnd, SW_NORMAL);

Page 570: C++ Módulo I e II

249

UpdateWindow(ghMainWnd); return true; //========================================================= // Name: Run // Desc: Encapsulates the message loop. //========================================================= int Run() MSG msg; ZeroMemory(&msg, sizeof(MSG)); // Get the current time. float lastTime = (float)timeGetTime(); while(msg.message != WM_QUIT) // IF there is a Windows message then process it. if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) TranslateMessage(&msg); DispatchMessage(&msg); // ELSE, do game stuff. else // Get the time now. float currTime = (float)timeGetTime(); // Compute the differences in time from the last // time we checked. Since the last time we checked // was the previous loop iteration, this difference // gives us the time between loop iterations... // or, I.e., the time between frames. float deltaTime = (currTime - lastTime)*0.001f; // Clamp speed to 100 units per second. if(gF15->mVelocity.length() > 100.0f) gF15->mVelocity.normalize() *= 100.0f; // Update ship. gF15->update(deltaTime); // Make sure F15 stays in the map boundary. if( gF15->mPosition.x < gMapRect.left ) gF15->mPosition.x = (float)gMapRect.left; gF15->mVelocity.x = 0.0f; gF15->mVelocity.y = 0.0f; if( gF15->mPosition.x > gMapRect.right ) gF15->mPosition.x = (float)gMapRect.right; gF15->mVelocity.x = 0.0f; gF15->mVelocity.y = 0.0f;

Page 571: C++ Módulo I e II

250

if( gF15->mPosition.y < gMapRect.top ) gF15->mPosition.y = (float)gMapRect.top; gF15->mVelocity.x = 0.0f; gF15->mVelocity.y = 0.0f; if( gF15->mPosition.y > gMapRect.bottom ) gF15->mPosition.y = (float)gMapRect.bottom; gF15->mVelocity.x = 0.0f; gF15->mVelocity.y = 0.0f; // Draw objects. gBackground->draw(gBackBuffer->getDC(), ghSpriteDC); gF15->draw(gBackBuffer->getDC(), ghSpriteDC); gF18->draw(gBackBuffer->getDC(), ghSpriteDC); gF117->draw(gBackBuffer->getDC(), ghSpriteDC); list<Vec2>::iterator i = gBulletPos.begin(); while( i != gBulletPos.end() ) Vec2 bulletVelocity(0.0f, -300.0f); // Update the position. *i += bulletVelocity * deltaTime; POINT p = *i; // Only draw bullet if it is still inside // the map boundaries. // Otherwise, delete it. if( !PtInRect(&gMapRect, p) ) i = gBulletPos.erase(i); else gBullet->mPosition = *i; gBullet->draw( gBackBuffer->getDC(), ghSpriteDC); ++i; // Next in list. DrawFramesPerSecond(deltaTime); // Now present the backbuffer contents to the main // window client area. gBackBuffer->present(); // We are at the end of the loop iteration, so // prepare for the next loop iteration by making // the "current time" the "last time." lastTime = currTime; // Free 20 miliseconds to Windows so we don't hog

Page 572: C++ Módulo I e II

251

// the system resources. Sleep(20); // Return exit code back to operating system. return (int)msg.wParam; //========================================================= // Name: DrawFramesPerSecond // Desc: This function is called every frame and updates // the frame per second display in the main window // caption. //========================================================= void DrawFramesPerSecond(float deltaTime) // Make static so the variables persist even after // the function returns. static int frameCnt = 0; static float timeElapsed = 0.0f; static char buffer[256]; // Function called implies a new frame, so increment // the frame count. ++frameCnt; // Also increment how much time has passed since the // last frame. timeElapsed += deltaTime; // Has one second passed? if( timeElapsed >= 1.0f ) // Yes, so compute the frames per second. // FPS = frameCnt / timeElapsed, but since we // compute only when timeElapsed = 1.0, we can // reduce to: // FPS = frameCnt / 1.0 = frameCnt. sprintf(buffer, "--Frames Per Second = %d", frameCnt); // Add the frames per second string to the main // window caption--that is, we'll display the frames // per second in the window's caption bar. string newCaption = gWndCaption + buffer; // Now set the new caption to the main window. SetWindowText(ghMainWnd, newCaption.c_str()); // Reset the counters to prepare for the next time // we compute the frames per second. frameCnt = 0; timeElapsed = 0.0f;

Page 573: C++ Módulo I e II

252

//========================================================= // Name: WndProc // Desc: The main window procedure. //========================================================= LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) Circle bc; // Not needed in this demo, leave default. Vec2 p0(gClientCenter); Vec2 v0(0.0f, 0.0f); switch( msg ) // Create application resources. case WM_CREATE: // Create the sprites gBackground = new Sprite(ghAppInst, IDB_BACKGROUND, IDB_BACKGROUNDMASK, bc, p0, v0); gF15 = new Sprite(ghAppInst, IDB_F15, IDB_F15MASK, bc, p0, v0); p0.x = 100; p0.y = 100; gF18 = new Sprite(ghAppInst, IDB_F18, IDB_F18MASK, bc, p0, v0); p0.x = 600; p0.y = 100; gF117 = new Sprite(ghAppInst, IDB_F117, IDB_F117MASK, bc, p0, v0); p0.x = 0.0f; p0.y = 0.0f; gBullet = new Sprite(ghAppInst, IDB_BULLET, IDB_BULLETMASK, bc, p0, v0); // Create system memory DCs ghSpriteDC = CreateCompatibleDC(0); // Create the backbuffer. gBackBuffer = new BackBuffer( hWnd, gClientWidth, gClientHeight); return 0; case WM_COMMAND: switch(LOWORD(wParam)) // Destroy the window when the user selects the 'exit' // menu item. case ID_FILE_EXIT:

Page 574: C++ Módulo I e II

253

DestroyWindow(ghMainWnd); break; return 0; case WM_KEYDOWN: switch(wParam) // Accelerate left. case 'A': gF15->mVelocity.x -= 5.0f; break; // Accelerate right. case 'D': gF15->mVelocity.x += 5.0f; break; // Accelerate up (remember +y goes down and -y goes up) case 'W': gF15->mVelocity.y -= 5.0f; break; // Accelerate down. case 'S': gF15->mVelocity.y += 5.0f; break; case VK_SPACE: // Add a bullet to the bullet list. gBulletPos.push_back(gF15->mPosition); break; return 0; // Destroy application resources. case WM_DESTROY: delete gBackground; delete gF15; delete gF18; delete gF117; delete gBullet; delete gBackBuffer; DeleteDC(ghSpriteDC); PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the // default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam);

Page 575: C++ Módulo I e II

254

17.6 Summary

1. The Windows media library (#include <mmsystem.h> and link winmm.lib) provides a timer function called timeGetTime. This function returns the number of milliseconds that has elapsed since Windows started. In order to use this function, you will need to link winmm.lib, which is not linked by default. We can convert a time mst ms in milliseconds to a time st s in seconds by multiplying mst ms by the ratio 1 s / 1000 ms. The number of milliseconds that has elapsed since Windows started is not particularly interesting, but what is interesting is that if we call this function at time 0t , and then again at a later time 1t , we can compute the elapsed time

t∆ (change in time) as follows: 01 ttt −=∆ . This is used to determine how much time has elapsed per frame.

2. A frame is one complete image in an animation sequence. For example, a game running at sixty

frames per second is displaying sixty slightly different images per second. The rapid successive display of slightly different images over time gives the illusion of a smooth and continuous animation.

3. We create a new message loop, which is more game-friendly, called the game loop. This loop

focuses less on Windows messages and more on processing game tasks. It can be summarized like so: If there is a Windows message that needs to be processed then process it, otherwise, execute our game code, which will typically be updating and drawing a frame. In order to implement this behavior we need a function to determine if there is a message that needs to be processed. The Win32 API provides such a function called PeekMessage, which returns true if there is a message that needs to be processed. Otherwise, it returns false.

4. Flickering occurs when you draw one thing and then draw something over it quickly afterwards.

The process of drawing over a previously drawn pixel is called overdraw. The eye, which is quite sensitive to change and movement, picks up on overdraw and a flickering phenomenon is seen. To solve the flickering problem we use a double buffering technique. The idea of double buffering is quite simple: instead of drawing directly to the client area of the window, we instead draw to an off screen surface called the backbuffer, which will be a blank bitmap that will represent our virtual “canvas.” Since the backbuffer is not directly visible, overdraw is not a problem. That is, we will not see the overdraw as it happens because we cannot see the backbuffer—we are no longer watching the image get “painted.” Once we have drawn all the graphics to the backbuffer, we will have a completed 2D image on the backbuffer. We can then copy the backbuffer image contents to the window client area all at once with the BitBlt function. Since the backbuffer contains the completed frame, the only thing we will need to draw to the window client area is the contents of the backbuffer. Therefore, we will have zero overdraw on the client area window, and thus avoid flicker. To keep consistent with Direct3D vocabulary, we will refer to the process of copying the backbuffer image to the window client area as presenting. For example, saying, “we present the backbuffer,” means we copy the backbuffer to the window client area.

Page 576: C++ Módulo I e II

255

5. Given a velocity vr , the change in position of a point pr∆ (displacement) over t∆ seconds is tvp ∆⋅=∆

rr . Recall that t∆ is the time elapsed between frames, thus this formula tells us how much to displace a point per frame given the velocity vr . To displace a point pr , we simply add the displacement vector to it: ppp rrr

∆+=′ .

6. We can rotate a point (or vector) ( )yx, , about the origin of a coordinate system by an angle θ , to a new point ( )yx ′′, , using the following equations:

)sin()cos()sin()cos(

θθθθ

xyyyxx

+=′−=′

7. We call the images we use to represent game objects “sprites” (e.g., jets, missiles, explosions,

trees, characters). Sprites are not typically rectangular, but bitmaps always are. In order to not draw the part of the bitmap that does not make up the sprite, we use a mask bitmap, which essentially marks the pixels on the sprite image bitmap that should be drawn.

17.7 Exercises

17.7.1 Colors

What is the hex value for the colors: red, green, blue, yellow, cyan, magenta, black, and white? (Hint: Cyan is a mix of green and blue. Yellow is a mix of green and red. Magenta is a mix of red and blue.)

17.7.2 Draw Order

Is the order in which you draw the graphics to the backbuffer important? Explain.

17.7.3 Masking

Explain, in your own words, how a bitmap masking technique prevents the part of the bitmap that does not make up the sprite from being drawn. Also, why do you think they call the mask bitmap a “mask?”

Page 577: C++ Módulo I e II

256

17.7.4 Make Your Own Sprite

Draw your own 2D sprite image and its corresponding mask bitmap in an image editor. Then using the Sprite class, load and draw your sprite to the window.

17.7.5 Bouncing Ball

Create a ball sprite, or use an ellipse, and write a program where the ball bounces around the client area rectangle. When the ball hits the edge “wall,” it should reflect off the wall in the correct direction.

17.7.6 Modify the Ship Program

Modify the Ship sample program and animate the other jets. How you move them is up to you. Perhaps oscillate their movement from side-to-side, or have them follow a circular trajectory—it is open ended.

17.7.7 Pong

Write a pong game as Figure 17.12 shows. If you need to see how this game plays, you can find an online pong game at http://www.80smusiclyrics.com/games.shtml.

Figure 17.12: A Screenshot of a typical pong game.

Page 578: C++ Módulo I e II

257

17.7.8 More on Animation

In this chapter, the animation we did consisted of updating the positions of our objects, such as the tank/ship position, and bullet/missile positions, over time. However, let us recall the traditional animator’s technique, where the artist actually draws each frame of an animation on a different page of paper, and where each frame is slightly different then the previous. By doing this, the artist can create an animation by rapidly flipping the pages (frames) of animation. There is no reason why we cannot do this in code as well. That is, we can load in an array of sprite images that represent the different sprite animation frames, and then for each game loop cycle, we cycle to the next sprite animation frame. For example, Figure 17.13 shows a list of explosion animation frames. When an explosion occurs, we draw the first explosion frame. On the subsequent game loop cycle, we draw the second explosion frame, and on the following game loop cycle, we draw the third explosion frame, and so on, until we have drawn all the explosion frames.

Figure 17.13: Frames of an explosion sprite animation.

Your assignment for this exercise is to find a sprite animation sequence on the Internet (or make your own if you are so inclined), and to animate it on the client area of a window. That is, to cycle through frames of animation every cycle of the game loop until the animation is over. Implementation Hints. For each sprite animation frame, you will need a sprite. So if your entire sprite animation has 16 frames, you will represent it as:

Page 579: C++ Módulo I e II

258

Sprite* explosionAnim[16]; // Load all 16 animation frames into the elements // of explosionAnim // […] int currAnim = 0; // Start at first animation frame. // In the game loop: // Draw the current sprite animation frame. explosionAnim[currAnim]->draw(…); // Draw the next animation frame next cycle currAnim++;

Page 580: C++ Módulo I e II

259

Chapter 18

The Air Hockey Game

Page 581: C++ Módulo I e II

260

Introduction

After much hard work, we have finally reached the culmination of this course—the development of a simple but functional game. Figure 18.1 on the next page shows our goal: a 2D Air Hockey game. In addition to solidifying many of the course topics with this program, we also take this opportunity to introduce a three-stage software development guide:

1. Analysis: In the analysis stage, we spend some time studying the software requirements in detail. In particular, we identify which problems will need to be solved in order to implement the software with said requirements; in other words, a feature list. To aid in the identification of all problems that must be solved, a clear picture of how the program ought to run should be realized. For games, concept art and storyboards work well.

2. Design: In the design stage, we do two things. First, we devise algorithmic solutions to the

problems identified in the Analysis stage. Second, we make decisions on how the actual program should be constructed; that is, what classes of objects do we need to model the simulation, how are the objects related, how will they interact with each other, and what kind of data structures do we need? Thus, we say that the design stage consists of an algorithmic part—that is, devising algorithmic solutions to specific problems—and a software engineering part—that is, determining how the actual program should be organized.

3. Implementation: The final stage is implementing our design in a programming language in order

to actually create the software we have designed. If the design step is done correctly, the transition from design to implementation is rather smooth. In fact, the design stage is where the real work is done; that is where all the problems are solved. In theory, any competent programmer should be able to take a design specification and implement the program. Of course, reality tells a different story: unanticipated problems surface, features need to be extended and/or added, and miscalculations of software design decisions become detrimental to the implementation process. On the bright side, at least you know that you are in good company, as this happens even to the best and the brightest.

Chapter Objectives

• Become familiar with the three-stage software development process of analysis, design, and implementation.

• Practice the three-stage software development process by analyzing an Air Hockey game,

designing an Air Hockey game, and implementing an Air Hockey game.

Page 582: C++ Módulo I e II

261

18.1 Analysis

As briefly noted in the introduction, in the Analysis stage we identify the problems that will need to be solved in order to implement the software. A clear picture of what we want the software to look like helps the Analysis stage of software development. Let us look at a picture of the Air Hockey game we want to develop.

Figure18.1: A Screenshot of the Air Hockey Game.

We proceed as follows:

1. We first identify the objects in our game. 2. We then identify how the game requires these objects to behave. 3. Finally, we identify the specific problems that will need to be solved in order to implement the

stated game requirements.

Page 583: C++ Módulo I e II

262

18.1.1 Object Identification

We identify eight game objects in our Air Hockey simulation.

• The game board, which defines the boundaries for the game action. All game play must occur inside the game board boundaries.

• The blue paddle, which is the instrument the player controls in order to hit the puck.

• The red paddle, which is the instrument the computer controls in order to hit the puck.

• The puck, which is the object each player tries to hit into the other player’s goal box.

• The blue paddle’s “side”. That is, the rectangular boundaries that the blue paddle must stay in.

In particular, we stipulate that the blue paddle must stay in the game board boundaries and, additionally, cannot cross the center blue line.

• The red paddle’s “side”. That is, the rectangular boundaries that the red paddle must stay in. In

particular, we stipulate that the red paddle must stay in the game board boundaries and, additionally, cannot cross the center blue line.

• The blue player’s goal box.

• The red player’s goal box.

18.1.2 Game Behavior and Corresponding Problems to Solve

We now identify seven game features and the corresponding problem for each that will need to be solved in order to implement the feature. Note that we are using the word “feature” in the broadest sense of the word; that is, we are not referring to special features, but rather general game behavioral features; in other words, how the game ought to behave.

• Blue Paddle Motion. The player (human) controls the blue paddle and we specify that it is to be moved with the mouse. Consequently, it should move in the direction and speed that the mouse cursor position moves.

Problem: We need to be able to determine the velocity of the mouse during any given frame of animation.

• Red Paddle Motion. The computer (artificial intelligence) controls the red paddle.

Problem: We need to be able to create fairly convincing artificial intelligence for the red paddle so that it behaves in a manner that roughly simulates a human player.

Page 584: C++ Módulo I e II

263

• Puck / Paddle Collision. When a paddle, be it blue or red, hits the puck, a physically realistic response should follow.

Problem: We need to determine how two circular masses (paddle and puck) physically respond when they collide.

• Puck / Wall Collision. When the puck collides with a wall on the game board then it should

respond in a physically realistic way. We separate this collision problem from the puck paddle collision because the objects colliding are different, and hence respond differently.

Problem: We need to determine how an immovable line (2D wall) and a circle (the puck) physically respond when they collide.

• Paddle / Wall Collision. A paddle is not allowed to move outside of its “side.” We defined

what is meant by a player “side” in Section 18.1.1. Collision between a paddle and a wall is different than collision between a puck and wall because a player (human or computer) holds the paddle down. This implies that the player can apply external forces to the paddle to directly influence its position. Thus, when a paddle collides with any game board wall, we do not do a physical response. Instead, we simply say that the paddle can move anywhere on its “side” that the player specifies as long as it remains on its side. So if the player tries to move out-of-bounds then we only need to force the paddle inbounds.

Problem: If a player (human or computer) attempts to move the paddle out-of-bounds we must override that action and force the paddle to stay inbounds.

• Pause / Unpause. The game should be able to be paused and unpaused, which stops and

resumes play, respectively.

Problem: When the player pauses the game, we must be able to stop all game activity. When the player unpauses the game, we must be able to resume all game activity from the point preceding the pause.

• Detecting a Score. A player scores a point when the puck enters the opponent’s goal.

Problem: We must be able to detect when a point (center of the puck) intersects a goal box (modeled as a rectangle).

Page 585: C++ Módulo I e II

264

18.2 Design

In the design stage, we do two things. First, we devise algorithmic solutions to the problems identified in the Analysis stage. And second, we make decisions on how the actual program should be constructed. Initially we will discuss how we will solve the problems specified in Section 18.1.2, and afterwards we will discuss how we might design our Air Hockey game from a software engineering perspective.

18.2.1 Algorithms

18.2.1.1 Mouse Velocity

Recall that velocity is a vector quantity representing speed and direction, and speed is distance per unit of time. The player controls the blue paddle with the mouse, so the velocity of the mouse directly determines the velocity of the paddle at any instant. (For us, an instant means a frame since a frame is the smallest increment of time we have.) To find the instantaneous velocity, we need to find the direction and distance the mouse has moved over the course of t∆ seconds (time between the previous frame and current frame). To do this, we save the mouse position of the previous frame 1−ir

r . We then obtain the mouse position for the current frame ir

r . Thus the mouse displaced rrr iirrr

∆=− −1 over the time period of one frame t∆ (Figure 18.2). The mouse velocity for a given frame is:

tr

trr

v ii

∆∆

=∆−

= −rrr

r 1

Figure18.2: Computing the mouse displacement between frames.

Page 586: C++ Módulo I e II

265

18.2.1.2 Red Paddle Artificial Intelligence

We need to be able to create fairly convincing AI (artificial intelligence) for the red paddle so that it behaves in a manner that roughly simulates a human player. What we suggest is that when the puck enters the red player’s side, the red paddle (computer controlled) will simply move directly towards the puck to hit it. After the red paddle hits the puck we will halt the red paddle for a tenth of a second for some “recovery time.” This “recovery time” is used to model the fact that when a human air hockey player hits the puck, a human experiences a slight shock delay after impact. When the puck leaves the red player’s side then the AI will move the red paddle to a “defense” position near its goal so that it is in a “ready” position. We note that the AI we create here is not very “good” AI because the “move directly toward the puck” is not the smartest strategy and often causes the red paddle to hit the puck into its own goal. However, it is satisfactory for our purposes. Assuming the puck lies on the red player’s side, in order to move the red paddle in the direction of the puck we need to compute the direction from the red paddle to the puck. This is done with a simple point subtraction followed by a vector normalization, as shown in Figure 18.3.

Figure 18.3: Here we compute the normal vector that is aimed from the red paddle to the puck.

Here, dr

gives the direction, which is one part of velocity, but it does not give the magnitude (meaning the speed) of the red paddle since it is a unit vector. What we will do is define a speed constant for the red paddle. That is, the red paddle will always move at this defined speed (RED_SPEED). In order to

Page 587: C++ Módulo I e II

266

give dr

some speed so that it transforms from a purely directional vector to a velocity vector vr (specifically the red paddle velocity) we simply scale it by RED_SPEED:

=vr RED_SPEED * dr

This velocity vr aims in the direction of the puck and therefore will move the red paddle towards the puck at the speed RED_SPEED. Observe that because the puck is moving each frame, we must recalculate vr every frame to get the new direction. If the puck lies in the blue player’s side, we want to move the red paddle to a “defense” position in front of its goal. The logic is the same as moving the red paddle towards the puck, but instead we now want to move the red paddle towards the defense position. Once the red paddle reaches the defense position it stops. So again we need to find the velocity that aims the red paddle towards the defense position, which incidentally we define to be (700, 200) in Windows coordinates and relative to the upper-left corner of the client area rectangle. Let 200,700=xr and let 1cr be the red paddle position then the direction from the red paddle to the defense position is:

1

1ˆcxcxd rr

rrr

−−

=

To transform the direction to a velocity vector, we multiply by the speed:

=vr RED_SPEED * dr

The last problem regarding the AI is to work out is how to handle the “recovery time.” The solution is quite simple: when the red paddle hits the puck, we will set a variable like so:

recoveryTime = 0.1 We will then put a conditional statement in the program:

If( recoveryTime <= 0.0 ) UpdateRedPaddle Else RedPaddleCannotMove

In this way, the red paddle cannot move unless there is zero or less recovery time. In addition, for each frame we will decrement recoveryTime by the time elapsed t∆ . Thus, after 0.1 seconds has elapsed the recovery time will again be 0.0, which means that we can update the red paddle again. This is the exact functionality we seek. We essentially disable the red paddle for 0.1 seconds after it hits the puck in order for the paddle to “recover.”

Page 588: C++ Módulo I e II

267

18.2.1.3 Puck Paddle Collision

When a paddle, be it blue or red, hits the puck, a physically realistic response should follow. Thus, we must be able to determine how two circular masses (paddle and puck) physically respond when they collide. The physics of this collision requires a lengthy discussion, so we have given it its own separate section in Section 18.4. What follows now is an informal conceptual explanation. A more rigorous explanation is given in Section 18.4. Before we can even discuss anything about collisions, we need some criterion to determine if the paddle and puck collided at all. From Figure 18.4 it follows that if the length from 1cr to 2cr is less than or equal to the sum of the radii then the circles must intersect or touch, which implies a collision. This will be our criterion for testing whether a collision took place. In pseudocode that is, Collision Test

if 2112 RRcc +≤−rr

Intersect = true; Else Intersect = false;

Figure 18.4: (a) The circles are not touching or interpenetrating. (b) The circles are tangents (i.e., touching). (c) The circles are interpenetrating.

Consider Figure 18.5, where two circular masses 1m and 2m collide. Intuitively, each object ought to

“feel” an equal and opposite impulse in the direction nr , which is perpendicular to the collision point.

Page 589: C++ Módulo I e II

268

This vector nr is called the collision normal. Incidentally, nr can be computed by normalizing the vector that goes from 1cr to 2cr ; that is,

12

12ˆccccn rr

rrr

−−

=

Figure 18.5: A collision. The impulse vectors lie on the line of the collision normal.

Here we use the word “impulse” very loosely. We give its formal physical definition in Section 18.4. For now, think of impulse as a vector quantity that changes the velocity (direction or magnitude or both) of an object in the direction of the impulse. In addition to velocities, the mass of the objects plays a role in the response. For example, if 2m is more massive than 1m we would expect the impulse of the collision to not effect 2m as drastically as 1m . To compensate for the mass, we can divide the impulse

Page 590: C++ Módulo I e II

269

vector by the mass of the object. That way, if the object is massive then the impulse vector will be less influential, and if the object is not so massive then the impulse vector will be more influential. Based on

this discussion, if nj r and nj r− are the impulses (remember the impulse vectors are in the direction nr

or opposite of nr ) and if iv1r and iv2

r are the initial velocities of 1m and 2m , respectively, before the

collision, then the velocities after the collision should be computed as follows:

(1) 1

11

ˆ

mnjvv if

r

+=

(2) 2

22

ˆ

mnjvv if

r

−=

The reason equation (2) has a negative impulse is because we reasoned each object ought to “feel” an equal and opposite impulse in the direction nr , the key word being “opposite”. Also note that j is actually negative, and we compensate for that by negating the signs; that is why the impulse vectors in Figure 18.5 may seem backward. But just understand that the negative sign in the j will reverse them. Let us go over equations (1) and (2) briefly. We said that the impulse would change the direction of the velocity of an object in the direction of the impulse, which summing the initial velocity with the impulse vector does (e.g., njv i

ˆ2

r+ ). We also said that the mass of the object should determine how much

influence the impulse vector has to change the initial velocity. We include the mass factor by dividing the impulse vector by the mass. Thus, if the mass is large then the effect of the impulse vector diminishes and if the mass is small then the effect of the impulse vector increases. Dividing the impulse vector by the mass gives us equations (1) and (2). We still do not know what j is, and therefore we do not know the impulse vector (we just know its direction nr and that it has some magnitude j). It turns out that the magnitude of the impulse j is determined by the relative velocity of 1m and 2m , nr , and the masses, which is not surprising. By relative velocity we mean the velocity of mass 1m relative to mass 2m ; in other words, how are the velocities moving with respect to each other? We can compute the velocity of 1m relative to 2m like so:

2112 vvv rrr−= , where 12vr means the velocity of mass 1m relative to the velocity of mass 2m . (Note then

that 1221 vvv rrr+= .) For example, consider the two cars in Figure (18.6a).

Page 591: C++ Módulo I e II

270

Figure 18.6: Relative velocities.

Car 1 is moving faster than Car 2 and as such, Car 1 will eventually collide with Car 2. However, Car 1 is only going 5 miles per hour faster than Car 2. When Car 1 hits Car 2, will Car 2 feel an 80 miles per hour “impact?” Common sense tells us this would not be the case. Relative to Car 2, Car 1 is only going 5 miles per hour and so Car 2 will only feel a 5 miles per hour “impact” ( 575802112 =−=−= vvv rrr miles per hour). Consider Figure (18.6b). Here, Car 2 is stopped, so Car 1 is going 80 miles per hour relative to Car 2. Consequently, Car 2 will this time feel an 80 miles per hour “impact” ( 800802112 =−=−= vvv rrr miles per hour). The point of this discussion is that when talking about two objects colliding, it only makes sense to talk in relative terms. We will derive j in Section 18.4, but for now we will accept without proof that,

(3) ( )

⎟⎟⎠

⎞⎜⎜⎝

⎛+

⋅−=

21

,12

11

ˆ2

mm

nvj i

rr

With (3) we can rewrite (1) and (2) as,

Page 592: C++ Módulo I e II

271

(4) ( )

⎟⎟⎠

⎞⎜⎜⎝

⎛+

⋅−+=

2

1

1

1

,1211

ˆˆ2

mm

mm

nnvvv i

if

rrr

(5) ( )

⎟⎟⎠

⎞⎜⎜⎝

⎛+

⋅−−=

2

2

1

2

,1222

ˆˆ2

mm

mm

nnvvv i

if

rrr

Observe the negative sign that comes out of j, which was mentioned before. We now know everything we need to solve for fv1 and fv2 ; the initial velocities are given, the masses

are given, the relative initial velocity is known, and nr can be found via geometric means. Equations (4) and (5) are pretty general, but we can simplify things by looking at our specific paddle-puck collision case. Suppose 1m is the paddle and 2m is the puck. Because the player holds down the paddle (either human or AI), we say that the paddle is not influenced by the collision at all (a player can easily stand his/her ground against a small air hockey puck). Physically, we can view this as the paddle

1m having a mass of infinity, which makes equation (4) reduce to: (6) iif vvv 111 0 =+= , This says that the collision does not change the velocity of the paddle. Similarly, an infinitely massive paddle causes equation (5) to reduce to: Collision Response of the Puck

(7) ( )nnvvv iifˆˆ2 ,1222rrr

⋅+=

Equation (7) is really the only equation we will need in our implementation. This equation will be used to compute the new velocity of the puck after it collides with a paddle. Incidentally, if 0ˆ

,12 <⋅ nv irr , it means that the objects 1m and 2m are actually already moving away from

each other, which means no collision response is necessary.

Page 593: C++ Módulo I e II

272

18.2.1.4 Puck Wall Collision

We need to determine how an immovable line (2D wall) and a circle (the puck) physically respond when they collide. Simple real world observation tells us that if an air hockey puck is traveling with a velocity Ir

(we call Ir

the incident vector) and hits a wall, then it will reflect and have a new velocity Rr

—see Figure 18.7 (we approximate a zero speed loss from the bounce which is only approximately true). Observe that the reflection vector R

r has the same magnitude as the incident vector I

r; the only

difference is the direction is reflected. So our task at hand is, given the incoming velocity Ir

, to compute the reflected velocity R

r. To do this, let us again examine Figure 18.7.

Figure 18.7: When a puck hits an edge it reflects. We assume the puck does not lose speed during the collision.

Figure 18.7 shows four cases that correspond to the four edges off of which the puck can bounce. When the puck bounces off the left or right edge, we notice that the difference between I

r and R

r is simply that

that the vector x-components are opposite. Therefore, to reflect off the left or right edge we just negate the x-component of I

r to get R

r. Similarly, when the puck bounces off the top or bottom edge, we

notice that the difference between Ir

and Rr

is simply that the vector y-components are opposite. Therefore, to reflect off the top or bottom edge we just negate the y-component of I

r to get R

r.

If (x, y) is the puck center point and r its radius, then we have the following puck/wall collision algorithm: // Reflect velocity off left edge (if we hit the left edge). if( x - r < left ) mPuck->mVelocity.x *= -1.0f;

Page 594: C++ Módulo I e II

273

// Reflect velocity off right edge (if we hit the right edge). if(x + r > right ) mPuck->mVelocity.x *= -1.0f; // Reflect velocity off top edge (if we hit the top edge). if(y - r < top ) mPuck->mVelocity.y *= -1.0f; // Reflect velocity off bottom edge (if we hit the bottom edge). if(y + r > bottom ) mPuck->mVelocity.y *= -1.0f;

18.2.1.5 Paddle Wall Collision

If a player (human or computer) attempts to move the paddle out-of-bounds, we must override that action and force the paddle to stay inbounds. To solve this problem we will define a rectangle that surrounds each player’s side—see Figure 18.8.

Figure 18.8: The rectangles marking the blue “side” and red “side.”

If the paddle goes outside the rectangle, we will simply force it back in. A circle with radius r and center point (x, y) (modeling a paddle) is outside a rectangle R = left, top, right, bottom if the following compound logical statement is true:

leftrx <− || rightrx >+ || topry <− || bottomry >+

Page 595: C++ Módulo I e II

274

Again, we are using Windows coordinates where +y goes “down.” Essentially, the above condition asks if the circle crosses any of the rectangle edges. For example, if leftrx <− is true then it means part (or all) of the circle is outside the left edge—see Figure 18.9.

Figure 18.9: Criteria for determining if a circle crosses an edge boundary.

We can force a circle back inside the rectangle by adjusting the circle’s center point based on the rectangle edge that was crossed. For instance, from Figure 18.9 it follows that the smallest x-coordinate a circle can have while still being inside the rectangle is R.left + r. So if the circle crosses the left edge of the rectangle we modify the circle’s center x-coordinate to be R.left + r: // Did the circle cross the rectangle’s left edge? if(p.x - r < R.left) p.x = R.left + r; // Yes, so modify center The test and modification for the other edges is analogous: // Did the circle cross the rectangle’s right edge? if(p.x + r > R.right) p.x = R.right - r; // Yes, so modify center // Did the circle cross the rectangle’s top edge? if(p.y - r < R.top) p.y = R.top + r; // Yes, so modify center // Did the circle cross the rectangle’s bottom edge? if(p.y + r > R.bottom) p.y = R.bottom - r; // Yes, so modify center

Page 596: C++ Módulo I e II

275

18.2.1.6 Pausing/Unpausing

When the player pauses the game, we must be able to stop all game activity, and when the player unpauses the game we must be able to resume all game activity from the point preceding the pause. This might seem like a difficult problem at first glance, but it turns out to be extremely simple to handle. We keep a Boolean variable paused, which is true if the game is paused and false otherwise. Then, when we go to update the game we can do a quick check:

If( not paused ) UpdateGame Else DoNotUpdateGame

In this way, if the game is paused, then we do not update the game. If the game is not paused, then we do update the game.

18.2.1.7 Detecting a Score

We must be able to detect when a point (center of the puck) intersects a goal box (modeled as a rectangle). This problem is similar to Section 18.2.1.5, but instead of wanting to detect when a circle goes outside a rectangle, we want to detect when a point goes inside a rectangle. (We could detect when a circle—the puck—goes inside a rectangle, but just using the point gives a good approximation for detecting a score.) A point (x, y) (the center of the puck) is inside a rectangle (goal box) R = left, top, right, bottom if and only if the following logical expression is true:

leftx >= && rightx <= && topy >= && bottomy <=

This implies:

• leftx >= : x is to the right of the left edge or lies on the left edge. • rightx <= : x is to the left of the right edge or lies on the right edge. • topy >= : y is below the top edge or lies on the top edge. • bottomy <= : y is above the bottom edge or lies on the bottom edge.

If all four of these conditions are true then we can conclude that the point is inside the rectangle or lies on the edge of a rectangle, which for our purposes we consider to be inside. Again, everything is in Windows coordinates.

Page 597: C++ Módulo I e II

276

18.2.2 Software Design

From the previous chapter, we already know how we are going to handle the graphics of the game. We will use double buffering and timers to provide smooth animation, and we will use sprites to draw the background game board, the paddles, and the puck. For the paddle-puck collision detection, we will need to determine if a paddle hits the puck. Since both the paddle and puck are circular, it is natural to model these objects’ areas with a circle. Thus, we shall create a Circle class as follows: #ifndef CIRCLE_H #define CIRCLE_H #include "Vec2.h" class Circle public: Circle(); Circle(float R, const Vec2& center); bool hits(Circle& A, Vec2& normal); float r; // radius Vec2 c; // center point ; #endif // CIRCLE_H The only method this class contains other than the two constructors is the hits method. This method returns true if the circle object invoking the method hits (or intersects) the circle passed in as a parameter A, otherwise it returns false. If the two circles do hit each other then the method also returns the collision normal via the reference parameter normal. In addition to circles, many of the Air Hockey game elements are rectangular; specifically, the game board, the player side regions, and the goal box. To facilitate representation of these elements we define a Rect class: #ifndef RECT_H #define RECT_H #include "Circle.h" class Rect public: Rect(); Rect(const Vec2& a, const Vec2& b); Rect(float x0, float y0, float x1, float y1); void forceInside(Circle& A);

Page 598: C++ Módulo I e II

277

bool isPtInside(const Vec2& pt); Vec2 minPt; Vec2 maxPt; ; #endif // RECT_H You might suggest that we just use the Win32 RECT structure. This would work. However, we want to add some new methods, so we might as well implement our own. Moreover, we want to use Vec2 objects for points, as their components are floating-point, whereas the coordinates of RECT are integers. With RECT we used coordinates left, top, right, bottom to define a rectangle. But if we think about it, the point (left, top) defines the minimum point of the rectangle and the point (right, bottom) defines the maximum point of the rectangle (note we are using Windows coordinates where +y goes “down”). So instead of using left, top, right, bottom data members, we just use a minPt (for the minimum point) and maxPt (for the maximum point).

1. void forceInside(Circle& A); This method forces the Circle A to be inside the Rect object that invokes this method. This method implements the algorithm discussed in Section 18.2.1.5 for keeping the paddle inside its boundary rectangle.

2. bool isPtInside(const Vec2& pt);

This method returns true if the point (represented as a vector) pt is inside the Rect object that invokes this method, otherwise the method returns false. This method implements the algorithm discussed in Section 18.2.1.7 for detecting when the puck’s center point is inside the goal box rectangle.

In an effort to organize the Air Hockey program code, we have decided to encapsulate the code specific to the Air Hockey game in a separate class called AirHockeyGame. In this way, the Windows specific code such as window creation, the window procedure, and the message loop, will be separated from the game code (i.e., in separate files). This makes the implementation cleaner and more structured. In large projects, organization is significant, if you want to avoid getting lost in complexity. The AirHockeyGame class is defined like so: #ifndef AIR_HOCKEY_GAME_H #define AIR_HOCKEY_GAME_H #include <windows.h> #include "Sprite.h" #include "Rect.h" #include "Circle.h" class AirHockeyGame public:

Page 599: C++ Módulo I e II

278

AirHockeyGame(HINSTANCE hAppInst, HWND hMainWnd, Vec2 wndCenterPt); ~AirHockeyGame(); void pause(); void unpause(); void update(float dt); void draw(HDC hBackBufferDC, HDC hSpriteDC); private: void updateBluePaddle(float dt); void updateRedPaddle(float dt); void updatePuck(float dt); bool paddlePuckCollision(Sprite* paddle); void increaseScore(bool blue); private: HINSTANCE mhAppInst; HWND mhMainWnd; Vec2 mWndCenterPt; int mBlueScore; int mRedScore; bool mPaused; const float MAX_PUCK_SPEED; const float RED_SPEED; float mRedRecoverTime; Sprite* mGameBoard; Sprite* mBluePaddle; Sprite* mRedPaddle; Sprite* mPuck; POINT mLastMousePos; POINT mCurrMousePos; Rect mBlueBounds; Rect mRedBounds; Rect mBoardBounds; Rect mBlueGoal; Rect mRedGoal; ; #endif // AIR_HOCKEY_GAME_H We begin by examining the data members.

1. mhAppInst: A handle to the application instance. 2. mhMainWnd: A handle to the main window. 3. mWndCenterPt: Specifies the center point of the window’s client area. 4. mBlueScore: Integer to keep track of how many points blue has scored.

Page 600: C++ Módulo I e II

279

5. mRedScore: Integer to keep track of how many points red has scored. 6. mPaused: A Boolean value that is true if the game is currently paused, and false otherwise. 7. MAX_PUCK_SPEED: A constant that specifies the maximum speed the puck can move. 8. RED_SPEED: A constant that specifies the speed at which the red (AI) player moves the paddle. 9. mRedRecoverTime: Used to implement the recovery time idea as discussed in Section 18.2.1.2.

That is, this value stores the remaining time before the red paddle can move again. 10. mGameBoard: The sprite that represents the game board graphic. 11. mBluePaddle: The sprite that represents the blue paddle graphic. 12. mRedPaddle: The sprite that represents the red paddle graphic. 13. mPuck: The sprite that represents the puck graphic. 14. mLastMousePos: Used to store the mouse cursor position from the previous frame. 15. mCurrMousePos: Used to store the mouse cursor of the current frame. 16. mBlueBounds: A rectangle describing the blue side. 17. mRedBounds: a rectangle describing the red side. 18. mBoardBounds: A rectangle describing the entire game board; that is, blue’s side and red’s side. 19. mBlueGoal: A rectangle describing the blue side goal box. 20. mRedGoal: A rectangle describing the red side goal box.

We now turn attention to the methods:

1. void pause(); This method is used to pause the game.

2. void unpause(); This method is used to unpause the game.

3. void update(float dt); This method updates all of the game objects; essentially it calls all of the private “helper” update methods.

4. void draw(HDC hBackBufferDC, HDC hSpriteDC); This method draws all the sprites to the backbuffer, and it also draws the current red and blue scores to the backbuffer.

5. void updateBluePaddle(float dt); This method updates the blue paddle’s position after dt seconds have passed. It computes the current mouse velocity as Section 18.2.1.1 describes, and updates the position accordingly. In addition, it also ensures that the blue paddle stays inbounds (18.2.1.5).

Page 601: C++ Módulo I e II

280

6. void updateRedPaddle(float dt); This method updates the red paddle’s position after dt seconds have passed using the AI algorithm as described in Section 18.2.1.1. In addition, it also ensures that the red paddle stays inbounds (18.2.1.5).

7. void updatePuck(float dt); This method updates the puck’s position after dt seconds have passed. It computes the reflected velocity if the puck hits a wall (18.2.1.4). It also detects paddle-puck collisions, and if there is a collision then it computes the new velocity of the puck after the collision (18.2.1.3). In addition to making sure the puck stays within the game board boundaries, it also checks to see if the puck’s center made it into one of the goal boxes (18.2.1.7); i.e., did a player score a goal?

8. bool paddlePuckCollision(Sprite* paddle); This method is called internally by updatePuck. This method actually performs the mathematical calculations of the paddle-puck collision (18.2.1.3). Note that this function takes a pointer to the sprite representing the paddle as a parameter. In this way, the function can be called for either the blue or red paddle.

9. void increaseScore(bool blue); This method simply increases the score of the blue player if blue is true, otherwise it increases the score of the red player. Furthermore, after increasing the score this method resets the puck to the middle of the game board.

18.3 Implementation

With our design work accomplished in the preceding section, the implementation stage is relatively straightforward.

18.3.1 Circle

The implementation to the circle class is as follows: #include "Circle.h" Circle::Circle() : r(0.0f), c(0.0f, 0.0f)

Page 602: C++ Módulo I e II

281

Circle::Circle(float R, const Vec2& center) : r(R), c(center) bool Circle::hits(Circle& A, Vec2& normal) Vec2 u = A.c - c; if( u.length() <= r + A.r ) normal = u.normalize(); // Make sure circles never overlap--at most // they can be tangent. A.c = c + (normal * (r + A.r)); return true; return false; Most of this is clear; the “hit” criterion comes straight from Section 18.2.1.3 from the “Collision Test” box. One interesting thing we do, however, is if the circles are interpenetrating then we adjust the center of A so that they are no longer interpenetrating. The circles can become interpenetrating in code since our position updates occur in discrete steps; however, this should never happen physically. Therefore, as soon as we encounter interpenetrating circles, we modify the center of A to make them tangent (i.e., touching).

18.3.2 Rect

Likewise, the Rect class implementation is straightforward as well: #include "Rect.h" Rect::Rect() Rect::Rect(const Vec2& a, const Vec2& b) : minPt(a), maxPt(b) Rect::Rect(float x0, float y0, float x1, float y1) : minPt(x0, y0), maxPt(x1, y1) void Rect::forceInside(Circle& A)

Page 603: C++ Módulo I e II

282

Vec2 p = A.c; float r = A.r; // Modify coordinates to force inside. if(p.x - r < minPt.x) p.x = minPt.x + r; if(p.x + r > maxPt.x) p.x = maxPt.x - r; if(p.y - r < minPt.y) p.y = minPt.y + r; if(p.y + r > maxPt.y) p.y = maxPt.y - r; // Save forced position. A.c = p; bool Rect::isPtInside(const Vec2& pt) return pt.x >= minPt.x && pt.y >= minPt.y && pt.x <= maxPt.x && pt.y <= maxPt.y; The implementation of forceInside comes straight from the work done in Section 18.2.1.5, and the implementation of isPtInside comes straight from the work done in Section 18.2.1.7.

18.3.3 AirHockeyGame

We now present the implementation to the AirHockeyGame class. The implementation comes directly from our design discussion in Section 18.2. For the most part, there are no new concepts here and therefore we will just present the code. However, Figure 18.10 may help in understanding where the game rectangle coordinates came from.

Page 604: C++ Módulo I e II

283

Figure 18.10: The coordinates of the various game rectangles defined.

#include "AirHockeyGame.h" #include <cstdio> #include "resource.h" // For Bitmap resource IDs AirHockeyGame::AirHockeyGame(HINSTANCE hAppInst, HWND hMainWnd, Vec2 wndCenterPt) : MAX_PUCK_SPEED(1000.0f), RED_SPEED(300.0f) // Save input parameters. mhAppInst = hAppInst; mhMainWnd = hMainWnd; mWndCenterPt = wndCenterPt; // Players start game with score of zero. mBlueScore = 0; mRedScore = 0; // The game is initially paused. mPaused = true; // No recovery time for red to start. mRedRecoverTime = 0.0f; // Create the sprites: Circle bc; Vec2 p0 = wndCenterPt; Vec2 v0(0.0f, 0.0f);

Page 605: C++ Módulo I e II

284

mGameBoard = new Sprite(mhAppInst, IDB_GAMEBOARD, IDB_GAMEBOARD_MASK, bc, p0, v0); bc.c = p0; bc.r = 18.0f; // Puck radius = 18 mPuck = new Sprite(mhAppInst, IDB_PUCK, IDB_PUCK_MASK, bc, p0, v0); p0.x = 700; p0.y = 200; bc.c = p0; bc.r = 25.0f; // Paddle radius = 25 mRedPaddle = new Sprite(mhAppInst, IDB_REDPADDLE, IDB_PADDLE_MASK, bc, p0, v0); p0.x = 100; p0.y = 100; bc.c = p0; bc.r = 25.0f; // Paddle radius = 25 mBluePaddle = new Sprite(mhAppInst, IDB_BLUEPADDLE, IDB_PADDLE_MASK, bc, p0, v0); // Initialize the rectangles. mBlueBounds = Rect(7, 40, 432, 463); mRedBounds = Rect(432, 40, 854, 463); mBoardBounds = Rect(7, 40, 854, 463); mBlueGoal = Rect(0, 146, 25, 354); mRedGoal = Rect(838, 146, 863, 354); AirHockeyGame::~AirHockeyGame() delete mGameBoard; delete mBluePaddle; delete mRedPaddle; delete mPuck; void AirHockeyGame::pause() mPaused = true; // Game is unpaused--release capture on mouse. ReleaseCapture(); // Show the mouse cursor when paused. ShowCursor(true); void AirHockeyGame::unpause() // Fix cursor to paddle position. POINT p = mBluePaddle->mPosition; ClientToScreen(mhMainWnd, &p);

Page 606: C++ Módulo I e II

285

SetCursorPos(p.x, p.y); GetCursorPos(&mLastMousePos); mPaused = false; // Capture the mouse when not paused. SetCapture(mhMainWnd); // Hide the mouse cursor when not paused. ShowCursor(false); void AirHockeyGame::update(float dt) // Only update the game if the game is not paused. if( !mPaused ) updateBluePaddle(dt); updateRedPaddle(dt); updatePuck(dt); // Decrease recovery time as time passes. if( mRedRecoverTime > 0.0f ) mRedRecoverTime -= dt; void AirHockeyGame::draw(HDC hBackBufferDC, HDC hSpriteDC) // Draw the sprites. mGameBoard->draw(hBackBufferDC, hSpriteDC); mBluePaddle->draw(hBackBufferDC, hSpriteDC); mRedPaddle->draw(hBackBufferDC, hSpriteDC); mPuck->draw(hBackBufferDC, hSpriteDC); // Draw the player scores. char score[32]; sprintf(score, "Blue's score = %d", mBlueScore); SetBkMode(hBackBufferDC, TRANSPARENT); TextOut(hBackBufferDC, 15, 45, score, (int)strlen(score)); sprintf(score, "Red's score = %d", mRedScore); TextOut(hBackBufferDC, 740, 45, score, (int)strlen(score)); void AirHockeyGame::updateBluePaddle(float dt) GetCursorPos(&mCurrMousePos); // Change in mouse position. int dx = mCurrMousePos.x - mLastMousePos.x; int dy = mCurrMousePos.y - mLastMousePos.y; Vec2 dp((float)dx, (float)dy);

Page 607: C++ Módulo I e II

286

// Velocity is change in position with respect to time. mBluePaddle->mVelocity = dp / dt; // Update the blue paddle's position. mBluePaddle->update(dt); // Make sure the blue paddle stays inbounds. mBlueBounds.forceInside(mBluePaddle->mBoundingCircle); mBluePaddle->mPosition = mBluePaddle->mBoundingCircle.c; // The current position is now the last mouse position. mLastMousePos = mBluePaddle->mPosition; // Keep mouse cursor fixed to paddle. ClientToScreen(mhMainWnd, &mLastMousePos); SetCursorPos(mLastMousePos.x, mLastMousePos.y); void AirHockeyGame::updateRedPaddle(float dt) // The red paddle's AI is overly simplistic: When the // puck moves into red's boundary, the red paddle // simply moves directly towards the puck to hit it. // When the puck leaves red's boundaries, the red // paddle returns to the center of its boundary. if( mRedRecoverTime <= 0.0f ) // Is the puck in red's boundary? If yes, then // move the red paddle directly toward the puck. if( mRedBounds.isPtInside(mPuck->mPosition) ) // Vector directed from paddle to puck. Vec2 redVel = mPuck->mPosition - mRedPaddle->mPosition; redVel.normalize(); redVel *= RED_SPEED; mRedPaddle->mVelocity = redVel; // If no, then move the red paddle to the point (700, 200). else Vec2 redVel = Vec2(700, 200) - mRedPaddle->mPosition; if(redVel.length() > 5.0f) redVel.normalize(); redVel *= RED_SPEED; mRedPaddle->mVelocity = redVel; // Within 5 units--close enough. else mRedPaddle->mVelocity = Vec2(0.0f, 0.0f); // Update the red paddle's position. mRedPaddle->update(dt);

Page 608: C++ Módulo I e II

287

// Make sure the red paddle stays inbounds. mRedBounds.forceInside(mRedPaddle->mBoundingCircle); mRedPaddle->mPosition = mRedPaddle->mBoundingCircle.c; void AirHockeyGame::updatePuck(float dt) paddlePuckCollision(mBluePaddle); // If red hits the puck then make a small 1/10th of a second // delay before the red paddle can move away as sort of a // "recovery period" after the hit. This is to model the // fact that when a human player hits something, it takes // a short period of time to recover from the collision. if(paddlePuckCollision(mRedPaddle)) mRedRecoverTime = 0.1f; // Clamp puck speed to some maximum velocity; this provides // good stability. if( mPuck->mVelocity.length() >= MAX_PUCK_SPEED ) mPuck->mVelocity.normalize() *= MAX_PUCK_SPEED; // Did the puck hit a wall? If so, reflect about edge. Circle puckCircle = mPuck->mBoundingCircle; if( puckCircle.c.x - puckCircle.r < mBoardBounds.minPt.x ) mPuck->mVelocity.x *= -1.0f; if( puckCircle.c.x + puckCircle.r > mBoardBounds.maxPt.x ) mPuck->mVelocity.x *= -1.0f; if( puckCircle.c.y - puckCircle.r < mBoardBounds.minPt.y ) mPuck->mVelocity.y *= -1.0f; if( puckCircle.c.y + puckCircle.r > mBoardBounds.maxPt.y ) mPuck->mVelocity.y *= -1.0f; // Make sure puck stays inbounds of the gameboard. mBoardBounds.forceInside(mPuck->mBoundingCircle); mPuck->mPosition = mPuck->mBoundingCircle.c; mPuck->update(dt); if( mBlueGoal.isPtInside(mPuck->mPosition) ) increaseScore(false); if( mRedGoal.isPtInside(mPuck->mPosition) ) increaseScore(true); bool AirHockeyGame::paddlePuckCollision(Sprite* paddle) Vec2 normal; if(paddle->mBoundingCircle.hits(mPuck->mBoundingCircle, normal)) // Hit updates cirle's position. So update pucks // position as well since the two correspond. mPuck->mPosition = mPuck->mBoundingCircle.c; //*******************

Page 609: C++ Módulo I e II

288

// Apply equation (7) //******************* // Compute the paddle's velocity relative to the puck's // velocity. Vec2 relVel = paddle->mVelocity - mPuck->mVelocity; // Get the component of the relative velocity along // the normal. float impulseMag = relVel.dot(normal); // Are the objects getting closer together? if( impulseMag >= 0.0f ) // Project the relative velocity onto the normal. Vec2 impulse = impulseMag * normal; // Add the impulse to the puck. mPuck->mVelocity += 2.0f * impulse; return true; return false; void AirHockeyGame::increaseScore(bool blue) if( blue ) ++mBlueScore; else ++mRedScore; // A point was just scored, so reset puck to center and // pause game. mPuck->mPosition = Vec2(mWndCenterPt.x, mWndCenterPt.y); mPuck->mVelocity = Vec2(0.0f, 0.0f); mPuck->mBoundingCircle.c = Vec2(mWndCenterPt.x, mWndCenterPt.y); // After score, pause the game so player can prepare for // next round. pause();

18.3.4 Main Application Code

Finally, we show the main application code, which creates the window, implements the window procedure, and enters the message loop. We have seen most of this code before. // giAirHockey.cpp // By Frank Luna // August 24, 2004.

Page 610: C++ Módulo I e II

289

//========================================================= // Includes //========================================================= #include <string> #include "resource.h" #include "AirHockeyGame.h" #include "BackBuffer.h" using namespace std; //========================================================= // Globals //========================================================= HWND ghMainWnd = 0; HINSTANCE ghAppInst = 0; HMENU ghMainMenu = 0; HDC ghSpriteDC = 0; BackBuffer* gBackBuffer = 0; AirHockeyGame* gAirHockey = 0; string gWndCaption = "Game Institute Air Hockey"; // Client dimensions exactly equal dimensions of // background bitmap. This is found by inspecting // the bitmap in an image editor, for example. const int gClientWidth = 864; const int gClientHeight = 504; // Center point of client rectangle. const POINT gClientCenter = gClientWidth / 2, gClientHeight / 2 ; // Pad window dimensions so that there is room for window // borders, caption bar, and menu. const int gWindowWidth = gClientWidth + 6; const int gWindowHeight = gClientHeight + 52; //========================================================= // Function Prototypes //========================================================= bool InitMainWindow(); int Run(); void DrawFramesPerSecond(float deltaTime); LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL CALLBACK AboutBoxProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

Page 611: C++ Módulo I e II

290

//========================================================= // Name: WinMain // Desc: Program execution starts here. //========================================================= int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd) ghAppInst = hInstance; // Create the main window. if( !InitMainWindow() ) MessageBox(0, "Window Creation Failed.", "Error", MB_OK); return 0; // Enter the message loop. return Run(); //========================================================= // Name: InitMainWindow // Desc: Creates main window for drawing game graphics //========================================================= bool InitMainWindow() WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = ghAppInst; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)::GetStockObject(NULL_BRUSH); wc.lpszMenuName = 0; wc.lpszClassName = "MyWndClassName"; RegisterClass( &wc ); // WS_OVERLAPPED | WS_SYSMENU: Window cannot be resized // and does not have a min/max button. ghMainMenu = LoadMenu(ghAppInst, MAKEINTRESOURCE(IDR_MENU)); ghMainWnd = ::CreateWindow("MyWndClassName", gWndCaption.c_str(), WS_OVERLAPPED | WS_SYSMENU, 200, 200, gWindowWidth, gWindowHeight, 0, ghMainMenu, ghAppInst, 0); if(ghMainWnd == 0) ::MessageBox(0, "CreateWindow - Failed", 0, 0); return 0; ShowWindow(ghMainWnd, SW_NORMAL); UpdateWindow(ghMainWnd); return true;

Page 612: C++ Módulo I e II

291

//========================================================= // Name: Run // Desc: Encapsulates the message loop. //========================================================= int Run() MSG msg; ZeroMemory(&msg, sizeof(MSG)); // Get the current time. float lastTime = (float)timeGetTime(); float timeElapsed = 0.0f; while(msg.message != WM_QUIT) // IF there is a Windows message then process it. if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) TranslateMessage(&msg); DispatchMessage(&msg); // ELSE, do game stuff. else // Get the time now. float currTime = (float)timeGetTime(); // Compute the differences in time from the last // time we checked. Since the last time we checked // was the previous loop iteration, this difference // gives us the time between loop iterations... // or, I.e., the time between frames. float deltaTime = max((currTime - lastTime)*0.001f, 0.0f); timeElapsed += deltaTime; // Only update once every 1/100 seconds. if( timeElapsed >= 0.01 ) // Update the game and draw everything. gAirHockey->update((float)timeElapsed); // Draw every frame. gAirHockey->draw(gBackBuffer->getDC(), ghSpriteDC); DrawFramesPerSecond(timeElapsed); // Now present the backbuffer contents to // the main window client area. gBackBuffer->present(); timeElapsed = 0.0; // We are at the end of the loop iteration, so // prepare for the next loop iteration by making

Page 613: C++ Módulo I e II

292

// the "current time" the "last time." lastTime = currTime; // Return exit code back to operating system. return (int)msg.wParam; //========================================================= // Name: DrawFramesPerSecond // Desc: This function is called every frame and updates // the frame per second display in the main window // caption. //========================================================= void DrawFramesPerSecond(float deltaTime) // Make static so the variables persist even after // the function returns. static int frameCnt = 0; static float timeElapsed = 0.0f; static char buffer[256]; // Function called implies a new frame, so increment // the frame count. ++frameCnt; // Also increment how much time has passed since the // last frame. timeElapsed += deltaTime; // Has one second passed? if( timeElapsed >= 1.0f ) // Yes, so compute the frames per second. // FPS = frameCnt / timeElapsed, but since we // compute only when timeElapsed = 1.0, we can // reduce to: // FPS = frameCnt / 1.0 = frameCnt. sprintf(buffer, "--Frames Per Second = %d", frameCnt); // Add the frames per second string to the main // window caption--that is, we'll display the frames // per second in the window's caption bar. string newCaption = gWndCaption + buffer; // Now set the new caption to the main window. SetWindowText(ghMainWnd, newCaption.c_str()); // Reset the counters to prepare for the next time // we compute the frames per second. frameCnt = 0; timeElapsed = 0.0f;

Page 614: C++ Módulo I e II

293

//========================================================= // Name: WndProc // Desc: The main window procedure. //========================================================= LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) switch( msg ) // Create application resources. case WM_CREATE: // Create the hockey game. gAirHockey = new AirHockeyGame( ghAppInst, hWnd, gClientCenter); // Create system memory DCs ghSpriteDC = CreateCompatibleDC(0); // Create the backbuffer. gBackBuffer = new BackBuffer( hWnd, gClientWidth, gClientHeight); return 0; case WM_COMMAND: switch(LOWORD(wParam)) // Destroy the window when the user selects the 'exit' // menu item. case ID_FILE_EXIT: DestroyWindow(ghMainWnd); break; // Display the about dialog box when the user selects // the 'about' menu item. case ID_HELP_ABOUT: DialogBox(ghAppInst, MAKEINTRESOURCE(IDD_ABOUTBOX), ghMainWnd, AboutBoxProc); break; return 0; // Left mouse button to unpause the game. case WM_LBUTTONDOWN: gAirHockey->unpause(); return 0; // Right mouse button to pause the game. case WM_RBUTTONDOWN: gAirHockey->pause(); return 0; // Destroy application resources. case WM_DESTROY: delete gAirHockey;

Page 615: C++ Módulo I e II

294

delete gBackBuffer; DeleteDC(ghSpriteDC); PostQuitMessage(0); return 0; // Forward any other messages we didn't handle to the // default window procedure. return DefWindowProc(hWnd, msg, wParam, lParam); //========================================================= // Name: AboutBoxProc // Desc: The window procedure of the about dialog box. //========================================================= BOOL CALLBACK AboutBoxProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) switch( msg ) case WM_INITDIALOG: return true; case WM_COMMAND: switch(LOWORD(wParam)) // Terminate the dialog when the user presses the // OK button. case IDOK: EndDialog(hDlg, 0); break; return true; return false;

Page 616: C++ Módulo I e II

295

18.4 Collision Physics Explanation (Optional)

In Section 18.2.1.3, “Puck Paddle Collision,” we said we would discuss the physics of the puck-paddle collision more rigorously. Recall Figure 18.11, where two circular masses 1m and 2m collide.

Intuitively, each object ought to “feel” an equal and opposite impulse in the direction nr , which is perpendicular to the collision point. Our first task at hand is to discuss some fundamental physics principles so that we can give a precise definition to “impulse.”

Figure 18.11: A collision.

Given our definition of impulse and also of linear momentum, we can derive equations (1) and (2), which we restate here:

Page 617: C++ Módulo I e II

296

(1) 1

11

ˆ

mnjvv if

r

+=

(2) 2

22

ˆ

mnjvv if

r

−=

Note: Throughout this discussion we use the subscript notation i to indicate an initial value and the subscript f to indicate a final value. In the context of collisions, the initial value will mean “before the collision” and the final value will mean “after the collision.”

These equations allow us to compute the final velocity of the masses after the collision, given the initial velocities before the collision and the magnitude of the impulse; that is, j. Once we have obtained (1) and (2) we must still solve for j. However, in order to do so we need some more physics background. We proceed to state the law of conservation of momentum and the conservation of kinetic energy for elastic collisions. Given these two laws, we have enough relationships to solve for j. And once we know j, we will know everything we need to solve for fv1 and

fv2 ; the initial velocities are given, the masses are given, the initial velocities are known, and nr can be found via geometric means. Finally, before we begin, note that the mathematics and physics may be overwhelming for students who have never taken a course in mechanics. For this reason, we have made this section optional. You should make an effort to understand this material, but if you find yourself completely lost then you can fall back on Section 18.2.1.3, which gives a more intuitive explanation. You will not be tested on any of the material in Section 18.4.

18.4.1 Linear Momentum

Definition: A physical object has a property called linear momentum pr , which is defined to be the product of its mass m and velocity vr . That is, vmp rr

= . A system of objects has a total linear momentum P

r, which is the vector sum of the linear momentum of all the objects in the system. That is,

nnn vmvmvmpppP rrrrrrr+++=+++= ...... 221121 for n objects in the system. (By system we mean the

objects we are considering; for example, in our puck-paddle collision the puck and paddle are the two objects we are considering and thus form our system.) Conceptually, the magnitude of an object’s linear momentum determines how hard it is to change the object’s path of motion. For example, consider a truck driving down a steep hill. It is both massive and moving at a fairly quick velocity (at least relative to a bystander). The product of a massive object and a high speed means a large linear momentum magnitude, which means the truck’s motion is not easily altered. And this makes sense intuitively; it will take a lot of force to change the motion of the truck.

Page 618: C++ Módulo I e II

297

Conversely, consider a soccer ball rolling down a slight incline. The soccer ball has a small mass and is not moving very fast, which means it has a small linear momentum magnitude, which means the soccer ball’s motion is easily altered. And this also makes sense intuitively. A person can easily alter the motion of the soccer ball by giving it a kick. It is important to remember that the linear momentum is a product of both the mass and the velocity. For example, although a bullet is not very massive, when fired it is difficult to change its path because of its high velocity.

18.4.2 Newton’s Second Law of Motion

Definition: Newton’s second law of motion defines the net force, at some particular time, acting on an object to be equal to its instantaneous change in linear momentum. In order to avoid calculus notation we will approximate “instantaneous” as a very short time interval t∆ . Then,

( ) tpptpF if ∆−=∆∆=rrrr

, which reads the net force equals the change in linear momentum over a short time period t∆ . Note that the change linear momentum pr∆ is just the difference in momentum from some initial momentum ipr to some final momentum fpr ; that is, if ppp rrr

−=∆ . We can rewrite Newton’s second law as follows: (8) tFptpF ∆⋅=∆⇒∆∆=

rrrr

In words, this says that the change in linear momentum is equal to the applied force times the length of time the force is applied. Intuitively this makes sense. If we apply a large (relatively speaking) force to an object for one second, then we would expect to see a large change in its linear momentum. Conversely, if we apply a weak force for one second, then we would expect to see a small change in its linear momentum. On the other hand, we might also apply a weak (relatively speaking) force to an object but for a long period of time—say sixty seconds. This could make the change in linear momentum large assuming that the force is not extremely weak. So we see from the relationship tFp ∆⋅=∆

rr that both the force strength and the duration for which it is applied determines the change in linear momentum.

18.4.3 Impulse Defined

Definition: When a constant force Fr

(may actually be a varying force but we do not need to handle such complexities here so we simplify by making the force constant) is applied to an object, over some time t∆ , it changes the linear momentum of the object ( ptF rr

∆=∆⋅ ). In the context of collisions, we call this change in momentum the impulse and denote it with the symbol J

r. That is, ptFJ rrr

∆=∆⋅= .

Page 619: C++ Módulo I e II

298

Essentially, impulse is just a special name given to the change in linear momentum due to a collision, which implies a strong force for a short amount of time. Given the initial linear momentum ii vmp rr

= of an object before a collision and the impulse Jr

it undergoes during the collision, we can find the final linear momentum ff vmp rr

= of the object after the collision using basic algebra:

pppppp ififrrrrrr

∆+=⇒−=∆ But ptFJ rrr

∆=∆⋅= , so: (9) Jpp if

rrr+=

Typically, we are not so concerned with the final linear momentum after the collision, but rather we are concerned with the final velocity after the collision. Easily enough, the final velocity follows directly from (9): Jpp if

rrr+=

Jvmvm if

rrr+=

(10) mJvv if

rr+=

This result is important because it will allow us to solve for the final velocity for an object after a collision in terms of the initial velocity and impulse. Observe that formula (10) gives us the form from which formulas (1) and (2) come. Although we used njJ rr

= , the form is the same.

18.4.4 Newton’s Third Law of Motion

Definition: Newton’s third law of motion states that if an object 1m exerts a force 12Fr

on object 2m then object 2m exerts an equal and opposite force 1221 FF

rr−= on object 1m .

A direct consequence of Newton’s third law of motion is the law of conservation of linear momentum, which states that in a closed and isolated system, the total (sum) linear momentum remains constant. By closed we mean no objects enter or leave the system. By isolated we mean that no net external forces from outside the system can act on the system. So if our system is two spheres and neither of the two objects leaves the system and if no new objects enter the system, and if no net external force acts on any object in the system then the total linear momentum of that two-sphere system will always stay the same. Observe that we emphasize the word “total” here; the linear momentum of each object may change but the total linear momentum of the system will not change, according to the law of conservation of linear momentum. That is,

Page 620: C++ Módulo I e II

299

(11) ffii vmvmvmvm 22112211rrrr

+=+ This reads that the total momentum at some initial time is equal to the total momentum at some final time. Equation (11) is specific to a system consisting of two objects only. How does Newton’s third law imply the conservation of linear momentum? For simplicity, let us explain with our preceding two-sphere system. There are two cases:

1. The spheres in the system do not collide. In this situation the total momentum of the system obviously stays the same. That is, no external net forces are acting on the system and therefore the objects will not change velocity. And the mass of the objects are not changing, thus the total linear momentum of the two spheres ffii vmvmvmvm 22112211

rrrr+=+ remains the same (i.e.,

constant).

2. The objects collide. A collision will change the objects’ velocities. However, just because the velocity of the individual objects in the system changes, it does not mean the total linear momentum of the system changes. On the contrary, it still remains constant, but we must prove this.

Consider Figure 18.12.

Figure 18.12: When the two masses collide they exert equal and opposite forces on each other for the duration of the collision.

During the collision, which lasts t∆ seconds, object 1m exerts an average force F

r during that short time

interval on object 2m . Thus object 2m experiences a change in linear momentum of JtFprr

=∆⋅=∆ 2 . However, by Newton’s third law then, object 2m exerts an equal and opposite force on object 1m . Thus, object 1m experiences a change in linear momentum JtFp

rr−=∆⋅−=∆ 1 . If we sum the total change in

linear momentum that occurs in the system we have:

021

rrrrr=+−=∆⋅+∆⋅−=∆+∆ JJtFtFpp

Page 621: C++ Módulo I e II

300

This shows that even though the linear momentum of each object in the system changed, the total linear momentum of the system did not change. Thus total linear momentum was conserved, which we wanted to prove.

18.4.5 Kinetic Energy and Elastic Collisions

Definition: It is said that an object in motion possesses kinetic energy. The kinetic energy of an object is defined as: 221 vmEK ⋅⋅= , where m is the mass of the object, and vv = is the magnitude of the velocity vector. Definition: If there is no loss of kinetic energy in a collision then we say that the collision is an elastic collision. Although in reality no collision is elastic (some energy is always lost), some events are almost elastic, such as two billiard balls striking each other. Although, intuitively we know that two billiard balls cannot truly be elastic because we hear a sound when they strike, thus we know some energy was transferred to sound energy. Elastic collisions are important for approximating almost elastic collisions. More specifically, if we assume the collision is elastic then we know the total level of kinetic energy before the collision is equal to the total level of kinetic energy after the collision. In other words, we say that kinetic energy is conserved. For a two-object system of 1m and 2m , that is,

(20) 222

211

222

211 2

121

21

21

ffii vmvmvmvm +=+

This reads that the total initial kinetic energy of the system is equal to the total final kinetic energy of the system after the collision. Consider the elastic collision shown in Figure 18.13. It is elastic because kinetic energy is conserved. In addition, observe that we have made the objects to have the same mass, that is 1=m ; this makes the calculations simpler.

Page 622: C++ Módulo I e II

301

Figure 18.13: An elastic collision.

Let us show that linear momentum is conserved; that is, the total initial linear momentum before the collision is equal to the total final linear momentum after the collision.

ffii vmvmvmvm 22112211rrrr

+=+ 15,510,105,520,0 mmmm +−=−+ 25,525,5 −=− Now let us show that this collision is really elastic by showing that kinetic energy is conserved.

222

211

222

211 2

121

21

21

ffii vmvmvmvm +=+

2

22

12

22

1 ffii vvvv +=+ [ ] ( )[ ] ( )[ ] [ ]22222222 155101055200 +++−=+−++ 450450 = This shows that the kinetic energy remained the same before and after the collision (no energy loss).

Page 623: C++ Módulo I e II

302

Let us take a moment to make an observation about Figure 18.13. We note that the relative velocity projected onto nr is equal and opposite before and after the collision: (21) ( ) ( ) nvvnvv ffii

ˆˆ2121

rrrrrr⋅−−=⋅−

( ) ( ) 7.0,7.015,510,107.0,7.05,520,0 ⋅−−−=⋅−− 7.0,7.05,157.0,7.015,5 ⋅−−−=⋅ ( ) ( ) ( ) ( )7.057.0157.0157.05 +=+ 1414 = This makes sense given the fact that kinetic energy is conserved; we would suspect that the total relative motion of the system along the line of impulse remains unchanged. But we wonder whether the equation for relative motion before and after the collision is true in general for elastic collisions. That is, is equation (21) always true for elastic collisions? To begin deriving (21), in general from fundamental physical principles, let us select a convenient coordinate system. Figure 18.14 shows that we choose a coordinate system where the x-axis is parallel and coincident with nr .

Figure 18.14: The coordinate system with the x-axis coincident to the collision normal. For clarity we have left out drawing the object bodies and have just kept the vectors. Moreover, we translate the vectors parallel to themselves so that the vectors originate with the origin; note that this translation is perfectly legal—a parallel transport does not alter the magnitude or direction of the vector.

Page 624: C++ Módulo I e II

303

Note that Figure 18.14 is a general figure; that is, we are not looking at a particular collision case, but rather we are looking at any collision case. Let us now find the components of our velocities iv1

r and iv2r relative to this coordinate system. The x-

component of the velocities are found by projecting the vectors onto the normal vector nr :

nnvv inxiˆ)ˆ( 1,1rrrr

⋅=

nnvv inxiˆ)ˆ( 2,2rrrr

⋅= The y-components of the velocities are found by taking the difference between the velocity vector and the projected x-component:

nnvvv iinyiˆ)ˆ( 11,1rrrrr

⋅−=

nnvvv iinyiˆ)ˆ( 22,2rrrrr

⋅−= Thus, our velocities relative to this new coordinate system can be expressed in terms of their components along the x- and y-axis as:

nnvvnnvv iiiniˆ)ˆ(ˆ)ˆ( 111,1rrrrrrrr

⋅−+⋅=

nnvvnnvv iiiniˆ)ˆ(ˆ)ˆ( 222,2rrrrrrrr

⋅−+⋅= From the conservation of kinetic energy for elastic collisions and from the conservation of linear momentum, we can relate our total initial velocities

2,22

2,11

2,22

2,11 2

121

21

21

nfnfnini vmvmvmvm +=+

nfnfnini vmvmvmvm ,22,11,22,11

rrrr+=+

A key observation of Figure 18.14 is that the impulse vectors are parallel to the x-axis. Consequently, the y-components of the velocities are untouched by the impulse vectors (there is no impulse component on the y-axis). What this means is that the y-component of the velocities are going to remain the same before and after the collision, and therefore we do not need to consider the y-component in either of our conservation equations (It does not change! This is the reason why we originally picked this coordinate system.) Consequently, we will drop the arrowheads and only look at the x-components:

(22) 2,22

2,11

2,22

2,11 2

121

21

21

nxfnxfnxinxi vmvmvmvm +=+

(23) nxfnxfnxinxi vmvmvmvm ,22,11,22,11 +=+

Page 625: C++ Módulo I e II

304

After doing some algebra we can rewrite (22) as: ( ) ( )2

,22

,222

,12

,11 nxfnxinxfnxi vvmvvm −−=− (24) ( )( ) ( )( )nxfnxinxfnxinxfnxinxfnxi vvvvmvvvvm ,2,2,2,22,1,1,1,11 −+−=−+ Similarly, we can rewrite (23) as: (25) ( ) ( )nxfnxinxfnxi vvmvvm ,2,22,1,11 −−=− If we divide equation (24) by equation (25) we get:

( )( )

( )( )( )

( )nxfnxi

nxfnxinxfnxi

nxfnxi

nxfnxinxfnxi

vvmvvvvm

vvmvvvvm

,2,22

,2,2,2,22

,1,11

,1,1,1,11

−−

−+−=

−+

nxfnxinxfnxi vvvv ,2,2,1,1 +=+ (26) ( ) ( )nxfnxfnxinxi vvvv ,2,1,2,1 −−=− But equation (26) says that the initial relative velocity along the x-axis (i.e., normal vector nr ) is equal but opposite to the final relative velocity along the x-axis (i.e., normal vector nr ). But that is the same as: ( ) ( ) nvvnvv ffii

ˆˆ2121

rrrrrr⋅−−=⋅−

This was the relationship we wanted to derive from fundamental physics definitions, which we have just done.

18.4.6 Collision and Response

We now have enough physics vocabulary and formulas to solve the original section problem: how two circular masses (paddle and puck) respond physically when they collide. Note that we assume the collision is elastic, and this is a close enough approximation; the collision between an air hockey paddle and puck is almost elastic in practice. The fact that we assume the collision is elastic is important because, if we do not, then equation (33) does not hold. Consider two circular masses 1m and 2m with initial linear momentum ivm 11

r and ivm 22r colliding as

Figure 18.15 shows (we have already seen this picture twice before).

Page 626: C++ Módulo I e II

305

Figure 18.15: A collision.

Clearly, the impulse is in the direction nr that is perpendicular to the point of contact. Let

njtFJ rrr=∆⋅= , where nr gives the direction of the impulse and j gives its magnitude (which is

unknown), and Fr

is the average force exerted during the collision and t∆ is the temporal duration of the collision. We will know the objects’ incoming initial velocities before the collision. What we want to find is the velocities after the collision fv1

r and fv2r ; informally that is, “how do the circular masses bounce off of

each other?” To begin, we observe that formula (9) gives the final velocity in terms of the initial velocity and impulse. Thus, we need only evaluate the right side of the following two equations to find the final velocities:

Page 627: C++ Módulo I e II

306

(27) 1

11

11

ˆ

mnjv

mJvv iif

rr

rrr

+=+=

(28) 2

22

22

ˆ

mnjv

mJvv iif

rr

rrr

−=−=

As an aside, let us confirm here that linear momentum is conserved. If we add the total system momentum—that is equations (27) and (28)—and do some algebra rearrangements then we find that:

ffii vmvmJvmJvm 22112211rrrrrr

+=−++ The impulses cancel out, leaving:

ffii vmvmvmvm 22112211rrrr

+=+ This states that the total system momentum before the collision is the same as the total system momentum after the collision, and that satisfies the conservation law of linear momentum. Recall that the J

r− quantity in (28) comes from our discussion of Newton’s third law; that is, the two

objects feel equal but opposite impulses. Unfortunately, we cannot evaluate the right hand side (27) and (28) because we do not know j. In order to find j we need another equation. We recall that in Section 18.4.5 we did some work to show that the following relationship is true: (29) ( ) ( ) nvvnvv ffii

ˆˆ2121

rrrrrr⋅−−=⋅−

Substituting (27) and (28) into (29) yields:

( ) nm

njvmnjvnvv iiii

ˆˆˆˆ2

21

121r

rr

rrrrr

⋅⎟⎟⎠

⎞⎜⎜⎝

⎛⎟⎟⎠

⎞⎜⎜⎝

⎛−−⎟

⎟⎠

⎞⎜⎜⎝

⎛+−=⋅−

nm

njnvnmnjnvnvnv iiii

ˆˆˆˆˆˆˆˆ2

21

121r

rrrr

rrrrrrr

⋅−⋅+⋅−⋅−=⋅−⋅

( ) ⎟⎟⎠

⎞⎜⎜⎝

⎛+⋅=⋅−−

2121

11ˆˆˆ2mm

nnjnvv iirrrrr

Using the fact that 1ˆˆ =⋅ nn rr and rearranging leads to:

Page 628: C++ Módulo I e II

307

(30) ( )

⎟⎟⎠

⎞⎜⎜⎝

⎛+

⋅−−=

21

21

11

ˆ2

mm

nvvj ii

rrr

Substituting (30) in for j in equations (27) and (28) gives:

(31) ( ) ( )

⎟⎟⎠

⎞⎜⎜⎝

⎛+

⋅−+=

⎥⎥⎥⎥⎥

⎢⎢⎢⎢⎢

⎟⎟⎠

⎞⎜⎜⎝

⎛+

⋅−−+=

2

1

1

1

,121

1

21

2111

ˆˆ2ˆ

11

ˆ2

mm

mm

nnvv

mn

mm

nvvvv i

iii

if

rrrr

rrrrrr

(32) ( ) ( )

⎟⎟⎠

⎞⎜⎜⎝

⎛+

⋅−−=

⎥⎥⎥⎥⎥

⎢⎢⎢⎢⎢

⎟⎟⎠

⎞⎜⎜⎝

⎛+

⋅−−−=

2

2

1

2

,122

2

21

2122

ˆˆ2ˆ

11

ˆ2

mm

mm

nnvv

mn

mm

nvvvv i

iii

if

rrrr

rrrrrr

Equations (31) and (32) are the same as equations (4) and (5) given in Section 18.2.1.3, which at the time we gave without proof.

Page 629: C++ Módulo I e II

308

18.5 Closing Remarks

The Air Hockey game illustrated five subparts of the game loop. Specifically, each game loop cycle we do the following:

1. Check user input. 2. Execute Artificial Intelligence code. 3. Update physics (including collision detection) 4. Game logic. 5. Draw the updated frame of animation.

Each of these processes makes up a component of the overall game engine. Two other components missing from our game, but typically mandatory for most games, is sound and networking. These two additional components would also need to be updated frequently and be part of the main game loop. To summarize, a game engine can be thought of as having seven core components: Input, AI, Physics, Graphics, Sound, Networking, and Game Logic. With the successful completion of this course, you have learned the C++ programming language, received some exposure and a basic introduction to Windows programming with the Win32 API, and most importantly, you have learned fundamental game programming concepts and graphics concepts such as animation, double buffering, sprites, the game loop, collision detection, checking user input, and basic artificial intelligence. You have successfully climbed one mountain, but your journey is just getting started. We hope that you continue your education here at Game Institute. It is the school’s recommendation that the next course you should take following this one is the 3D Graphics Programming with DirectX Module I course. This course will teach you the fundamentals of hardware accelerated 3D graphics using the Direct3D API, which is mandatory study for all contemporary games programmers. Many other 3D programming related topics are also covered in that course, so it is an excellent next step. In addition, as you have already seen from the Air Hockey game, math, physics, input, and artificial intelligence also play a key role in game development. As a result, your training will soon lead you to the Game Mathematics, Game Physics, and Introduction to Artificial Intelligence courses. Finally, even though there is still much to learn, it might be a good idea to review what you have learned here and try to implement your own classic 2D games using the basic sprite and backbuffer code we used for the Air Hockey game. Possible game ideas are Asteroids, PacMan, Space Invaders, Commander Keen, and so on. A search for “arcade games” on www.google.com will yield many more ideas. We wish you the very best of luck as you continue your game programming adventures!


Recommended