Navigation: Introduction to Programming

Tutorial 4 - Further C++


Overview

This tutorial covers subclassing, casting, pointers and namespaces.

Subclassing

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:

Character.h
class Character
{
	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:

Wizard.h
#include "Character.h"

class Wizard : public Character
{
	void CastSpell();
};
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.

Wizard.cpp
#include "Wizard.h"

void Wizard::CastSpell()
{
	//Do something.
}
main.cpp
int main()
{
	//Create an instance of Wizard and 
	//access inherited members
	Wizard wizard1;

	wizard1.health = 100.0f;
	wizard1.x = 50.0f;
	wizard1.y = 5.0f;

	return 0;
}
We can do the same thing for Warrior:

Warrior.h
#include "Character.h"

class Warrior : public Character
{
	void SwingSword();
};
Warrior.cpp
#include "Warrior.h"

void Warrior::SwingSword()
{
	//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:

Dwarf.h
class Dwarf
{
	AnimatedModel dwarf_model;
};
Human.h
class Human
{
	AnimatedModel human_model;
};
Maximus.h
#include "Warrior.h"
#include "Dwarf.h"

class Maximus : public Warrior, Dwarf
{
	//Maximus is a subclass of Warrior and Dwarf
};
Amadeus.h
#include "Wizard.h"
#include "Human.h"

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.

main.cpp
#include "Maximus.h"

int main()
{
	//create instance.
	Maximus maximus;

	//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.
	maximus.dwarf_model.Render();

	return 0;
}

Casting

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 TypeA
{
	int x;
	int y;
	float z;
};

struct TypeB : public TypeA
{
	int a;
};

TypeA my_var1;
TypeB my_var2;

//Initialise my_var2.
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

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.

main.cpp
struct MyDataType
{
	int x;
	float a;
	float b;
	double c;
};

int main()
{
	MyDataType t;

	//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;

	return 0;
}
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:

CustomStructure.h
struct CustomObject
{
	//Constructor
	CustomObject();

	float n1;
	float n2;
};
CustomStructure.cpp
#include "CustomStructure.h"

CustomObject::CustomObject()
{
	//Set initial values on instance creation.
	n1 = 5.0f;
	n2 = 6.0f;
}
main.cpp
#include "CustomStructure.h"

float MyFunction(CustomObject* o)
{
	return o->n1 + o->n2;
}

int main()
{
	CustomObject my_obj;

	MyFunction(&my_obj);

	return 0;
}
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.

Namespaces

You may have noticed "using namespace std;" at the top of some of the files.

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.

For example:

main.cpp
namespace my_namespace
{
	float MyFunction()
	{
		return 7.0f;
	}
}

int main()
{
	//Call MyFunction()
	
	float result = my_namespace::MyFunction();

	return 0;
}
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.

main.cpp
namespace my_namespace
{
	float MyFunction()
	{
		return 7.0f;
	}
}

using namespace my_namespace; //This is the bit that let's 
			      //us use MyFunction()
			      //without my_namespace::

int main()
{
	//Call MyFunction()
	
	float result = MyFunction();

	return 0;
}
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: std::string my_string;
Next Tutorial >>