C++ - Classes I
About classes
I'm pretty sure that most of the people who are trying to learn how to program hear the term 'Class' at least once whilst learning.
Most people, including myself, when first faced with classes just start scrolling and scrolling and looking and trying to understand even use classes if functions do a pretty similar thing, right?
Well, that was at least my problem, nobody, not one tutorial or book that I've read explained why one would want to use classes. It was up to me, to do endless nights of programming, searching the web, and reading loads of books on the matter.
This "tutorial" is aimed at explaining why one would even want to use a class and when should you use a class. This tutorial expects you, the reader, to have a solid knowledge of c++, things like data types, loops, functions and pointers to name a few.
A simple example.
So, to jump to the point.
A class is basically a blueprint for creating an object and you've probably already read that somewhere, but what exactly does that mean?
Well, let's we have a class called Person, what does every single person have?
Well, they have a name, a last name, and they do have a number of years they've lived on this planet, aka years.
So, how would we go about "representing" that data in c++? Well simple, just like this:
string first_name = "John";
string last_name = "Doe";
int years = 24;
What can we do with this data?
Well for starters, we could print it to a console, maybe write it to a file, or even add all those values to 3 different vectors called first_name, last_name and years.
But, what would you do if you wanted to have another person, what if you wanted to print all the people you've created?
You would have to either create a loop, which would ask for an input, and then store that input to a vector of names, and then another loop to print all those names, and last names and years.
Something like this:
vector<string>vFname; vector<string>vLname; vector<int>vYears; int choice; cout << "Enter how many people you would like to add: " << endl; cin >> choice; string sFname; string sLname; int nYears; for(int i = 0; i<choice; i++){ cout << "Enter first name: " << endl; cin >> sFname; cout << "Enter last name: " << endl; cin >> sLname; cout << "Enter years: " << endl; cin >> nYears; vFname.push_back(sFname); vLname.push_back(sLname); vYears.push_back(nYears); } for(int k = 0; k< vFname.size(); k++){ cout << "First name: " << vFname[k] << endl; cout << "Last name: " << vLname[k] << endl; cout << "Years: " << vYears[k] << endl; }
Now, you probably see why this code is bad, you have 2 for loops, and it's pretty much hard coded, which is a bad practice and is frowned upon for a reason.
Do you see what the problem is with the last for loop? (Try and think about the problem, the solution is right underneath if you get stuck.)
For those of you who have noticed it, good job!
For those of you who haven't, the problem is, what if, for some reason, not all of those vectors are the same size?
If one of them is smaller, the compiler is going to tell us "I can't find that index".
If you thought of some other problem, that's okay, this code snippet is full of problems and should not be used as such.
Same problem, but with classes
Now that we have shown an example without classes, let's see how it looks like with classes.
So instead of creating all of those variables here, we will mostly be declaring them in our class files.
Let's create a class, shall we?
The process is not all that same for all the IDE's out there, but basically, press right click on your project, and click add class.
The process is not all that same for all the IDE's out there, but basically, press right click on your project, and click add class.
If you are using a regular text editor, such as vim, notepad++, atom, sublime, etc.
You can just create two new files, Person.hpp and Person.cpp.
So, now that you have your files, and you have your main.cpp file, we can start.
So your main file should consist of some header inclusion and a main function called 'main'.
Your other files will either be empty, or will have some code in them, depending on the IDE.
My files are empty(I am using visual studio code), so let's add some code in it, and explain it.
You first want to head into your Person.hpp file and add the following code. (You might need to include some headers, such as string)
class Person{ public: Person(); ~Person(); protected: std::string m_sFirstName;
std::string m_sLastName;
int m_nAge;
};
The code we just added is a basic blueprint for our not yet created objects.
The first keyword here is class, the keyword class tells our compiler that the next "Word" is the name of our class, and that everything between the curly braces is a part of that class.
Quick rundown on Class access modifiers
Second thing you may notice are these "public" and "protected" keywords, those keywords represent if the rest of the program can see the things declared inside and are also called "Class access modifiers".
For example, everything inside "public" can be seen by the rest of the program, our constructor and destructor must be public so that we can instantiate that class(These terms are explained right after this little chapter).
So if we were to create a variable called "heightOfPerson" inside of the public 'domain', every part of our program could read that variable and change it on accident or could just cause a bunch of issues which should have never happened.
The "protected" keyword means that the only parts of the program that can access that are the methods inside our class or classes that were inherited from our class Person (More about inheritance in some other post).
There is also one more keyword, and it's called "private", the only part of the program that can access private domain is a method inside of the class Person.
Here is a quick example of class access modifiers in action:
Person.hpp
class Access { public: string name; void tellName(); private: int age; protected: int last_name; void giveLName(); }; int main(){ Access new_access; cout << new_access.name; //// This is allowed, variable name is declared in the public domain new_access.tellName(); //// This is also allowed, method tellName is in the public domain cout << new_access.age; //// This is not allowed, age is a variable declared in the private domain cout << new_access.giveLName(); //// Nopeeee, the method giveLName is in the protected ////domain }
Back to our class.
So, the next thing we want to take a look at, are these "Person()" and "~Person()" things, what are those and what do they represent?
Person() is called a constructor, it's a special method that is automatically called when an object is about to be created, the constructor must always be public, must be called the same as the class, and must not have any return values.
A constructor can take in parameters, which when the object is being created must also be "supplied" (This will be shown on the example below).
Also, in our .cpp file, the constructor must be implemented, this will also be shown in the example.
~Person() is a destructor, destructor is also a method which delete the object, it is automatically called when the function ends, the program ends, or the object is being deleted.
Example - implementation of constructor, destructor and our class
Person.cpp
#include "Person.hpp"
Person::Person(){
}
Person::~Person(){
}
As you might see, this is our declaration of the constructor and destructor, they are declared in a manner of specifying the class name then typing "::"(Scope resolution operator) then the constructor name.
Same goes for the destructor.
Let's place some data members into our class.
Person.hpp
#include <string> class Person{ public: Person(std::string sFirstName, std::string sLastName, int nAge); ~Person(); protected: std::string m_sFirstName; std::string m_sLastName; int m_nAge; };
As you might see, our code is changed a bit, inside of our protected domain, we have added three new data members: first name, last name and age.
These variables can be called what ever you wish, but do keep in mind that when you implement them in the .cpp folder, the name must be the same.
We have added our data members into the protected domain, so that only our class can access them.
*note*
You might see that in our constructor, the data members aren't called as they are in the protected domain, the thing is, in the constructor, the variables can be called whatever you wish, same goes for the implementation
Here is the implementation
Person.cpp
#include "Person.hpp" Person::Person(std::string sFirstName, std::string sLastName, int nAge){ m_sFirstName = sFirstName; m_sLastName = sLastName; m_nAge = nAge; } Person::~Person(){ }
As you might see, our Person.cpp has also changed a bit, we have added our parameters into our constructor, and then inside it, we have said that our protected data memember m_sFirstName,m_sLastName and m_nAge are equal to the variables that we will supply later on when we create the object(I will mention it again when it happens).
So, what can we do with this?
Well we can create an object, but currently we don't have any methods inside our class so we can't really do much with our object currently, so let's add in a method.
Person.hpp
#include <string> class Person{ public: Person(std::string sFirstName, std::string sLastName, int nAge); ~Person(); std::string GiveName(); protected: std::string m_sFirstName; std::string m_sLastName; int m_nAge; };
Person.cpp
#include "Person.hpp" Person::Person(std::string sFirstName, std::string sLastName, int nAge){ m_sFirstName = sFirstName; m_sLastName = sLastName; m_nAge = nAge; } Person::~Person(){ } std::string Person::GiveName(){ return m_sFirstName; } |
As you might see, we have added the declaration to the .hpp file by stating that we need a method called GiveName, which is of type string (it returns a string).
In the .cpp file, we have created the implementation by stating that we need a method of type string, which belongs to the class Person (We have done this by typing "Person::") and then stating its name, inside the body of the method, it's the same as all the other functions that you've worked with, you type in some code and it returns or prints or whatever you want it to do.
Let's now finally create an object.
main.cpp
#include <iostream> #include <string> #include "Person.hpp" int main(){ Person person1("John", "Doe", 24); std::cout << person1.GiveName() << std::endl; }
What have we done here?
Well for starters we included "Person.hpp", which means we have included the blueprint for our class.
Next up we have this line: "Person person1("John", "Doe", 24);" - What does this mean?
The first thing is "Person" We are basically telling our compiler that we want to create an object of type Person right here.
If it's a bit confusing, its pretty much the same thing as typing 'int age' or 'string person', that word "Person" states what type of object we are creating.
We can say, to make you understand it better, that when you create a variable of type int called age (Int age) you have created an object of type integer, which then holds a value.
So by typing Person we have created an object of type(class blueprint) Person which is called person1.
Now remember that a few chapters back I've said that "I will mention it again when it happens" when I was talking about our parameters?
Well, basically, by typing ' "John", "Doe", 24 ' we have told our constructor that "m_sFirstName" is equal to "John", that "m_sLastName" is equal to "Doe" and that the "m_nAge" is equal to "24".
Now you have successfully created an object, awesome!
What can you do with it ?
Remember that method that we have created called 'GiveName' ?
Well we can now use that method, and by typing in "person1->GiveName()" we are telling our compiler that we want to call the method GiveName with the object person1.
Our constructor then goes on and sees what the method looks like(what it should do) and gives us the result.
When you run the code, you should see "John" pop up in your terminal.
*Quick note*
If you are using linux and get the "undefined reference to ".
If you are running under linux and you are manually building the code with g++, when compiling, you must specify the class that is used when compiling, this is how to do it in our example:
g++ -o main Person.cpp main.cpp
The real deal.
You now have a working class, you have successfully created an object, now you probably ask yourself, how was this more simple than writing that code from beginning?
I will explain it right now!
Let's say that you have 3 vectors filled with names, last names and ages and they are all the same size.
Or that you are reading from a file and then entering that data (Creating objects from XML will be covered in some other post).
What you can do with that information is create objects out of all of those information and call the methods onto them.
Tasks:
1. Create a method GiveLastName() which when called returns the last name.
2. Create a method GiveAge() which when called returns the age.
So for the sake of this tutorial, let's say you have some information, let's say that you have 3 vectors filled with names, last names and ages of people. You can do the following:
#include <iostream> #include <string> #include <vector> #include "Person.hpp" int main(){ std::vector<std::string> vFname; std::vector<std::string> vLname; std::vector<int> vAge; std::vector<Person*> vPeople; for(int k = 0; k<vFname.size(); k++){ vPeople.push_back(new Person(vFname[k], vLname[k], vAge[k])); } std::vector<Person*>::iterator itPeople; for(itPeople = vPeople.begin(); itPeople != vPeople.end(); ++itPeople){ std::cout << (*itPeople)->GiveName() << std::endl; std::cout << (*itPeople)->GiveLastName() << std::endl; std::cout << (*itPeople)->GiveAge() << std::endl; } }
Yet again, we have quite a bit of things over here, so let me explain them.
We have created 3 new vectors which, for the sake of this tutorial we will say are filled with names, you can fill the vectors from an xml file, json or a regular text file. (As I said, this will be covered later)
You can also ask for user input and add that input to vectors.
The second thing that might seem off is that the fourth vector is of a type "Person*", now you are probably asking yourself, what does that mean ?
Well it's pretty simple actually, when you have a vector of strings, such as vFname, it's content can be viewed in this fashion: vFname = {"Name1", "Name2","Name3","Name4","etc"}.
When you have a vector whose type is a class that you have created, that vector is a vector whos type is a pointer to objects of a class you have specified.
That vectors content can be viewed in this fashion:
vPeople = {("fname1",lname1, 20 ), ("fname2", "lname2", 24), ("fname3", "lname3", 26),
("fname4", "lname4", 23), ("etc","etc", xyz)}.
As you might see, that vector has objects in it, it has objects we have created.
You can see this in action right inside the body of the first for loop, where we push back new instances of the class Person, or it would be better to say that we are pushing newly created objects into our class.
This is possible due to that vector being the type of that class, so that vector has the "blueprint" for creating the classes, and it accepts object in it.
This can also be said for the string vector, string vector accepts "objects" of type string, we gave the vector the set of rules(blueprint) which it has to follow.
The next thing you might see is an 'Iterator', an iterator points to the memory location(address) of a container.
You can see the iterator in action, when we say that we want to "cout" the GiveName() of a current object, we simply state (*itPeople)->GiveName(); and it magically gives us the name of the current person(last name and age as well) and it goes on until it is finished.
Iterators are extremely useful in many scenarios, and I recommend that you get familiar with them and start using them.
Tasks:
1. When printing the content, check if a certain name is in the vector, and if it is, print only that persons information.
2. Create another class, called animal
2.1 Add a data member called m_nAnimalId into our person class (Don't forget to add it into the constructor!)
2.2 Inside the animal class, create data members called m_nId, m_nName, m_nSpecies and methods called GiveName() and GiveOwner()
HINTS:
When creating objects for the class Person, you need to supply the Id of his animal as well.
You can do this by first using an iterator through vector of type "Animal", and then appending that id to the owner.
You can later check if an animal belongs to an owner through two nested 'for(iterators)' like so:
for(itPeople = .... )
for(itAnima = ....)
if( (*itPeople)->GiveName() == (*itAnimal)->GiveName() )
/*
Now you can print the persons name, lname animal name, species and so on.
*/
Advanced tasks:
1. Implement a system that reads an xml file and adds information from that XML file into a vector.
2. Implement a system that reads a text file and adds information from that text file into a vector.
Completed codes:
main.cpp
#include <iostream> #include <string> #include <vector> #include "Person.hpp" int main(){ std::vector<std::string> vFname; std::vector<std::string> vLname; std::vector<int> vAge; std::vector<Person*> vPeople; for(int k = 0; k<vFname.size(); k++){ vPeople.push_back(new Person(vFname[k], vLname[k], vAge[k])); } std::vector<Person*>::iterator itPeople; for(itPeople = vPeople.begin(); itPeople != vPeople.end(); ++itPeople){ std::cout << (*itPeople)->GiveName() << std::endl; std::cout << (*itPeople)->GiveLastName() << std::endl; std::cout << (*itPeople)->GiveAge() << std::endl; } }
Person.hpp
#include <string> class Person{ public: Person(std::string sFirstName, std::string sLastName, int nAge); ~Person(); std::string GiveName(); std::string GiveLastName(); int GiveAge(); protected: std::string m_sFirstName; std::string m_sLastName; int m_nAge; };
Person.cpp
#include "Person.hpp" Person::Person(std::string sFirstName, std::string sLastName, int nAge){ m_sFirstName = sFirstName; m_sLastName = sLastName; m_nAge = nAge; } Person::~Person(){ } std::string Person::GiveName(){ return m_sFirstName; } std::string Person::GiveLastName(){ return m_sLastName; } int Person::GiveAge(){ return m_nAge; }
Comments
Post a Comment