Abstraction is a core concept in computer science. It is the process of providing essential information to the outside world, while hiding the background details. Think of your Blu-Ray player connected to your TV. You know that it has a disc tray to load Blu-Ray discs, you know that you push the play button to output some video signal. But what about the internal mechanics of the player? The way the laser reads the disc, how the disc spins inside the player, these are all hidden from the user.
Thus, we can say the Blu-Ray player clearly separates its internal implementation from its external interface. It allows you to utilize its externals like playing, pausing and going back and forward through the movie, while having no knowledge of what happens inside.
Getting back to programming, we will see that abstraction plays a big role in classes.
Say for example, someone asks you to write for them a class which represents a fraction of the from:
F = A / B
It should hold an integer for the numerator and an integer for the denominator.
So say we write:
class Fraction{ int num; int denom; };
This class now holds a numerator and denominator.
However note that the denominator cannot be equal to 0.
How can we be sure that when someone takes this class and uses it they can never set it to 0?
Private and Public Members
One key difference between the C struct and the C++ class is that, in your C struct, everything by default is considered “public” meaning you can declare an struct type variable in main and automatically access it’s data members using the dot operator.
However you have a distinction in C++.
By default, all members of a class are private.
Your class really looks like this:
class Fraction{ private: int num; int denom; };
If we wanted to make our class identical to the C struct, we have to declare our members as public members like so:
class Fraction{ public: int num; int denom; };
We can now create objects of the Fraction class and access it’s internal members with the dot operator like so:
class Fraction{ public: int num; int denom; }; int main() { Fraction a1; a1.num = 2; a1.denom = 0; return 0; }
Observe that in the above example we are setting the denominator of the a1 object to 0.
How can we prevent a user of our class from doing so?
Methods
An additional difference between the traditional C struct and the class is that a class can have functions as members in addition to variables.
These are called function members, or methods.
Let’s take a look at an example.
class Fraction{ public: int num; int denom; int get_Num() { return num; } };
We have written our first function member.
It’s called get_Num, it returns an integer and doesn’t need an argument.
Inside we just say return the data member “num”.
It’s very important to understand this part.
Every single object we create of the Fraction class will have 2 data members, an integer called num and an integer called denom.
What happens if we have multiple Fraction objects like so:
class Fraction{ public: int num; int denom; int get_Num() { return num; } }; int main() { Fraction a1; a1.num = 2; a1.denom = 5; Fraction a2; a2.num = 9; a2.denom = 1; Fraction a3; a3.num = 6; a3.denom = 4; int q = a2.get_Num(); return 0; }
Okay, so we instantiated 3 Fraction objects: a1, a2, a3.
We set their respective members with the dot operator.
Finally we called our function member, the function returns an int, so we just store it in the variable q.
In our function member body we have the line
return num;
Since we have 3 objects, all of which have their own integer called num, which num gets returned??
The answer is: the num of the object on which we invoked get_Num() on.
We wrote:
int q = a2.get_Num();
Thus get_Num() is invoked on the object a2, resulting in the num of a2 to be returned.
q now holds 9.
Let’s look at another function member.
class Fraction{ public: int num; int denom; int get_Num() { return num; } int get_Denom() { return denom; } void set_Num(int x) { num = x; } void set_Denom(int y) { if (y != 0) denom = y; else printf("Error!"); } };
Observe that we have written a set_Num and set_Denom function, through these functions we can set the member functions of any object. Inside the body of the set_Num function, we just take the argument and assign that to the object’s Num variable.
For the set_Decom function, we are checking, is the argument 0? If it’s zero, print out an error since the denominator of a fraction cannot equal 0. Otherwise, set the Denom member of the object to the provided argument.
So, these functions through which we access our data members are called accessors.
So through these we can control what values a user is allowed to assign to our data members. Now, we just have to stop the user from having direct control over the data members. How can we do that?
Easy. Make the private!
class Fraction{ private: int num; int denom; public: int get_Num() { return num; } int get_Denom() { return denom; } void set_Num(int x) { num = x; } void set_Denom(int y) { if (y != 0) denom = y; else printf("Error!"); } };
There you go, our data members: Num and Denom are private and cannot be directly accessed. Our function members: the 4 accessor functions are public and the only way to access our private data members.
Note that inside the bodies of the function members, we do directly access the private members: Num, and Denom.
Private data members of a class can only be accessed within function members of that class.
So any function member of the Fraction class can directly access Num and Denom.
That is fine, since we, the designer, write this class. We decide exactly how each function behaves and write it ourselves.
This whole idea of hiding data, or protecting it, is called encapsulation.
We than pass this on to anyone who needs to use our class in their program.
What if we don’t want someone to see the body of our functions?
Maybe we wrote some genius Add_Fraction() function member in our Fraction class. It accepts 2 Fraction objects and adds them. However, when we give our class for someone to use, we just want them to see what the function members that our class contains are. We don’t want to give away source code.
To do this, we can break up our class into 2 files.
Fraction.h
Fraction.cpp
Our first file, Fraction.h is the following:
This .h file is what we give to a user, they can see how the class looks, what functions it has, what private members, etc.
The .cpp file is the source code itself. Note the syntax each function prototype is written as follows:
int Fraction::get_Num()
When we put the class name, followed by the 2 colons, that means that the function belongs to that class.
Note, we can make 1 small change to our Fraction.h file.
Observe that our .cpp file is including the Fraction.h file, since it needs to use the class it must include it.
If we are using our Fraction.h file in a huge program. We can have hundreds of files in the program, different files including each other.
A possible error that can occur is if Fraction.h is included more than once.
To remove this possibility we use something called an include guard.
#ifndef FLAG #define FLAG //code here #endif
What that says is, if our .h file hasn’t already been included, then include the .h file and raise a flag that says “I’ve been included!” if another file tries including the .h file again, it will see the raised flag and not include it.
The keyword FLAG can be anything, but it must be unique to the .h file we are using it with. That means if we add an include guard to multiple .h files, they obviously cannot all use the same FLAG, or else it would be meaningless.
It’s common convention to use the class name as the FLAG name, in all capital letters.
Thus in our case, we have:
#ifndef FRACTION #define FRACTION //code here #endif
You must be logged in to post a comment.