Tutorial 4 - Further C++
This tutorial covers subclassing, casting, pointers and namespaces.
We have looked at classes in the previous lesson. Here we will look
at something called subclassing.
If you need to have a number of objects that all share the same behaviour, for example
all the characters in your game have a position on the screen x and y (x is
the amount along the screen and y is the amount down the screen) and all the characters
should have x, y but some characters also have different properties then you can
use subclassing to achieve this.
Let's say you want to have a Wizard character and a Warrior character. Each wizard in our
game is allowed to cast a spell and each warrior is allowed to swing his sword. They
are both characters so they should have the properties that every character has but
only wizards can cast spells and only warriors can swing swords so they are different.
Our character class looks like this:
float health; //Every character has health.
float x, y; //Two member variables x and y.
Now we want a Wizard class and Warrior class that both have the properties that every character has,
in this case health, x and y. We do it like this:
class Wizard : public Character
By typing : public Character, every wizard we create will have the member variables health, x and y. We call
Wizard a subclass of Character because it has inherited the properties of Character and we call Character
the superclass of Wizard. Now if we implement the Wizard class and create an instance of it we can access
it's inherited members health, x and y.
//Create an instance of Wizard and
//access inherited members
wizard1.health = 100.0f;
wizard1.x = 50.0f;
wizard1.y = 5.0f;
We can do the same thing for Warrior:
class Warrior : public Character
//Play graphics animation.
By typing : public Character, every warrior we create will have the member variables health, x and y.
In this way we can create classes that share common behaviour but also have behaviour that are unique to those classes themselves.
Wizard and Warrior share the behaviour of every character but they also have behaviour unique to them.
Not every character can cast a spell, e.g. warriors, so we do not add the CastSpell() function to the Character class.
Instead we add CastSpell() to Wizard and likewise wizards cannot swing a sword in this game so SwingSword() is not
a member of Character so wizards do not inherit SwingSword().
In C++ you can have a class inherit from multiple classes. Maybe you have defined properties of a Human and a Dwarf,
Human is one class and Dwarf is another. You have a main character called Maximus and he is a Dwarf and you have another
main character called Amadeus and he is a Human. Maximus is a Dwarf but he is also a Warrior and Amadeus is a Human and
he is also a Wizard. So you could have Maximus inherit the properties of a Dwarf and a Warrior and you could have Amadeus
inherit the properties of a Human and a Wizard. Consider the following example:
class Maximus : public Warrior, Dwarf
//Maximus is a subclass of Warrior and Dwarf
class Amadeus : public Wizard, Human
//Amadeus is a subclass of Wizard and Human
Then we might create an instance of Maximus and draw him in the 3d world:
Note: This is just a demonstration of subclassing, AnimatedModel has not been implemented so the program won't run.
//Set the position of maximus.
maximus.x = 10.0f;
maximus.y = 5.0f;
//draw the 3d character.
//dwarf_model is a member inherited
//from the Dwarf class.
C++ supports something called C-style casting. C was the language before C++ that a man named Stroustrup set out to improve, the inventor of C++.
So C++ has inherited some parts of the C language.
Here I want to describe what casting is.
Consider the following code that casts a float to an int:
float my_float = 5.4f;
int my_int = 1;
//Cast my_float to my_int.
my_int = (int)my_float;
Because an integer does not have a decimal point, my_float is actually truncated.
my_int becomes 5. Actually if my_float was equal to 5.5f, my_int would still become 5,
it is not rounded. When you cast an instance to another type, the memory that the instance takes up
is copied to new memory, in the example above, the memory of my_float is copied to the memory of an int because
we have casted to int using (int).
And because int takes up less memory than a float, not all the memory that my_float takes up can be copied to the memory of an int.
I find using custom data types to be an easier way to describe casting than using the built-in primitive types,
int, float, double etc.
You can only cast custom data types that are a subclass to a superclass, you can cast pointers to pointers and
you can cast a primitive type to a primitive type.
And now I will explain by example how it works.
struct TypeB : public TypeA
my_var2.x = 1;
my_var2.y = 3;
my_var2.z = 4;
//Cast my_var2 to TypeA.
my_var1 = (TypeA)my_var2;
Here, the members x, y and z of typeB, my_var2, are cast to x, y and z of TypeA because we have used (TypeA).
The last member "a" of my_var2 is ignored and not copied because it does not exist in TypeA.
After the cast, the members of my_var1 become, x=1, y=3 and z=4.
Pointers provide a mechanism to interact with instances without copying them to local parameter variables every time
you want to use them in a function. With pointers you can work generically with varaibles of different
types by using void* memory addresses, which can be useful in certain cases. Pointers also allow you to work with member variables that have
the same name as a function parameter of a function defined in the same class.
What is a pointer?
int my_int = 3;
int* p = &my_int;
Here, p is a pointer. It points to my_int.
A pointer is actually a variable that holds a memory address to an instance or segment of memory.
We declare p to be a pointer by putting a star * after int.
A pointer can store any memory address like an int can store any integer.
Like int takes up 4 bytes, float takes up 4 bytes and every double takes up 8 bytes, every pointer takes up 4 bytes
in a 32-bit application because 4bytes is enough to store any memory address on a computer. We have worked with int and float up until now. We know that
int is a type and float is a type. Well let me tell you that int* is also a type and float* is also a type.
However, unlike ordinary variables, c++ has a syntax that let's you work with pointers slightly differently.
Once you understand that every instance in c++ is stored at a location in memory, a memory address,
and takes up an amount of memory starting at that location, pointers become much easier to understand.
I will do my best to explain this concept as well as possible.
Ok, so we know that pointers hold memory addresses, but how do we use them?
If you look at the example above, I have put an & ampersand symbol before my_int. The & ampersand
gets the memory address of a variable, in this case the memory address of my_int. And the memory
address of my_int is assigned to p. You can put the & symbol before any instance to get the memory address of that instance.
Now p holds the memory address of my_int.
The value stored at that memory address is 3 because my_int = 3. We can access that value
using p because p has the memory address stored in it.
To access the value 3 use:
int value = *p;
The * before p is called the dereferencer operator. Don't confuse this with the * that turns
a type like int into a pointer type. The * dereferencer retrieves the value stored
at p and because p is an int* pointer and not a float* pointer for example, the value at
the address is converted to an ordinary int.
The main use for pointers is probably with custom data types.
//Create a pointer to t
MyDataType* my_ptr = &t;
//Set the members of t to some values.
my_ptr->x = 2;
my_ptr->a = 31;
my_ptr->b = 44;
my_ptr->c = 1.5;
Ok, here we have declared a struct; You could make it a class if you wanted.
When we create an instance t, enough memory to store t in main memory is allocated and
t gets stored in memory. The amount of memory that t actually takes up is int+float+float+double because
that is what the struct defines.
We store the address of t in my_ptr, and because the pointer has MyDataType* as the type,
it gives us access to the members x, a, b and c. We can then update the values stored in memory
by using the arrow -> notation. In the example above, my_ptr points to the memory of
t so in fact, by setting the values stored in that memory we have actually set the values
of t itself to new values.
Now let's look at passing a pointer to a function. In the last tutorial I talked about
passing by reference and passing by value. It probably didn't make much sense to you at the time because I
hadn't covered pointers.
But now you understand pointers hopefully it will make sense.
Now let's create a structure to test out the concept:
//Set initial values on instance creation.
n1 = 5.0f;
n2 = 6.0f;
float MyFunction(CustomObject* o)
return o->n1 + o->n2;
Can you work out what's going on above?
We pass the address of my_obj to the function MyFunction(). Then we access n1 and n2 of my_obj
within the function and add n1 and n2 together to get a result. This is called passing
by reference because o is a reference to the instance my_obj.
And That's it! Hopefully you understand pointers and passing by reference now.
You may have noticed "using namespace std;" at the top of some of the files.
Next Tutorial >>
std is a namespace.
It contains lots of classes and functions related to the standard library.
Hopefully you remember I talked about scope in Tutorial 3. A namespace is simply
a definition of a scope. A scope might be everything defined within a class or it might
just be two curly brackets. If the scope is a class then you access members such as functions
when you define them using the scope resolution operator ::.
It is much the same with namespaces, you use the scope resolution operator to access
anything contained wihin a namespace.
float result = my_namespace::MyFunction();
In the code above we have defined a namespace called my_namespace.
Now that MyFunction() is defined within a namespace we have to use the scope resolution operator to access it otherwise
we can type "using namespace my_namespace;".
Let's modify the code above to demonstrate how we could use MyFunction() without the scope resolution operator.
using namespace my_namespace; //This is the bit that let's
//us use MyFunction()
float result = MyFunction();
If we did not have using namespace std; then we would have to use the scope resolution operator
for objects within the std namespace as well. To use string for example: