In this course, we will explore the basic concepts and fundamentals of pointers in C++ and you'll learn how to use them to point to dynamically allocated memory.
Learning Objectives
- Learn the fundamentals of pointers
- Learn how to allocate memory dynamically and also how to return it
- Explore rectangle and circle classes and create instances of them dynamically
Intended Audience
- Beginner coders, new to C++
- Developers looking to upskill by adding C++ to their CV
- College students and anyone studying C++
Prerequisites
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 our feet wet talking about the fundamentals of dynamic memory allocation and how we can use pointers to point to the addresses of these dynamically created data. We worked with simple data, hints and bulls but we haven't done anything with classes or erased gesture. So, that's about to change. We're going to combine all of the object oriented goodness that we learned earlier with pointers and dynamic memory. Let's learn about this with an example. We'll start with a simple immutable dog class. So, let's write the class first and then we'll worry about dynamic memory management and pointers. So, the dog has a name of type string, a breed of type string as its data and it has a constructor that takes the name and the breed and it has getters for both of its field. So, let's write the class. So, let's create a visual studio project called dynamic dogs. 'Create new project'. Empty project- DynamicDogs. So, we're going to create the typical three files that we work with dog.h dog.CPP and main.CPP. So, we'll do the header first I guess. It doesn't really matter what order we actually create the files but we'll get them ready.
Dog.h We will create new item dog.CPP and another new item source file main.CPP. Great. Now, let's fill in the files. So, let's start with dog.h and we're going to put the include guards that we learned about last time ifndef DOG_H define DOG_H, and of course and endif. We're going to also include the string library using name space standard and we have the public section, or the class rather. Class Dog and the public section. I want that there. And we also are going to have a private section as well for the data. So, in the data, that's the simple two little fields here. So, the name and the breed and then the public section we just have the constructor, string breed and we have string getName and we'll make it const because it does not modify the data. This is an immutable class remember. getBreed. All right, looks good to me. Now, of course we want to fill in the implementation as well in the dog.CPP. So, dog .h. We could actually just copy over the three little signatures here over into here, give them bodies and let me see here then we want to use the class name and the scope resolution operator as well.
So, dog right there. And copy that there and there as well. So, this name equals the name that is passed in in the constructor and this breed equals the breed that has passed in in the constructor. Forget name. We just want to return get to return the name rather. And then for the breed, we want to return the breed. So, now we've got our dog class setup. So, there's nothing real new with this nothing dynamic inside this class which we could we could have, dynamic strings for the fields and do anything we'd like with that. But the one thing you might want to keep in mind is if we did that, let's say we made these string pointers and then, equals new name or new string rather. And set the name and the breed and all this other stuff. If we did anything like that dynamically right here, we would have to make sure we wrote a destructor and called delete in the destructor. So, there would be your key and what the destructor might be good for. Inside of main, we will include iostream and also will include Dog.h int main and return zero.
So, there's our skeleton. So, let's get going here. We will make a dog myDogPtr equals new dog and it has Rover and he's a German Shepherd and dog pointer yourDogPtr, new dog named Fido and Fido is a Beagle. With this dog class, we can actually create poodles. Remember our last time we did something like this and we had the class that refused poodles. But this one we could make a poodle if we wanted to, just throwing that out there for all of you poodle lovers. So, we could use what's called the arrow member access operator. We'll look at what this looks like and we'll talk about it in just a minute. So, myDogPtr and then I do it's like a minus sign and then a greater than sign and then you'll notice it actually pops up anything public. So, in this case getName and we will put a hyphen in between just to provide some spacing and myDog pointer not myDog. And getBreed and we will do the same thing with yourDog. yourDog pointer getName hyphen in between with some spacing and yourDogPtr getBreed. So, now put a little extra space here and now we're going to use the de-referencing with the dot operator to show an alternative way to print out the same data.
So, this is using de-referencing and the dot operator. This one will look more traditional possibly to you. Just know that the de-referencing operator actually has priority over the dot operator. So, we have to put this in a parentheses first. So, we put myDogPtr. So, we're de-referencing the dog pointer and saying get me the dog you point to. And then once you have that object you can call them with dots just like we did with regular classes before. So, you can say getName hyphen and then myDogPtr in parentheses dot getBreed and then I bet you know what's gonna happen next. So, you've got yourDogPtr.getName and hyphen and then yourDogPtr.getBreed. So, we printed out all the data now. What do we need to do now? delete myDog Ptr. Delete yourDog Ptr and I could set them both to null pointer if I wanted to. I guess we'll do that. It's right at the end of main it's not exactly required but because there's not really a chance we're going to use these again but we'll do it for practicing and of course we have to run it because we don't want to just write a bunch of code and then not test it.
So, let's run it, make sure we don't have any errors and there we go. So, we have using the arrow operator Rover and Fido, where Rover is a German Shepherd and Fido a Beagle. And then using the referencing of the dot operator, we get the exact same output which is what we should expect. We should talk about this a little bit. So, you can see that we created two dog objects on the heap and pointed to them using dog pointers and of course we deleted them at the end of main when we were done using them and we also decided we'd set them to null pointer. This returns the memory back to the heap using delete. And then we set it to null pointer just to be in practice so that we don't end up with any dangling pointers and we have a expected value that we can test against if we were to test later to see if it points to something valid. Because the assumption is if it's not pointing to null pointer then it's pointing to a valid object. If that's not a good assumption to make, then we would be kind of lost. We wouldn't be able to determine, is this pointing to a dog object or not? So, we also saw that we have two ways of accessing the methods of our dog objects through the pointers. The arrow operator does automatic de-referencing and is shorter to write. So, usually C++ developers prefer this syntax over the other option of de-referencing the pointer using indirection with the asterisk inside parentheses and then accessing the methods using the dot operator. So, it's pretty cool that we have a shortcut but notice again, we don't need to put the star in front of this. If this is a pointer we can use the arrow already. If it weren't a pointer, we would use the dot operator if it was just a dog object. That we did not dynamically create. So, it's pretty cool. So, what about built an arrays?
Can we dynamically create an array? The answer is an emphatic yes. So, let's close this project. We're actually going to make another one. So, we'll close this project, make another project called dynamic array test. So, empty project- we call it Dynamic Array Test, then hit 'create' and we will this time just need the main file here. Now using classes, but you can obviously have objects in an array. But let's do the skeleton, using namespace std; All right, good. So, we want to have const int ARR_SIZE = 5; int* myArray. So, in this case, we just say its kind of weird because we're saying that it's just a pointer to an integer. That seems kind of strange, but this is where it's different. So, do you see how I'm using brackets? Square brackets not parentheses. Right? So, what I do here? So, I can say for(int i = 0; i < ARR_SIZE; i++) and then inside the loop, I can say myArray[i] = i * 2; So, I've just set a bunch of values in here and if I want to, I could print the values. So, I could say for(int i = 0; i < ARR_SIZE; i++), we could have broken this out into a function, maybe that would have been nicer but we'll just keep it easy and quick here.
Printing out myArray[i] and then at the very end, this is somehow a little bit different, just like the new syntax is different for arrays with square brackets here, in the initialization side. We also need to do this with delete. So, we put delete [] myArray; The spacing is actually optional and actually it fixes it for you. So, a lot of times it prefers you to have the brackets next to delete. So, we'll do that. So, let's run it and see what we get. Okay, 0, 2, 4, 6, 8. Nothing crashes, nothing funky goes on. We seem to be in pretty good working order. So, it works just like a regular built-in array, right? Pretty cool. You don't even have to do the same type of dereferencing that you do with regular variables. We just access it using the brackets. I don't have to put a star in front of it. Well, arrays are sequences of contiguous memory cells already.
So, they actually are automatically dereferenced. So, there is a strong, strong similarity between arrays in general and pointers. So, you'll notice the way we did this even though we use just a plain old integer pointer. What happens when we allocate this memory and what does new return? Well, new actually returns the address of the first element at index 0 in the array, but because we are treating it like an array, you can treat any pointer like an array, it won't always work. But here, it knows that it's a pointer it knows it's an array and we are able to access the elements of contiguous memory that belonged to us and it doesn't crash. It's pretty cool. So, what's the advantage of using the dynamic memory with arrays? Just like with primitive types like int or with our custom objects like dog, the memory is allocated at runtime and that is the key.
If you create an array statically, we could not ask the user what size they want it to be because it's too late by that point. It's already been sized. And we know arrays, by themselves, cannot be resized, right? The memory is allocated already, but with dynamic memory allocation we could. Even though in our simple example we worked on we used a constant array size, it doesn't have to be a constant. We could create a regular int and then ask the user, what size do you want the array to be? And guess what? I've got a challenge for you. You will do just that. Continue using the same DynamicArrayTest project but you're going to re-factor and modify it. You should remove the constant ARR_SIZE and instead use a variable to obtain that size. So, the user will be able to pick the size at runtime and the new statement will dynamically allocate the appropriate amount of memory. Prompt the user for a size for the array to be input into the variable, then update the for loops accordingly. Pause the video, give this one your best shot, come back when you're done or if you need some help.
How did that work out for you? Let's work on it together. So, we're going to remove this constant or we can at least, we comment it out just to keep the code there to see what it looks like. And I'm going to create this and say arrSize = 0. I'm going to prompt to the user, "Please enter an array size" and then they're going to enter an array size. And obviously, in industrial level code we would do things like testing to make sure that it went in right and that it was greater than zero and things like that. But for our purposes, we're good for right now. We are focused on dynamic allocation of arrays. Here we go. And this could work for any type, right? So, we probably need to remove that. Now for this one, we need to change ARR_SIZE to our variable version. All right, I think that's it. Let's double check here, array size, delete the array. Yes, okay. And of course, let's run it. So, in this case and since we're going through a loop here and it's just multiplying i by 2, this will take any size that we give it, as long as we got enough memory for it and it's within integer range, which is huge. All right. So, enter an array size. Let's say it's 20 elements. There we go. That's pretty cool, 0 all the way to 38, multiples of two.
What happens if we do, 5? There you go, same thing. Just 0 through 8. Okay, let's try another one. Let's try 50. There we go. So, it generated all the numbers; All the way from 0, all the way up to 100 not including, because that would be 2 * 50 but we didn't get quite to that, we would be at 49. So, it gives us the 98. Awesome. Now you know how to create the objects but you also know how to create arrays dynamically. So, now maybe you even have a little better idea how the C++ language developers created the vector class. It uses a dynamic array under the hood. When it runs out of room, out of memory, they create a bigger dynamic array, copy all the elements over, destroy the old one using delete, and then point to the new one. Pretty cool. Good work everyone. In the next lecture, we'll look at how to use const in different ways with pointers. Let's get going.
John has a Ph.D. in Computer Science and is a professional software engineer and consultant, as well as a computer science university professor and department chair.