Hello, and welcome back.
In this lecture, I want to talk about the use case for
pointers when we're going to use them how we're going to use them We know how to create them.
declare them and define them.
We know how to assign an existing variable's address to them using the address of operator.
And we know how to de-reference them,
how to get the value that the pointer is pointing to.
Now how do we actually start using them.
So c offers some several basic operations that you can perform on pointers.
You can assign an address to a pointer
which we've learned about already.
This is an existing variable where the variable has an address.
And you just look at that variable's address by using the address of operator,
and we have a point to that. We could also assign an existing pointer to another pointer.
That's 2 ways you can assign an address to a pointer.
So it can be an array name by itself.
We haven't talked about arrays yet, but when you assign a raise
to a pointer all you have to do is specify the name. You don't even have to use the address of operator.
So far we've just focused on using an existing variable and
passing in the address of the variable itself.
But the 3 ways you can do it are mentioned there.
You can also dereference a pointer. We just learned about that that's where you use,the asterisk
the indirection operator.
This will give you the value that's stored in the pointer to. The pointer
the pointer's value which is an address what the point is pointing to,
just use that. You can also take a pointer address.
This is the actual address of the pointer when you create the pointer
where the pointer itself is stored.
You can use the address i have operator on that pointer as well.
And you can also perform pointer arithmetic. We're going to have a separate lecture on this.
This is where you can use the plus and the minus operators
to either add integers to a pointer or subtract integers from a pointer.
And this allows you to have specific notation
and allows you to jump around in memory quicker, especially if you're using arrays.
So what you typically do is when you add
an integer to a pointer
using the plus operator,
it's going to just set the address of that pointer up so many bytes.
If you subtract
a number from a pointer, it's going to set
that address in minus it a certain number of bytes.
You can also increment decrement by one,
and you'll see this is very common when we use it in arrays.
So when you're in an array and you want to move to the next element instead of using the index,
you can use a pointer and just increment it by one.
If you want to move to the previous element instead of using an index
you can just decrement it pointing by one.
So pointer arithmetic, we're going to talk about exclusively,
but that is something that you can do with pointers.
And again, it's particular notation that allows you to
move around quickly in memory,
be more efficient.
You can also find the difference between 2 pointers.
So if you have 2 pointers and the elements are in the same array
you might maybe want to find out how far apart they are,
the elements, you can minus them.
This would be the difference of 2 pointers
and. So if you're index 5 and then you had a pointer point in index 5,
you had another pointing to point pointing to index 15, you could
minus them and that would give you
how many elements they're away,
because it would be the hexadecimal address minus the other hexadecimal address
assuming the array in continuous memory which it is,
it will display how far apart they are.
You can use relational operators to compare the values of 2 pointers.
Pointers have to be the same type. So if they're both pointing integers, they both have to be
the same type.
This is just going to tell you whether or not the addresses are greater than or less than.
Again, you could identify which part of the array you're in.
There are also 2 forms of subtraction.
So I just talked about subtraction where
you can actually take one pointer and subtract it from another one
which you'll get a pointer
or you can subtract one pointer
from an integer, which will give you a pointer.
So subtract one pointer from another to get an integer
or to subtract an integer from a pointer to get a pointer. Those are the 2 cases.
So we'll keep in that mind we'll go through some more examples.
Be careful when you use the incrementation or decrementation of pointers
and causing the array out of bounds. So most of this stuff that you do with pointer arithmetic
is when you're operating on arrays.
Because again, it allows you to move quickly, it's simple syntax.
But remember, the arrays have an out of bounds error.
So if you increment too far,
you're going to go out of bounds in the array.If you decrement too far, you're going to have an under
under bounds, you're going into a negative number.
The computer itself is not going to keep track of whether a pointer still points to an array element.
So using the point of arithmetic on arrays, you got to be careful.
If you want to pointers used in expressions, you can also do that.
So we talked about this a little earlier, and the we dereferenced the pointer and we added
15 to it. I think we got 20.
So the value of pi pointer can be used in arithmetic expressions.
So if a variable is defined to be of type pointer to integer,
then it is evaluated using the rules of integer arithmetic.
And we showed that with the dereference. So if you have a variable
and you have a pointer, initialize it to null, you then sign your pointer
that variable's address. You then can just say star pnumber plus equals 25.
Remember, star pnumber is getting the value that's pointing to.
So what this is doing is it's saying the value of pnumber
equals the value pnumber plus 25.
So the value is going to be equal to 35.
This is not changing what it's pointing to because we're using asterisk,
so it's not changing the address that we're pointing to,
it's not changing the address itself because we don't have the address of operator.
We're changing the value that it's pointing to.
So incrementing the value of the number variable by 25,
the star indicates that you're asking the contents which the variable called pnumber is pointing to.
If a pointer points to variable x,
that pointer has been defined to be a pointer to the same data type as x.
So if you create an end pointer a float pointer,
you can only have an address of
a data type that is float or int depending on which one you declared.
So the use of the indirection operator
in an expression is identical to the use of x in the same expression.
And that's all it's saying is if you use a float or you have an int and
you use the indirection operator, you dereference it,
you can do whatever you can if you had a float in it, whatever it's pointing to.
A variable defined as a pointer to an int can store the address of
any variable of type in, we know that.
So here int value equals 999, we can assign that int
to pnumber as long as pnumber is a pointer to an int,
and then we can add to it.
The statement will operate with the new variable value
and the new contents value will be 1024.
Pnumber is pointing to value,
the address of value. So if you change the pointer what it's pointing
to value, you're going to change the value of this variable value.
So just remember that this is the concept of changing a variable in one spot
the pointer and you're changing it in multiple variables.
A pointer can contain the address of any variable of the appropriate type.
So you can use one pointer variable to change the values of many different variables if there are ints
or doubles or floats. As long as they are the type compatible with the pointer type,
you can change that value. You can't change pnumbers value
if pnumber is pointing to a float and
the value is an integer. You wouldn't be able to do that. You would have a compile error.
Another example here. Here we're going to
show you how to get the address of some variables. So here in this main,
we're doing some pointer arithmetic
on the actual expressions.
So we're not doing anything on array, so we're not incrementing or decrementing
pointers in that. So we're decrementing values. So we have 2 long
types num1 and num2 and then we have a pointer of type long,we initialize it to null.
We assign the address of num1 to our pointer pnum.
And then we assign the value of pnum to L,
so that means num1 is now going to be 2l.
If we increment num2, now num2
haven't done anything to yet.
So value of pnum is 2l, that means the value of num1 is 2l.
L is just the suffix to signify that it's a long.
If we now increment the long num2 value,
it's 0l, it's going to be 1l.
If we say num2 plus equals star pnum,
pnum is 2l, so it's going to be
1l plus 2l, num2 is now going to be equal to 3l.
And then if we assign the address of num2 to pnum, pnum
is now going to be equal to the address of num2.
So if we increment pnum
what we're pointing to, we're now going to have 4l.
And we'll look at this in inside of cold blocks,
but I'm walking you through it. Num1 is going to be equal to 2l.
Num2 is going to be equal to
it's pointing to pnum.
Pnum's 3l, so i think it's going to be 3l.
Pnum is going to be 3l, and then pneum plus num2
is going to be 6l.
So let's see if I'm right, go through this in code blocks.
So here we are in code blocks, tried to go through this by hand,
but now we want to see if we're right. Let's use the debugger.
We'll put this right here and we'll see as we go through the program how values are changing.
So let's build this, and let's debug it.
This is going to start up and it's going to stop at line 10.
And if we bring up our watch window,
which we can do by going to debug in windows watches,
watches, we can see the values as they're going. So right now
num1 is zero, num2 is zero, pnum is pointing to null.
That's why it's 0x0,
because we know that null represents 0 in pnumber.
And we can use that when we compare it in if statements.
So after we execute line 10, pnum should now have the address of num1.
So let's just step over.
Now pnum has an actual address 0x60ff08.
After we execute line 11, we're going to say the value of pnum is equal to 2l,
and we're not going to be actually seeing anything in the watch window for that. But let's go ahead and execute it,
pnum still has the same address.
Num1 is now changed though because pnum
had the address of num1, and we changed it.
So now num1 is 2. Now in lines 12, we're going to increment num2.
So num2 is just going to become 1l.
It's going to be 1. The address of pnum is still the same.
Now we're going to add to num2
whatever pnum was. Pnum remember is pointing to num1,
so num2 should be 3.
Then we go to 14, num2 is 3, num1 is still 2.
Now on line 15,
we're going to assign pnum a new address of num2. So after this,
we're no longer going to be 0x60ff08.
We're going to be 060ff04.
So now if we increment the value that pnum's pointing to which is number 2
and number 2 is 3, number 2 should now be 4.
And we see it's 4.
Now we're going to print out all these values and you're going to see the value of num1
should be 2.
The value of num2 should be 4.
Pnum is pointing to num2, so that should be 4 still.
And then we're going to say the value of pnum which is 4 plus,
num2 which is 4 should be 8.
So I might have actually been off with 6.
So now we won't be able to see that in the watch window. So let's just play it
and see the output as printf statements. We should see 2448.
So let's run this now in non-debug mode,
and we see 2 4 4 8.
So there we go. I hope that made sense.
Let's go back and continue our discussion.
So how about when we receive input, how can we use pointers in that way.
So far when we use scanf to input values, we have to use the ampersand
operator whenever we have an inter or float data type. The only time we have it
is when we had a character array and the reason for that is becausethe character a
actually is a character pointer which we're going to talk about.
But if we use scanf to input values and we use them on normal
data types that aren't character arrays, we've always had to use this add or sub operator.
And the reason for that is scanf's second argument is a pointer.
So we have to send it an address. Pointers only have values of addresses.
So when you have a pointer that already contains an address, you can use the pointer name as an argument for scanf.
So now you don't have to use the ampersand operator if you actually created a pointer,
because the second argument to the scanf is a pointer
and so you can just say the pointer name.
In the past if
we wanted to store a value in value,
we would have say ampersand value in the scanf second argument.
But if we have a pointer that we create and it already points to an address
as it does in this example,
then when we use scanf, all we have to say is the name of the value,
the name of the pointer.
So here's a case when we have an integer,
but we don't have to use ampersand because we have a pointer to an integer.
We just say percent d, p value.
So there are other times when you can pass in
regular names of variables to scanf besides character arrays
if you have a pointer.
You can also print out what they entered afterwards and you will notice that value
is actually going to be changed
to whatever they entered because we're pointing to that address.
Testing for null.
There's one rule that you have to burn into your memory. This is very very important.
We talked about dereferencing, but what happens if you dereference
an unintentionalized pointer, a pointer that's pointing to nothing
or a pointer that doesn't have memory allocated for it.
So what if we do something like this. We declare a pointer,
but we've never actually initialized it, and then we try to assign to it.
We de-reference and assign to it, that's a big mistake.
So the second line basically means that you want to store the value
5 in the location to which pt points to,
but pt doesn't point to anything because it hasn't been initialized.
So pt is now going to have a random value.
So there is no knowing where 5 will be placed, it could be placed anywhere in memory.
This might be harmless. It might overrate
existing data or existing state variables in your program.
It might cause the program to crash. It's very problematic.
So when you create a pointer and you allocate memory to store the pointer itself,
it does not allocate memory to store the data.
So by saying instar pt,
that allocated memory for the pointer, but it didn't allocate
any memory for what for the value of the pointer,
the address. Remember, pointers contain addresses.
It created the pointer but it didn't create any memory for what it's pointing to.
So you can't dereference it.
So it does not allocate memory to store the data.
Before you use a pointer, it should be assigned a memory location that has already been allocated
or you have to use dynamic memory allocation using malloc which we'll talk about
So you either assign the address of an existing variable to the pointer
or you use malloc which is dynamic memory allocation.
But keep that in mind whenever you use a pointer if you
created it and it was null,
you want to check it for null beforehand, we'll talk about that in a second. But if you created a
pointer wasn't initialized, do not assign anything to it
unless it's either pointing to another variable initialized that way or having memory allocated.
So this is where null comes into play.
You can actually test for null
to make sure you don't have a dereference error. Make sure your program doesn't crash.
So we already know that null basically points to nothing
and we should initialize all of our variables,
our pointers to null.
So we should always do something like this.
Null is a special symbol in c that represents the pointer equivalent to zero
with ordinary numbers.
The below also sets a pointer to null using zero.
So we can also if we don't want to pass it or assign it to null, we can just say 0
and that's the equivalent of saying pointing to nothing.
So because null is the equivalent of 0,
if you want to test whether p value is null, you can do something like
using explicit tests. You can say pointer equals null.
And if pointer is equal to null meaning it's zero,
then you can actually do something.
So if it's not zero or not null, it's one true or any other value,
that's a way to check against having crashes.
You make sure before you actually try to dereference something,
this will tell you whether or not it has memory allocated.
So you want to check for null before you dereference a pointer.
And you often do this in in functions because functions will take
pointers as parameters.
And if the caller
is passing in bad data or passing in a pointer doesn't have any memory allocated,
it could crash your code. So what you want to do is you want to make sure that the caller has
a valid address so you can check it for null.
Basically, what this means always to have these if checks
to say not p value or not pointer.
And if it's not pointer, then you can dereference it.
We're talking a lot about pointers. We've discussed quite a bit.
I'm sure it's getting a little overwhelming. You may want to take a break
and go through the previous lectures try to really sync this concept in your head.
Pointers just contain addresses.
But there's 3 parts to it, so it can get problematic.
But stay positive. Keep with me
and I know it's confusing.
We've talked about working with addresses.
We've talked about working with values. We've talked about working with the pointer itself,
and we've talked about working with variables that point to pointer.
So there's all this stuff you got to remember,
and it can be very very overwhelming and frustrating.
And it's always hard to try to figure out what's going on and it's going to take a lot of practice.
The best way to understand this concept and everything we talked to up until now
is to practice,
write short programs that use all the concepts I've mentioned.
So write a program that gets the values using a pointer,
this would be maybe using the indirection operator,
dereferencing. Write a program that changes values using pointers.
So this would be dereferencing, getting the values using pointers would just be the name of the
pointer which would be the address. Printing addresses,we've looked through all that syntax.
Practice this stuff over and over and over again that's how you're going to get good at it.
You have to get your confidence up and you have to study these concepts in detail
to master them.
We still have a lot more to talk about pointers, but this may be a time to just step away
and go over what we've talked about so far so that it's drilled into your head.
Thank you.