The first class which we build will model a mathematical vector, which is an essential tool for 3D computer graphics and 3D game programming.. In 3D computer graphics programming and man
Trang 1Write 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:
Trang 2Chapter 7
Operator Overloading
Trang 3Introduction
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:
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
Trang 4does 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)
{
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
Trang 5forces (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
Trang 6Thus we can describe a vector analytically by merely specifying the coordinates of its “head.” This motivates the following structural representation of a 3D vector:
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 ur= u x, u y, u z and
z y
x
u = = u z =v z
: The sum of two vectors is found by adding corresponding components:
z z y y x x z y x z y
u v
Example:
4,1,613,32,511,3,53,2,
1 + − = + − + = −
=
+ q
Trang 7Geometrically, we add two vectors ur +vr by parallel translating vr so that its tail is coincident with the head of ur and then the sum ur +vr 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 ur +vr 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:
z z y y x x z y x z y
u v
Example:
( )3, 3 1 4, 5, 22
,511,3,53,2,
Trang 8Figure 7.4: Vector subtraction We view vector subtraction as the sum ur−vr =ur+( )−vr So first we negate vr and
then translate −vr so that its tail coincides with ur Then ur+( )−vr 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, ur−vr=ur+( )−vr , 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:
3 2 1 3
2
v k
Example:
( ) ( ) ( )1, 32, 33 3, 6, 93
3,2,13
3pr= = =
As the name implies, scalar multiplication scales the length of a vector Figure 7.5 shows some examples
Trang 9Figure 7.5: Scalar multiplication Multiplying a vector by a scalar changes the magnitude of the vector A negative
scalar flips the direction
2 2
2
12 + 2 + 2 =
=
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:
Trang 10Normalizing 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:
vˆr
v v
vr =ˆ r r
Example:
143142,1413,2,1141
The Dot Product:
The dot product of two vectors is the sum of the products of corresponding components:
z z y y x x z y x z y
u v
1,3,53,2,
1 ⋅ − = + − + = − + =
=
⋅ q
p rr
Observe that this vector product returns a scalar—not a vector
The dot product of a vector vr with a unit vector nˆr evaluates to the magnitude of the projection of vr
onto , as Figure 7.7 shows nˆr
Trang 11Figure 7.7: The dot product of a vector vr with a unit vector nˆr evaluates to the magnitude of the projection of vr onto
We can get the actual projected vector
nˆr vr n by scaling nˆr by that magnitude
Given the magnitude of the projection of vr onto nˆr , the actual projected vector is: vrn =( )vr⋅nrˆ rnˆ, which
makes sense: vr⋅ returns the magnitude of tnˆr he projection, and nˆr is the unit vector along which the
projection lies Therefore, to get the projected vector we sim ly scale the unit vector p nˆr 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
n
vr
n
v v
Trang 127.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( 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);
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
Trang 13previous 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:
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)
ethod we specified was the equals method The equals m
// Return true if the corresponding c
Trang 147.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 Vector3::mul( float scalar)
Trang 15v v
vr =ˆ r r
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:
z z y y x x z y x z y
u v
Trang 167.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 floatarray would allow us to use the Vector3 class seamlessly with OpenGL
Trang 177.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
Trang 18float 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
// Part 7: Create a new vector and have user specify its
// components, then print the vector
cout << "Input vector " << endl;
Trang 19Press 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
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