XIIB-CS (POINTERS- NOTE)
POINTERS
Key points to
remember:
Pointer:
•
A kind
of memory variable that holds the address of other variable of same data type.
•
Reference
operator (&) As soon
as we declare a variable, the amount of memory needed is assigned for it at a
specific location in memory (its memory address).
•
This
reference to a variable can be obtained by preceding the identifier of a
variable with an ampersand sign (&),
known as reference operator, and which can be literally translated as
"address of".
For example:
ted = &andy;
Consider the following
code fragment:
andy = 25;
fred = andy;
ted = &andy;
The values contained in
each variable after the execution of this, are shown in the following diagram:
First, we have assigned
the value 25 to andy (a variable whose address
in memory we have assumed to be 1776).
Dereference operator (*)
•
a variable which stores a reference to another variable is called
a pointer.
•
Pointers
are said to "point to" the variable whose reference they store.Using
a pointer we can directly access the value stored in the variable which it
points to.
•
To do
this, we simply have to precede the pointer's identifier with an asterisk (*),
which acts as dereference operator
and that can be literally translated to "value pointed by".
•
Therefore,
following with the values of the previous example, if we write:
beth = *ted;
(that we could read as:
"beth
equal to value pointed by ted") beth would
take the value 25, since ted is 1776, and the value pointed by
1776 is 25. You must clearly differentiate that the expression ted refers to the value 1776, while
*ted
(with an asterisk * preceding the identifier) refers to the value stored at address 1776, which in this case is 25.
Notice the difference of including or not including the dereference operator (I
have included an explanatory commentary of how each of
these two expressions
could be read):
beth = ted; // beth equal to ted ( 1776 )
beth = *ted; // beth equal to value pointed by ted ( 25 )
Declaring variables of pointer types
- Due
to the ability of a pointer to directly refer to the value that it points
to, it becomes necessary to specify in its declaration which data type a
pointer is going to point to. It is not the same thing to point to a char as to point to an int or
a float.
- The
declaration of pointers follows this format: type * name; where
type is the data type of the value that the pointer is
intended to point to. This type is not the type of the pointer itself! but the type
of the data the pointer points to. For example:
- int * number;
- char * character;
- float * greatnumber;
Now have a look at this code:
// my first pointer
#include <iostream>
int main ()
{
int firstvalue, secondvalue;
int * mypointer;
mypointer = &firstvalue;
*mypointer = 10;
mypointer = &secondvalue;
*mypointer = 20;
cout << "firstvalue is " << firstvalue <<
endl;
cout << "secondvalue is " << secondvalue <<
endl;
return 0;
}
firstvalue is 10
secondvalue is 20
// more pointers
#include <iostream>
int main ()
{
int firstvalue = 5, secondvalue = 15;
int * p1, * p2;
p1 = &firstvalue; // p1 = address of firstvalue
p2 = &secondvalue; // p2 = address of secondvalue
*p1 = 10; // value pointed by p1 = 10
*p2 = *p1; // value pointed by p2 = value pointed by
p1
p1 = p2; // p1 = p2 (value of pointer is copied)
*p1 = 20; // value pointed by p1 = 20
cout << "firstvalue is " << firstvalue <<
endl;
cout << "secondvalue is " << secondvalue <<
endl;
return 0;
}
firstvalue is 10
secondvalue is 20
Notice that there are
expressions with pointers p1 and p2, both with and without dereference operator (*). The meaning of an expression using the dereference operator (*) is very different from one that does not: When this operator
precedes the pointer name, the expression refers to the value being pointed,
while when a pointer name appears without this operator, it refers to the value
of the pointer itself (i.e. the address of what the pointer is pointing to).
Pointers and arrays
·
The
concept of array is very much bound to the one of pointer.
·
In
fact, the identifier of an array is equivalent to the address of its first
element, as a pointer is equivalent to the address of the first element that it
points to, so in fact they are the same concept.
For example, supposing
these two declarations:
int numbers [20];
int * p;
The following assignment
operation would be valid:
p = numbers;
- After
that, p
and numbers would
be equivalent and would have the same properties.
- The
only difference is that we could change the value of pointer p by another one, whereas numbers will
always point to the first of the 20 elements of type int with which it was defined.
- Therefore,
unlike p, which is an ordinary pointer, numbers is an array, and an array can be considered a constant
pointer. Therefore, the following allocation would not be valid:
numbers = p;
- Because
numbers
is an array, so it operates as a
constant pointer, and we cannot assign values to constants.
- Due
to the characteristics of variables, all expressions that include pointers
in the following example are perfectly
valid:
// more pointers
#include <iostream>
int main ()
{
int numbers[5];
int * p;
p = numbers; *p = 10;
p++; *p = 20;
p = &numbers[2]; *p = 30;
p = numbers + 3; *p = 40;
p = numbers; *(p+4) = 50;
for (int n=0; n<5; n++)
cout << numbers[n] << ", ";
return 0;
}
10, 20, 30, 40, 50,
In arrays we used brackets
([]) to specify the index of an element of the array to which we
wanted to refer. Well, these bracket sign operators [] are also a dereference operator known as offset operator.
They dereference the variable they follow just as * does, but they also add the number between brackets to the address
being dereferenced. For example:
a[5] = 0; // a [offset of 5] = 0
*(a+5) = 0; // pointed by (a+5) = 0
These two expressions are
equivalent and valid both if a is a
pointer or if a is an array.
Pointer initialization
- When declaring pointers we may
want to explicitly specify which variable we want them to point to:
int number;
int *tommy = &number;
The behavior of this code
is equivalent to:
int number;
int *tommy;
tommy = &number;
When a pointer initialization takes place we are always assigning
the reference value to where the pointer points (tommy), never the value being pointed (*tommy).
As in the case of arrays,
the compiler allows the special case that we want to initialize the content at
which the pointer points with constants at the same moment the pointer is
declared:
char * terry = "hello";
In this case, memory space
is reserved to contain "hello" and then a pointer to the first character of this memory block is
assigned to terry. If we imagine that "hello" is stored at the memory locations that start at addresses 1702, we
can represent the previous declaration as: It is important to indicate that terry contains the value 1702, and not 'h' nor hello",
although 1702 indeed is the address of both of these. For example, we can
access the fifth element of the array with any of these two expression:
*(terry+4)
terry[4]
Both expressions have a
value of 'o' (the fifth element of the
array).
Pointer arithmetics
- To
conduct arithmetical operations on pointers is a little different than to
conduct them on regular integer data types.
- To
begin with, only addition and subtraction operations are allowed to be
conducted with them, the others make no sense in the world of pointers.
- But
both addition and subtraction have a different behavior with pointers according
to the size of the data type to which they point.
For example, let's assume that in a given compiler for a specific
machine,
char takes 1 byte, short takes 2 bytes and long takes 4.
Suppose that we define three pointers in this compiler:
char *mychar;
short *myshort;
long *mylong;
and that we know that they
point to memory locations 1000, 2000 and 3000 respectively.
So if we write:
mychar++;
myshort++;
mylong++;
mychar, as you may expect, would
contain the value 1001. But not so obviously, myshort would contain the value 2002, and mylong would contain 3004, even
though they have each been increased only once. The reason is that when adding
one to a pointer we are making it to point to the following element of the same
type with which it has been defined, and therefore the size in bytes of the
type pointed is added to the pointer. This is applicable both when adding and
subtracting any number to a pointer. It would happen exactly the same if we
write:
mychar = mychar + 1;
myshort = myshort + 1;
mylong = mylong + 1;
Both the increase (++) and decrease (--)
operators have greater operator precedence than the dereference operator (*), but both have a special behavior when used as suffix (the
expression is evaluated with the value it
had before being increased). Therefore, the following expression may
lead to confusion:
*p++
Because ++ has greater precedence than *, this expression is equivalent to *(p++). Therefore, what it does is to increase the value of p (so it now
points to the next element), but because ++ is used as postfix the whole expression
is evaluated as the value pointed by the original reference (the address the
pointer pointed to before being increased).
Notice the difference
with:
(*p)++
Here, the expression would
have been evaluated as the value pointed by p increased by one. The value of p (the pointer itself) would not be modified (what is being modified
is what it is being pointed to by this pointer).
If we write:
*p++ = *q++;
Because ++ has a higher precedence than *, both p and q are increased, but because both increase operators (++) are used as postfix and not prefix, the value assigned to *p is *q before both p and q are increased. And then
both are increased. It would be roughly equivalent to:
*p = *q;
++p;
++q;
Like always, I recommend
you to use parentheses () in
order to avoid unexpected results and to give more legibility to the code.
Pointers to pointers
- C++
allows the use of pointers that point to pointers, that these, in its
turn, point to data (or even to other pointers). In order to do that, we
only need to add an asterisk (*)
for each level of reference in their declarations:
char a;
char * b;
char ** c;
a = 'z';
b = &a;
c = &b;
This, supposing the
randomly chosen memory locations for each variable of 7230, 8092 and 10502, could be represented as: The value of each variable is written
inside each cell; under the cells are their respective addresses in memory. The
new thing in this example is variable c, which
can be used in three different levels of indirection, each one of them would
correspond to a different value:
• c has type char** and a
value of 8092
• *c has type char* and a
value of 7230
• **c has type char and a
value of 'z'
Null pointer
- A null pointer is a regular
pointer of any pointer type which has a special value that indicates that
it is not pointing to any valid reference or memory address.
- This value is the result of
type-casting the integer value zero to any pointer type.
int * p;
p = 0; // p has a null pointer value
- Do not confuse null pointers with
void pointers.
- A null pointer is a value that
any pointer may take to represent that it is pointing to
"nowhere", while a void pointer is a special type of pointer
that can point to somewhere without a specific type. One refers to the
value stored in the pointer itself and the other to the type of data it
points to.
Dynamic Memory
- Until
now, in all our programs, we have only had as much memory available as we
declared for our variables, having the size of all of them to be
determined in the source code, before the execution of the program.
- But,
what if we need a variable amount of memory that can only be determined
during runtime? For example, in the case that we need some user input to
determine the necessary amount of memory space.
- The
answer is dynamic memory, for which C++ integrates the operators new and delete.
Operators new and new[]
- In
order to request dynamic memory we use the operator new.
- new is
followed by a data type specifier and -if a sequence of more than one
element is required- the number of these within brackets []. It returns a pointer to the beginning of the new block of memory allocated. Its form is:
pointer = new type
pointer = new type
[number_of_elements]
The first expression is
used to allocate memory to contain one single element of type type. The second one is used to assign a block (an array) of elements
of type type, where number_of_elements is an integer value representing the amount of these. For example:
int * bobby;
bobby = new int [5];
In this case, the system
dynamically assigns space for five elements of type int and returns a pointer to the first element of the sequence, which
is assigned to bobby. Therefore, now, bobby points to a valid block of memory with space for five elements of
type int. The first element pointed by bobby can be accessed either with
the expression bobby[0] or the
expression *bobby. The second element can
be accessed either with bobby[1] or *(bobby+1) and so on...
Operators delete and delete[ ]
- Since
the necessity of dynamic memory is usually limited to specific moments
within a program, once it is no longer needed it should be freed so that
the memory becomes available again for other requests of dynamic memory.
- This
is the purpose of the operator delete,
whose format is:
- delete pointer;
- delete [] pointer;
- The first expression should be used to delete memory
allocated for a single element, and the second one for memory allocated
for arrays of elements.
Practice :- Rewrite the following codes after removing errors, if any, in
the following snippet. Explain each error.
void main()
{
const int i = 20;
const int * const ptr = &i;
(*ptr)++;
int j = 15;
ptr = &j;
}
Practice: Give the output of the following program:
void main()
{
char *p = “School”;
char c;
c = ++ *p ++;
cout<<c;
}
Practice: Give the output of the
following program:
void main()
{
int x [] = { 50, 40, 30, 20, 10}:
int *p, **q, *t;
p = x;
t = x + 1;
q = &t;
cout << *p << “,” << **q << “,” <<
*t++;
}
Practice: Give the output of the following program( Assume all necessary
header files are included):
void main( )
{
char * x = “WorLd CuP”;
char c;
c = ++ *x ++;
cout<<c;
}
Practice: Give the output of the following program(Assume all necessary
header files are included) :
void main( )
{
char *x = “WorLD CuP”;
char c;
c = ( *(x+1) ) ++ ;
cout<<c;
}
Practice. What will be the output of the program( Assume all necessary
header files are included) :
void print (char * p )
{
p = "Comp";
cout<<"value is "<<p<<endl;
}
void main( )
{
char * x = "Class XII";
print(x);
cout<<"new value is "<<x<<endl;}
Practice:
Give output of following code fragment:
char
*msg = “a ProFile”;
for
(int i = 0; i < strlen (msg); i++)
if
(islower(msg[i]))
msg[i]
= toupper (msg[i]);
else
if
(isupper(msg[i]))
if(
i % 2 != 0)
msg[i]
= tolower (msg[i-1]);
else
msg[i--];
cout
<< msg << endl;