|
|
Summary
1) Pointers
2) Declaration of Pointers
3) Example 1 (Bubble Sort)
4) Pointers and Call By Reference
5) Example 2
Pointers
In the earlier lectures, we had briefly referred to the concept of pointers. Let’s
see what a
pointer is and how it can be useful.
Pointers are a special type of variables in which a memory address is stored. They
contain
a memory address, not the value of the variable. The concept of the pointers can
be well
understood from the following example.
Suppose, we request someone to take a parcel to the house of a person, named Ahmad.
Here the point of reference is a name. However, if we specifically tell him the
number of
house and the street number. Then this is a reference by the address of the house.
It
means that we have two ways to locate an address. To understand further the concept
of
memory address, the example of the computers can be helpful. In computers, one can
have a name x which
is associated with a memory location. We can have the memory
address of x, say
6000 or whatever it is. So the simple variable names are those of
specific locations in memory. But in terms of addresses, these are the addresses
of those
memory locations. We can use these names and addresses interchangeably to refer
to
memory locations. When a value is referred by a normal variable is known as direct
reference. While the value referred through the use of memory address may be known
as
indirect reference.
To understand further the terms of direct reference and indirect reference, suppose
that
we want to assign a value 10 to
x. This can be done by
writing x = 10.
In this
statement, the value 10 will be assigned to the memory location which has label
(name) x.
The second way to assign a value to a memory location is with reference to the address
of
Page 151
that memory location. In other words, ‘assign a value to the memory location whose
address is contained in the variable (that is a pointer) on right hand side of the
assignment
operator’. Operators are used to refer the address of memory locations and to refer
the
values at those addresses.
Following figure shows directly and indirectly referencing a variable.
x directly references
a variable whose value is 10 x
xptr indirectly references a variable whose
value is 10 xptr x
Now we will try to comprehend the concept with another daily life example. Suppose,
hundreds of people are sitting in an auditorium. The host is going to announce a
prize for
a person amongst the audience. There are two methods to call the prizewinner to
dais.
The host can either call the name of the person or the number of the seat. These
are
equivalent to ‘call by name’ and ‘call by address’ methods. In both cases, the prize
will
be delivered to a person whether he is called by name or referred by address (seat
number
in this case). In programming, pointers are used to refer by the addresses.
Declaration of Pointers
Pointers work by pointing to a particular data type. We can have pointer to an integer,
pointer to a double, pointer to a character and so on. It means that a type is associated
to a
pointer. Pointer, being a variable, needs a name. The rules for naming a pointer
are the
same as for the simple variable names. The pointers are declared in a specific way.
The
syntax of declaring a pointer is:
data type *name ;
Here ‘name’ is the
name of the pointer and data type is the type of the data to which the
pointer (name) points.
There is no space between asterisk (*) and the name. Each variable
being declared as a pointer must be preceded by *. The * is associated with the
name of
the variable, not with the data type. To associate the * (asterisk) with data type
(like int* )
may confuse the declaration statement. Suppose, we want to declare a pointer to
an
integer. We will write as:
int *myptr;
Here myptr is the
name of the pointer. The easiest way to understand the pointer
declaration line is the reading the statement from right to left. For the above
statement,
we say that myptr
is a pointer to an integer (int). Similarly for the declaration
double *x ,
x is a pointer to a data of type double. The declaration of
char *c shows that
c is a pointer
to a data of type character. The declaration of multiple pointers requires the use
of * with
each variable name. This is evident from the following example which declares three
10
10
Page 152
pointers.
int *ptr1, *ptr2, *ptr3 ;
Moreover, we can mix the pointers declaration with simple variables on one line.
int *ptr, x, a [10] ;
In this declaration ptr
is a pointer to data of type
int,
x is a simple variable
of type int and
a is an array of integers.
Whenever used, these pointers hold memory addresses.
Now we will try to understand what address a pointer holds. Suppose, we declare
a
pointer variable ptr
and a variable x
and assign a value 10 to it. We write this as under.
int *ptr ;
int x ;
x = 10 ;
Here x is a name
of a memory location where a value 10 is stored. We want to store the
address of this memory location (which is labeled as
x) into the pointer
ptr. To get the
address of x, we
use address operator i.e. &. (it is & not &&, the && is logical AND). To
assign the address of x
to pointer ptr,
we write
ptr = &x ;
This statement assigns the memory address of the location
x to the pointer
ptr. The
following figure shows a schematic representation of memory after the preceding
assignment is executed.
x
ptr
The pointers contain whole numbers as they contain memory addresses. An address
can
be represented only in whole numbers. Therefore, a pointer is a whole number, sufficient
enough, to hold any memory address of the computer. The pointers have no specific
data
type.
In the above assignment statement, we have a pointer to a memory location. Now,
it can
be ascertained what value is stored in that memory location. To get the value stored
at a
memory address, we use the dereferencing operator, represented by asterisk (*).
The * is
used with the name of the pointer to get the value stored at that address. To get
the value
stored at the memory address
ptr, we write *ptr
which is read as the value of whatever
ptr
points to. Thus the line z
= *ptr; means, z
has the value of whatever
ptr points to.
The following example can explain the representation of the pointer in memory. Assume
that variable x is stored at location 400000 and pointer variable
ptr is stored at location
1
Page 153
500000.
ptr
x
Address: 500000
400000
We can use this operator (*) to get the value and can do any arithmetic operation
with it.
The following statements make it further clear.
z = *ptr + 2 ;
z = *ptr * 2 ;
z = *ptr – 2 ;
Here *ptr gives
the value stored at memory address where the pointer
ptr points to.
We know that it is a good programming practice to initialize a variable when we
declare
it. This will ensure that there will be no unknown value in the variable at some
later
stage.
Similarly, we should assign an initial value to a pointer after declaring it. Taking
the
address of a variable and assigning it to the pointer is one way of initializing
a pointer. A
pointer can be initialized by assigning either value 0 or the word NULL. The NULL
is a
global variable declared in many header files that we include at the start of the
program.
The pointer initialized by NULL as
ptr = NULL; is called null
pointer which points to
nothing. Similarly, when we assign a zero to a pointer like
ptr = 0; it means that
the
pointer is pointing to nothing at the moment. Here zero is not considered as a valid
address for a memory location. However, at some later stage, we use the pointer
in an
assignment statement either on left hand side to assign a value to it or as a part
of an
expression on right hand side. The pointer must have a valid memory address where
a
value should have stored. We get the address of a variable by putting & operator
before
the name of the variable and assign it to a pointer as in the following statement
ptr = &x;
We know that in C language, the default mechanism of function call is ‘call by value’.
Sometimes we want to make a call by reference. In call by reference, we pass the
address
of the variable to a function by using & operator.
One of the major usages of pointers is to simulate call by reference while using
it with
function calls. In the calling function, we pass the address of the variable to
a function
being called by using & operator. We write a function call as
fn( &x ) where
&x indicates
that the address of variable
x is being passed to the function
fn. In the receiving function,
the function must know that the parameter passed to it is an address. So the declaration
of
the receiving function will be as
void fn ( int *num)
{
400000 10
Page 154
statement(s) ;
}
The int *num in
the function declaration indicates that the receiving variable is a pointer
to a memory address. In the body of the function, we will use this variable as:
cin >> *num ;
This statement describes that the value entered through the keyboard (as
cin is used) will
be stored at the memory address wherever the pointer num is pointing to.
While using value associated with the pointer, we write
*num and
&num in case of using
the address. This thing can be summarized as follows
“*num means the
value of whatever the num
points to and
&num means the address of the variable
num”
The pointers can appear on the left hand side exactly like ordinary variables. In
this case,
you would have an address statement on the right hand side. The address (operator
(&) )
cannot be of an expression. Rather, it is always of a simple variable. We cannot
write
&(x+y). The address (&) would be either of
x (&x) or of
y (&y). The address operator
(&)
operates on a simple variable. Precisely speaking, whenever we have a pointer on
left
hand side, the right hand side should have an address. If a pointer appears on the
right
hand side of an expression, it can participate in any expression. In this case,
we use the
operator * with the pointer name and get the value stored where the pointer points
to.
Obviously we can do any calculation with this value (i.e. it can be used in any
expression).
Example (Bubble Sort)
You might be knowing the technique of bubble sorting. Its application helps us compare
two values each time and interchange the larger and smaller values. In this way,
we sort
the arrays. To interchange the position of larger and smaller value, the technique
of
swapping is used. Swapping is very common in programming. While using this
technique, we put value of one variable in a temporary location to preserve it and
assign
the value of second variable to the first. Then the temporary value is assigned
to the
second variable.
Suppose, we want to swap the values of two variables
x and
y. For this purpose, a
third
variable temp is used in the following fashion.
temp = x ;
x = y ;
y = temp ;
We can write the above three statements in a program to swap the value of x and
y. Now
the question arises, can we call a function swap (x, y) which has a code to swap
the
values of x and y. We call the function swap by passing
x and
y. When the control comes
back to the calling function, the values of
x and
y are the same as before.
These are not
Page 155
swapped. This is mainly due to the fact that passing value to function swap is a
call by
value. It does not change the values in the calling function. The swap function
receives a
copy of the values and interchanges the values in that copy. The original values
remain
the same.
To interchange two values in a function, we make a call by reference to the function.
Here comes the use of pointers. To write the swap function to interchange two values
always use pointers in the function to get the swapped values in the calling function.
The
code fragment in our main program will be written as follows:
yptr = &y ; // address of y is stored in yptr
xptr = &x ; // address of x is stored in xptr
swap (yptr, xptr) ; // addresses are passed
The receiving function must know that addresses are being passed to it. So the
declaration of swap function will be:
swap (int *yptr, int *xptr)
{
… … …
}
This use of pointers implements a call by reference. We can use this technique in
bubble
sort. The swap function can switch the elements of the array by using pointers and
*
operator.
The code of the program that sorts an array by bubble sort and use the swap function
to
interchange the elements of the array is given here.
/* This program uses bubble sorting to sort a given array.
* We use swap function to interchange the values by using pointers
*/
#include <iostream.h>
#include <stdlib.h>
/* Prototye of function swap used to swap two values */
void swap(int *, int *) ;
main()
{
int x [] = {1,3,5,7,9,2,4,6,8,10};
int i, j, tmp, swaps;
for(i = 0; i < 10; i ++)
{
swaps = 0;
for(j = 0; j < 10; j ++)
Page 156
{
if ( x[j] > x[j+1]) // compare two values and interchange if needed
{
swaps++;
swap(&x[j],&x[j+1]);
}
}
//display the array’s elements after each comparison
for (j=0; j<10; j++)
cout << x[j] << '\t';
cout << endl;
if (swaps == 0)
break;
}
}
void swap(int *x, int *y) //function using pointers to interchange the values
{
int tmp;
if(*x > *y)
{
tmp = *x;
*x = *y;
*y = tmp;
}
}
Following is the output of the program of bubble sort.
1 3 5 7 2 4 6 8
9 10
1 3 5 2 4 6 7 8
9 10
1 3 2 4 5 6 7 8
9 10
1 2 3 4 5 6 7 8
9 10
1 2 3 4 5 6 7 8
9 10
Pointers and Call By Reference
Suppose, we have a function that performs a specific task again and again but with
different variables each time. One way to do this is to pass a different variable
to the
Page 157
function, each time, by reference. We can also write the function with pointers.
In this
case, before calling the function, put the address of the simple variable in the
pointer
variable and pass it to the function. This is a call by reference. Thus the same
pointer
variable can be used each time by assigning it the address of a different variable.
The mechanism behind calling a function is that, when we call a function we pass
it some
variables. The values of these variables are used with in the function. In call
by value
mechanism, the values of these variables are written somewhere else in the memory.
That
means a copy of these values is made. Then control goes to the called function and
this
copy of values is used in the function. If we have to pass a huge number of values
to a
function, it is not advisable to copy these huge numbers of values. In such cases,
it is
better to pass the reference of the variables, which is a call by reference phenomenon.
We
perform a similar function in case of an array, where we can pass, say, 100 values
(size of
the array) to the called function, by only passing the name of the array. When we
pass an
array to a function, actually the starting address of the array is passed to the
function.
Thus the default calling mechanism to call a function while passing an array to
it is a call
by reference.
The problem with call by reference is that ‘we are letting the function to change
the
values at their actual storage place in the memory’. Sometimes, we want to do this
according to the requirement of the logic of the program. At some other occasion,
we
may pass the addresses for efficiency while not affecting the values at that addresses.
The
use of const can be helpful in overcoming this problem..
Let’s look at the use of const. Consider the following line of declaration:
int *const myptr = &x ;
The right hand side of this assignment statement could be read as,
myptr is a constant
pointer to an integer. Whenever we use the keyword const with a variable, the value
of
that variable becomes constant and no other value can be assigned to it later on.
We know
that when we declare a constant variable like const int x ; it is necessary to assign
a value
to x and we write
const int x = 10
. After this, we cannot assign some other value to x.
The value of x can not be changed as it is declared as a constant.
Now consider the previous statement
int *const myptr = &x ;
Here we declare a constant pointer to an integer. Being a constant pointer, it should
immediately point to something. Therefore, we assign this pointer an address of
a
variable x at the
time of declaration. Now this pointer cannot be changed. The pointer
myptr will hold the address of variable
x throughout the program.
This way, it becomes
just another name for the variable
x. The use of constant
pointers is not much useful.
The use of keyword const in declaration statement is a little tricky. The statement
int *const myptr = &x ;
means myptr is a
constant pointer to an integer. But if we change the place of const in this
statement and write
Page 158
const int *myptr = &x ;
This statement describes that
myptr is a pointer to a constant integer. This means that the
value of pointer myptr
can be changed but the value stored at that location cannot be
changed. This declaration is useful. It has a common use in call by reference mechanism.
When we want to pass the arguments to a function by reference without changing the
values stored at that addresses. Then we use this construct of declaration (i.e.
const int
*myptr) in the called function declaration. We write the declaration of the function
like
fn ( const int *myptr)
{
….
}
This declaration informs the function that the receiving value is a constant integer.
The
function cannot change this value. Thus we can use the address of that value for
manipulations but cannot change the value stored at that location.
Example 2
Let’s consider an example in which we use the pointers to make a call by reference.
We want to convert the lowercase letters of a string (character array), to their
corresponding uppercase letters.
We write a function convertToUppercase, which processes the string s one character
at a
time using pointer arithmetic. In the body of the function, we pass the character
to a
function islower. This function returns true if the character is a lowercase letter
and false
otherwise. The characters in the range ‘a’ through ‘z’ are converted to their
corresponding uppercase letters by function toupper. Function toupper takes one
character as an argument. If the character is a lowercase letter, the corresponding
uppercase letter is returned, otherwise the original character is returned. The
functions
toupper and islower are part of the character handling <ctype.h>. So we
have to
include this header file in our program. We include it in the same way, as we include
<iostream.h>.
The complete code of the program is given below.
//This program converts a string into an uppercase string
# include <iostream.h>
# include <ctype.h>
# include <stdlib.h>
//declare the functions prototype
void convertToUppercase (char *)
main ()
{
Page 159
char s [30] = “Welcome To Virtual University” ;
cout << “The string before conversion is: “ << s << endl ;
convertToUppercase ( s) ; //function call
cout << “The string after conversion is: “ << s ;
}
void convertToUppercase (char *sptr)
{
while ( *sptr != ‘\0’ )
{
if ( islower ( *sptr) )
*sptr = toupper ( *sptr ); //convert to uppercase
++ sptr; // move sptr to the next
character
}
}
Following is the output of the program.
The string before conversion is : Welcome To Virtual University
The string after conversion is : WELCOME TO VIRTUAL UNIVERSITY
Exercise
1. Modify the above program so that it gets a string from user and converts it into
lowercase.
2. Write a program, which converts a string of uppercase letters into its corresponding
lowercase letters string. |
|
|
|