Abstraction and Classes
Learn how to define, instantiate, and utilize classes, understanding how they form the backbone of object-oriented programming.
In the previous chapter, we discussed how we can approach programming by thinking about objects - things that exist within the world our program is trying to simulate.
For example, were we making a fantasy game, we might be simulating objects like monsters, weapons, and magical spells.
We saw how we have two fundamental tools to simulate an object:
- variables, that represent the current state of the object
- functions, that allow the object to perform actions
// Variable
int Health{150};
// Function
void TakeDamage(int Damage){
Health -= Damage;
}
Abstraction
Our game might have thousands of objects. We don't want to define tens of thousands of variables and functions to manage this.
Instead, we want to write code that can apply to large sets of these objects. For example, maybe 100 of our objects will be monsters that the player can fight.
All the monster objects in our game likely share a lot of similar characteristics - for example, they may all have Health
variables, and they all have TakeDamage()
functions.
So, rather than writing code for 100 individual monster objects, we write code that works for all monsters, which they can then share.
This process of generalization, where we take specific objects and group them into more general categories is called abstraction
Test your Knowledge
Abstraction
What is Abstraction?
Built-in Types
We already saw examples of abstraction in the built-in types we've been using. For example, int
is an abstraction.
C++ compilers didn't have to define code for every individual integer, like 3
, 42
, and 53,195
. Rather, they created an abstract concept of what an integer is, and the things an integer can do.
All integer objects then share those capabilities - like the ability to be incremented using the ++
operator or be logged to the terminal using the <<
operator.
The int
data type is what provides that abstraction. Similarly, bool
provides an abstraction for true and false values, whilst string
abstracts blocks of text.
User-Defined Types
Just as C++ provides some built-in types that are useful to a wide range of programs:
int Level;
bool isAlive;
string Name;
So too does it give us the ability to create user-defined types. These are types that we can set up to generalize the categories of objects that will exist in our specific project:
Monster Goblin;
Weapon IronSword;
Spell Fireball;
A class is the main way we define a custom type. To create a user-defined type, such as Monster
, we would create a class
like this:
#include <iostream>
using namespace std;
class Monster {
public:
// Class code here
};
int main() {
// We can now create Monsters
Monster Goblin;
}
We describe the meaning of the public:
line later in this chapter. For now, let's just ensure it is included at the top of our classes.
Test your Knowledge
Creating Classes
How can we define a class called Weapon
in C++?
Classes vs Objects
There is often some confusion at this point about what differentiates an object from a class. This will become clear as we start creating classes and objects in future lessons, but let's discuss it briefly here.
The English definition of class is: a category of things having some property or attribute in common
It means the same thing in programming. A class defines an abstract category of things that are, in some way, similar. The things within that category are objects.
Consider this example.
Monster Bonker;
Monster Basher;
Here, Monster
is a class. It is not a specific monster - it is an abstract idea of what it is to be a monster. It includes a description of what variables and functions all monsters have.
A class also grants the ability to create new objects of that class. Above, we're using that ability to create two specific monsters. Bonker and Basher are both objects.
Test your Knowledge
Instantiating Classes
What is a Class?
What statement allows us to create a new Weapon object from this class?
class Weapon {
public:
}
Class Members
To provide objects of our classes with the ability to store their state and perform actions, we need to define those variables and functions as part of our class.
Variables and functions that belong to a class are sometimes referred to as class members.
The syntax we use for class members is identical to what we're familiar with in the previous chapter. We just place our variables and functions within the curly braces of a class definition.
For now, we should also ensure it is below the public:
line within our class:
class Monster {
public:
// a variable
int Health { 150 };
// a function
void TakeDamage(int Damage) {
Health -= Damage;
}
};
The Member Access Operator
Once we've created an object from our class, we will want to access the variables and functions that are granted to our object by the class.
We do that through the member access operator, which is a simple period: .
For example, accessing the Health
variable of a Monster
object would look like this:
Monster Bonker;
Bonker.Health;
Calling a function would look like this:
Monster Bonker;
Bonker.TakeDamage();
Below, we show these concepts in a simple program:
#include <iostream>
using namespace std;
class Monster {
public:
int Health{150};
void TakeDamage(int Damage){
Health -= Damage;
}
};
int main(){
Monster Bonker;
cout << "Bonker Health: " << Bonker.Health;
Bonker.TakeDamage(25);
cout << "\nBonker Health: " << Bonker.Health;
};
Bonker Health: 150
Bonker Health: 125
We can use a class variable in the same way we use any other variable of that same type. Above, Bonker.Health
is an int
, so we can use it like any other int
. For example, we can:
- Pass it as an argument to a function
- Copy its value to a new variable
- Use the
++
to increment it - Use the
!=
to compare it to another number, generating a boolean result
Test your Knowledge
Class Member Access
Imagine we had the following code:
class Weapon {
public:
int Damage { 50 };
};
Weapon IronSword;
What statement allows us to access the Damage
integer of our object?
Class Function Prototypes
In the previous chapter on forward declarations, we introduced how a function can be declared and defined in different places in our code.
Forward Declarations
Understand what function prototypes are, and learn how we can use them to let us order our code any way we want.
We can apply this same technique to class functions too, should we need to. Within our class definition, we provide the prototype for our class function:
class Monster {
public:
int Health { 150 };
// Declaration
void TakeDamage(int Damage);
};
We can then provide the definition elsewhere in our code.
The only difference is that, when we're defining a class function, we need to additionally provide the class name. We do that by using the ClassName::FunctionName
pattern.
For example, to define the TakeDamage
function for the Monster
class, we would do this:
class Monster {
public:
int Health { 150 };
// Declaration
void TakeDamage(int Damage);
};
// Definition
void Monster::TakeDamage(int Damage) {
Health -= Damage;
}
This might seem quite weird but, for reasons we'll cover later, a lot of C++ code is written this way. We'll stick with declaring and defining functions together in this course, but for now, just note that this separation is possible.
Summary
The key points covered in this lesson were:
- Introduced the concept of classes as fundamental to object-oriented programming.
- Explained abstraction as the process of generalizing specific objects into broader categories, allowing for more efficient and manageable code.
- Discussed built-in types in C++ like
int
,bool
, andstring
, showcasing them as examples of abstraction. - Covered the creation of user-defined types through classes, enabling the definition of custom objects specific to a project's needs.
- Clarified the difference between a class and an object, with a class being an abstract category and an object being an instance of that class.
- Introduced the concept of instantiating a class to create objects, and the use of the member access operator (
.
) to interact with an object's variables and functions. - Demonstrated how to define class members, including both variables and functions.
- Provided insights into class function prototypes, showing how functions can be declared within a class and defined elsewhere.
Encapsulation and Access Specifiers
A guide to encapsulation, class invariants, and controlling data access with public
and private
specifiers.