Ever wondered how they maintain all the data and behaviors for the items in your inventory in a video game? Or maybe you've thought about how super-complex complex systems like banking systems, store management software, automotive digital diagnostic systems and tons of other applications were built? There's often a good chance that many of these applications use some form of Object-Oriented Programming. In this course, we look at Object-Oriented Programming, focusing on objects and classes.
- Understand how classes are designed and constructed and how objects are created from them
- Explore both the data members and member functions from which we build classes
- Learn how to instantiate classes as objects
- Learn how to use a rectangle class and a book class together and test them out
- Beginner coders, new to C++
- Developers looking to upskill by adding C++ to their CV
- College students and anyone studying C++
To get the most out of this course, you should have a basic understanding of the fundamentals of C++.
In the previous lecture, we got a brief taste of what classes and objects are, and discussed the three primary principles of object oriented programming. In this lecture, we discuss encapsulation, which is one of those three principles. Encapsulation, again, refers to the uniting of behaviors and data into a single entity, namely an object. There are multiple advantages that encapsulation gives us, specifically in terms of security and portability. In terms of security, encapsulation promotes, so called information hiding, where the data inside the object is protected from unauthorized access from the outside world, in other words, anything outside of the object. In terms of portability, we gain the advantage of being able to combine various data and behaviors into a single entity and move that entity, the object, around without having all the different parts spread all over the place. To demonstrate encapsulation, we must first create a class. A class acts like a blueprint or schematic for an object. You might think of an architectural diagram for the style of a house and the actual physical houses that can be made from that diagram. So, in that case the diagram acts as sort of like the class, and the actual houses are sort of like the objects. So, let's dive in to creating a class and then swing back and explain the features in more detail. First, we'll create a project called Houses. So, create a new project, Houses, there we go. We will create it and see what happens here. We're going to fill in our skeleton program like we normally do with a single main file. So cpp file, main, and let's give it a skeleton. Okay, using namespace standard, int main. Here we go and then return zero, of course. All right. When we build our own class in just a moment, we're going to use some keywords and syntax that is new, but don't worry we'll make much more sense of this in just a little bit. So, I'm going to include string because we're going to make use of string, so we include the string library here and then I'm going to have my class defined before main. So, that's the end of the class named House. We have public and I like having the word public and the word private indented. So, I don't like that it tries to line it up right here, but that's really a matter of preference, we'll talk about what this means in just a moment. So, in the private section, we'll have int numStories int numWindows and string representing the color. That's the private data. Now, we said that classes have behaviors and data and behaviors are really functions. That's really what they are. So, void setNumStories. So, this is the number of stories or levels that the building has and this is a new keyword, so this may not make sense right now, but what I'm saying is my numStories right here is equal to this parameter right here and we're going to make setnumWindows and of course, this numWindows equals the parameter that has passed in, and we have of course setColor, color equals color. So, those are our setters, and now we need getters, and we'll describe what the difference is between these in just a moment, and also some of this extra syntax here, so numStories and then right here int getNumbWindows const, returnNumWindows and string getColor const, that returns the color. And now in the main function, we're going to make use of our house, so I'm going to say myHouse right here and yourHouse right there. So, even though we're not explaining it thoroughly just yet, you can probably see that this class is kind of acting as a blueprint and it's describing the different behaviors which are functions or member functions in this case, and the data that we are protecting right here which is private to our class. We'll talk about that in just a minute. So you can kind of get an idea of what a house is, it has a number of stories, number of windows and a color, and we have different ways of accessing that data. So, myHouse.setNumStories to two, so it's got two stories setNumWindows, windows to six, and then myHouse.setColor to red, yourHouse on the other hand we're going to set the number of stories to three, yourHouse.setNumWindows to 10, and yourHouse.setColor to blue. So, myHouse has two stories, six windows and is red, yourHouse has three stories 10 windows and is blue. Now I'm going to print out some information about our houses. So, My House is and then we'll say myHouse.getColor and has, I think I'm going to put this on the next line here. There we go. So, notice I'm putting spaces in here to make the output cleaner. We'll say myHouse.getNumbStories and myHouse.getNumWindows endl, perfect. Now, I have to print out stuff about your house. So, what I'm going to do is, since it's going to be printing the same thing I'm going to copy and paste. So, that should give you maybe a hint. There might be a better way to do this. But for right now we're good. We're going to say that, Your House is and then I'm going to take yourHouse and I'm going to copy and paste it over where the copied myHouse was. All right. And of course we have to run it to test it. So, I'm going to go to Debug, Start Without Debugging, and if there are no errors, then it should run just fine. Okay, so as we probably expected, says my house is red, has two stories and six windows, which is what we entered into an event originally, right? And then it says your house is blue and has three stories and 10 windows, correct? So, that's what we set for your house. Three stories, 10 windows and it's blue, awesome. The class definition might take a little bit of explaining. So, let's go back up to here, class definition. So, some of this should look familiar though. Can you see that there's functions and also variables? So, the functions that live inside of a class are called member functions or methods. Those are two different terms that are used generally synonymously. Languages like Java tend to use the word method more often, and a lot of developers for C++ use the term member function, but a lot of the documentations started to use the term member more often too. So, method and member function meaning a function that is not one of the global functions we defined outside of main and outside of any class like we've done before. But a member function is a function or behavior that it belongs to this class, so each object exhibits this behavior. The data that lives inside the class are not just regular old variables. These three pieces of data right here, numStories, numbWindows and color, are called data members or fields. Fields is synonymous with data members. You'll also note some new keywords that we haven't really explored together and their brand new. We've got public and private. So, the methods or member functions are public and then this data down here is private. Public and private are called access modifiers or access specifiers. If you try to access any private member of a class outside like the private data in our house class, namely numStories, numWindows and color, then if you try that you'll get an error. So, let's just try it briefly. So, let me think here. So, over here after I set the color I called setColor and everything worked fine because that's a public method, right here SetColor. But if I try to access the private data directly for color, let's see what happens. So, let's say yourHouse.color, see it's even trying to correct me already, equals orange. You'll notice that it's underlining it with red. If I hover over it, it says member house color is inaccessible. So it knows what we're talking about. It knows that it's declared at line 40, but it says it's inaccessible. The reason is it's private. So we have a compile time error. So we can't access the private members of a class from outside that class. That would include methods, if we had any private methods and data such as the color that we just tried to access. To provide access to them... We can define member functions that are public, so that you can access the private data through a well defined interface. Two of the major types of member functions or methods that we deal with when we work with classes, are setters also called mutators and getters also called accessors. So, setters, mutators, getters or called accessors. Setter methods modify the internal data of an object. So, just like we've done here, we have setters, setNumStories sets my numStories meaning the field numStories to whatever you pass in as a parameter. NumStories right here represents the parameter. The getters on the other hand, called accessors will just return data that you have stored inside of your class. They should not modify the data inside of the method at all. So, if we look at the code, strictly speaking, the way we wrote these getters with const at the end, you don't really have to do this. If I took const off of all of these, the code would still work. However, it's considered good software engineering to only give a method as much access as it needs but no more. The keyword const is very versatile. We've seen it before earlier in the course when we created variables that were constants, that is that they couldn't be changed. In the context of writing const after a member function header, we call it just the method header. This means that the method has access to the data members but cannot change them. Another important feature of the class we wrote is the this keyword I keep talking about it kind of informally and the syntax associated with it. So, if you wish to refer to data members of a class from inside one of the class's methods you can use the this keyword. In our case, since the parameter name is the same as the data member, we have to use the this keyword because if I just put numStories = numStories it thinks we're talking about this parameter. So, more on that in just a second. So, if you don't have this here, it's going to think you're talking about this parameter of course. So, this effect that it has those called name shadowing. The parameter name eclipses or shadows if you will, the data member of the same name. So, the using the this keyword overcomes the problem. So, we know the system will know when we say this, we're talking about a data member not a parameter. So, you might wonder if we could just change the name of the parameter, so it's different from the data member it's changing? The answer is yes. In fact you could if I just say ns right here and then change this to numStories = ns, there's no ambiguity here and this would actually work. You wouldn't need the this keyword. In fact you could use it but you wouldn't need it. But it's preferred to do it this way and we'll discuss why. All right, we're restoring it back to normal. So, just like any regular function, the parameter name can be any valid identifier. But the advantage of reusing the same name as the data member is as a parameter; we don't have to come up with an entirely different set of names for different pieces of data or just settle on a poorly crafted name. So, it's quite common to use the same name for a parameter in a setter as you do for the data members you're setting. Thus by saying this color, with the arrow, this arrow color for example, or this arrow numWindows, this arrow numStories. So, this arrow color = color, makes it so we don't have to come up with a brand new name. Also notice that the setters cannot have the const keyword after their headers. If they did, there would be a compile time error. So, I want to show you something because main is calling set, say set color, set numStories. Let's let's work on color, we'll just do that. If I put const here and then wait a second, you'll notice, wait a minute. What's this little red squiggly here? And if we try to run it, we get a similar error here. It says no operator = matches these operands. Operand types are const string set to standard string. So, it's saying this is a constant. Well we know it's not a constant because we didn't label it as a constant there, but guess what? Because we put this at the end of the header of the method it treats it like it's a constant so you can't change it. So, it makes it useless if we try to make a setter const at the end because then you can't change the data internally. So, we only use const for any methods that don't modify the internal private data of the class. So, another thing you might have noted is that I said the methods act like gatekeepers to the data inside the class. This of course is the information hiding characteristic we discussed a bit ago. We could put some logic such as if or if else statements in the setter methods specifically to protect the internal data. For example, we might use an if statement to ensure the user of our objects including us to not try to set the numbers of windows or a number of stories to a non-positive value such as zero or a negative value. So, if they try to enter an invalid value, we could say print an error after the console or something like that. Better ways to do what we call throwing an exception to indicate the problem has occurred but we haven't discussed exceptions yet but will in another section coming up. For our purposes right now, let's keep our code simple and just let the setters set the data directly without any checking. But keep in mind, to make the code more appropriate for industry level production and deployment, which needs to be more robust, we have to protect the internal data from invalid values and that's quite important. So, before we move on I'd like to issue you a challenge. In this same project houses, I want you to create a function, not a member function, but a regular global function like we learned about in the last section called printHouseData, that takes a house object as its parameter and prints out the color, number of stories and number of windows like we do in our main. Then replace the manual data printing with calls to the functions on the two different house objects. So, this challenge involves refactoring our current code to make it better by creating a function to promote reusability. Make sure to write both a prototype and function definition. The prototype should go after the class definition by the way, and you can change the wording of the printouts to be more general. For example by saying the house instead of my house or your house. So, pause the video and give it a shot. Come back when you're done or if you need some help. How did that work out for you? Were you able to complete the challenge? I hope so. But even if you weren't we'll do it right here together. So, we'll go below the class here and we're going to add a function prototype. So, void printHouseData, so far so good; it's not a member function, it's outside the class and it's above main. And this takes a house. So, it takes a house. Now, the next thing is below main, I want to put a definition. So, the function definition printHouseData and then this takes House house. And in here we're actually going to, I'm going to copy one of the things we did up in main here. So, we're going to actually clean this up a bit instead of saying my house, I'm going to say the house is, I'm going to replace all the my houses with just house. Okay. Because that's the name of the parameter. The house is house.getColor and has some number of stories and number of windows, etc. Excellent. Now we're going to refactor what we did up here instead of this copy and paste. I told you, you know, pay attention maybe there's a better way to do this. Well, this is the better way to do this. We're going to call printHouseData, pass myHouse. So, we need to call also printHouseData(yourHouse). All right, now, let's see what we want to do here. We'll do 'debug', 'start without debugging'. See if we get the same output. And we do, the house is red and has two stories and six windows. The House is blue and has three stories and 10 windows. Excellent. Good, good job. Now I'm going to show you an even better solution which is slightly different to the one above. You might have noticed that the house object is passed by value if you'll remember passed by value. Remember whenever you pass something by value; the argument is copied into the parameter, for our house, this isn't that big of a deal. But what if this class had like 15 or 20 or 100 data members? Each one would have to be copied anytime the method is called. That is inefficient and could be bad for the performance of your program. Can you think of any way that we can maybe tell the function about the house object but without copying all of its data members to make the code more efficient? You might have guessed it, we have pass by reference. So, let's change it to pass by reference. So, I'm going to add the & up there and the & down here. That's all we need to do to change it to a regular pass by reference. Now we have the advantage that the parameter only copies the address of the house object instead of copying all of its data members. This is much more efficient. But wait, now we have a potential problem. Even though we're using it correctly inside the function right here. The function still has access to actually change the object and in this case we don't want to do it, right? Remember you give functions only as much access as they need. So, just like we did with the getter member functions, we want to restrict our function. So, it can't change the object but this printHouseData isn't a member function, so do we have an option? We had the advantage of the original house object not being changed before we made it pass by reference because pass by value makes a copy. But we want to be able to use pass by reference for the sake of efficiency and also to be able to prevent our function from calling any setters to change the data, so how do we do this? The answer is quite simple. Anytime you want both the efficiency of pass by reference but the security of a function not being able to change the original object through its setters or other non const member functions of course, we can make it a const reference. This time just put constant in front of the data type, keeping the reference operator where it is. So, I'm going to put const there, right here in the definition. I'm also going to go up here and put const in the prototype as well. So, now the function we wrote has to treat the house like it's a constant. It can't change it. So, even though our house isn't a constant, the function printHouseData has to treat it like it is. So, that's pretty cool. We get the best of both worlds. So, we can run it real quick just make sure it does the same thing, and it does the exact same thing. But if we were to try to call something in here like house.set something, notice I'm typing set but it's not letting me do any set. Set number of windows to 3. It's saying class house has no member setWindows, so the setNumWindows, let's try that. That's what we really want. So, this time the error is here; says the object has type qualifiers that are not compatible with the member function setNumWindows. The object type is const House, correct. So, it's a constant reference. So, that means we cannot call the setters. We can only call things that are explicitly labeled as const. With a const at the end right here, very cool. So, if we wrote a function that's supposed to change the house object, we would keep it as a reference and remove the const. But again, whether it's a member function or not, it is best to only give a function as much access as it needs to do its job, but no more. Good job everyone. That was a lot of information. In the next lecture, we will discuss separate compilation, where we can separate the specification of what our class can do from the implementation of the class itself. I'll see you there. Let's get going.