Navigation: Introduction to Programming

Tutorial 5 - The Standard Library


Overview

In this tutorial I want to cover some of the most useful classes that are part of the C++ Standard Library. string, vector, map, ofstream and ifstream. Sometimes you need to work with your game objects dynamically, you might have to remove a dead character from the game or spawn new objects. Or you might need to look up associated data very fast like a dictionary, for example you want to get a list of possible enemy targets for character1. You might also want functionality to save game levels or states to a file. The Standard library provides all you need to do these things and it is often more practical to use the standard library instead of writing your own code to complete common tasks.

Strings

A string is a sequence of characters like "I am a string". Each character is generally a char, 1 byte or a wchar_t, 2 bytes, which means wide character. A character set, for example Unicode or Ascii, determines the range of characters each string can have. Unicode let's you use a greater range, so it will allow you to use obscure symbols that are out of the Ascii range. A symbol might be @ or , for example, which are both available in Ascii and Unicode but it might be useful just to know that if you build your project with Unicode setting, it will give you a greater range of symbols.

string is an object that is part of the std library and it makes working with strings much easier than using plain char arrays that you allocate and deallocate memory for yourself. To use it you must either use std::string or using namespace std;(See Tutorial 4 on namespaces).

Here is an example of using std::string:

main.cpp
#include <string>

using namespace std;

int main()
{
	string str1 = "Hello World";

	return 0;
}

Vectors

A vector here does not mean the mathematical term vector. A std::vector in c++ is a dynamic array. I have not covered arrays in any of the previous tutorials but now I hope to explain what they are and how to use them in the case of the vector.

An array is a list of 0 or more values or objects.

An array of strings for example is a list of strings.

And a dynamic array is an array that can have elements added or removed or the memory it uses allocated or deallocated.

Here is how we would create a vector of strings in code and add and remove elements.

main.cpp
#include <string>
#include <vector>

using namespace std;

int main()
{
	string str1 = "Blue";
	string str2 = "Red";
	string str3 = "Yellow";

	//string in angled brackets makes the vector
	//a vector of strings.
	vector<string> list_of_colours;

	//Add "Blue", "Red" and "Yellow" to the list.
	list_of_colours.push_back(str1);
	list_of_colours.push_back(str2);
	list_of_colours.push_back(str3);

	//Remove the second element "Red" from the list.
	//First element is 0
	//so second element is 1
	list_of_colours.erase(list_of_colours.begin() + 1);

	return 0;
}
Now let's create a vector of Monsters.

main.cpp
#include <vector>

using namespace std;

class Monster
{
private:
	float Health;
	
public:
	Monster(){
		Health = 100.0f;
	}
};

int main()
{
	vector<Monster> list_of_monsters;

	//Create 3 monsters.
	list_of_monsters.push_back(Monster());
	list_of_monsters.push_back(Monster());
	list_of_monsters.push_back(Monster());

	//Remove the first monster from the list.
	//First element is 0
	list_of_monsters.erase(list_of_monsters.begin() + 0);

	return 0;
}
Now let's create a vector of Monsters using a loop.

Note: I have made Health private. If you are unsure whether to make a variable public or private then make it private. You can change it later if you need to.

main.cpp
#include <vector>

using namespace std;

class Monster
{
private:
	float Health;
	
public:
	Monster(){
		Health = 100.0f;
	}

	float GetHealth(){
		return Health;
	}
};

int main()
{
	vector<Monster> list_of_monsters;

	//Create 3 monsters.
	for(int i=0; i<3; i++)
	{
		list_of_monsters.push_back(Monster());
	}

	//Remove dead monsters from the list.
	//size() is the number of elements in the list.
	for(int i=0; i<list_of_monsters.size(); i++)
	{
		if(list_of_monsters.at(i).GetHealth() <= 0)
		{
			//Monster is dead.
			list_of_monsters.erase(list_of_monsters.begin() + i);

			//Element has been removed,
			//so next element becomes one index less.
			i--;
		}
	}

	return 0;
}

Maps

A map is an object in the Standard Library that allows you to associate(map) one piece of data with another like in a dictionary and look up the associated data instantly. A dictionary has a number of words and each word has a description associated with it. When you look up one of the words you get it's description. A std::map works in the same way. The word, or object, that you use to look up an associated piece of data is called the key and the data itself is called the value. Each mapping in the map is called a key/value pair.

Let's say in your game a weapon can be one of the following: wooden, iron or silver. wooden weapons cost 50 gold pieces, iron weapons cost 100 gold pieces and silver weapons cost 150 gold pieces.

Let's define wooden as 0, iron as 1 and silver as 2. When a weapon is created it will randomly be between 0 and 2, if 0 it is wooden, if 1 it is iron and if 2 it is silver.

