Introduction
In the previous tutorial, we introduced structures, which allow you to store multiple, related values of different data types. In this tutorial, we are going to introduce classes which are a way to store related values of different data types and functions to manipulate that data. Classes help organize your code so that related information is in the same place making it easy to find and use.
Code
From our previous example of Fruit Wars, here’s Inventory as a class, well part of it at least:
class Inventory
{
public:
double Money;
int GrapesCount;
void BuyGrapes(int quantity, double price)
{
if(price * quantity <= Money)
{
Money -= price * quantity;
GrapesCount += quantity;
}
else
{
cout << "You don't have enough money." << endl;
}
}
void SellGrapes(int quantity, double price)
{
if(GrapesCount >= quantity)
{
Money += price * quantity;
GrapesCount -= quantity;
}
else
{
cout << "You don't have enough grapes." << endl;
}
}
};
Classes are like a struct, but they have methods as well. (Full disclosure, through the magic of pointers, a C style struct can have methods as well. See the comment below about C++.) The other difference is that in a class, the variables/methods contained within it are private by default. This means that only the class itself can use these variables/methods. That is why we have to have the public: keyword at the top of the class, so that we can access this data from outside of the class.
So, you might be wondering when would I ever want to just access something from within the class? Well, if you class had some internal state that wanted to keep, storing it in a private variable would make it impossible for someone to randomly change it out from underneath you. Also, private variables allow you to control access to the data stored in the class, to help limit the changes that it is used incorrectly.
When I first implemented Inventory, I just copied and pasted the GrapesCount variable and BuyGrapes and SellGrapes methods for the other types of fruit. This leads to a lot of duplicated code which is a very bad idea. Copied and pasted code is a problem because if there is a bug in that code, you need to hunt down every place that it was copied and pasted and fix it there. This can be very difficult to do with a large code base, so if you find yourself copying and pasting, there is usually a better way. In this case, I created a class called Fruit to store the quantity and handling buying and selling:
class Fruit
{
private:
int Quantity;
public:
Fruit(int initialQuantity)
{
Quantity = initialQuantity;
}
int GetQuantity()
{
return Quantity;
}
void Buy(int quantity)
{
Quantity += quantity;
}
void Sell(int quantity)
{
Quantity -= quantity;
}
};
This class demonstrates the use of a private variable to store the amount of fruit that we have. The only way to change the quantity is to buy and sell fruit. Something like this:
Fruit apples(3);
apple.Quantity = 5;
Would result in a compiler error. Another interesting aspect of the Fruit class is that it has a method that has the same name as the class that is called when the object is created. This is a special method that is called a constructor. This method is special because it has to be called when the object is created and it can only be called once. Since Quantity is now private and can’t be directly accessed, the constructor gives the user a way to set the initial number of apples that they have lying around the house.
So, here’s what the Inventory class looks like using the Fruit class:
class Inventory
{
public:
double Money;
Fruit Grapes;
Fruit Blueberries;
Fruit Strawberries;
Fruit Raspberries;
Inventory() :
Grapes(10),
Blueberries(3),
Strawberries(0),
Raspberries(0)
{
Money = 25.00;
}
void Print()
{
cout << "Inventory:" << endl;
cout << "\t$" << Money << endl;
cout << "\tGrapes: " << Grapes.GetQuantity() << endl;
cout << "\tBlueberries: " << Blueberries.GetQuantity() << endl;
cout << "\tStrawberries: " << Strawberries.GetQuantity() << endl;
cout << "\tRaspberries: " << Raspberries.GetQuantity() << endl;
}
Fruit Buy(Fruit item, int quantity, double price)
{
if(price * quantity <= Money)
{
Money -= price * quantity;
item.Buy(quantity);
}
else
{
cout << "You don't have enough money." << endl;
}
return item;
}
Fruit Sell(Fruit item, int quantity, double price)
{
if(item.GetQuantity() >= quantity)
{
Money += price * quantity;
item.Sell(quantity);
}
else
{
cout << "You don't have enough product." << endl;
}
return item;
}
};
Notice how the class now has a constructor as well, because we need to set the initial quantities of each of our pieces of fruit. The colon after the constructor is where we need to call the constructors of the Fruit classes contained within our class.
At the beginning of the tutorial, I said that classes help organize your code by grouping together related variables and methods. Since this is the case, it is also a great place to break your code up into multiple files. Each class is broken up into two files: one that states the name of the class and what variables/methods it contains (*.h) and one that fleshes out the details of how each method works (*.cpp). Here’s what the .h file looks like:
#ifndef FRUIT_H
#define FRUIT_H
class Fruit
{
private:
int Quantity;
public:
Fruit(int initialQuantity);
int GetQuantity();
void Buy(int quantity);
void Sell(int quantity);
};
#endif
And here is what the .cpp file looks like:
#include "Fruit.h"
Fruit::Fruit(int initialQuantity)
{
Quantity = initialQuantity;
}
int Fruit::GetQuantity()
{
return Quantity;
}
void Fruit::Buy(int quantity)
{
Quantity += quantity;
}
void Fruit::Sell(int quantity)
{
Quantity -= quantity;
}
What happens with multiple files is that when the compiler finds a #include statement, it copies and pastes the code from the .h file into the file. That’s why you need all of the #ifndef and #define stuff at the top of the file. This tells the compiler to ignore it, if it has already seen it, so that you don’t get an error about defining something multiple times. This is a standard convention that should be used with all of the classes that you create.
In the .cpp file, we need to tell the compiler the containing class of the method that we wish to implement, so that is what the Fruit:: is doing. The reason for this is that the compiler doesn’t use file names (and they don’t have to match), so this is the way that the compiler knows what logic goes with the method on the class. It can be pretty daunting at first, but it is a standard pattern that that you can follow every time.
Attached is the updated code for FruitWars, using classes.
Summary
In this tutorial, we learned about classes, which are a way to group related variables and methods.
In the next tutorial, we will start diving into pointers, which are traditionally one of the more difficult parts of C++. If you look at the way that the Buy method works on the Inventory class works after the introduction of the Fruit class, you will notice something a bit strange. It takes in a Fruit object and it returns a Fruit object. In the next tutorial, we will go over why this is the case, and how we can make a slight modification to the code so that we can manipulate the class within the method.
If you have any questions or comments about what was covered here, post them to the comments. I watch them closely and will respond and try to help you out.