Then we will create a map of costs as stated. If we want to find the cost of a wooden weapon for example then we use WOODEN as the key and we will get it's cost, which is the value.

main.cpp
#include <map>
#include <iostream>
#include <stdlib.h>
#include <time.h>

using namespace std;

#define WOODEN 0
#define IRON 1
#define SILVER 2

class Weapon
{
private:
	int type;
public:
	Weapon()
	{
		type = rand() % 3;//0-2
	}

	int GetType(){return type;}
};

class Sword : public Weapon
{
};

class Axe : public Weapon
{
};

int main()
{
	//Setup rand()
	srand (time(NULL));

	map<int, float> costs;

	//Create some key/value pairs.
	costs[WOODEN] = 50;
	costs[IRON] = 100;
	costs[SILVER] = 150;

	//Create some weapons.
	Sword sword1;
	Sword sword2;
	Axe axe1;

	//Get the cost of each weapon.
	float cost;

	//Get the cost of sword1.
	cost = costs[sword1.GetType()];
	cout << "sword1 costs " << cost << "\n";

	//Get the cost of sword2.
	cost = costs[sword2.GetType()];
	cout << "sword2 costs " << cost << "\n";

	//Get the cost of axe1.
	cost = costs[axe1.GetType()];
	cout << "axe1 costs " << cost << "\n";

	//Wait for user to press Enter.
	getchar();

	return 0;
}
This demonstrates how you could use a map. This is probably not how you would generate the costs of weapons. You would probably be better just using an if statement in the weapon constructor for example if(WOODEN){cost = 50;} because that wouldn't take up memory whereas using a map does. However you will probably find circumstances where a map will be very useful; I used a map in a game I am working on to associate an Ability class with an ability id where an item could have some ability like deal fire damage and I could check if the ability id exists as a key to determine if the item has that ability and then retrieve the ability itself by using the key.

Input / Output

In this final part of the tutorial I will cover reading and writing objects to files. You might want to save the state of a number of instances to a file to save a game or read what you have saved to load a game.

To write to a file you can use the ofstream object.
To read a file you can use the ifstream.

Let's say we want to save the properties of an instance to a file and then load it's properties back into the program.

Here is how we would save and load some instances to and from a file:

main.cpp
#include <fstream>
#include <iostream>

using namespace std;

struct MyObject
{
	int x;
	int y;

	MyObject()
	{
		x = 0;
		y = 0;
	}
};

int main()
{
	//Save two instances of MyObject to file.

	MyObject o1;
	MyObject o2;

	//Set the values of o1 and o2 we want to save.
	o1.x = 4;
	o1.y = 3;

	o2.x = 7;
	o2.y = 53;

	//Print the values of o1 and o2 to the console
	//so we can see the values before the game has
	//been saved.
	cout << "Before Save:" << "\n";
	cout << "o1.x = " << o1.x << "\n";
	cout << "o1.y = " << o1.y << "\n";
	cout << "o2.x = " << o2.x << "\n";
	cout << "o2.y = " << o2.y << "\n";

	//Create the file "mysave.sav" and open it for writing.
	ofstream ofs("mysave.sav", std::ios::binary);

	//Save the first instance to the file.
	ofs.write((char*)&o1, sizeof(MyObject)); //sizeof() gets the number 
						 //of bytes that MyObject 
						 //takes up, int + int.

	//Save the second instance to the file.
	ofs.write((char*)&o2, sizeof(MyObject));

	//Close the file.
	ofs.close();

	//Change the values of o1 and o2 so that we can see loading in action.
	o1.x = 100;
	o1.y = 100;
	o2.x = 100;
	o2.y = 100;

	//Print the values of o1 and o2 to the console
	//so we can see that the values have been changed.
	cout << "New Values:" << "\n";
	cout << "o1.x = " << o1.x << "\n";
	cout << "o1.y = " << o1.y << "\n";
	cout << "o2.x = " << o2.x << "\n";
	cout << "o2.y = " << o2.y << "\n";

	//Now load the game that we have saved.
	//Open the file for reading.
	ifstream ifs("mysave.sav", std::ios::binary);

	//Load the values of the two instances from the file.
	//o1
	ifs.read((char*)&o1, sizeof(MyObject));
	//o2
	ifs.read((char*)&o2, sizeof(MyObject));

	//Close the file.
	ifs.close();

	//Print the values of o1 and o2 to the console
	//so we can see that the values have been loaded
	//correctly.
	cout << "Values Loaded:" << "\n";
	cout << "o1.x = " << o1.x << "\n";
	cout << "o1.y = " << o1.y << "\n";
	cout << "o2.x = " << o2.x << "\n";
	cout << "o2.y = " << o2.y << "\n";

	cout << "\n" << "Press Enter to exit.";

	getchar();

	return 0;
}
Next Tutorial >>